Compare commits

..
917 changed files with 24908 additions and 40705 deletions

No files matched your search

@@ -39,7 +39,6 @@ body:
- Java 19
- Java 20
- Java 21
- Java 25
- Other (Please Specify)
validations:
required: true
+2 -1
View File
@@ -36,4 +36,5 @@ addon.local.gradle
addon.local.gradle.kts
addon.late.local.gradle
addon.late.local.gradle.kts
local/
/*.csv
!gl_functions.csv
+11 -14
View File
@@ -7,41 +7,40 @@
* It's a cute angle -mitchej123
* It's the angel that pulls you out of OptiFine's hell -Roadhog360
## **Beta Quality - Here there be Dragons**
* Shaders - Beta
* Complementary + Euphoria Patches is the primary compatibility target
* Sildurs, Seus, and any other shaders might work
## **Beta Quality - Here there be Dragons**
* Shaders sneak preview - Please don't report issues yet, but please do submit fixes
* Complementary & BSL mostly function, but have some bugs
* Sildurs, Seus, and any other shader probably doesn't work
# Required Dependencies
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.9.0
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.14
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.4.2
* NOTE: Some mods are not required, but a specific version is required if present - see: `Permanent Incompatibilities` below.
# Known (temporary) Incompatibilities
* Forge Relocation - Currently non Thread Safe; safe if not in use
* ProjectRed Frames - Currently non Thread Safe; safe if not in use
* ProjectRed Frames - Currently non Thread Safe; safe if not in use'
# Potential Incompatibilities
* EndlessIDs - Compat commits have been made, might work, might not - not fully tested
# Known Incompatibilities
* FalseTweaks - Not currently planned (Looking for volunteers) [Might work if you effectively disable most of the mod]
* SmoothFont - Not currently planned (Looking for volunteers)
* RPLE - Not currently planned (Looking for volunteers)
* LiteLoader - Install [LiteloaderLoader](https://github.com/Midnight145/LiteloaderLoader)
# Permanent Incompatibilities
* OptiFine [Disabled, won't fix]
* FastCraft [Disabled, won't fix]
* BetterFPS [Disabled, won't fix]
* OptimizationsAndTweaks [Disabled, won't fix]
* [Amazing Trophies 1.2.1 and below](https://github.com/GTNewHorizons/Amazing-Trophies/releases) - Use 1.2.2+
* [Amazing Trophies 1.2.1 and below](https://github.com/GTNewHorizons/Amazing-Trophies/releases) - use 1.2.2+
* [LWJGL3ify 1.5.4 and below](https://github.com/GTNewHorizons/lwjgl3ify/releases/) - Use the latest version available, 1.5.7+
* [ArchaicFix 0.6.2 and below](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) - Use 0.7.0 or above
* [Hodgepodge 2.4.3 and below](https://github.com/GTNewHorizons/Hodgepodge/releases) - Use 2.4.5 or above
* [Hodgepodge 2.4.3 and below](https://github.com/GTNewHorizons/Hodgepodge/releases) - Use 0.4.5 or above
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 - Threading issues, use 1.3.0+
* Neodymium - Untested, but unlikely to be compatible. (Chunk Meshing is definitely incompatible, other features might work depending on config)
* SmoothFont - Equivalent functionality added
# Credits
* ShadersMod
@@ -65,7 +64,7 @@
* Makamys for [Neodymium](https://github.com/makamys/Neodymium) - Various Tessellator -> Quad functionality taken under the LGPL 3
* MCPatcherForge
* Original [MCPatcher](https://bitbucket.org/prupe/mcpatcher/src/master/): Paul Rupe
* [Forge version](https://github.com/mist475/MCPatcherForge): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
* [Forge version](https://bitbucket.org/prupe/mcpatcher/src/master/): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
* HUDCaching
* Created by [Moulberry](https://github.com/Moulberry/MCHUDCaching), backported from [Patcher](https://github.com/Sk1erLLC/Patcher) mod by Alexdoru under the CC-BY-SA-NC 4.0 license
* Angelica
@@ -83,5 +82,3 @@
* Code from both glsl-transformation-lib and Monocle taken under their respective LGPL 3.0 licenses
* FalseTweaks
* FalsePattern for [FalseTweaks](https://github.com/falsepattern/falsetweaks) - Item Rendering Optimizations taken from FalseTweaks under the LGPL 3.0 license
* Beddium
* Venn and Falsepattern for [Beddium](https://github.com/vfx-dev/Beddium) - MinimapRenderer, SafeChunkTracker, and some debug commands adapted under the LGPL 3.0 License
+2 -22
View File
@@ -4,10 +4,6 @@ plugins {
minecraft {
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true")
extraRunJvmArguments.add("-Dangelica.dumpClass=true")
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
//extraRunJvmArguments.add("-Dangelica.redirectorLogspam=true")
}
tasks.test {
@@ -15,24 +11,17 @@ tasks.test {
testLogging {
events("passed", "skipped", "failed")
}
// On macOS ARM64, use an ARM64-compatible JVM for tests
val isMacOsArm64 = System.getProperty("os.name").lowercase().contains("mac") &&
System.getProperty("os.arch") == "aarch64"
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(8)
if (isMacOsArm64) {
vendor = JvmVendorSpec.AZUL
}
}
dependsOn(tasks.extractNatives2)
jvmArgs("-Djava.library.path=${tasks.extractNatives2.get().destinationFolder.asFile.get().path}")
}
tasks.processResources {
val projectVersion = project.version.toString()
inputs.property("version", projectVersion)
inputs.property("version", project.version.toString())
filesMatching("META-INF/rfb-plugin/*") {
expand("version" to projectVersion)
expand("version" to project.version.toString())
}
}
@@ -42,12 +31,3 @@ tasks.register<Copy>("copyDependencies") {
from(configurations.default)
into("testDependencies")
}
val embedOnly: Configuration by configurations
tasks.shadowJar {
from(embedOnly.map(::zipTree))
relocate("com.mitchej123", "com.mitchej123")
relocate("org.embeddedt", "org.embeddedt")
}
+30 -51
View File
@@ -46,58 +46,45 @@ configurations {
// Can be enabled for testing if desired
// runtimeClasspath.extendsFrom(transformedMod)
// testRuntimeClasspath.extendsFrom(transformedMod)
// Embed in JAR without relocation (compile only, shadowed into JAR)
embedOnly
embedOnly.canBeConsumed = false
embedOnly.canBeResolved = true
compileClasspath.extendsFrom(embedOnly)
testCompileClasspath.extendsFrom(embedOnly)
}
// Exclude self-reference from all configurations - prevents transitive Angelica deps from resolving against this project
configurations.all {
exclude group: "com.github.GTNewHorizons", module: "Angelica"
}
dependencies {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.14:dev")
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.5.78")
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.7") { transitive = false }
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:0.4.0") { transitive = false }
compileOnly("org.projectlombok:lombok:1.18.42") { transitive = false }
annotationProcessor("org.projectlombok:lombok:1.18.42")
compileOnly("org.projectlombok:lombok:1.18.22") { transitive = false }
annotationProcessor("org.projectlombok:lombok:1.18.22")
// Use modern models+textures to inject QuadProviders
devOnlyNonPublishable('com.github.GTNewHorizons:TX-Loader:1.7.2:dev')
// Iris Shaders
compileOnly('org.jetbrains:annotations:26.0.2')
api("com.github.GTNewHorizons:GTNHLib:0.9.6:dev")
compileOnly('org.jetbrains:annotations:24.0.1')
api("com.github.GTNewHorizons:GTNHLib:0.5.20:dev")
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-25.g3a943bb") {
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-4.g6b42bca") {
exclude module: "antlr4" // we only want to shadow the runtime module
}
shadowImplementation("org.antlr:antlr4-runtime:4.13.2")
compileOnly "org.apache.ant:ant:1.8.2"
// Celeritas (transitive=false: deps provided by GTNHLib/runtime)
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.2-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.2-GTNH") { transitive = false }
// Because who doesn't want NEI
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.8.35-GTNH:dev')
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.4.7:dev')
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.6.45-GTNH:dev')
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.3.10:dev')
// Notfine Deps
compileOnly("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles-Expanded:2.2.2-GTNH:dev")
compileOnly("com.github.GTNewHorizons:twilightforest:2.7.13:dev") { transitive = false }
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles:1.0.4:dev")
compileOnly("com.github.GTNewHorizons:twilightforest:2.6.35:dev") { transitive = false }
compileOnly(rfg.deobf('curse.maven:witchery-69673:2234410'))
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.14.6-GTNH:dev") { transitive = false }
compileOnly("com.github.GTNewHorizons:Natura:2.8.9:dev")
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.12.14-GTNH:dev") { transitive = false }
compileOnly("com.github.GTNewHorizons:Natura:2.7.5:dev")
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.8.4:dev")
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.6.4:dev")
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.3:dev") { transitive = false }
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.1:dev") { transitive = false }
compileOnly(rfg.deobf('maven.modrinth:backhand:1.4.1')) { transitive = false }
compileOnly("com.falsepattern:chunkapi-mc1.7.10:0.5.1:api") { transitive = false }
compileOnly("com.falsepattern:endlessids-mc1.7.10:1.5-beta0003:dev") { transitive = false }
@@ -107,18 +94,15 @@ dependencies {
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev") // Mixin Version
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.6:dev") // Mixin Version
compileOnly("com.github.GTNewHorizons:NotEnoughIds-Legacy:1.4.7:dev") // ASM Version
compileOnly(rfg.deobf("curse.maven:ntm-space-1121235:6974420"))
compileOnly(rfg.deobf("curse.maven:campfirebackport-387444:4611675"))
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
// 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")))
compileOnly("com.github.GTNewHorizons:HoloInventory:2.4.13-GTNH:dev")
runtimeOnlyNonPublishable(rfg.deobf("CoreTweaks:CoreTweaks:0.3.3.2"))
// Hodgepodge
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
@@ -126,18 +110,13 @@ dependencies {
// Better Crashes
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.0-GTNH:dev")
afterEvaluate {
dependencies {
testImplementation(platform('org.junit:junit-bom:5.14.1'))
testImplementation('org.junit.jupiter:junit-jupiter')
testImplementation("org.mockito:mockito-core:3.+")
testImplementation("org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209")
// Tests
testImplementation(platform('org.junit:junit-bom:5.9.2'))
testImplementation('org.junit.jupiter:junit-jupiter')
testImplementation("org.mockito:mockito-core:3.+")
testImplementation("org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209")
testImplementation('org.junit.platform:junit-platform-engine')
testImplementation('org.junit.platform:junit-platform-reporting')
testImplementation('org.junit.platform:junit-platform-launcher')
}
devOnlyNonPublishable("com.github.javaparser:javaparser-symbol-solver-core:3.26.2") {
exclude module: "guava"
}
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
}
+2084
View File
File diff suppressed because it is too large. Load diff
+16 -43
View File
@@ -14,7 +14,7 @@ modId = angelica
modGroup = com.gtnewhorizons.angelica
# Whether to use modGroup as the maven publishing group.
# When false, com.github.GTNewHorizons is used.
# Due to a history of using JitPack, the default is com.github.GTNewHorizons for all mods.
useModGroupForPublishing = false
# Updates your build.gradle and settings.gradle automatically whenever an update is available.
@@ -39,33 +39,9 @@ remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/co
# `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE.
developmentEnvironmentUserName = Developer
# Enables modern Java syntax support. Valid values:
# - false: No modern syntax, Java 8 only
# - jabel: Jabel syntax-only support, compiles to J8 bytecode
# - jvmDowngrader: Full modern Java via JVM Downgrader (syntax + stdlib APIs)
# - modern: Native modern Java bytecode, no downgrading
enableModernJavaSyntax = jvmDowngrader
# If set, ignores the above setting and compiles with the given toolchain. This may cause unexpected issues,
# and should *not* be used in most situations. -1 disables this.
# forceToolchainVersion = -1
# Target JVM version for JVM Downgrader bytecode downgrading.
# Only used when enableModernJavaSyntax = jvmDowngrader
# downgradeTargetVersion = 8
# Comma-separated list of Java versions for multi-release jar support (JVM Downgrader only).
# Classes will be available in META-INF/versions/N/ for each version N in this list.
# Default: "21,25" (J25+ gets native classes, J21-24 gets partial downgrade, J8-20 gets full downgrade).
jvmDowngraderMultiReleaseVersions = 17, 21, 25
# Specifies how JVM Downgrader API stubs are provided. Options:
# - shade: Shade minimized stubs into the jar
# - gtnhlib: GTNHLib provides stubs at runtime (adds version constraint)
# - external: Another dependency provides stubs (no constraint, no warning)
# - (empty): Warning reminding you to configure stubs
# Note: 'shade' option requires you to verify license compliance, see: https://github.com/unimined/JvmDowngrader/blob/main/LICENSE.md
jvmDowngraderStubsProvider = gtnhlib
# Enables using modern Java syntax (up to version 17) via Jabel, while still targeting JVM 8.
# See https://github.com/bsideup/jabel for details on how this works.
enableModernJavaSyntax = true
# Enables injecting missing generics into the decompiled source code for a better coding experience.
# Turns most publicly visible List, Map, etc. into proper List<E>, Map<K, V> types.
@@ -79,13 +55,22 @@ generateGradleTokenClass = com.gtnewhorizons.angelica.Tags
# Name of the token containing the project's current version to generate/replace.
gradleTokenVersion = VERSION
# [DEPRECATED] Mod ID replacement token.
gradleTokenModId =
# [DEPRECATED] Mod name replacement token.
gradleTokenModName =
# [DEPRECATED] Mod Group replacement token.
gradleTokenGroupName =
# [DEPRECATED]
# Multiple source files can be defined here by providing a comma-separated list: Class1.java,Class2.java,Class3.java
# public static final String VERSION = "GRADLETOKEN_VERSION";
# The string's content will be replaced with your mod's version when compiled. You should use this to specify your mod's
# version in @Mod([...], version = VERSION, [...]).
# Leave these properties empty to skip individual token replacements.
# replaceGradleTokenInFile =
replaceGradleTokenInFile =
# In case your mod provides an API for other mods to implement you may declare its package here. Otherwise, you can
# leave this property empty.
@@ -111,9 +96,7 @@ usesMixinDebug = false
# Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise.
mixinPlugin =
# Specify the package that contains all of your Mixins. The package must exist or
# the build will fail. If you have a package property defined in your mixins.<modid>.json,
# it must match with this or the build will fail.
# Specify the package that contains all of your Mixins. You may only place Mixins in this package or the build will fail!
mixinsPackage = mixins
# Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin!
@@ -168,7 +151,7 @@ modrinthProjectId = angelica
# type can be one of [project, version],
# and the name is the Modrinth project or version slug/id of the other mod.
# Example: required-project:fplib;optional-project:gasstation;incompatible-project:gregtech
# Note: UniMixins is automatically set as a required dependency if usesMixins = true.
# Note: GTNH Mixins is automatically set as a required dependency if usesMixins = true
modrinthRelations = required-project\:gtnhlib
# Publishing to CurseForge requires you to set the CURSEFORGE_TOKEN environment variable to one of your CurseForge API tokens.
@@ -189,12 +172,6 @@ curseForgeRelations = requiredDependency\:gtnhlib
# projects. New projects should not use this parameter.
# customArchiveBaseName =
# Optional parameter to customize the default working directory used by the runClient* tasks. Relative to the project directory.
# runClientWorkingDirectory = run/client
# Optional parameter to customize the default working directory used by the runServer* tasks. Relative to the project directory.
# runServerWorkingDirectory = run/server
# Optional parameter to have the build automatically fail if an illegal version is used.
# This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'.
# The check is ONLY performed if the version is a git tag.
@@ -224,7 +201,3 @@ disableSpotless = true
# This is meant to be set in $HOME/.gradle/gradle.properties.
# ideaCheckSpotlessOnBuild = true
# Non-GTNH properties
org.gradle.configuration-cache = true
org.gradle.jvmargs = -Xmx2g
org.gradle.parallel = true
-12
View File
@@ -1,12 +0,0 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/5ee463876563a46fff651cce1bc53a4c/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/5ee463876563a46fff651cce1bc53a4c/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e1118b4c7c2858d28154d086dbff010e/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/59a9771cad43219260d9aac9a8ec4d6a/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/5ee463876563a46fff651cce1bc53a4c/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/832229f0f6f0be60ed817c5a5e5848eb/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/b96cb7670c095245220e7d01d5927b58/redirect
toolchainVersion=25
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.2.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+8 -4
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -86,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -114,6 +115,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -171,6 +173,7 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -203,14 +206,15 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
Vendored
+2 -1
View File
@@ -70,10 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
+15 -5
View File
@@ -8,11 +8,21 @@ repositories {
includeGroup 'com.falsepattern'
}
}
maven {
name = 'mega'
url = 'https://mvn.falsepattern.com/gtmega_releases/'
content {
includeGroup 'mega'
exclusiveContent {
forRepository {
ivy {
name = 'CoreTweaks releases'
url = 'https://github.com/makamys/CoreTweaks/releases/download/'
patternLayout {
artifact '[revision]/[module]-1.7.10-[revision]+nomixin(-[classifier])(.[ext])'
}
metadataSources {
artifact()
}
}
}
filter {
includeGroup('CoreTweaks')
}
}
maven {
+3 -3
View File
@@ -2,8 +2,8 @@ pluginManagement {
repositories {
maven {
// RetroFuturaGradle
name = "GTNH Maven"
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
name "GTNH Maven"
url "https://nexus.gtnewhorizons.com/repository/public/"
mavenContent {
includeGroup("com.gtnewhorizons")
includeGroupByRegex("com\\.gtnewhorizons\\..+")
@@ -16,5 +16,5 @@ pluginManagement {
}
plugins {
id("com.gtnewhorizons.gtnhsettingsconvention") version("2.0.18")
id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.29'
}
+120
View File
@@ -0,0 +1,120 @@
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.visitor.VoidVisitorWithDefaults;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Used to generate gl_functions.csv. Which contains a mapping of OpenGL functions provided by LWJGL
* corresponding to the earliest GL version they appear in.
*/
public class GLFunctionParser {
final static String glPath = "util/gl"; // Path to LWJGL OpenGL bindings source code
public static void main(String[] args) throws FileNotFoundException {
HashMap<String, Set<String>> byFunction = new HashMap<>();
File dir = new File(glPath);
for (File file : dir.listFiles()) {
final String filename = file.getName();
if (filename.endsWith(".java") && filename.startsWith("GL") && Character.isDigit(filename.charAt(2))) {
Path p = Paths.get(filename);
String parsedFile = removeFileExtension(p.getFileName().toString());
CompilationUnit cu = StaticJavaParser.parse(file);
cu.accept(new VoidVisitorWithDefaults<>() {
@Override
public void defaultAction(Node n, Object arg) {
for( Node child : n.getChildNodes()) {
if(child instanceof ClassOrInterfaceDeclaration cd) {
for (MethodDeclaration md : cd.getMethods()) {
String methodName = md.getNameAsString() + "(";
for (Parameter p : md.getParameters()) {
methodName = methodName + p.getTypeAsString();
}
methodName = methodName + ")";
if(methodName.startsWith("n")) continue;
Set<String> functionSet = byFunction.computeIfAbsent(methodName, k -> new HashSet<>());
functionSet.add(parsedFile);
}
}
}
}
}, null);
}
}
Map<String, String> functionVersionMap = new HashMap<>();
for(String function : byFunction.keySet()) {
Set<String> versionsForFunction = byFunction.get(function);
int lowestInt = 46;
String lowestString = "GL46";
for(String version : versionsForFunction) {
int i = Integer.parseInt(version.substring(2, 4));
if (i < lowestInt) {
lowestInt = i;
lowestString = version;
}
}
functionVersionMap.put(function, lowestString);
}
List<String[]> outputLines = new ArrayList<>();
for (Map.Entry<String, String> entry : functionVersionMap.entrySet()) {
outputLines.add(new String[] {entry.getKey(), entry.getValue()});
}
File output = new File("output.csv");
try (PrintWriter writer = new PrintWriter(output)) {
outputLines.stream().map(GLSMFunctionComparison::convertToCSV).forEach(writer::println);
}
}
public static String convertToCSV(String[] data) {
return Stream.of(data).map(GLSMFunctionComparison::escapeSpecialCharacters).collect(Collectors.joining(","));
}
public static String escapeSpecialCharacters(String data) {
if (data == null) {
throw new IllegalArgumentException("Input data cannot be null");
}
String escapedData = data.replaceAll("\\R", " ");
if (data.contains(",") || data.contains("\"") || data.contains("'")) {
data = data.replace("\"", "\"\"");
escapedData = "\"" + data + "\"";
}
return escapedData;
}
public static String removeFileExtension(String filename) {
if (filename == null || filename.isEmpty()) {
return filename;
}
String extPattern = "(?<!^)[.]" + "[^.]*$";
return filename.replaceAll(extPattern, "");
}
}
+135
View File
@@ -0,0 +1,135 @@
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.Parameter;
import com.github.javaparser.ast.visitor.VoidVisitorWithDefaults;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Used to compare OpenGL functions from gl_functions.csv to current GLStateManager
* source code to determine what functions have an implementation in GLSM, and what ones don't.
* Only considers functions which appear in GL 2.1 and lower
*/
public class GLSMFunctionComparison {
/**
* If set to true, then the outputted CSV will show functions which do have an implementation in GLSM
*/
final static boolean INCLUDE_TRUE = false;
final static String glFunctionsCsvPath = "gl_functions.csv";
final static String glsmPath = "src/main/java/com/gtnewhorizons/angelica/glsm/GLStateManager.java";
public static Map<String, String> parseGLFunctions() {
Map<String, String> map = new HashMap<>();
try (BufferedReader br = new BufferedReader(new FileReader(glFunctionsCsvPath))) {
String line;
while ((line = br.readLine()) != null) {
String[] values = line.split(",");
if (values.length == 2) {
String key = values[0].trim();
String value = values[1].trim();
int versionInt = Integer.parseInt(value.substring(2, 4));
if (versionInt < 30) map.put(key, value);
}
}
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
public static void main(String[] args) throws FileNotFoundException {
//GLSM will fail to parse without increasing language level
StaticJavaParser.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17);
Map<String, String> functionMap = parseGLFunctions();
Set<String> glsmFunctions = new HashSet<>();
File glsmFile = new File(glsmPath);
CompilationUnit cu = StaticJavaParser.parse(glsmFile);
cu.accept(new VoidVisitorWithDefaults<>() {
@Override
public void defaultAction(Node n, Object arg) {
for (Node child : n.getChildNodes()) {
if (child instanceof ClassOrInterfaceDeclaration cd) {
for (MethodDeclaration md : cd.getMethods()) {
String methodName = md.getNameAsString() + "(";
for (Parameter p : md.getParameters()) {
methodName = methodName + p.getTypeAsString();
}
methodName = methodName + ")";
glsmFunctions.add(methodName);
}
}
}
}
}, null);
Map<String, String> functionGLSMMap = new HashMap<>();
for(String function : functionMap.keySet()) {
if (!glsmFunctions.contains(function)) {
functionGLSMMap.put(function, "false");
} else if (INCLUDE_TRUE) {
functionGLSMMap.put(function, "true");
}
}
List<String[]> outputLines = new ArrayList<>();
for (Map.Entry<String, String> entry : functionGLSMMap.entrySet()) {
outputLines.add(new String[] {entry.getKey(), functionMap.get(entry.getKey()), entry.getValue()});
}
File output = new File("glsm_function_comparison.csv");
try (PrintWriter writer = new PrintWriter(output)) {
outputLines.stream().map(GLSMFunctionComparison::convertToCSV).forEach(writer::println);
}
}
public static String convertToCSV(String[] data) {
return Stream.of(data).map(GLSMFunctionComparison::escapeSpecialCharacters).collect(Collectors.joining(","));
}
public static String escapeSpecialCharacters(String data) {
if (data == null) {
throw new IllegalArgumentException("Input data cannot be null");
}
String escapedData = data.replaceAll("\\R", " ");
if (data.contains(",") || data.contains("\"") || data.contains("'")) {
data = data.replace("\"", "\"\"");
escapedData = "\"" + data + "\"";
}
return escapedData;
}
public static String removeFileExtension(String filename) {
if (filename == null || filename.isEmpty()) {
return filename;
}
String extPattern = "(?<!^)[.]" + "[^.]*$";
return filename.replaceAll(extPattern, "");
}
}
@@ -2,26 +2,21 @@ package com.gtnewhorizons.angelica;
import com.gtnewhorizon.gtnhmixins.ILateMixinLoader;
import com.gtnewhorizon.gtnhmixins.LateMixin;
import com.gtnewhorizon.gtnhmixins.builders.IMixins;
import com.gtnewhorizons.angelica.mixins.Mixins;
import java.util.List;
import java.util.Set;
@LateMixin
@SuppressWarnings("unused")
public class AngelicaLateMixins implements ILateMixinLoader {
@Override
public String getMixinConfig() {
int v = Runtime.version().feature();
if (v >= 21) return "mixins.angelica.late.j21.json";
if (v >= 17) return "mixins.angelica.late.j17.json";
return "mixins.angelica.late.json";
}
@Override
public List<String> getMixins(Set<String> loadedMods) {
return IMixins.getLateMixins(Mixins.class, loadedMods);
return Mixins.getLateMixins(loadedMods);
}
}
@@ -1,7 +1,5 @@
package com.gtnewhorizons.angelica;
import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
import com.gtnewhorizons.angelica.common.BlockError;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.proxy.CommonProxy;
@@ -9,30 +7,20 @@ import com.gtnewhorizons.angelica.utils.AnimationMode;
import com.gtnewhorizons.angelica.utils.ManagedEnum;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.SidedProxy;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
@Mod(
modid = MOD_ID,
modid = "angelica",
name = "Angelica",
version = Tags.VERSION,
dependencies = """
required-after:gtnhlib@[0.8.21,);\
before:lwjgl3ify@[1.5.3,);\
after:hodgepodge@[2.4.4,);\
after:CodeChickenCore@[1.2.0,);\
after:archaicfix@[0.7.0,);\
""",
dependencies = " before:lwjgl3ify@[1.5.3,);" + " after:hodgepodge@[2.4.4,);" + " after:CodeChickenCore@[1.2.0,);"
+ " after:archaicfix@[0.7.0,);" + " required-after:gtnhlib@[0.4.2,);",
acceptedMinecraftVersions = "[1.7.10]",
acceptableRemoteVersions = "*",
guiFactory = "com.gtnewhorizons.angelica.config.AngelicaGuiConfigFactory")
public class AngelicaMod {
public static final String MOD_ID = "angelica";
public static final Logger LOGGER = LogManager.getLogger("Angelica");
@SidedProxy(clientSide = "com.gtnewhorizons.angelica.proxy.ClientProxy", serverSide = "com.gtnewhorizons.angelica.proxy.CommonProxy")
public static CommonProxy proxy;
@@ -1,8 +0,0 @@
package com.gtnewhorizons.angelica.api;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
public interface IBlockAccessExtended extends IBlockAccess {
World getWorld();
}
@@ -1,27 +0,0 @@
package com.gtnewhorizons.angelica.api;
import com.gtnewhorizons.angelica.mixins.interfaces.ITexturesCache;
import com.gtnewhorizons.angelica.rendering.celeritas.SpriteExtension;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.util.IIcon;
/**
* Exposes a method to the API that allow mods to add texture update compat
*/
@SuppressWarnings("unused")
public class TextureServices {
@SuppressWarnings("unused")
public static void updateBlockTextureAnimation(IIcon icon, RenderBlocks renderBlocks) {
if (renderBlocks instanceof ITexturesCache texturesCache) {
texturesCache.getRenderedTextures().add(icon);
}
}
@SuppressWarnings("unused")
public static void updateTextureAnimation(IIcon icon) {
if (icon instanceof SpriteExtension ext) {
ext.celeritas$markActive();
}
}
}
@@ -1,31 +1,25 @@
package com.gtnewhorizons.angelica.client.font;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
import it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.coderbot.iris.gl.program.Program;
import net.coderbot.iris.gl.program.ProgramBuilder;
import jss.util.RandomXoshiro256StarStar;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.MathHelper;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.io.IOUtils;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
/**
* A batching replacement for {@code FontRenderer}
*
@@ -35,6 +29,8 @@ public class BatchingFontRenderer {
/** The underlying FontRenderer object that's being accelerated */
protected FontRenderer underlying;
/** Cached locations for each unicode page atlas */
private final ResourceLocation[] unicodePageLocations;
/** Array of width of all the characters in default.png */
protected int[] charWidth = new int[256];
/** Array of the start/end column (in upper/lower nibble) for every glyph in the /font directory. */
@@ -46,59 +42,50 @@ public class BatchingFontRenderer {
private int[] colorCode;
/** Location of the primary font atlas to bind. */
protected final ResourceLocation locationFontTexture;
/** The RenderEngine used to load and setup glyph textures. */
private final TextureManager renderEngine;
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
private final int AAMode;
private final int AAStrength;
private final int texBoundAttrLocation;
private final int fontShaderId;
/** The full list of characters present in the default Minecraft font, excluding the Unicode font */
@SuppressWarnings("UnnecessaryUnicodeEscape")
private static final String MCFONT_CHARS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
final boolean isSGA;
final boolean isSplash;
private static final short[] MCFONT_ASCII_LUT = new short[512];
private static final Char2ShortOpenHashMap MCFONT_UNI_LUT = new Char2ShortOpenHashMap();
private static class FontAAShader {
private static Program fontShader = null;
public static Program getProgram() {
if (fontShader == null) {
String vsh, fsh;
try {
fsh = new String(IOUtils.toByteArray(Objects.requireNonNull(FontAAShader.class.getResourceAsStream("/assets/angelica/shaders/fontFilter.fsh"))), StandardCharsets.UTF_8);
vsh = new String(IOUtils.toByteArray(Objects.requireNonNull(FontAAShader.class.getResourceAsStream("/assets/angelica/shaders/fontFilter.vsh"))), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException(e);
}
ProgramBuilder builder = ProgramBuilder.begin("fontFilter", vsh, null, fsh, ImmutableSet.of(0));
fontShader = builder.build();
static {
Arrays.fill(MCFONT_ASCII_LUT, (short) -1);
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
char ch = MCFONT_CHARS.charAt(i);
if (ch < MCFONT_ASCII_LUT.length) {
MCFONT_ASCII_LUT[ch] = i;
} else {
MCFONT_UNI_LUT.put(ch, i);
}
return fontShader;
}
}
public BatchingFontRenderer(FontRenderer underlying, int[] charWidth, byte[] glyphWidth,
int[] colorCode, ResourceLocation locationFontTexture) {
public static int lookupMcFontPosition(char ch) {
if (ch < MCFONT_ASCII_LUT.length) {
return MCFONT_ASCII_LUT[ch];
} else {
return MCFONT_UNI_LUT.getOrDefault(ch, (short) -1);
}
}
public BatchingFontRenderer(FontRenderer underlying, ResourceLocation[] unicodePageLocations, int[] charWidth,
byte[] glyphWidth, int[] colorCode, ResourceLocation locationFontTexture, TextureManager renderEngine) {
this.underlying = underlying;
this.unicodePageLocations = unicodePageLocations;
this.charWidth = charWidth;
this.glyphWidth = glyphWidth;
this.colorCode = colorCode;
this.locationFontTexture = locationFontTexture;
this.renderEngine = renderEngine;
for (int i = 0; i < 64; i++) {
batchCommandPool.add(new FontDrawCmd());
}
this.isSGA = Objects.equals(this.locationFontTexture.getResourcePath(), "textures/font/ascii_sga.png");
this.isSplash = FontStrategist.isSplashFontRendererActive(underlying);
FontProviderMC.get(this.isSGA).charWidth = this.charWidth;
FontProviderMC.get(this.isSGA).locationFontTexture = this.locationFontTexture;
FontProviderUnicode.get().glyphWidth = this.glyphWidth;
//noinspection deprecation
fontShaderId = FontAAShader.getProgram().getProgramId();
AAMode = GL20.glGetUniformLocation(fontShaderId, "aaMode");
AAStrength = GL20.glGetUniformLocation(fontShaderId, "strength");
texBoundAttrLocation = GL20.glGetAttribLocation(fontShaderId, "texBounds");
}
// === Batched rendering
@@ -110,29 +97,24 @@ public class BatchingFontRenderer {
private static final int INITIAL_BATCH_SIZE = 256;
private static final ResourceLocation DUMMY_RESOURCE_LOCATION = new ResourceLocation("angelica$dummy",
"this is invalid!");
private FloatBuffer batchVtxPositions = 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 FloatBuffer batchVtxPositions = BufferUtils.createFloatBuffer(INITIAL_BATCH_SIZE * 2);
private ByteBuffer batchVtxColors = BufferUtils.createByteBuffer(INITIAL_BATCH_SIZE * 4);
private FloatBuffer batchVtxTexCoords = BufferUtils.createFloatBuffer(INITIAL_BATCH_SIZE * 2);
private IntBuffer batchIndices = BufferUtils.createIntBuffer(INITIAL_BATCH_SIZE / 2 * 3);
private final ObjectArrayList<FontDrawCmd> batchCommands = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
private final ObjectArrayList<FontDrawCmd> batchCommandPool = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
private int blendSrcRGB = GL11.GL_SRC_ALPHA;
private int blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
/** */
private void pushVtx(float x, float y, int rgba, float u, float v, float uMin, float uMax, float vMin, float vMax) {
private void pushVtx(float x, float y, int rgba, float u, float v) {
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);
batchVtxPositions = CompatMemoryUtil.memReallocDirect(batchVtxPositions, newCap * 2);
batchVtxColors = CompatMemoryUtil.memReallocDirect(batchVtxColors, newCap * 4);
batchVtxTexCoords = CompatMemoryUtil.memReallocDirect(batchVtxTexCoords, newCap * 2);
final int oldIdxCap = batchIndices.capacity();
final int newIdxCap = oldIdxCap * 2;
batchIndices = memRealloc(batchIndices, newIdxCap);
batchIndices = CompatMemoryUtil.memReallocDirect(batchIndices, newIdxCap);
}
final int idx = vtxWriterIndex;
final int idx2 = idx * 2;
@@ -146,28 +128,15 @@ public class BatchingFontRenderer {
batchVtxColors.put(idx4 + 3, (byte) ((rgba >> 24) & 0xFF));
batchVtxTexCoords.put(idx2, u);
batchVtxTexCoords.put(idx2 + 1, v);
batchVtxTexBounds.put(idx4, uMin);
batchVtxTexBounds.put(idx4 + 1, uMax);
batchVtxTexBounds.put(idx4 + 2, vMin);
batchVtxTexBounds.put(idx4 + 3, vMax);
vtxWriterIndex++;
}
private void pushUntexRect(float x, float y, float w, float h, int rgba) {
final int vtxId = vtxWriterIndex;
pushVtx(x, y, rgba, 0, 0, 0, 0, 0, 0);
pushVtx(x, y + h, rgba, 0, 0, 0, 0, 0, 0);
pushVtx(x + w, y, rgba, 0, 0, 0, 0, 0, 0);
pushVtx(x + w, y + h, rgba, 0, 0, 0, 0, 0, 0);
pushQuadIdx(vtxId);
}
private void pushTexRect(float x, float y, float w, float h, float itOff, int rgba, float uStart, float vStart, float uSz, float vSz) {
final int vtxId = vtxWriterIndex;
pushVtx(x + itOff, y, rgba, uStart, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x - itOff, y + h, rgba, uStart, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x + itOff + w, y, rgba, uStart + uSz, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x - itOff + w, y + h, rgba, uStart + uSz, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x, y, rgba, 0, 0);
pushVtx(x, y + h, rgba, 0, 0);
pushVtx(x + w, y, rgba, 0, 0);
pushVtx(x + w, y + h, rgba, 0, 0);
pushQuadIdx(vtxId);
}
@@ -184,7 +153,7 @@ public class BatchingFontRenderer {
return idx;
}
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture, boolean isUnicode) {
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture) {
if (!batchCommands.isEmpty()) {
final FontDrawCmd lastCmd = batchCommands.get(batchCommands.size() - 1);
final int prevEndVtx = lastCmd.startVtx + lastCmd.idxCount;
@@ -200,7 +169,7 @@ public class BatchingFontRenderer {
}
}
final FontDrawCmd cmd = batchCommandPool.pop();
cmd.reset(startIdx, idxCount, texture, isUnicode);
cmd.reset(startIdx, idxCount, texture);
batchCommands.add(cmd);
}
@@ -208,14 +177,12 @@ public class BatchingFontRenderer {
public int startVtx;
public int idxCount;
public boolean isUnicode;
public ResourceLocation texture;
public void reset(int startVtx, int vtxCount, ResourceLocation texture, boolean isUnicode) {
public void reset(int startVtx, int vtxCount, ResourceLocation texture) {
this.startVtx = startVtx;
this.idxCount = vtxCount;
this.texture = texture;
this.isUnicode = isUnicode;
}
@Override
@@ -274,12 +241,7 @@ public class BatchingFontRenderer {
}
}
int lastActiveProgram;
int fontAAModeLast = -1;
int fontAAStrengthLast = -1;
private void flushBatch() {
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
// Sort&Draw
batchCommands.sort(FontDrawCmd.DRAW_ORDER_COMPARATOR);
@@ -291,26 +253,9 @@ public class BatchingFontRenderer {
GLStateManager.enableTexture();
GLStateManager.enableAlphaTest();
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(blendSrcRGB, blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glShadeModel(GL11.GL_FLAT);
final boolean canUseAA = FontConfig.fontAAMode != 0 && prevProgram == 0;
if (canUseAA) {
GL20.glVertexAttribPointer(texBoundAttrLocation, 4, false, 0, batchVtxTexBounds);
GL20.glEnableVertexAttribArray(texBoundAttrLocation);
lastActiveProgram = prevProgram;
GLStateManager.glUseProgram(fontShaderId);
if (FontConfig.fontAAMode != fontAAModeLast) {
fontAAModeLast = FontConfig.fontAAMode;
GL20.glUniform1i(AAMode, FontConfig.fontAAMode);
}
if (FontConfig.fontAAStrength != fontAAStrengthLast) {
fontAAStrengthLast = FontConfig.fontAAStrength;
GL20.glUniform1f(AAStrength, FontConfig.fontAAStrength / 120.f);
}
}
GL13.glClientActiveTexture(GL13.GL_TEXTURE0);
GL11.glTexCoordPointer(2, 0, batchVtxTexCoords);
GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GL11.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 0, batchVtxColors);
@@ -319,9 +264,6 @@ public class BatchingFontRenderer {
GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GL11.glDisableClientState(GL11.GL_NORMAL_ARRAY);
GL11.glNormal3f(0.0f, 0.0f, 1.0f);
// Use plain for loop to avoid allocations
final FontDrawCmd[] cmdsData = batchCommands.elements();
final int cmdsSize = batchCommands.size();
@@ -341,13 +283,8 @@ public class BatchingFontRenderer {
}
batchIndices.limit(cmd.startVtx + cmd.idxCount);
batchIndices.position(cmd.startVtx);
GL11.glDrawElements(GL11.GL_TRIANGLES, batchIndices);
}
if (canUseAA) {
GLStateManager.glUseProgram(lastActiveProgram);
GL20.glDisableVertexAttribArray(texBoundAttrLocation);
}
GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GL11.glDisableClientState(GL11.GL_COLOR_ARRAY);
@@ -375,28 +312,17 @@ public class BatchingFontRenderer {
return (what >= fromInclusive) && (what <= toInclusive);
}
public boolean forceDefaults() {
return this.isSGA || this.isSplash;
}
public float getGlyphScaleX() {
return forceDefaults() ? 1 : (float) (FontConfig.glyphScale * Math.pow(2, FontConfig.glyphAspect));
}
public float getGlyphScaleY() {
return forceDefaults() ? 1 : (float) (FontConfig.glyphScale / Math.pow(2, FontConfig.glyphAspect));
}
public float getGlyphSpacing() {
return forceDefaults() ? 0 : FontConfig.glyphSpacing;
}
public float getWhitespaceScale() {
return forceDefaults() ? 1 : FontConfig.whitespaceScale;
}
public float getShadowOffset() {
return forceDefaults() ? 1 : FontConfig.fontShadowOffset;
private ResourceLocation getUnicodePageLocation(int page) {
final ResourceLocation lookup = unicodePageLocations[page];
if (lookup == null) {
final ResourceLocation rl = new ResourceLocation(String.format(
"textures/font/unicode_page_%02x.png",
page));
unicodePageLocations[page] = rl;
return rl;
} else {
return lookup;
}
}
private static final char FORMATTING_CHAR = 167; // §
@@ -409,9 +335,6 @@ public class BatchingFontRenderer {
}
final int shadowColor = (color & 0xfcfcfc) >> 2 | color & 0xff000000;
FontProviderMC.get(this.isSGA).charWidth = this.charWidth;
FontProviderMC.get(this.isSGA).locationFontTexture = this.locationFontTexture;
this.beginBatch();
float curX = anchorX;
try {
@@ -431,14 +354,10 @@ public class BatchingFontRenderer {
boolean curStrikethrough = false;
boolean curUnderline = false;
float glyphScaleY = getGlyphScaleY();
float heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY / 2);
final float underlineY = heightNorth + (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY;
final float underlineY = anchorY + underlying.FONT_HEIGHT - 1.0f;
float underlineStartX = 0.0f;
float underlineEndX = 0.0f;
final float strikethroughY = heightNorth + ((float) (underlying.FONT_HEIGHT / 2) - 1.0f) * glyphScaleY;
final float strikethroughY = anchorY + (float) (underlying.FONT_HEIGHT / 2) - 1.0F;
float strikethroughStartX = 0.0f;
float strikethroughEndX = 0.0f;
@@ -450,8 +369,8 @@ public class BatchingFontRenderer {
if (curUnderline && underlineStartX != underlineEndX) {
final int ulIdx = idxWriterIndex;
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
pushDrawCmd(ulIdx, 6, null, false);
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, 1.0f, curColor);
pushDrawCmd(ulIdx, 6, null);
underlineStartX = underlineEndX;
}
if (curStrikethrough && strikethroughStartX != strikethroughEndX) {
@@ -460,9 +379,9 @@ public class BatchingFontRenderer {
strikethroughStartX,
strikethroughY,
strikethroughEndX - strikethroughStartX,
glyphScaleY,
1.0f,
curColor);
pushDrawCmd(ulIdx, 6, null, false);
pushDrawCmd(ulIdx, 6, null);
strikethroughStartX = strikethroughEndX;
}
@@ -507,89 +426,148 @@ public class BatchingFontRenderer {
continue;
}
if (FontConfig.enableCustomFont && FontConfig.enableGlyphReplacements) {
String chrReplacement = GlyphReplacements.customGlyphs.get(String.valueOf(chr));
if (chrReplacement != null) {
char replacement = chrReplacement.charAt(0);
boolean isReplacementCharAvailable =
FontProviderCustom.getPrimary().isGlyphAvailable(replacement)
|| FontProviderCustom.getFallback().isGlyphAvailable(replacement);
if (isReplacementCharAvailable) {
chr = replacement;
}
}
int lutIndex = lookupMcFontPosition(chr);
if (curRandom && lutIndex != -1) {
int randomReplacementIndex;
do {
randomReplacementIndex = fontRandom.nextInt(this.charWidth.length);
} while (this.charWidth[lutIndex] != this.charWidth[randomReplacementIndex]);
lutIndex = randomReplacementIndex;
chr = MCFONT_CHARS.charAt(lutIndex);
}
if (curRandom) {
chr = FontProviderMC.get(this.isSGA).getRandomReplacement(chr);
}
FontProvider fontProvider = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, unicodeFlag);
heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY * fontProvider.getYScaleMultiplier() / 2);
float heightSouth = (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY * fontProvider.getYScaleMultiplier();
final float shadowOffset = unicodeFlag ? 0.5F : 1.0F;
// Check ASCII space, NBSP, NNBSP
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
curX += 4 * this.getWhitespaceScale();
curX += 4;
continue;
}
final float uStart = fontProvider.getUStart(chr);
final float vStart = fontProvider.getVStart(chr);
final float xAdvance = fontProvider.getXAdvance(chr) * getGlyphScaleX();
final float glyphW = fontProvider.getGlyphW(chr) * getGlyphScaleX();
final float uSz = fontProvider.getUSize(chr);
final float vSz = fontProvider.getVSize(chr);
final float uStart;
final float vStart;
final float xAdvance;
final float glyphW;
final float uSz;
final float vSz;
final float itOff = curItalic ? 1.0F : 0.0F; // italic offset
final float shadowOffset = fontProvider.getShadowOffset();
final int shadowCopies = FontConfig.shadowCopies;
final int boldCopies = FontConfig.boldCopies;
final ResourceLocation texture = fontProvider.getTexture(chr);
final ResourceLocation texture;
if (lutIndex == -1 || unicodeFlag) {
if (glyphWidth[chr] == 0) {
continue;
}
// Draw unicode char
final int uniPage = chr / 256;
texture = getUnicodePageLocation(uniPage);
final int startColumn = this.glyphWidth[chr] >>> 4;
final int endColumn = this.glyphWidth[chr] & 15;
final float startColumnF = (float) startColumn;
final float endColumnF = (float) (endColumn + 1);
uStart = ((float) (chr % 16 * 16) + startColumnF) / 256.0f;
vStart = ((float) ((chr & 255) / 16 * 16)) / 256.0f;
final float chrWidth = endColumnF - startColumnF - 0.02F;
glyphW = chrWidth / 2.0f + 1.0f;
xAdvance = (endColumnF - startColumnF) / 2.0F + 1.0F;
uSz = chrWidth / 256.0f;
vSz = 15.98f / 256.0f;
} else {
// Draw "ASCII" char
uStart = ((lutIndex % 16) * 8) / 128.0F;
vStart = (float) ((lutIndex / 16) * 8) / 128.0F;
xAdvance = this.charWidth[lutIndex];
if (xAdvance == 0) {
continue;
}
glyphW = xAdvance - 0.01F;
uSz = (glyphW - 1.0F) / 128.0F;
vSz = 7.99F / 128.0F;
texture = locationFontTexture;
}
final int vtxId = vtxWriterIndex;
final int idxId = idxWriterIndex;
if (enableShadow) {
for (int n = 1; n <= shadowCopies; n++) {
final float shadowOffsetPart = shadowOffset * ((float) n / shadowCopies);
pushTexRect(curX + shadowOffsetPart, heightNorth + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz);
int vtxCount = 0;
if (curBold) {
pushTexRect(curX + 2.0f * shadowOffsetPart, heightNorth + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz);
}
if (enableShadow) {
pushVtx(curX + itOff + shadowOffset, anchorY + shadowOffset, curShadowColor, uStart, vStart);
pushVtx(
curX - itOff + shadowOffset,
anchorY + 7.99F + shadowOffset,
curShadowColor,
uStart,
vStart + vSz);
pushVtx(curX + glyphW - 1.0F + itOff + shadowOffset,
anchorY + shadowOffset,
curShadowColor,
uStart + uSz,
vStart);
pushVtx(curX + glyphW - 1.0F - itOff + shadowOffset,
anchorY + 7.99F + shadowOffset,
curShadowColor,
uStart + uSz,
vStart + vSz);
pushQuadIdx(vtxId + vtxCount);
vtxCount += 4;
if (curBold) {
final float shadowOffset2 = 2.0f * shadowOffset;
pushVtx(curX + itOff + shadowOffset2, anchorY + shadowOffset, curShadowColor, uStart, vStart);
pushVtx(
curX - itOff + shadowOffset2,
anchorY + 7.99F + shadowOffset,
curShadowColor,
uStart,
vStart + vSz);
pushVtx(
curX + glyphW - 1.0F + itOff + shadowOffset2,
anchorY + shadowOffset,
curShadowColor,
uStart + uSz,
vStart);
pushVtx(curX + glyphW - 1.0F - itOff + shadowOffset2,
anchorY + 7.99F + shadowOffset,
curShadowColor,
uStart + uSz,
vStart + vSz);
pushQuadIdx(vtxId + vtxCount);
vtxCount += 4;
}
}
pushTexRect(curX, heightNorth, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz);
pushVtx(curX + itOff, anchorY, curColor, uStart, vStart);
pushVtx(curX - itOff, anchorY + 7.99F, curColor, uStart, vStart + vSz);
pushVtx(curX + glyphW - 1.0F + itOff, anchorY, curColor, uStart + uSz, vStart);
pushVtx(curX + glyphW - 1.0F - itOff, anchorY + 7.99F, curColor, uStart + uSz, vStart + vSz);
pushQuadIdx(vtxId + vtxCount);
vtxCount += 4;
if (curBold) {
for (int n = 1; n <= boldCopies; n++) {
final float shadowOffsetPart = shadowOffset * ((float) n / boldCopies);
pushTexRect(curX + shadowOffsetPart, heightNorth, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz);
}
pushVtx(shadowOffset + curX + itOff, anchorY, curColor, uStart, vStart);
pushVtx(shadowOffset + curX - itOff, anchorY + 7.99F, curColor, uStart, vStart + vSz);
pushVtx(shadowOffset + curX + glyphW - 1.0F + itOff, anchorY, curColor, uStart + uSz, vStart);
pushVtx(shadowOffset + curX + glyphW - 1.0F - itOff,
anchorY + 7.99F,
curColor,
uStart + uSz,
vStart + vSz);
pushQuadIdx(vtxId + vtxCount);
vtxCount += 4;
}
/*
Vertex-per-char counts for different configurations
default: 4
shadow only: 4(1 + shadowCopies)
bold only: 4(1 + boldCopies)
both: 4(1 + 2 * shadowCopies + boldCopies)
*/
int charCount = 1;
if (enableShadow) { charCount += shadowCopies * (curBold ? 2 : 1); }
if (curBold) { charCount += boldCopies; }
final int vtxCount = 4 * charCount;
pushDrawCmd(idxId, vtxCount / 2 * 3, texture, chr > 255);
curX += (xAdvance + (curBold ? shadowOffset : 0.0f)) + getGlyphSpacing();
pushDrawCmd(idxId, vtxCount / 2 * 3, texture);
curX += xAdvance + (curBold ? shadowOffset : 0.0f);
underlineEndX = curX;
strikethroughEndX = curX;
}
if (curUnderline && underlineStartX != underlineEndX) {
final int ulIdx = idxWriterIndex;
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
pushDrawCmd(ulIdx, 6, null, false);
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, 1.0f, curColor);
pushDrawCmd(ulIdx, 6, null);
}
if (curStrikethrough && strikethroughStartX != strikethroughEndX) {
final int ulIdx = idxWriterIndex;
@@ -597,9 +575,9 @@ public class BatchingFontRenderer {
strikethroughStartX,
strikethroughY,
strikethroughEndX - strikethroughStartX,
glyphScaleY,
1.0f,
curColor);
pushDrawCmd(ulIdx, 6, null, false);
pushDrawCmd(ulIdx, 6, null);
}
} finally {
@@ -608,25 +586,4 @@ public class BatchingFontRenderer {
return curX + (enableShadow ? 1.0f : 0.0f);
}
public float getCharWidthFine(char chr) {
if (chr == FORMATTING_CHAR) { return -1; }
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
return 4 * this.getWhitespaceScale();
}
FontProvider fp = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, underlying.getUnicodeFlag());
return fp.getXAdvance(chr) * this.getGlyphScaleX();
}
public void overrideBlendFunc(int srcRgb, int dstRgb) {
blendSrcRGB = srcRgb;
blendDstRGB = dstRgb;
}
public void resetBlendFunc() {
blendSrcRGB = GL11.GL_SRC_ALPHA;
blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
}
}
@@ -1,21 +0,0 @@
package com.gtnewhorizons.angelica.client.font;
import net.minecraft.util.ResourceLocation;
public interface FontProvider {
/**
* For use with §k. Should fetch a character of the same width as provided.
*/
char getRandomReplacement(char chr);
boolean isGlyphAvailable(char chr);
float getUStart(char chr);
float getVStart(char chr);
float getXAdvance(char chr);
float getGlyphW(char chr);
float getUSize(char chr);
float getVSize(char chr);
float getShadowOffset();
ResourceLocation getTexture(char chr);
float getYScaleMultiplier();
}
@@ -1,299 +0,0 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizon.gtnhlib.config.ConfigurationManager;
import com.gtnewhorizons.angelica.config.FontConfig;
import lombok.Setter;
import lombok.Value;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.ITextureObject;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.util.ResourceLocation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.imageio.ImageIO;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Objects;
public final class FontProviderCustom implements FontProvider {
public static final Logger LOGGER = LogManager.getLogger("Angelica");
public static final String FONT_DIR = "fonts/custom/";
static final int ATLAS_SIZE = 128;
static final int ATLAS_COUNT = 512;
private final byte id; // 0 - primary font, 1 - fallback font
private FontAtlas[] fontAtlases = new FontAtlas[ATLAS_COUNT];
private float currentFontQuality = FontConfig.customFontQuality;
@Setter
private Font font;
private FontProviderCustom(byte id) {
this.id = id;
Font[] availableFonts = FontStrategist.getAvailableFonts();
String myFontName = switch (this.id) {
case 0 -> FontConfig.customFontNamePrimary;
case 1 -> FontConfig.customFontNameFallback;
default -> null;
};
if (Objects.equals(myFontName, "(none)")) {
font = null;
return;
}
int fontPos = -1;
for (int i = 0; i < availableFonts.length; i++) {
if (Objects.equals(myFontName, availableFonts[i].getFontName())) {
fontPos = i;
break;
}
}
if (fontPos == -1) {
LOGGER.info("Could not find previously set font \"{}\". ", myFontName);
font = null;
return;
}
font = availableFonts[fontPos].deriveFont(currentFontQuality);
}
private static class InstLoader {
static final FontProviderCustom instance0 = new FontProviderCustom((byte)0);
static final FontProviderCustom instance1 = new FontProviderCustom((byte)1);
}
public static FontProviderCustom getPrimary() { return InstLoader.instance0; }
public static FontProviderCustom getFallback() { return InstLoader.instance1; }
public void reloadFont(int fontID) {
currentFontQuality = FontConfig.customFontQuality;
font = FontStrategist.getAvailableFonts()[fontID].deriveFont(currentFontQuality);
File[] files = new File(getFontDir()).listFiles();
if (files != null) {
for (File f : files) {
if (!Files.isSymbolicLink(f.toPath())) {
f.delete();
}
}
}
TextureManager tm = Minecraft.getMinecraft().getTextureManager();
Map mapTextureObjects = tm.mapTextureObjects;
for (int i = 0; i < ATLAS_COUNT; i++) {
ResourceLocation key = new ResourceLocation(getAtlasResourceName(i));
if (mapTextureObjects.containsKey(key)) {
ITextureObject obj = (ITextureObject) mapTextureObjects.get(key);
TextureUtil.deleteTexture(obj.getGlTextureId());
mapTextureObjects.remove(key);
}
}
fontAtlases = new FontAtlas[ATLAS_COUNT];
}
private String getFontDir() {
return FONT_DIR + "f" + this.id + "/";
}
private String getAtlasFilename(int atlasId) {
return "f" + this.id + "p" + atlasId;
}
String getAtlasResourceName(int atlasId) {
return "minecraft:angelica_c" + getAtlasFilename(atlasId);
}
String getAtlasFullPath(int atlasId) {
return getFontDir() + getAtlasFilename(atlasId) + ".png";
}
@Value
private class GlyphData {
float uStart;
float vStart;
float xAdvance;
float glyphW;
float uSz;
float vSz;
}
private class FontAtlas {
GlyphData[] glyphData = new GlyphData[ATLAS_SIZE];
private ResourceLocation texture;
private final int id;
FontAtlas(int id) {
this.id = id;
}
void construct(Font font) {
float separator = currentFontQuality / 3f;
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics();
g2d.dispose();
int width = 0;
int actualChars = 0;
for (int i = 0; i < ATLAS_SIZE; i++) {
final char ch = (char)(i + ATLAS_SIZE * this.id);
if (font.canDisplay(ch)) {
width += (int) (separator + fm.charWidth(ch));
actualChars++;
}
}
if (actualChars == 0) {
return;
}
final int atlasTilesX = (int) Math.ceil(Math.sqrt(actualChars) * 1.5f);
final int atlasTilesY = (int) Math.ceil((double) actualChars / atlasTilesX);
LOGGER.info("constructing custom font atlas (ID {}) of nominal size {} chars, actual: {}, {} rows by {} columns",
this.id, ATLAS_SIZE, actualChars, atlasTilesX, atlasTilesY);
width = 0;
actualChars = 0;
int maxRowWidth = 0;
for (int i = 0; i < ATLAS_SIZE; i++) {
if (actualChars % atlasTilesX == 0) {
maxRowWidth = Math.max(maxRowWidth, width);
width = 0;
}
final char ch = (char)(i + ATLAS_SIZE * this.id);
if (font.canDisplay(ch)) {
width += (int) (separator + fm.charWidth(ch));
actualChars++;
}
}
maxRowWidth = Math.max(maxRowWidth, width);
final int lineHeight = fm.getHeight();
final float desc = fm.getDescent();
final int imageWidth = (int) (maxRowWidth + separator);
final int imageHeight = (int) ((separator + lineHeight) * atlasTilesY + separator);
image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
g2d = image.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setFont(font);
fm = g2d.getFontMetrics();
int tileX = 0, tileY = 0; // position in atlas tiles
int imgX = (int) separator; // position in pixels
for (int i = 0; i < ATLAS_SIZE; i++) {
final char ch = (char)(i + ATLAS_SIZE * this.id);
if (!font.canDisplay(ch)) { continue; }
if (tileX >= atlasTilesX) {
tileX = 0;
imgX = (int) separator;
tileY++;
}
final int charWidth = fm.charWidth(ch);
final float charAspectRatio = (float) charWidth / lineHeight;
final float inset = currentFontQuality / 16;
g2d.drawString(Character.toString(ch), imgX, (lineHeight + separator) * (tileY + 1) - desc);
final float uStart = (float) (imgX - inset * charAspectRatio) / imageWidth;
final float vStart = ((lineHeight + separator) * (tileY + 1) - lineHeight - inset) / imageHeight;
final float xAdvance = charAspectRatio * 8 * charWidth / (charWidth + 2 * inset * charAspectRatio);
final float glyphW = charAspectRatio * 8 + 1;
final float uSz = (float) (charWidth + 2 * inset * charAspectRatio) / imageWidth;
final float vSz = (float) (lineHeight + 2 * inset) / imageHeight;
glyphData[i] = new GlyphData(uStart, vStart, xAdvance, glyphW, uSz, vSz);
imgX += (int) (charWidth + separator);
tileX++;
}
g2d.dispose();
try {
LOGGER.info("writing custom font atlas texture ({}x{} px) to {}", imageWidth, imageHeight, getAtlasFullPath(this.id));
Files.createDirectories(Paths.get(getFontDir()));
ImageIO.write(image, "png", new File(getAtlasFullPath(this.id)));
} catch (IOException e) {
e.printStackTrace();
}
this.texture = new ResourceLocation(getAtlasResourceName(this.id));
}
}
private FontAtlas getAtlas(char chr) {
int id = chr / ATLAS_SIZE;
FontAtlas fa = fontAtlases[id];
if (fa == null) {
fa = new FontAtlas(id);
fa.construct(font);
}
fontAtlases[id] = fa;
return fa;
}
@Override
public boolean isGlyphAvailable(char chr) {
if (font == null) { return false; }
return (getAtlas(chr).glyphData[chr % ATLAS_SIZE] != null);
}
@Override
public char getRandomReplacement(char chr) {
return chr;
}
@Override
public float getUStart(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].uStart;
}
@Override
public float getVStart(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].vStart;
}
@Override
public float getXAdvance(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].xAdvance * FontConfig.customFontScale;
}
@Override
public float getGlyphW(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].glyphW * FontConfig.customFontScale;
}
@Override
public float getUSize(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].uSz;
}
@Override
public float getVSize(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].vSz;
}
@Override
public float getShadowOffset() {
return FontConfig.fontShadowOffset;
}
@Override
public ResourceLocation getTexture(char chr) {
return getAtlas(chr).texture;
}
@Override
public float getYScaleMultiplier() {
return FontConfig.customFontScale;
}
}
@@ -1,111 +0,0 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.FontConfig;
import it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
import jss.util.RandomXoshiro256StarStar;
import net.minecraft.util.ResourceLocation;
public final class FontProviderMC implements FontProvider {
/**
* The full list of characters present in the default Minecraft font, excluding the Unicode font
*/
@SuppressWarnings("UnnecessaryUnicodeEscape")
private static final String MCFONT_CHARS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u2302\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
private FontProviderMC() {}
private static class InstLoader {
static final FontProviderMC instance = new FontProviderMC();
static final FontProviderMC instanceSGA = new FontProviderMC();
}
public static FontProviderMC get(boolean isSGA) {
return (isSGA ? InstLoader.instanceSGA : InstLoader.instance);
}
private static final Char2ShortOpenHashMap MCFONT_ASCII_MAP = new Char2ShortOpenHashMap();
ResourceLocation locationFontTexture = null;
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
public int[] charWidth;
static {
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
MCFONT_ASCII_MAP.put(MCFONT_CHARS.charAt(i), i);
}
}
public int lookupMcFontPosition(char ch) {
if (isGlyphAvailable(ch)) {
return MCFONT_ASCII_MAP.get(ch);
}
return -1;
}
@Override
public boolean isGlyphAvailable(char chr) {
return MCFONT_ASCII_MAP.containsKey(chr);
}
public char getRandomReplacement(char chr){
int lutIndex = lookupMcFontPosition(chr);
if (lutIndex != -1) {
int randomReplacementIndex;
do {
randomReplacementIndex = fontRandom.nextInt(this.charWidth.length);
} while (this.charWidth[lutIndex] != this.charWidth[randomReplacementIndex]);
lutIndex = randomReplacementIndex;
return MCFONT_CHARS.charAt(lutIndex);
}
return chr;
}
@Override
public float getUStart(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return ((lutIndex % 16) * 8) / 128.0F;
}
@Override
public float getVStart(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return (float) ((lutIndex / 16) * 8) / 128.0F;
}
@Override
public float getXAdvance(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return charWidth[lutIndex];
}
@Override
public float getGlyphW(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return charWidth[lutIndex] - 0.01F;
}
@Override
public float getUSize(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return (charWidth[lutIndex] - 1.01F) / 128.0F;
}
@Override
public float getVSize(char chr) {
return 7.99F / 128.0F;
}
@Override
public float getShadowOffset() {
return FontConfig.fontShadowOffset;
}
@Override
public ResourceLocation getTexture(char chr) {
return locationFontTexture;
}
@Override
public float getYScaleMultiplier() {
return 1;
}
}
@@ -1,98 +0,0 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.FontConfig;
import net.minecraft.util.ResourceLocation;
public final class FontProviderUnicode implements FontProvider {
private FontProviderUnicode() {}
private static class InstLoader { static final FontProviderUnicode instance = new FontProviderUnicode(); }
public static FontProviderUnicode get() { return FontProviderUnicode.InstLoader.instance; }
private static final ResourceLocation[] unicodePageLocations = new ResourceLocation[256];
public byte[] glyphWidth;
private ResourceLocation getUnicodePageLocation(int page) {
final ResourceLocation lookup = unicodePageLocations[page];
if (lookup == null) {
final ResourceLocation rl = new ResourceLocation(String.format(
"textures/font/unicode_page_%02x.png",
page));
unicodePageLocations[page] = rl;
return rl;
} else {
return lookup;
}
}
@Override
public boolean isGlyphAvailable(char chr) {
return true;
}
@Override
public char getRandomReplacement(char chr) {
return chr;
}
@Override
public float getUStart(char chr) {
final float startColumnF = (float)((this.glyphWidth[chr] >>> 4) & 15);
return ((float) (chr % 16 * 16) + startColumnF + 0.21f) / 256.0f;
}
@Override
public float getVStart(char chr) {
return ((float) ((chr & 255) / 16 * 16) + 0.21f) / 256.0f;
}
@Override
public float getXAdvance(char chr) {
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
final int endColumn = this.glyphWidth[chr] & 15;
final float startColumnF = (float) startColumn;
final float endColumnF = (float) (endColumn + 1);
return (endColumnF - startColumnF) / 2.0F + 1.0F;
}
@Override
public float getGlyphW(char chr) {
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
final int endColumn = this.glyphWidth[chr] & 15;
final float startColumnF = (float) startColumn;
final float endColumnF = (float) (endColumn + 1);
final float chrWidth = endColumnF - startColumnF - 0.02F;
return chrWidth / 2.0f + 1.0f;
}
@Override
public float getUSize(char chr) {
final int startColumn = (this.glyphWidth[chr] >>> 4) & 15;
final int endColumn = this.glyphWidth[chr] & 15;
final float startColumnF = (float) startColumn;
final float endColumnF = (float) (endColumn + 1);
final float chrWidth = endColumnF - startColumnF - 0.02F;
return (chrWidth - 0.42f) / 256.0f;
}
@Override
public float getVSize(char chr) {
return (16.0f - 0.42f) / 256.0f;
}
@Override
public float getShadowOffset() {
return FontConfig.fontShadowOffset;
}
@Override
public ResourceLocation getTexture(char chr) {
final int uniPage = chr / 256;
return getUnicodePageLocation(uniPage);
}
@Override
public float getYScaleMultiplier() {
return 1;
}
}
@@ -1,127 +0,0 @@
package com.gtnewhorizons.angelica.client.font;
import com.google.common.collect.HashMultiset;
import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.mixins.interfaces.ResourceAccessor;
import cpw.mods.fml.client.SplashProgress;
import lombok.Getter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.resources.DefaultResourcePack;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.io.File;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
public class FontStrategist {
@Getter
private static final Font[] availableFonts;
public static final Logger LOGGER = LogManager.getLogger("Angelica");
static {
if (GraphicsEnvironment.isHeadless()) {
LOGGER.warn("GraphicsEnvironment.isHeadless() returned true! Custom fonts will be unavailable. This is likely a MacOS issue.");
availableFonts = new Font[0];
} else {
// get available fonts without duplicates (250 copies of dialog.plain need not apply)
Font[] availableFontsDirty = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
HashMap<String, Font> fontSet = new HashMap<>();
HashMultiset<String> duplicates = HashMultiset.create(); // for debugging
for (Font font : availableFontsDirty) {
String fontName = font.getFontName();
if (fontSet.containsKey(fontName)) {
duplicates.add(fontName);
} else {
fontSet.put(fontName, font);
}
}
if (!duplicates.isEmpty()) {
StringBuilder sb = new StringBuilder(duplicates.size() + " duplicate font(s) found in the list reported by Java: ");
for (Iterator<String> iter = duplicates.stream().distinct().iterator(); iter.hasNext(); ) {
String dupe = iter.next();
sb.append(duplicates.count(dupe)).append("x ").append(dupe);
if (iter.hasNext()) {
sb.append(", ");
}
}
sb.append(". Some fonts may be missing from the font selection menu.");
LOGGER.warn(sb.toString());
}
availableFonts = fontSet.values().stream().sorted(Comparator.comparing(Font::getFontName)).toArray(Font[]::new);
LOGGER.info("Got {} fonts from GraphicsEnvironment ({} after deduplication)", availableFontsDirty.length, availableFonts.length);
}
// create and add the resource pack that provides fonts
HashMap<String, File> packMap = new HashMap<>();
for (int i = 0; i < FontProviderCustom.ATLAS_COUNT; i++) {
packMap.put(FontProviderCustom.getPrimary().getAtlasResourceName(i), new File(FontProviderCustom.getPrimary().getAtlasFullPath(i)));
packMap.put(FontProviderCustom.getFallback().getAtlasResourceName(i), new File(FontProviderCustom.getFallback().getAtlasFullPath(i)));
}
DefaultResourcePack fontResourcePack = new DefaultResourcePack(packMap);
List defaultResourcePacks = ((ResourceAccessor) Minecraft.getMinecraft()).angelica$getDefaultResourcePacks();
defaultResourcePacks.add(fontResourcePack);
Minecraft.getMinecraft().refreshResources();
}
/**
Lets you get a FontProvider per char while respecting font priority and fallbacks, the unicode flag, whether or not
SGA is on, if we're in a splash screen, if a font can even display a character in the first place, etc.
*/
public static FontProvider getFontProvider(BatchingFontRenderer me, char chr, boolean customFontEnabled, boolean forceUnicode) {
if (me.isSGA && FontProviderMC.get(true).isGlyphAvailable(chr)) {
return FontProviderMC.get(true);
}
if (customFontEnabled && !me.isSplash) {
FontProvider fp;
fp = FontProviderCustom.getPrimary();
if (fp.isGlyphAvailable(chr)) { return fp; }
fp = FontProviderCustom.getFallback();
if (fp.isGlyphAvailable(chr)) { return fp; }
return FontProviderUnicode.get();
} else {
if (!forceUnicode && FontProviderMC.get(false).isGlyphAvailable(chr)) {
return FontProviderMC.get(false);
} else {
return FontProviderUnicode.get();
}
}
}
public static void reloadCustomFontProviders() {
FontProviderCustom.getPrimary().setFont(null);
FontProviderCustom.getFallback().setFont(null);
for (int i = 0; i < availableFonts.length; i++) {
if (Objects.equals(FontConfig.customFontNamePrimary, availableFonts[i].getFontName())) {
FontProviderCustom.getPrimary().reloadFont(i);
}
if (Objects.equals(FontConfig.customFontNameFallback, availableFonts[i].getFontName())) {
FontProviderCustom.getFallback().reloadFont(i);
}
}
}
public static boolean isSplashFontRendererActive(FontRenderer fontRenderer) {
// noinspection deprecation
boolean active = fontRenderer instanceof SplashProgress.SplashFontRenderer;
try {
Class<?> customSplashClass = Class.forName("gkappa.modernsplash.CustomSplash$SplashFontRenderer");
active = active || customSplashClass.isInstance(fontRenderer);
} catch (ClassNotFoundException ignored) {
}
return active;
}
}
@@ -1,105 +0,0 @@
package com.gtnewhorizons.angelica.client.gui;
import com.google.common.collect.ImmutableList;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfig;
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfigStorage;
import com.gtnewhorizons.angelica.dynamiclights.config.EntityTypeEntry;
import jss.notfine.core.Settings;
import me.jellysquid.mods.sodium.client.gui.options.Option;
import me.jellysquid.mods.sodium.client.gui.options.OptionGroup;
import me.jellysquid.mods.sodium.client.gui.options.OptionImpl;
import me.jellysquid.mods.sodium.client.gui.options.OptionImpact;
import me.jellysquid.mods.sodium.client.gui.options.OptionPage;
import me.jellysquid.mods.sodium.client.gui.options.control.TickBoxControl;
import net.minecraft.client.resources.I18n;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class DynamicLightsOptionPages {
public static OptionPage dynamicLights() {
if (!DynamicLights.configEnabled) {
return null;
}
List<OptionGroup> groups = new ArrayList<>();
groups.add(OptionGroup.createBuilder()
.add(Settings.DYNAMIC_LIGHTS.option)
.add(Settings.DYNAMIC_LIGHTS_SHADER_FORCE.option)
.build());
groups.add(OptionGroup.createBuilder()
.add(Settings.DYNAMIC_LIGHTS_FRUSTUM_CULLING.option)
.add(Settings.DYNAMIC_LIGHTS_ADAPTIVE_TICKING.option)
.add(Settings.DYNAMIC_LIGHTS_CULL_TIMEOUT.option)
.build());
groups.add(OptionGroup.createBuilder()
.add(Settings.DYNAMIC_LIGHTS_SLOW_DIST.option)
.add(Settings.DYNAMIC_LIGHTS_SLOWER_DIST.option)
.add(Settings.DYNAMIC_LIGHTS_BACKGROUND_DIST.option)
.build());
// Entity type toggles - grouped by mod ID
List<EntityTypeEntry> allEntities = EntityLightConfig.getAllEntityTypes();
Map<String, List<EntityTypeEntry>> byMod = new LinkedHashMap<>();
for (EntityTypeEntry entry : allEntities) {
byMod.computeIfAbsent(entry.getModId(), _ -> new ArrayList<>()).add(entry);
}
// Create a group for each mod's entities
for (Map.Entry<String, List<EntityTypeEntry>> modEntry : byMod.entrySet()) {
String modId = modEntry.getKey();
List<EntityTypeEntry> entities = modEntry.getValue();
OptionGroup.Builder groupBuilder = OptionGroup.createBuilder();
for (EntityTypeEntry entity : entities) {
Option<Boolean> option = createEntityOption(entity);
if (option != null) {
groupBuilder.add(option);
}
}
// Only add group if it has options
OptionGroup group = groupBuilder.build();
if (!group.getOptions().isEmpty()) {
groups.add(group);
}
}
return new OptionPage(I18n.format("options.dynamiclights.page"), ImmutableList.copyOf(groups));
}
private static Option<Boolean> createEntityOption(EntityTypeEntry entry) {
final String className = entry.getClassName();
// Resolve class once during option creation, not on every get/set
Class<?> resolvedClass;
try {
resolvedClass = Class.forName(className);
} catch (ClassNotFoundException e) {
// Class not loaded, skip this option
return null;
}
final Class<?> entityClass = resolvedClass;
return OptionImpl.createBuilder(Boolean.class, EntityLightConfigStorage.INSTANCE)
.setName(entry.getDisplayName() + " (" + entry.getModId() + ")")
.setTooltip(I18n.format("options.dynamiclights.entity.tooltip", entry.getDisplayName()))
.setControl(TickBoxControl::new)
.setBinding(
(_, value) -> EntityLightConfig.setEntityTypeEnabled(entityClass, value),
storage -> EntityLightConfig.isEntityTypeEnabled(entityClass)
)
.setImpact(OptionImpact.LOW)
.build();
}
}
@@ -1,561 +0,0 @@
package com.gtnewhorizons.angelica.client.gui;
import com.gtnewhorizon.gtnhlib.config.ConfigException;
import com.gtnewhorizon.gtnhlib.config.ConfigurationManager;
import com.gtnewhorizons.angelica.client.font.FontStrategist;
import com.gtnewhorizons.angelica.config.FontConfig;
import net.coderbot.iris.gui.element.widget.IrisButton;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.gui.GuiSlot;
import net.minecraft.client.gui.GuiTextField;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.MathHelper;
import org.lwjgl.input.Keyboard;
import java.awt.Font;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class FontConfigScreen extends GuiScreen {
private static final Font[] availableFonts = FontStrategist.getAvailableFonts();
private final GuiScreen parent;
private final String title;
private final String searchPrompt;
private final String testAreaPrompt;
private List<String> testAreaInfo;
private List<String> noFontsWarning;
private String currentPrimaryFontName;
private String currentFallbackFontName;
private FontList fontList;
private int selectedPrimaryFontListPos = -1;
private int selectedFallbackFontListPos = -1;
private ArrayList<Font> displayedFonts;
private GuiTextField searchBox;
private GuiTextField testArea;
private int sliderPages = 0;
private int displayedSliderPage = 0;
private final int sliderWidth = 160;
private final int sliderHeight = 20;
// need to save these separately
private IrisButton fwdButton;
private IrisButton backButton;
SliderClone.Option optFontQuality = new SliderClone.Option(6, 72, 6);
SliderClone.Option optShadowOffset = new SliderClone.Option(0, 2, 0.05f);
SliderClone.Option optShadowCopies = new SliderClone.Option(1, 8, 1);
SliderClone.Option optBoldCopies = new SliderClone.Option(1, 8, 1);
SliderClone.Option optGlyphAspect = new SliderClone.Option(-1, 1, 0.05f);
SliderClone.Option optGlyphScale = new SliderClone.Option(0.1f, 3, 0.05f);
SliderClone.Option optWhitespaceScale = new SliderClone.Option(0.1f, 3, 0.05f);
SliderClone.Option optGlyphSpacing = new SliderClone.Option(-2f, 2f, 0.05f);
SliderClone.Option optFontAAMode = new SliderClone.Option(0, 2, 1);
SliderClone.Option optFontAAStrength = new SliderClone.Option(1, 24, 1);
SliderClone.Option optCustomFontScale = new SliderClone.Option(0.1f, 3, 0.05f);
public FontConfigScreen(GuiScreen parent) {
this.title = I18n.format("options.angelica.fontconfig.title");
this.searchPrompt = I18n.format("options.angelica.fontconfig.searchprompt");
this.testAreaPrompt = I18n.format("options.angelica.fontconfig.testareaprompt");
this.parent = parent;
this.currentPrimaryFontName = FontConfig.customFontNamePrimary;
this.currentFallbackFontName = FontConfig.customFontNameFallback;
this.displayedFonts = new ArrayList<>(Arrays.asList(availableFonts));
for (int i = 0; i < availableFonts.length; i++) {
if (Objects.equals(this.currentPrimaryFontName, availableFonts[i].getFontName())) {
selectedPrimaryFontListPos = i;
}
if (Objects.equals(this.currentFallbackFontName, availableFonts[i].getFontName())) {
selectedFallbackFontListPos = i;
}
}
}
public void initGui() {
Keyboard.enableRepeatEvents(true);
searchBox = new GuiTextField(this.fontRendererObj, this.width / 2 - 120, 24, 240, 20);
searchBox.setMaxStringLength(64);
fontList = new FontList();
testArea = new GuiTextField(this.fontRendererObj, this.width * 5 / 6 - this.width / 7, 63 - 10, this.width * 2 / 7, 20);
testArea.setMaxStringLength(512);
testArea.setText(I18n.format("options.angelica.fontconfig.testareaexample"));
initButtons();
}
private void initButtons() {
final int halfWidth = this.width / 2;
final int sliderSpacing = 4;
final int x1 = halfWidth - sliderWidth - sliderSpacing;
final int x2 = halfWidth;
final int x3 = halfWidth + sliderWidth + sliderSpacing;
final int y3 = this.height - 65 + 2 * (sliderHeight + sliderSpacing);
this.buttonList.add(new IrisButton(x1 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
FontConfig.enableCustomFont ? I18n.format("options.angelica.fontconfig.disable_custom_font") :
I18n.format("options.angelica.fontconfig.enable_custom_font"), this::toggleCustomFont));
this.buttonList.add(new IrisButton(x2 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
I18n.format("options.angelica.fontconfig.reset_values"), this::resetValues));
this.buttonList.add(new IrisButton(x3 - sliderWidth / 2, y3 - sliderHeight / 2, sliderWidth, sliderHeight,
I18n.format("gui.done"), button -> this.onClose()));
backButton = new IrisButton(halfWidth - 20 - 224, this.height - 89 - 10, 40, 20,
"<", button -> this.switchPage(-1));
fwdButton = new IrisButton(halfWidth - 20 + 224, this.height - 89 - 10, 40, 20,
">", button -> this.switchPage(1));
this.buttonList.add(fwdButton);
this.buttonList.add(backButton);
initSliders();
}
private void initSliders() {
ArrayList<SliderClone> sliders = new ArrayList<>();
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optFontQuality)
.initialValue(FontConfig.customFontQuality)
.setter(this::setFontQuality)
.langKey("options.angelica.fontconfig.font_quality")
.formatString("%2.0f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optShadowOffset)
.initialValue(FontConfig.fontShadowOffset)
.setter(value -> FontConfig.fontShadowOffset = value)
.langKey("options.angelica.fontconfig.shadow_offset")
.formatString("x%3.2f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optGlyphAspect)
.initialValue(FontConfig.glyphAspect)
.setter(value -> FontConfig.glyphAspect = value)
.langKey("options.angelica.fontconfig.glyph_aspect")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optGlyphScale)
.initialValue(FontConfig.glyphScale)
.setter(value -> FontConfig.glyphScale = value)
.langKey("options.angelica.fontconfig.glyph_scale")
.formatString("x%3.2f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optFontAAStrength)
.initialValue(FontConfig.fontAAStrength)
.setter(value -> FontConfig.fontAAStrength = value.intValue())
.langKey("options.angelica.fontconfig.font_aa_strength")
.formatString("%.0f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optFontAAMode)
.initialValue(FontConfig.fontAAMode)
.setter(value -> FontConfig.fontAAMode = value.intValue())
.formatter(this::fontAAModeFormat)
.langKey("options.angelica.fontconfig.aamode")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optWhitespaceScale)
.initialValue(FontConfig.whitespaceScale)
.setter(value -> FontConfig.whitespaceScale = value)
.langKey("options.angelica.fontconfig.whitespace_scale")
.formatString("x%3.2f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optGlyphSpacing)
.initialValue(FontConfig.glyphSpacing)
.setter(value -> FontConfig.glyphSpacing = value)
.langKey("options.angelica.fontconfig.glyph_spacing")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optShadowCopies)
.initialValue(FontConfig.shadowCopies)
.setter(value -> FontConfig.shadowCopies = value.intValue())
.langKey("options.angelica.fontconfig.shadow_copies")
.formatString("%.0f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optBoldCopies)
.initialValue(FontConfig.boldCopies)
.setter(value -> FontConfig.boldCopies = value.intValue())
.langKey("options.angelica.fontconfig.bold_copies")
.formatString("%.0f")
.build()
);
sliders.add(new SliderClone.SliderCloneBuilder()
.width(sliderWidth)
.height(sliderHeight)
.option(optCustomFontScale)
.initialValue(FontConfig.customFontScale)
.setter(value -> FontConfig.customFontScale = value)
.langKey("options.angelica.fontconfig.custom_font_scale")
.formatString("x%3.2f")
.build()
);
final int halfWidth = this.width / 2;
final int sliderSpacing = 4;
final int x1 = halfWidth - sliderWidth - sliderSpacing;
final int x2 = halfWidth;
final int x3 = halfWidth + sliderWidth + sliderSpacing;
final int y1 = this.height - 65;
final int y2 = this.height - 65 + sliderHeight + sliderSpacing;
this.sliderPages = sliders.size() / 6;
this.displayedSliderPage = Math.min(this.displayedSliderPage, this.sliderPages);
this.backButton.enabled = (this.displayedSliderPage != 0);
this.fwdButton.enabled = (this.displayedSliderPage != this.sliderPages);
final int start = 6 * this.displayedSliderPage;
final int end = Math.min(6 * (this.displayedSliderPage + 1), sliders.size());
List<SliderClone> s = sliders.subList(start, end);
try {
s.get(0).setCenterPosition(x1, y1);
s.get(1).setCenterPosition(x1, y2);
s.get(2).setCenterPosition(x2, y1);
s.get(3).setCenterPosition(x2, y2);
s.get(4).setCenterPosition(x3, y1);
s.get(5).setCenterPosition(x3, y2);
} catch (IndexOutOfBoundsException ignored) {
// we're on the last page and it has less than 6 sliders to display
}
this.buttonList.addAll(s);
}
private void onClose() {
applyChanges(true);
this.mc.displayGuiScreen(parent);
}
private void applyChanges(boolean finalApply) {
int pos;
pos = selectedPrimaryFontListPos;
if (pos >= 0 && pos < displayedFonts.size()) {
FontConfig.customFontNamePrimary = displayedFonts.get(pos).getFontName();
}
pos = selectedFallbackFontListPos;
if (pos >= 0 && pos < displayedFonts.size()) {
FontConfig.customFontNameFallback = displayedFonts.get(pos).getFontName();
}
FontStrategist.reloadCustomFontProviders();
if (finalApply) {
ConfigurationManager.save(FontConfig.class);
}
}
private void resetValues(IrisButton button) {
try {
ConfigurationManager.getConfigElements(FontConfig.class).forEach(elem -> elem.set(elem.getDefault()));
} catch (ConfigException e) {
throw new RuntimeException(e);
}
currentPrimaryFontName = null;
currentFallbackFontName = null;
selectedPrimaryFontListPos = -1;
selectedFallbackFontListPos = -1;
super.buttonList.clear();
this.initButtons();
}
private void toggleCustomFont(IrisButton button) {
FontConfig.enableCustomFont = !FontConfig.enableCustomFont;
applyChanges(false);
button.displayString = FontConfig.enableCustomFont ?
I18n.format("options.angelica.fontconfig.disable_custom_font") :
I18n.format("options.angelica.fontconfig.enable_custom_font");
}
private void switchPage(int offset) {
this.displayedSliderPage = MathHelper.clamp_int(this.displayedSliderPage + offset, 0, this.sliderPages);
this.buttonList.removeIf(guiButton -> guiButton instanceof SliderClone);
this.initSliders();
}
private int qualityLast = FontConfig.customFontQuality;
private void setFontQuality(float quality) {
FontConfig.customFontQuality = (int) quality;
if (qualityLast != (int) quality) {
applyChanges(false);
}
qualityLast = (int) quality;
}
private String fontAAModeFormat(float AAmode) {
return switch ((int) AAmode) {
case 2 -> I18n.format("options.angelica.fontconfig.aamode.aa_16x");
case 1 -> I18n.format("options.angelica.fontconfig.aamode.aa_4x");
default -> I18n.format("options.angelica.fontconfig.aamode.aa_none");
};
}
private float lastMouseX = 0;
private float lastMouseY = 0;
private long lastStillTime = 0;
@Override
public void drawScreen(int mouseX, int mouseY, float delta) {
drawBackground(0);
this.fontList.drawScreen(mouseX, mouseY, delta);
if (availableFonts.length == 0) {
if (this.noFontsWarning == null) {
this.noFontsWarning = this.fontRendererObj.listFormattedStringToWidth(
I18n.format("options.angelica.fontconfig.no_fonts_warning"), this.width / 2
);
}
int y = 80;
for (String s : this.noFontsWarning) {
drawCenteredString(this.fontRendererObj, s, this.width / 3, y, 0xFFFFFF);
y += this.fontRendererObj.FONT_HEIGHT;
}
}
this.searchBox.drawTextBox();
if (!this.searchBox.isFocused() && this.searchBox.getText().isEmpty()) {
this.drawCenteredString(this.fontRendererObj, this.searchPrompt,
this.searchBox.xPosition + this.fontRendererObj.getStringWidth(this.searchPrompt) / 2 + 4,
this.searchBox.yPosition + this.searchBox.height / 2 - 4, 0xFFFFFF);
}
drawCenteredString(this.fontRendererObj, this.title, (int) (this.width * 0.5), 8, 0xFFFFFF);
drawCenteredString(this.fontRendererObj, I18n.format("options.angelica.fontconfig.currentfonts",
FontConfig.customFontNamePrimary, FontConfig.customFontNameFallback), (int) (this.width * 0.5), this.height - 92, 0xFFFFFF);
if (this.testAreaInfo == null) {
this.testAreaInfo = this.fontRendererObj.listFormattedStringToWidth(
I18n.format("options.angelica.fontconfig.testareainfo"), this.width / 4
);
}
int cumulativeY = 73;
for (String s : this.testAreaInfo) {
drawCenteredString(this.fontRendererObj, s, this.width * 5 / 6, cumulativeY, 0xFFFFFF);
cumulativeY += this.fontRendererObj.FONT_HEIGHT;
}
cumulativeY += 4;
this.testArea.yPosition = cumulativeY;
this.testArea.drawTextBox();
if (!this.testArea.isFocused() && this.testArea.getText().isEmpty()) {
this.drawCenteredString(this.fontRendererObj, this.testAreaPrompt,
this.testArea.xPosition + this.fontRendererObj.getStringWidth(this.testAreaPrompt) / 2 + 4,
this.testArea.yPosition + this.testArea.height / 2 - 4, 0xFFFFFF);
}
List<String> testText = this.fontRendererObj.listFormattedStringToWidth(
this.testArea.getText().replace('&', '§'), this.width / 4
);
cumulativeY += this.testArea.height + 4;
for (String s : testText) {
drawCenteredString(this.fontRendererObj, s, this.width * 5 / 6, cumulativeY, 0xFFFFFF);
cumulativeY += this.fontRendererObj.FONT_HEIGHT;
}
super.drawScreen(mouseX, mouseY, delta);
for (GuiButton guiButton : buttonList) {
if (!(guiButton instanceof SliderClone slider)) { continue; }
final int top = slider.yPosition;
final int bot = slider.yPosition + slider.height;
final int left = slider.xPosition;
final int right = slider.xPosition + slider.width;
if (mouseY < top || mouseY >= bot || mouseX < left || mouseX >= right) { continue; }
if (mouseX == lastMouseX && mouseY == lastMouseY) {
if (lastStillTime == 0) {
lastStillTime = System.currentTimeMillis();
}
if (lastStillTime + 500L < System.currentTimeMillis()) {
displayTooltip(mouseX, mouseY, slider.tooltipKey);
}
} else {
lastStillTime = 0;
}
break;
}
lastMouseX = mouseX;
lastMouseY = mouseY;
}
private void displayTooltip(int x, int y, String langKey) {
List<String> lines = this.fontRendererObj.listFormattedStringToWidth(I18n.format(langKey), this.width / 2);
this.drawHoveringText(lines, x, y, this.fontRendererObj);
}
@Override
protected void actionPerformed(GuiButton guiButton) {
if (guiButton.enabled) {
if (guiButton instanceof IrisButton irisButton) {
irisButton.onPress();
}
}
}
@Override
protected void keyTyped(char typedChar, int keyCode) {
if (this.searchBox.isFocused()) {
if (keyCode == Keyboard.KEY_ESCAPE) {
this.searchBox.setFocused(false);
return;
}
this.searchBox.textboxKeyTyped(typedChar, keyCode);
this.displayedFonts = filterFonts(this.searchBox.getText().toLowerCase());
}
if (this.testArea.isFocused()) {
if (keyCode == Keyboard.KEY_ESCAPE) {
this.testArea.setFocused(false);
return;
}
this.testArea.textboxKeyTyped(typedChar, keyCode);
}
if (keyCode == Keyboard.KEY_ESCAPE) {
this.onClose();
}
}
@Override
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) {
super.mouseClicked(mouseX, mouseY, mouseButton);
this.searchBox.mouseClicked(mouseX, mouseY, mouseButton);
this.testArea.mouseClicked(mouseX, mouseY, mouseButton);
this.fontList.mouseClicked(mouseX, mouseY, mouseButton);
}
@Override
public void updateScreen() {
this.searchBox.updateCursorCounter();
this.testArea.updateCursorCounter();
}
// this could be more efficient, but it's good enough as is
private ArrayList<Font> filterFonts(String search) {
ArrayList<Font> results;
if (search == null || search.isEmpty()) {
results = new ArrayList<>(Arrays.asList(availableFonts));
} else {
results = Arrays.stream(availableFonts).filter((font -> font.getFontName().toLowerCase().contains(search))).collect(Collectors.toCollection(ArrayList::new));
}
selectedPrimaryFontListPos = -1;
selectedFallbackFontListPos = -1;
for (int i = 0; i < results.size(); i++) {
if (Objects.equals(currentPrimaryFontName, results.get(i).getFontName())) {
selectedPrimaryFontListPos = i;
}
if (Objects.equals(currentFallbackFontName, results.get(i).getFontName())) {
selectedFallbackFontListPos = i;
}
}
return results;
}
class FontList extends GuiSlot {
public FontList() {
super(FontConfigScreen.this.mc, FontConfigScreen.this.width * 2 / 3, FontConfigScreen.this.height, 52, FontConfigScreen.this.height - 102, 18);
}
private void mouseClicked(int mouseX, int mouseY, int mouseButton) {
if (mouseY < this.top || mouseY > this.bottom) { return; }
int listLeftBound = this.width / 2 - this.getListWidth() / 2;
int listRightBound = this.width / 2 + this.getListWidth() / 2;
if (mouseX < listLeftBound || mouseX > listRightBound) { return; }
int mousePosInList = mouseY - this.top - this.headerPadding + (int)this.amountScrolled - 4;
int slotIndex = mousePosInList / this.slotHeight;
if (slotIndex < 0 || slotIndex >= this.getSize()) { return; }
if (mouseButton == 0) {
onElemClicked(slotIndex, false);
} else if (mouseButton == 1) {
onElemClicked(slotIndex, true);
}
}
protected void onElemClicked(int index, boolean rightClick) {
if (!rightClick) {
selectedPrimaryFontListPos = index;
currentPrimaryFontName = displayedFonts.get(index).getFontName();
} else {
selectedFallbackFontListPos = index;
currentFallbackFontName = displayedFonts.get(index).getFontName();
}
applyChanges(false);
}
protected boolean isSelected(int index) {
return (index == selectedPrimaryFontListPos || index == selectedFallbackFontListPos);
}
protected int getSize() {
return displayedFonts.size();
}
@Override
protected int getContentHeight() {
return this.getSize() * 18;
}
@Override
protected int getScrollBarX() {
return this.width * 15 / 16 - 5;
}
@Override
public int getListWidth() {
return this.width * 2 / 3;
}
// see onElemClicked for the proper version that supports right-clicking
protected void elementClicked(int index, boolean doubleClicked, int mouseX, int mouseY) {}
protected void drawBackground() {
// do nothing, the font list doesn't occupy the entire screen
}
protected void drawSlot(int index, int x, int y, int p_148126_4_, Tessellator tessellator, int p_148126_6_, int p_148126_7_) {
int color = 0xffffff;
if (index == selectedPrimaryFontListPos) {
color &= 0xffff55;
}
if (index == selectedFallbackFontListPos) {
color &= 0x55ffff;
}
drawCenteredString(fontRendererObj, displayedFonts.get(index).getFontName(), this.width / 2, y + 1, color);
}
}
}
@@ -1,164 +0,0 @@
package com.gtnewhorizons.angelica.client.gui;
import lombok.AllArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.resources.I18n;
import org.lwjgl.opengl.GL11;
import java.util.function.Consumer;
import java.util.function.Function;
public class SliderClone extends GuiButton {
private final Function<Float, String> displayStringFormatter;
private final Consumer<Float> valueSetter;
private final Option option;
private float sliderPercentage;
public final String tooltipKey;
public boolean mouseDragging;
@AllArgsConstructor
public static class Option {
float min;
float max;
float step;
float denormalize(float alpha) {
float lerp = min * (1 - alpha) + max * alpha;
return Math.round(lerp / step) * step;
}
float normalize(float value) {
return (value - min) / (max - min);
}
}
public SliderClone(int x, int y, int width, int height, Option option, float initialValue,
Consumer<Float> setter, Function<Float, String> formatter, String tooltipKey) {
super(0, x, y, width, height, "");
this.option = option;
this.sliderPercentage = option.normalize(initialValue);
this.displayString = formatter.apply(initialValue);
this.valueSetter = setter;
this.displayStringFormatter = formatter;
this.tooltipKey = tooltipKey;
}
public static SliderCloneBuilder builder() {
return new SliderCloneBuilder();
}
public void setCenterPosition(int x, int y) {
this.xPosition = x - this.width / 2;
this.yPosition = y - this.height / 2;
}
@Accessors(fluent = true) @Setter
public static class SliderCloneBuilder {
private int x = 0, y = 0; // center position, not a corner
private int width = 144, height = 20;
private Option option;
private float initialValue;
private Consumer<Float> setter;
private String formatString = "%3.2f";
private String langKey;
// overrides: if null, these get set to default values
private String tooltipKey;
private Function<Float, String> formatter;
public SliderClone build() {
if (this.formatter == null) {
this.formatter = value -> I18n.format(this.langKey, String.format(this.formatString, value));
}
if (this.tooltipKey == null) {
this.tooltipKey = this.langKey + ".tooltip";
}
return new SliderClone(
this.x - this.width / 2,
this.y - this.height / 2,
this.width,
this.height,
this.option,
this.initialValue,
this.setter,
this.formatter,
this.tooltipKey
);
}
}
/**
* Returns 0 if the button is disabled, 1 if the mouse is NOT hovering over this button and 2 if it IS hovering over
* this button.
*/
public int getHoverState(boolean mouseOver) {
return 0;
}
/**
* Fired when the mouse button is dragged. Equivalent of MouseListener.mouseDragged(MouseEvent e).
*/
protected void mouseDragged(Minecraft mc, int mouseX, int mouseY) {
if (this.visible) {
if (this.mouseDragging) {
this.sliderPercentage = (float)(mouseX - (this.xPosition + 4)) / (float)(this.width - 8);
if (this.sliderPercentage < 0.0F) {
this.sliderPercentage = 0.0F;
}
if (this.sliderPercentage > 1.0F) {
this.sliderPercentage = 1.0F;
}
float f = this.option.denormalize(this.sliderPercentage);
valueSetter.accept(f);
this.sliderPercentage = this.option.normalize(f);
this.displayString = displayStringFormatter.apply(f);
}
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderPercentage * (float)(this.width - 8)), this.yPosition, 0, 66, 4, 20);
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderPercentage * (float)(this.width - 8)) + 4, this.yPosition, 196, 66, 4, 20);
}
}
/**
* Returns true if the mouse has been pressed on this control. Equivalent of MouseListener.mousePressed(MouseEvent e).
*/
public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) {
if (super.mousePressed(mc, mouseX, mouseY)) {
this.sliderPercentage = (float)(mouseX - (this.xPosition + 4)) / (float)(this.width - 8);
if (this.sliderPercentage < 0.0F) {
this.sliderPercentage = 0.0F;
}
if (this.sliderPercentage > 1.0F) {
this.sliderPercentage = 1.0F;
}
float f = this.option.denormalize(this.sliderPercentage);
valueSetter.accept(f);
this.sliderPercentage = this.option.normalize(f);
this.displayString = displayStringFormatter.apply(f);
this.mouseDragging = true;
return true;
} else {
return false;
}
}
/**
* Fired when the mouse button is released. Equivalent of MouseListener.mouseReleased(MouseEvent e).
*/
public void mouseReleased(int mouseX, int mouseY) {
this.mouseDragging = false;
}
}
@@ -1,94 +0,0 @@
package com.gtnewhorizons.angelica.commands;
// Debug commands adapted from Beddium by Ven and FalsePattern
import com.gtnewhorizons.angelica.debug.ChunkDebugMinimap;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
import net.minecraft.command.CommandBase;
import net.minecraft.command.ICommand;
import net.minecraft.command.ICommandSender;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.EnumChatFormatting;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class AngelicaCommand extends CommandBase {
private static final List<String> SUBCOMMANDS = Arrays.asList("wireframe", "fog", "minimap", "help");
@Override
public String getCommandName() {
return "angelica";
}
@Override
public String getCommandUsage(ICommandSender sender) {
return "/angelica <wireframe|fog|minimap|help>";
}
@Override
public int getRequiredPermissionLevel() {
return 0; // Available to all players (client-side only)
}
@Override
public List<String> addTabCompletionOptions(ICommandSender sender, String[] args) {
if (args.length == 1) {
return getListOfStringsMatchingLastWord(args, SUBCOMMANDS.toArray(new String[0]));
}
return new ArrayList<>();
}
@Override
public void processCommand(ICommandSender sender, String[] args) {
if (args.length == 0) {
sendHelp(sender);
return;
}
switch (args[0].toLowerCase()) {
case "wireframe" -> handleWireframe(sender);
case "fog" -> handleFog(sender);
case "minimap" -> handleMinimap(sender);
default -> sendHelp(sender);
}
}
private void handleWireframe(ICommandSender sender) {
CeleritasWorldRenderer.DEBUG_WIREFRAME_MODE = !CeleritasWorldRenderer.DEBUG_WIREFRAME_MODE;
final String state = CeleritasWorldRenderer.DEBUG_WIREFRAME_MODE ? "ON" : "OFF";
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Wireframe mode: " + state));
}
private void handleFog(ICommandSender sender) {
CeleritasDebugScreenHandler.showFogDebug = !CeleritasDebugScreenHandler.showFogDebug;
final String f3State = CeleritasDebugScreenHandler.showFogDebug ? "ON" : "OFF";
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Fog debug (F3): " + f3State));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " " + CeleritasDebugScreenHandler.getFogDebugString()));
}
private void handleMinimap(ICommandSender sender) {
ChunkDebugMinimap.toggle();
final String state = ChunkDebugMinimap.isEnabled() ? "ON" : "OFF";
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Chunk debug minimap: " + state));
}
private void sendHelp(ICommandSender sender) {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] Debug Commands:"));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica wireframe" + EnumChatFormatting.WHITE + " - Toggle wireframe rendering"));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica fog" + EnumChatFormatting.WHITE + " - Toggle fog debug on F3"));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica minimap" + EnumChatFormatting.WHITE + " - Toggle chunk debug overlay"));
}
@Override
public int compareTo(Object o) {
if (o instanceof ICommand cmd) {
return this.getCommandName().compareTo(cmd.getCommandName());
}
return 0;
}
}
@@ -26,7 +26,17 @@ public class BlockError extends Block {
public BlockError() {
super(Material.rock);
MinecraftForge.EVENT_BUS.register(new EventHandler());
MinecraftForge.EVENT_BUS.register(this);
}
@SubscribeEvent
@SideOnly(Side.CLIENT)
public void onTextureStitch(TextureStitchEvent.Pre event) {
if (event.map.getTextureType() == 0) {
for (int i = 0; i < icons.length; i++) {
icons[i] = event.map.registerIcon("angelica:error_block_" + i);
}
}
}
@Override
@@ -35,15 +45,4 @@ public class BlockError extends Block {
return meta >= 0 && meta < icons.length ? icons[meta] : null;
}
public static class EventHandler{
@SubscribeEvent
@SideOnly(Side.CLIENT)
public void onTextureStitch(TextureStitchEvent.Pre event) {
if (event.map.getTextureType() == 0) {
for (int i = 0; i < icons.length; i++) {
icons[i] = event.map.registerIcon("angelica:error_block_" + i);
}
}
}
}
}
@@ -1,20 +1,34 @@
package com.gtnewhorizons.angelica.common;
import static com.gtnewhorizon.gtnhlib.client.model.ModelISBRH.JSON_ISBRH_ID;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadProvider;
import com.gtnewhorizon.gtnhlib.client.renderer.util.DirectionUtil;
import com.gtnewhorizons.angelica.mixins.interfaces.ModeledBlock;
import com.gtnewhorizons.angelica.utils.AssetLoader;
import lombok.Getter;
import lombok.Setter;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import org.jetbrains.annotations.NotNull;
/// Unline {@link BlockError}, this *is* registered on both sides. It's intended to test functionality in a SP
/// development environment.
public class BlockTest extends Block {
import static com.gtnewhorizons.angelica.models.VanillaModels.LECTERN;
public class BlockTest extends Block implements ModeledBlock {
@Getter
@Setter
private QuadProvider model;
public BlockTest() {
super(Material.wood);
setHardness(0.7f);
super(Material.rock);
this.setBlockTextureName("missingno");
this.setModel((world, pos, block, meta, dir, random, color, quadPool) -> {
if (meta < 2 || meta > 5) meta = 2;
return LECTERN.models[meta - 2].getQuads(world, pos, block, meta, dir, random, color, quadPool);
});
}
@Override
@@ -22,6 +36,13 @@ public class BlockTest extends Block {
return false;
}
@Override
public void registerBlockIcons(IIconRegister reg) {
for (String s : AssetLoader.testTexs) {
reg.registerIcon(s);
}
}
/**
* Called when a block is placed using its ItemBlock. Args: World, X, Y, Z, side, hitX, hitY, hitZ, block metadata
*/
@@ -29,16 +50,11 @@ public class BlockTest extends Block {
public int onBlockPlaced(@NotNull World worldIn, int x, int y, int z, int side, float subX, float subY, float subZ, int meta) {
// Face NORTH if placed up or down
final var s = ForgeDirection.getOrientation(side);
final ForgeDirection s = DirectionUtil.ALL_DIRECTIONS[side];
if (s == ForgeDirection.UP || s == ForgeDirection.DOWN)
return 2;
// Face the placed side
return side;
}
@Override
public int getRenderType() {
return JSON_ISBRH_ID;
}
}
@@ -1,55 +0,0 @@
package com.gtnewhorizons.angelica.compat;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.ARBDebugOutput;
import org.lwjgl.opengl.GL11;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
/**
* Checks for known problematic driver configurations and applies workarounds. Adapted from Embeddium/Sodium's LateDriverScanner.
*
* <p>The NVIDIA workaround can be disabled via: {@code -Dangelica.disableNvidiaWorkaround=true}
*/
public class DriverCompatabilityCheck {
private static final Logger LOGGER = LogManager.getLogger("Angelica");
private static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
/**
* Should be called after GL context is created and GLStateManager is initialized.
*/
public static void checkDriverCompatibility() {
final String vendor = GL11.glGetString(GL11.GL_VENDOR);
final String renderer = GL11.glGetString(GL11.GL_RENDERER);
final String version = GL11.glGetString(GL11.GL_VERSION);
LOGGER.info("OpenGL Vendor: {}", vendor);
LOGGER.info("OpenGL Renderer: {}", renderer);
LOGGER.info("OpenGL Version: {}", version);
if (vendor != null && vendor.contains("NVIDIA")) {
if (DISABLE_NVIDIA_WORKAROUND) {
LOGGER.info("NVIDIA workaround disabled via -Dangelica.disableNvidiaWorkaround=true");
} else {
applyNvidiaWorkaround();
}
}
}
/**
* https://github.com/godotengine/godot/issues/33969#issuecomment-917846774
* Attempt to force the driver to disable its submission thread by enabling synchronous debug output.
* This usually suppresses bad behavior from NVIDIA's "Threaded Optimizations" feature.
*/
private static void applyNvidiaWorkaround() {
if (!GLStateManager.capabilities.GL_ARB_debug_output) {
LOGGER.warn("Cannot apply NVIDIA workaround: ARB_debug_output not available");
return;
}
LOGGER.info("Enabling workaround for NVIDIA threaded optimizations");
GL11.glEnable(ARBDebugOutput.GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB);
}
}
@@ -1,6 +1,8 @@
package com.gtnewhorizons.angelica.compat;
import com.falsepattern.chunk.api.DataRegistry;
import com.gtnewhorizons.angelica.mixins.interfaces.ExtendedBlockStorageAccessor;
import com.gtnewhorizons.angelica.mixins.interfaces.ExtendedNibbleArray;
import com.gtnewhorizons.neid.mixins.interfaces.IExtendedBlockStorageMixin;
import net.minecraft.world.chunk.Chunk;
@@ -17,7 +19,7 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
}
public ExtendedBlockStorageExt(Chunk chunk, ExtendedBlockStorage storage) {
super(storage.yBase, storage.getSkylightArray() != null);
super(((ExtendedBlockStorageAccessor) storage).getYBase(), storage.getSkylightArray() != null);
if (ModStatus.isChunkAPILoaded) {
if (storage.getSkylightArray() != null) {
@@ -31,7 +33,7 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
System.arraycopy(((IExtendedBlockStorageMixin)(Object)storage).getBlock16BArray(), 0, block16BArray, 0, block16BArray.length);
if(storage.getBlockMSBArray() != null) {
this.setBlockMSBArray(new NibbleArray(block16BArray.length, 4));
copyNibbleArray(storage.getBlockMSBArray(), this.getBlockMSBArray());
copyNibbleArray((ExtendedNibbleArray) storage.getBlockMSBArray(), (ExtendedNibbleArray) this.getBlockMSBArray());
}
arrayLen = block16BArray.length;
if (ModStatus.isNEIDMetadataExtended) {
@@ -50,31 +52,31 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
System.arraycopy(storage.getBlockLSBArray(), 0, blockLSBArray, 0, blockLSBArray.length);
if(storage.getBlockMSBArray() != null) {
this.setBlockMSBArray(new NibbleArray(blockLSBArray.length, 4));
copyNibbleArray(storage.getBlockMSBArray(), this.getBlockMSBArray());
copyNibbleArray((ExtendedNibbleArray) storage.getBlockMSBArray(), (ExtendedNibbleArray) this.getBlockMSBArray());
}
arrayLen = blockLSBArray.length;
}
if (!ModStatus.isNEIDMetadataExtended) copyNibbleArray(storage.getMetadataArray(), this.getMetadataArray());
copyNibbleArray(storage.getBlocklightArray(), this.getBlocklightArray());
if (!ModStatus.isNEIDMetadataExtended) copyNibbleArray((ExtendedNibbleArray) storage.getMetadataArray(), (ExtendedNibbleArray)this.getMetadataArray());
copyNibbleArray((ExtendedNibbleArray) storage.getBlocklightArray(), (ExtendedNibbleArray)this.getBlocklightArray());
if(storage.getSkylightArray() != null) {
hasSky = true;
if(this.getSkylightArray() == null) {
this.setSkylightArray(new NibbleArray(arrayLen, 4));
}
copyNibbleArray(storage.getSkylightArray(), this.getSkylightArray());
copyNibbleArray((ExtendedNibbleArray) storage.getSkylightArray(), (ExtendedNibbleArray) this.getSkylightArray());
}
}
this.blockRefCount = storage.blockRefCount;
((ExtendedBlockStorageAccessor) this).setBlockRefCount(((ExtendedBlockStorageAccessor) storage).getBlockRefCount());
}
private static void copyNibbleArray(NibbleArray srcArray, NibbleArray dstArray) {
private static void copyNibbleArray(ExtendedNibbleArray srcArray, ExtendedNibbleArray dstArray) {
if (srcArray == null || dstArray == null) {
throw new RuntimeException("NibbleArray is null src: " + (srcArray == null) + " dst: " + (dstArray == null));
}
final byte[] data = srcArray.data;
System.arraycopy(data, 0, dstArray.data, 0, data.length);
final byte[] data = srcArray.getData();
System.arraycopy(data, 0, dstArray.getData(), 0, data.length);
}
}
@@ -1,17 +1,14 @@
package com.gtnewhorizons.angelica.compat;
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.versioning.ArtifactVersion;
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
import mods.battlegear2.Battlegear;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.dries007.holoInventory.HoloInventory;
import xonin.backhand.Backhand;
public class ModStatus {
public static final Logger LOGGER = LogManager.getLogger("ModCompat");
public static BackhandReflectionCompat backhandCompat;
/**
* Mixin Version
*/
@@ -20,6 +17,7 @@ public class ModStatus {
* ASM Version
*/
public static boolean isOldNEIDLoaded;
public static boolean isBetterCrashesLoaded;
public static boolean isNEIDMetadataExtended;
public static boolean isLotrLoaded;
@@ -32,14 +30,8 @@ public class ModStatus {
public static boolean isThaumcraftLoaded;
public static boolean isThaumicHorizonsLoaded;
public static boolean isBaublesLoaded;
public static boolean isFluidLoggedLoaded;
public static void preInit(){
isBackhandLoaded = Loader.isModLoaded("backhand");
if(isBackhandLoaded) {
isBackhandLoaded = BackhandReflectionCompat.isBackhandLoaded();
}
isBetterCrashesLoaded = Loader.isModLoaded("bettercrashes");
isNEIDLoaded = Loader.isModLoaded("neid");
isOldNEIDLoaded = Loader.isModLoaded("notenoughIDs");
@@ -49,12 +41,17 @@ public class ModStatus {
isXaerosMinimapLoaded = Loader.isModLoaded("XaeroMinimap");
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
isBattlegearLoaded = Loader.isModLoaded("battlegear2");
isBackhandLoaded = Loader.isModLoaded("backhand");
isThaumcraftLoaded = Loader.isModLoaded("Thaumcraft");
isThaumicHorizonsLoaded = Loader.isModLoaded("ThaumicHorizons");
isBaublesLoaded = Loader.isModLoaded("Baubles");
isFluidLoggedLoaded = Loader.isModLoaded("fluidlogged");
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
if (isHoloInventoryLoaded){
isHoloInventoryLoaded = new DefaultArtifactVersion("2.4.4-GTNH")
.compareTo(
LoadControllerHelper.getOwningMod(HoloInventory.class).getProcessedVersion()
) <= 0;
}
// remove compat with original release of BG2
if (isBattlegearLoaded){
@@ -64,9 +61,17 @@ public class ModStatus {
) <= 0;
}
if (isBackhandLoaded){
isBackhandLoaded = new DefaultArtifactVersion("1.4.1")
.compareTo(
LoadControllerHelper.getOwningMod(Backhand.class).getProcessedVersion()
) <= 0;
}
isNEIDMetadataExtended = false;
if (isNEIDLoaded) {
final int majorVersion = Integer.parseInt(Loader.instance().getIndexedModList().get("neid").getVersion().split("\\.")[0]);
int majorVersion = Integer.parseInt(Loader.instance().getIndexedModList().get("neid").getVersion().split("\\.")[0]);
if (majorVersion >= 2) {
isNEIDMetadataExtended = true;
}
@@ -1,80 +0,0 @@
package com.gtnewhorizons.angelica.compat.backhand;
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
import cpw.mods.fml.relauncher.ReflectionHelper;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import static com.gtnewhorizons.angelica.compat.ModStatus.LOGGER;
public class BackhandReflectionCompat {
private static final String BACKHAND_CLASS = "xonin.backhand.Backhand";
private static final String ITEM_RENDERER_HOOKS_CLASS = "xonin.backhand.client.hooks.ItemRendererHooks";
private static final String BACKHAND_UTILS_CLASS = "xonin.backhand.api.core.BackhandUtils";
private static boolean isLoaded;
private static final MethodHandle renderOffhandReturn;
private static final MethodHandle getOffhandItem;
static {
MethodHandle renderOffhandReturnTemp = null;
MethodHandle getOffhandItemTemp = null;
try {
final Class<?> backhandClass = ReflectionHelper.getClass(BackhandReflectionCompat.class.getClassLoader(), BACKHAND_CLASS);
final boolean versionCheck = new DefaultArtifactVersion("1.6.9").compareTo(LoadControllerHelper.getOwningMod(backhandClass).getProcessedVersion()) <= 0;
if (versionCheck) {
final MethodHandles.Lookup lookup = MethodHandles.lookup();
final Class<?> itemRendererHooksClass = ReflectionHelper.getClass(BackhandReflectionCompat.class.getClassLoader(), ITEM_RENDERER_HOOKS_CLASS);
final Class<?> backhandUtilsClass = ReflectionHelper.getClass(BackhandReflectionCompat.class.getClassLoader(), BACKHAND_UTILS_CLASS);
renderOffhandReturnTemp = lookup.findStatic(itemRendererHooksClass, "renderOffhandReturn", MethodType.methodType(void.class, float.class));
getOffhandItemTemp = lookup.findStatic(backhandUtilsClass, "getOffhandItem", MethodType.methodType(ItemStack.class, EntityPlayer.class));
isLoaded = true;
LOGGER.info("Backhand compat loaded");
}
} catch (Exception e) {
LOGGER.error("Failed to load Backhand compat", e);
isLoaded = false;
}
renderOffhandReturn = renderOffhandReturnTemp;
getOffhandItem = getOffhandItemTemp;
}
public static boolean isBackhandLoaded() {
return isLoaded;
}
public static void renderOffhand(float partialTicks) {
if (isLoaded && renderOffhandReturn != null) {
try {
renderOffhandReturn.invokeExact(partialTicks);
} catch (Error e) {
throw e;
} catch (Throwable t) {
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
isLoaded = false;
}
}
}
public static ItemStack getOffhandItem(EntityPlayer player) {
if (isLoaded && getOffhandItem != null) {
try {
return (ItemStack) getOffhandItem.invokeExact(player);
} catch (Error e) {
throw e;
} catch (Throwable t) {
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
isLoaded = false;
}
}
return null;
}
}
@@ -1,15 +0,0 @@
package com.gtnewhorizons.angelica.compat.battlegear2;
import mods.battlegear2.api.core.IInventoryPlayerBattle;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
/**
* This class exists to isolate classes that exist in Battlegear 2 but not
* in Battlegear 2 for Backhand in order to avoid class loading exceptions.
*/
public class Battlegear2Compat {
public static ItemStack getBattlegear2Offhand(EntityPlayer player) {
return ((IInventoryPlayerBattle) player.inventory).battlegear2$getCurrentOffhandWeapon();
}
}
@@ -27,7 +27,7 @@ public class BetterCrashesCompat implements StateManager.IResettable {
@Override
public void resetState() {
LOGGER.info("Reloading SodiumRenderer");
((IRenderGlobalExt) Minecraft.getMinecraft().renderGlobal).angelica$reload();
((IRenderGlobalExt) Minecraft.getMinecraft().renderGlobal).reload();
LOGGER.info("Resetting GLStateManager");
GLStateManager.reset();
}
@@ -1,97 +0,0 @@
package com.gtnewhorizons.angelica.compat.holoinventory;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import static com.gtnewhorizons.angelica.compat.ModStatus.LOGGER;
public class HoloInventoryReflectionCompat {
private static final String RENDERER_CLASS = "net.dries007.holoInventory.client.Renderer";
private static boolean isLoaded;
private static final MethodHandle renderEventPostMethod;
private static final MethodHandle renderEventMethod;
private static final MethodHandle angelicaOverrideSetter;
static {
MethodHandle renderEventPostMethodTemp = null;
MethodHandle renderEventMethodTemp = null;
MethodHandle angelicaOverrideSetterTemp = null;
final Object rendererInstance;
final Class<?> rendererClass;
try {
rendererClass = Class.forName(RENDERER_CLASS);
// Get and cache the Renderer.INSTANCE
final Field instanceField = rendererClass.getDeclaredField("INSTANCE");
instanceField.setAccessible(true);
rendererInstance = instanceField.get(null);
final MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
renderEventPostMethodTemp = lookup.findVirtual(rendererClass, "renderEvent", MethodType.methodType(void.class, RenderGameOverlayEvent.Post.class));
} catch (NoSuchMethodException e) {
// Fallback for 2.5.0-GTNH and earlier
renderEventMethodTemp = lookup.findVirtual(rendererClass, "renderEvent", MethodType.methodType(void.class, RenderGameOverlayEvent.class));
}
final Field angelicaOverrideField = rendererClass.getDeclaredField("angelicaOverride");
angelicaOverrideField.setAccessible(true);
angelicaOverrideSetterTemp = lookup.unreflectSetter(angelicaOverrideField);
// Bind the method handles to the instance
if (rendererInstance != null) {
if (renderEventPostMethodTemp != null) {
renderEventPostMethodTemp = renderEventPostMethodTemp.bindTo(rendererInstance);
}
if (renderEventMethodTemp != null) {
renderEventMethodTemp = renderEventMethodTemp.bindTo(rendererInstance);
}
angelicaOverrideSetterTemp = angelicaOverrideSetterTemp.bindTo(rendererInstance);
}
isLoaded = true;
LOGGER.info("Successfully initialized HoloInventory compatibility layer");
} catch (Exception e) {
LOGGER.warn("Failed to initialize HoloInventory compatibility layer", e);
isLoaded = false;
}
renderEventPostMethod = renderEventPostMethodTemp;
renderEventMethod = renderEventMethodTemp;
angelicaOverrideSetter = angelicaOverrideSetterTemp;
}
public static void renderEvent(RenderGameOverlayEvent event) {
if (!isLoaded) return;
try {
if (event instanceof RenderGameOverlayEvent.Post && renderEventPostMethod != null) {
renderEventPostMethod.invokeExact((RenderGameOverlayEvent.Post) event);
} else {
renderEventMethod.invokeExact(event);
}
} catch (Error e) {
throw e;
} catch (Throwable t) {
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
isLoaded = false;
}
}
public static void setAngelicaOverride(boolean value) {
if (!isLoaded) return;
try {
angelicaOverrideSetter.invokeExact(value);
} catch (Error e) {
throw e;
} catch (Throwable t) {
LOGGER.error("Failed to invoke HoloInventory renderEvent", t);
isLoaded = false;
}
}
}
@@ -1,17 +0,0 @@
package com.gtnewhorizons.angelica.compat.iris;
/**
* Interface implemented by BiomeGenBase via mixin to cache biome category.
* This avoids recomputing the category every frame for shader uniforms.
*/
public interface BiomeCategoryCache {
/**
* Gets the cached biome category ordinal, or -1 if not yet computed.
*/
int iris$getCachedCategory();
/**
* Sets the cached biome category ordinal.
*/
void iris$setCachedCategory(int category);
}
@@ -1,168 +0,0 @@
package com.gtnewhorizons.angelica.compat.iris;
import com.gtnewhorizons.angelica.compat.ModStatus;
import net.coderbot.iris.parsing.BiomeCategories;
import net.minecraft.world.biome.BiomeGenBase;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Detects and categorizes biomes from popular modded biome mods using cached class checks.
* Currently supports: BiomesOPlenty (BOP), Realistic World Gen (RWG), lotr (b36.15)
*/
public class ModdedBiomeDetector {
private static boolean initialized = false;
// BiomesOPlenty (BOP) cache - dimension-specific base classes
// BOP 1.7.10 hierarchy: BOPBiome -> BOPEndBiome/BOPNetherBiome/BOPOverworldBiome -> specific biomes
private static Class<?> bopEndBiomeClass = null;
private static Class<?> bopNetherBiomeClass = null;
private static Class<?> bopOceanBiomeClass = null;
// Realistic World Gen (RWG) cache - base biome classes that extend BiomeGenBase
// LinkedHashMap maintains insertion order for deterministic behavior
private static final Map<Class<?>, BiomeCategories> rwgClassMap = new LinkedHashMap<>();
private static Class<?> lotrBiomeClass = null;
private static synchronized void initialize() {
if (initialized) return;
initialized = true;
initializeBOP();
initializeRWG();
initializeLOTR();
}
private static void initializeBOP() {
try {
bopEndBiomeClass = Class.forName("biomesoplenty.common.biome.BOPEndBiome");
bopNetherBiomeClass = Class.forName("biomesoplenty.common.biome.BOPNetherBiome");
bopOceanBiomeClass = Class.forName("biomesoplenty.common.biome.BOPOceanBiome");
} catch (ClassNotFoundException ignored) {
// BOP not installed
}
}
private static void initializeRWG() {
// RWG adds actual BiomeGenBase subclasses in rwg.biomes.base package
tryAddRWGClass("rwg.biomes.base.BaseBiomeOcean", BiomeCategories.OCEAN);
tryAddRWGClass("rwg.biomes.base.BaseBiomeRiver", BiomeCategories.RIVER);
tryAddRWGClass("rwg.biomes.base.BaseBiomePlains", BiomeCategories.PLAINS);
tryAddRWGClass("rwg.biomes.base.BaseBiomeColdPlains", BiomeCategories.PLAINS);
tryAddRWGClass("rwg.biomes.base.BaseBiomeHotPlains", BiomeCategories.PLAINS);
tryAddRWGClass("rwg.biomes.base.BaseBiomeHotDesert", BiomeCategories.DESERT);
tryAddRWGClass("rwg.biomes.base.BaseBiomeSnowDesert", BiomeCategories.DESERT); // Cold desert, but still desert
tryAddRWGClass("rwg.biomes.base.BaseBiomeJungle", BiomeCategories.JUNGLE);
tryAddRWGClass("rwg.biomes.base.BaseBiomeTropicalIsland", BiomeCategories.JUNGLE); // Tropical islands are jungle-like
tryAddRWGClass("rwg.biomes.base.BaseBiomeRedwood", BiomeCategories.FOREST); // Redwood/Sequoia forests
tryAddRWGClass("rwg.biomes.base.BaseBiomeTemperateForest", BiomeCategories.FOREST);
tryAddRWGClass("rwg.biomes.base.BaseBiomeColdForest", BiomeCategories.TAIGA); // Cold forests are taiga-like
tryAddRWGClass("rwg.biomes.base.BaseBiomeHotForest", BiomeCategories.FOREST);
tryAddRWGClass("rwg.biomes.base.BaseBiomeSnowForest", BiomeCategories.TAIGA); // Snow forests are taiga
}
private static void initializeLOTR() {
if (ModStatus.isLotrLoaded) {
try {
lotrBiomeClass = Class.forName("lotr.common.world.biome.LOTRBiome");
} catch (ClassNotFoundException ignored) {
// lotr not installed
}
}
}
private static void tryAddRWGClass(String className, BiomeCategories category) {
try {
final Class<?> clazz = Class.forName(className);
rwgClassMap.put(clazz, category);
} catch (ClassNotFoundException ignored) {
// Class not available, RWG not installed or different version
}
}
/**
* Attempts to detect the biome category for modded biomes.
* Returns null if no modded biome detection applies.
*/
public static BiomeCategories detectModdedBiome(BiomeGenBase biome) {
if (!initialized) {
initialize();
}
// BiomesOPlenty detection - dimension-specific classes give us clear categories
if (bopEndBiomeClass != null && bopEndBiomeClass.isInstance(biome)) {
return BiomeCategories.THE_END;
}
if (bopNetherBiomeClass != null && bopNetherBiomeClass.isInstance(biome)) {
return BiomeCategories.NETHER;
}
if (bopOceanBiomeClass != null && bopOceanBiomeClass.isInstance(biome)) {
return BiomeCategories.OCEAN;
}
// For other BOP overworld biomes (e.g., Alps, Arctic, BambooForest),
// fall through to name/property-based detection
// Realistic World Gen detection
if (biome.getBiomeClass().getName().contains("rwg.biomes")) {
for (Map.Entry<Class<?>, BiomeCategories> entry : rwgClassMap.entrySet()) {
if (entry.getKey().isInstance(biome)) {
return entry.getValue();
}
}
}
// lotr
if (lotrBiomeClass!= null && lotrBiomeClass.isInstance(biome)) {
return detectLOTRBiome(biome);
}
return null;
}
private static BiomeCategories detectLOTRBiome(BiomeGenBase biome) {
return switch (biome.biomeName) {
case "river", "farHaradJungleLake", "lake" -> BiomeCategories.RIVER;
case "rohan", "rivendell", "rhunIsland", "rhunLandHills", "rhunLandSteppe", "rhunLand", "andrast",
"blackrootVale", "lamedon", "imlothMelui", "towerHills", "dorwinion", "dale", "shireMoors", "wold",
"anduinVale", "rhun", "lebennin", "pertorogwaith", "breeland", "island", "pinnathGelin",
"minhiriath", "pelennor", "adornland", "nanCurunir", "ithilienWasteland", "fangornClearing",
"barrowDowns", "dorEnErnilHills", "dorEnErnil", "nurn", "wilderland", "eastBight", "lindon",
"eregion", "enedwaith", "meneltarma", "anduinHills", "eriador", "angle", "loneLandsHills",
"loneLands", "pelargir", "rohanUrukHighlands", "celebrant", "gondor", "shire", "nurnen", "farHarad" ->
BiomeCategories.PLAINS;
case "mistyMountains", "windMountains", "farHaradVolcano", "redMountains", "haradMountains",
"angmarMountains", "blueMountains", "greyMountains", "whiteMountains", "mordorMountains" ->
BiomeCategories.MOUNTAIN;
case "shireWoodlands", "rhunIslandForest", "rhunRedForest", "pukel", "lossarnach", "gulfHaradForest",
"nearHaradFertileForest", "dolGuldur", "rhunForest", "tauredainClearing", "umbarForest",
"farHaradForest", "chetwood", "woodlandRealmHills", "mirkwoodNorth", "whiteDowns", "ithilienHills",
"gondorWoodlands", "rohanWoodlands", "fangornWasteland", "mirkwoodMountains", "lindonWoodlands",
"lothlorienEdge", "erynVorn", "oldForest", "fangorn", "dunland", "ithilien", "mirkwoodCorrupted",
"woodlandRealm", "trollshaws", "lothlorien", "farHaradKanuka" -> BiomeCategories.FOREST;
case "mordor", "lastDesert", "harnedor", "easternDesolation", "morgulVale", "gorgoroth", "udun",
"nearHaradRedDesert", "nearHaradSemiDesert", "lostladen",
"nearHaradHills", "nearHarad", "nanUngol", "dagorlad", "brownLands" -> BiomeCategories.DESERT;
case "ironHills", "rivendellHills", "windMountainsFoothills", "lamedonHills", "redMountainsFoothills",
"tolfalas", "dorwinionHills", "whiteMountainsFoothills", "blueMountainsFoothills",
"greyMountainsFoothills", "mistyMountainsFoothills", "erebor", "angmar", "eriadorDowns",
"ettenmoors", "emynMuil" -> BiomeCategories.EXTREME_HILLS;
case "deadMarshes", "shireMarshes", "farHaradMangrove", "farHaradSwamp", "swanfleet", "nindalf",
"longMarshes", "entwashMouth", "nurnMarshes", "gladdenFields", "midgewater", "anduinMouth" ->
BiomeCategories.SWAMP;
case "harondor", "nearHaradOasis", "nearHaradRiverbank", "farHaradBushlandHills", "farHaradBushland",
"gulfHarad", "nearHaradFertile", "umbarHills", "umbar", "farHaradAridHills", "farHaradArid" ->
BiomeCategories.SAVANNA;
case "ocean" -> BiomeCategories.OCEAN;
case "forodwaith", "forodwaithGlacier", "tundra", "forodwaithMountains" -> BiomeCategories.ICY;
case "lindonCoast", "beachWhite", "farHaradCoast", "forodwaithCoast", "beachGravel", "beach" ->
BiomeCategories.BEACH;
case "coldfells", "wilderlandNorth", "taiga" -> BiomeCategories.TAIGA;
case "farHaradJungle", "halfTrollForest", "farHaradJungleMountains",
"farHaradCloudForest", "farHaradJungleEdge" -> BiomeCategories.JUNGLE;
case "utumno" -> BiomeCategories.NONE;
default -> null;
};
}
}
@@ -0,0 +1,327 @@
package com.gtnewhorizons.angelica.compat.lwjgl;
import static com.gtnewhorizons.angelica.compat.lwjgl.Pointer.BITS64;
import it.unimi.dsi.fastutil.longs.LongPredicate;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import org.lwjgl.BufferUtils;
/**
* Backported from LWJGL3 under the BSD 3-Clause "New" or "Revised" License license
*
* <p>This class provides functionality for managing native memory.
*
* <p>All methods in this class will make use of {@link sun.misc.Unsafe} if it's available, for performance. If Unsafe is not available, the fallback
* implementations make use of reflection and, in the worst-case, JNI.</p>
*
* <p>Method names in this class are prefixed with {@code mem} to avoid ambiguities when used with static imports.</p>
*/
public class CompatMemoryUtil {
public static final long NULL = 0;
static final sun.misc.Unsafe UNSAFE;
private static final Class<? extends ByteBuffer> BUFFER_BYTE;
private static final Class<? extends IntBuffer> BUFFER_INT;
private static final Class<? extends FloatBuffer> BUFFER_FLOAT;
private static final long MARK;
private static final long POSITION;
private static final long LIMIT;
private static final long CAPACITY;
private static final long ADDRESS;
static {
ByteBuffer bb = ByteBuffer.allocateDirect(0).order(ByteOrder.nativeOrder());
BUFFER_BYTE = bb.getClass();
BUFFER_INT = bb.asIntBuffer().getClass();
BUFFER_FLOAT = bb.asFloatBuffer().getClass();
UNSAFE = getUnsafeInstance();
MARK = getMarkOffset();
POSITION = getPositionOffset();
LIMIT = getLimitOffset();
CAPACITY = getCapacityOffset();
ADDRESS = getAddressOffset();
}
public static ByteBuffer memReallocDirect(ByteBuffer old, int capacity) {
ByteBuffer newBuf = BufferUtils.createByteBuffer(capacity);
int oldPos = old.position();
old.rewind();
newBuf.put(old);
newBuf.position(Math.min(capacity, oldPos));
return newBuf;
}
public static IntBuffer memReallocDirect(IntBuffer old, int capacity) {
IntBuffer newBuf = BufferUtils.createIntBuffer(capacity);
int oldPos = old.position();
old.rewind();
newBuf.put(old);
newBuf.position(Math.min(capacity, oldPos));
return newBuf;
}
public static FloatBuffer memReallocDirect(FloatBuffer old, int capacity) {
FloatBuffer newBuf = BufferUtils.createFloatBuffer(capacity);
int oldPos = old.position();
old.rewind();
newBuf.put(old);
newBuf.position(Math.min(capacity, oldPos));
return newBuf;
}
/* -------------------------------------
-------------------------------------
UNSAFE MEMORY ACCESS API
-------------------------------------
------------------------------------- */
private static final int FILL_PATTERN_32 = Integer.divideUnsigned(-1, 255);
private static final long FILL_PATTERN_64 = Long.divideUnsigned(-1L, 255L);
/**
* Sets all bytes in a specified block of memory to a fixed value (usually zero).
*
* @param ptr the starting memory address
* @param value the value to set (memSet will convert it to unsigned byte)
* @param bytes the number of bytes to set
*/
public static void memSet(long ptr, int value, long bytes) {
if (/*DEBUG*/ false && (ptr == NULL || bytes < 0)) {
throw new IllegalArgumentException();
}
/*
- Unsafe.setMemory is very slow.
- A custom Java loop is fastest at small sizes, approximately up to 256 bytes.
- The native memset becomes fastest at bigger sizes, when the JNI overhead becomes negligible.
TODO: verify this
*/
//UNSAFE.setMemory(ptr, bytes, (byte)(value & 0xFF));
//if (bytes < 256L) {
int p = (int)ptr;
if (BITS64) {
if ((p & 7) == 0) {
memSet64(ptr, value, (int)bytes & 0xFF);
return;
}
} else {
if ((p & 3) == 0) {
memSet32(p, value, (int)bytes & 0xFF);
return;
}
}
//}
//nmemset(ptr, value, bytes);
}
private static void memSet64(long ptr, int value, int bytes) {
int aligned = bytes & ~7;
// Aligned body
long valuel = (value & 0xFF) * FILL_PATTERN_64;
for (int i = 0; i < aligned; i += 8) {
UNSAFE.putLong(null, ptr + i, valuel);
}
// Unaligned tail
byte valueb = (byte)(value & 0xFF);
for (int i = aligned; i < bytes; i++) {
UNSAFE.putByte(null, ptr + i, valueb);
}
}
private static void memSet32(int ptr, int value, int bytes) {
int aligned = bytes & ~3;
// Aligned body
int vi = (value & 0xFF) * FILL_PATTERN_32;
for (int i = 0; i < aligned; i += 4) {
UNSAFE.putInt(null, (ptr + i) & 0xFFFF_FFFFL, vi);
}
// Unaligned tail
byte vb = (byte)(value & 0xFF);
for (int i = aligned; i < bytes; i++) {
UNSAFE.putByte(null, (ptr + i) & 0xFFFF_FFFFL, vb);
}
}
public static boolean memGetBoolean(long ptr) { return UNSAFE.getByte(null, ptr) != 0; }
public static byte memGetByte(long ptr) { return UNSAFE.getByte(null, ptr); }
public static short memGetShort(long ptr) { return UNSAFE.getShort(null, ptr); }
public static int memGetInt(long ptr) { return UNSAFE.getInt(null, ptr); }
public static long memGetLong(long ptr) { return UNSAFE.getLong(null, ptr); }
public static float memGetFloat(long ptr) { return UNSAFE.getFloat(null, ptr); }
public static double memGetDouble(long ptr) { return UNSAFE.getDouble(null, ptr); }
public static void memPutByte(long ptr, byte value) { UNSAFE.putByte(null, ptr, value); }
public static void memPutShort(long ptr, short value) { UNSAFE.putShort(null, ptr, value); }
public static void memPutInt(long ptr, int value) { UNSAFE.putInt(null, ptr, value); }
public static void memPutLong(long ptr, long value) { UNSAFE.putLong(null, ptr, value); }
public static void memPutFloat(long ptr, float value) { UNSAFE.putFloat(null, ptr, value); }
public static void memPutDouble(long ptr, double value) { UNSAFE.putDouble(null, ptr, value); }
// -------------------------------------------------
// -------------------------------------------------
// -------------------------------------------------
private static sun.misc.Unsafe getUnsafeInstance() {
java.lang.reflect.Field[] fields = sun.misc.Unsafe.class.getDeclaredFields();
/*
Different runtimes use different names for the Unsafe singleton,
so we cannot use .getDeclaredField and we scan instead. For example:
Oracle: theUnsafe
PERC : m_unsafe_instance
Android: THE_ONE
*/
for (java.lang.reflect.Field field : fields) {
if (!field.getType().equals(sun.misc.Unsafe.class)) {
continue;
}
int modifiers = field.getModifiers();
if (!(java.lang.reflect.Modifier.isStatic(modifiers) && java.lang.reflect.Modifier.isFinal(modifiers))) {
continue;
}
try {
field.setAccessible(true);
return (sun.misc.Unsafe)field.get(null);
} catch (Exception ignored) {
}
break;
}
throw new UnsupportedOperationException("LWJGL requires sun.misc.Unsafe to be available.");
}
/**
* Recursively searches an object and its superclasses for a field with the same name and type. If the field isn't
* public, it may not be stable across library/JVM versions, and these calls are likely not portable. You have been
* warned!
*/
private static long getFieldOffset(Class<?> containerType, Class<?> fieldType, String name) {
Class<?> c = containerType;
while (c != Object.class) {
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
if (!field.getType().isAssignableFrom(fieldType) || Modifier.isStatic(field.getModifiers()) || field.isSynthetic()) {
continue;
}
long offset = UNSAFE.objectFieldOffset(field);
if (field.getName().equals(name)) {
return offset;
}
}
c = c.getSuperclass();
}
throw new UnsupportedOperationException("Failed to find field offset in class.");
}
private static long getFieldOffset(Class<?> containerType, Class<?> fieldType, LongPredicate predicate) {
Class<?> c = containerType;
while (c != Object.class) {
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
if (!field.getType().isAssignableFrom(fieldType) || Modifier.isStatic(field.getModifiers()) || field.isSynthetic()) {
continue;
}
long offset = UNSAFE.objectFieldOffset(field);
if (predicate.test(offset)) {
return offset;
}
}
c = c.getSuperclass();
}
throw new UnsupportedOperationException("Failed to find field offset in class.");
}
private static long getFieldOffsetInt(Object container, int value) {
return getFieldOffset(container.getClass(), int.class, offset -> UNSAFE.getInt(container, offset) == value);
}
private static long getAddressOffset() {
final ByteBuffer bb = ByteBuffer.allocateDirect(0);
return getFieldOffset(bb.getClass(), long.class, "address");
// TODO: do this portably
// long MAGIC_ADDRESS = 0xDEADBEEF8BADF00DL & (BITS32 ? 0xFFFF_FFFFL : 0xFFFF_FFFF_FFFF_FFFFL);
// ByteBuffer bb = Objects.requireNonNull(NewDirectByteBuffer(MAGIC_ADDRESS, 0));
//return getFieldOffset(bb.getClass(), long.class, offset -> UNSAFE.getLong(bb, offset) == MAGIC_ADDRESS);
}
private static final int MAGIC_CAPACITY = 0x0D15EA5E;
private static final int MAGIC_POSITION = 0x00FACADE;
private static long getMarkOffset() {
// TODO: is this actually reliable without NewDirectByteBuffer?
ByteBuffer bb = ByteBuffer.allocateDirect(0);
return getFieldOffsetInt(bb, -1);
// ByteBuffer bb = Objects.requireNonNull(NewDirectByteBuffer(1L, 0));
// return getFieldOffsetInt(bb, -1);
}
private static long getPositionOffset() {
ByteBuffer bb = ByteBuffer.allocateDirect(MAGIC_CAPACITY);
bb.position(MAGIC_POSITION);
return getFieldOffsetInt(bb, MAGIC_POSITION);
}
private static long getLimitOffset() {
ByteBuffer bb = ByteBuffer.allocateDirect(MAGIC_CAPACITY);
bb.limit(MAGIC_POSITION);
return getFieldOffsetInt(bb, MAGIC_POSITION);
}
private static long getCapacityOffset() {
ByteBuffer bb = ByteBuffer.allocateDirect(MAGIC_CAPACITY);
bb.limit(0);
return getFieldOffsetInt(bb, MAGIC_CAPACITY);
}
static IntBuffer wrapBufferInt(long address, int capacity) {
IntBuffer buffer;
try {
buffer = (IntBuffer)UNSAFE.allocateInstance(BUFFER_INT);
} catch (InstantiationException e) {
throw new UnsupportedOperationException(e);
}
UNSAFE.putLong(buffer, ADDRESS, address);
UNSAFE.putInt(buffer, MARK, -1);
UNSAFE.putInt(buffer, LIMIT, capacity);
UNSAFE.putInt(buffer, CAPACITY, capacity);
return buffer;
}
static FloatBuffer wrapBufferFloat(long address, int capacity) {
FloatBuffer buffer;
try {
buffer = (FloatBuffer)UNSAFE.allocateInstance(BUFFER_FLOAT);
} catch (InstantiationException e) {
throw new UnsupportedOperationException(e);
}
UNSAFE.putLong(buffer, ADDRESS, address);
UNSAFE.putInt(buffer, MARK, -1);
UNSAFE.putInt(buffer, LIMIT, capacity);
UNSAFE.putInt(buffer, CAPACITY, capacity);
return buffer;
}
}
@@ -0,0 +1,382 @@
/*
* This class is backported from LWJGL3 under the BSD 3-clause "New" or "Revised" License
*/
package com.gtnewhorizons.angelica.compat.lwjgl;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memPutDouble;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memPutFloat;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memPutInt;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memPutLong;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memPutShort;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.memSet;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.wrapBufferFloat;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.wrapBufferInt;
import com.gtnewhorizons.angelica.loading.AngelicaTweaker;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.BufferUtils;
import org.lwjgl.MemoryUtil;
/**
* An off-heap memory stack.
*
* <p>This class should be used in a thread-local manner for stack allocations.</p>
*
* @ see Configuration#STACK_SIZE
* @ see Configuration#DEBUG_STACK
*/
@SuppressWarnings("LombokGetterMayBeUsed")
public class MemoryStack extends Pointer.Default implements AutoCloseable {
private static final int DEFAULT_STACK_SIZE = /*Configuration.STACK_SIZE.get(64)*/ 64 * 1024;
private static final int DEFAULT_STACK_FRAMES = 8;
private static final ThreadLocal<MemoryStack> TLS = ThreadLocal.withInitial(MemoryStack::create);
static {
if (DEFAULT_STACK_SIZE < 0) {
throw new IllegalStateException("Invalid stack size.");
}
}
@SuppressWarnings({"FieldCanBeLocal", "unused"})
private final @Nullable ByteBuffer container;
private final int size;
private int pointer;
private int[] frames;
protected int frameIndex;
/**
* Creates a new {@code MemoryStack} backed by the specified memory region.
*
* <p>In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.</p>
*
* @param container the backing memory buffer, may be null
* @param address the backing memory address
* @param size the backing memory size
*/
protected MemoryStack(@Nullable ByteBuffer container, long address, int size) {
super(address);
this.container = container;
this.size = size;
this.pointer = size;
this.frames = new int[DEFAULT_STACK_FRAMES];
}
/**
* Creates a new {@code MemoryStack} with the default size.
*
* <p>In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.</p>
*/
public static MemoryStack create() {
return create(DEFAULT_STACK_SIZE);
}
/**
* Creates a new {@code MemoryStack} with the specified size.
*
* <p>In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.</p>
*
* @param capacity the maximum number of bytes that may be allocated on the stack
*/
public static MemoryStack create(int capacity) {
return create(BufferUtils.createByteBuffer(capacity));
}
/**
* Creates a new {@code MemoryStack} backed by the specified memory buffer.
*
* <p>In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.</p>
*
* @param buffer the backing memory buffer
*/
public static MemoryStack create(ByteBuffer buffer) {
long address = MemoryUtil.getAddress(buffer);
int size = buffer.remaining();
return new MemoryStack(buffer, address, size);
}
/**
* Creates a new {@code MemoryStack} backed by the specified memory region.
*
* <p>In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.</p>
*
* @param address the backing memory address
* @param size the backing memory size
*/
public static MemoryStack ncreate(long address, int size) {
return new MemoryStack(null, address, size);
}
/**
* Stores the current stack pointer and pushes a new frame to the stack.
*
* <p>This method should be called when entering a method, before doing any stack allocations. When exiting a method, call the {@link #pop} method to
* restore the previous stack frame.</p>
*
* <p>Pairs of push/pop calls may be nested. Care must be taken to:</p>
* <ul>
* <li>match every push with a pop</li>
* <li>not call pop before push has been called at least once</li>
* <li>not nest push calls to more than the maximum supported depth</li>
* </ul>
*
* @return this stack
*/
public MemoryStack push() {
if (frameIndex == frames.length) {
frameOverflow();
}
frames[frameIndex++] = pointer;
return this;
}
private void frameOverflow() {
if (/*DEBUG*/ false) {
AngelicaTweaker.LOGGER.warn("[WARNING] Out of frame stack space (" + frames.length + ") in thread: " + Thread.currentThread());
}
frames = Arrays.copyOf(frames, frames.length * 3 / 2);
}
/**
* Pops the current stack frame and moves the stack pointer to the end of the previous stack frame.
*
* @return this stack
*/
public MemoryStack pop() {
pointer = frames[--frameIndex];
return this;
}
/**
* Calls {@link #pop} on this {@code MemoryStack}.
*
* <p>This method should not be used directly. It is called automatically when the {@code MemoryStack} is used as a resource in a try-with-resources
* statement.</p>
*/
@Override
public void close() {
//noinspection resource
pop();
}
/**
* Returns the address of the backing off-heap memory.
*
* <p>The stack grows "downwards", so the bottom of the stack is at {@code address + size}, while the top is at {@code address}.</p>
*/
public long getAddress() {
return address;
}
/**
* Returns the size of the backing off-heap memory.
*
* <p>This is the maximum number of bytes that may be allocated on the stack.</p>
*/
public int getSize() {
return size;
}
/**
* Returns the current frame index.
*
* <p>This is the current number of nested {@link #push} calls.</p>
*/
public int getFrameIndex() {
return frameIndex;
}
/** Returns the memory address at the current stack pointer. */
public long getPointerAddress() {
return address + (pointer & 0xFFFF_FFFFL);
}
/**
* Returns the current stack pointer.
*
* <p>The stack grows "downwards", so when the stack is empty {@code pointer} is equal to {@code size}. On every allocation {@code pointer} is reduced by
* the allocated size (after alignment) and {@code address + pointer} points to the first byte of the last allocation.</p>
*
* <p>Effectively, this methods returns how many more bytes may be allocated on the stack.</p>
*/
public int getPointer() {
return pointer;
}
/**
* Sets the current stack pointer.
*
* <p>This method directly manipulates the stack pointer. Using it irresponsibly may break the internal state of the stack. It should only be used in rare
* cases or in auto-generated code.</p>
*/
public void setPointer(int pointer) {
if (/*CHECKS*/ false) {
checkPointer(pointer);
}
this.pointer = pointer;
}
private void checkPointer(int pointer) {
if (pointer < 0 || size < pointer) {
throw new IndexOutOfBoundsException("Invalid stack pointer");
}
}
private static void checkAlignment(int alignment) {
if (Integer.bitCount(alignment) != 1) {
throw new IllegalArgumentException("Alignment must be a power-of-two value.");
}
}
/**
* Calls {@link #nmalloc(int, int)} with {@code alignment} equal to {@link Pointer#POINTER_SIZE POINTER_SIZE}.
*
* @param size the allocation size
*
* @return the memory address on the stack for the requested allocation
*/
public long nmalloc(int size) {
return nmalloc(POINTER_SIZE, size);
}
/**
* Allocates a block of {@code size} bytes of memory on the stack. The content of the newly allocated block of memory is not initialized, remaining with
* indeterminate values.
*
* @param alignment the required alignment
* @param size the allocation size
*
* @return the memory address on the stack for the requested allocation
*/
public long nmalloc(int alignment, int size) {
// Align address to the specified alignment
long address = (this.address + pointer - size) & ~Integer.toUnsignedLong(alignment - 1);
pointer = (int)(address - this.address);
if (/*CHECKS*/ false && pointer < 0) {
throw new OutOfMemoryError("Out of stack space.");
}
return address;
}
/**
* Allocates a block of memory on the stack for an array of {@code num} elements, each of them {@code size} bytes long, and initializes all its bits to
* zero.
*
* @param alignment the required element alignment
* @param num num the number of elements to allocate
* @param size the size of each element
*
* @return the memory address on the stack for the requested allocation
*/
public long ncalloc(int alignment, int num, int size) {
int bytes = num * size;
long address = nmalloc(alignment, bytes);
memSet(address, 0, bytes);
return address;
}
// -------------------------------------------------
/** Unsafe version of {@link #shorts(short)}. */
public long nshort(short value) {
long a = nmalloc(2, 2);
memPutShort(a, value);
return a;
}
// -------------------------------------------------
/** Int version of {@link #malloc(int)}. */
public IntBuffer mallocInt(int size) { return wrapBufferInt(nmalloc(4, size << 2), size); }
/** Unsafe version of {@link #ints(int)}. */
public long nint(int value) {
long a = nmalloc(4, 4);
memPutInt(a, value);
return a;
}
// -------------------------------------------------
/** Unsafe version of {@link #longs(long)}. */
public long nlong(long value) {
long a = nmalloc(8, 8);
memPutLong(a, value);
return a;
}
// -------------------------------------------------
/** Float version of {@link #malloc(int)}. */
public FloatBuffer mallocFloat(int size) { return wrapBufferFloat(nmalloc(4, size << 2), size); }
/** Unsafe version of {@link #floats(float)}. */
public long nfloat(float value) {
long a = nmalloc(4, 4);
memPutFloat(a, value);
return a;
}
// -------------------------------------------------
/** Unsafe version of {@link #doubles(double)}. */
public long ndouble(double value) {
long a = nmalloc(8, 8);
memPutDouble(a, value);
return a;
}
// -------------------------------------------------
// -------------------------------------------------
// -------------------------------------------------
/** Returns the stack of the current thread. */
public static MemoryStack stackGet() {
return TLS.get();
}
/**
* Calls {@link #push} on the stack of the current thread.
*
* @return the stack of the current thread.
*/
public static MemoryStack stackPush() {
return stackGet().push();
}
/**
* Calls {@link #pop} on the stack of the current thread.
*
* @return the stack of the current thread.
*/
public static MemoryStack stackPop() {
return stackGet().pop();
}
/** Thread-local version of {@link #nmalloc(int)}. */
public static long nstackMalloc(int size) { return stackGet().nmalloc(size); }
/** Thread-local version of {@link #nmalloc(int, int)}. */
public static long nstackMalloc(int alignment, int size) { return stackGet().nmalloc(alignment, size); }
/** Thread-local version of {@link #ncalloc}. */
public static long nstackCalloc(int alignment, int num, int size) { return stackGet().ncalloc(alignment, num, size); }
// -------------------------------------------------
}
@@ -0,0 +1,93 @@
/*
* This class is backported from LWJGL3 under the BSD 3-clause "New" or "Revised" License
*/
package com.gtnewhorizons.angelica.compat.lwjgl;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.NULL;
import static com.gtnewhorizons.angelica.compat.lwjgl.CompatMemoryUtil.UNSAFE;
import static org.lwjgl.LWJGLUtil.CHECKS;
import net.minecraft.util.Util;
import org.jetbrains.annotations.Nullable;
/**
* Pointer interface.
*
* <p>LWJGL can run on both 32bit and 64bit architectures. Since LWJGL applications deal with native memory directly, this interface provides necessary
* information about the underlying architecture of the running JVM process.</p>
*
* <p>When interacting with native functions, pointer values are mapped to Java {@code long}. LWJGL automatically converts long values to the correct pointer
* addresses when used in native code. Native functions sometimes require arrays of pointer values; the {@link PointerBuffer} class may be used for that
* purpose. It has an API similar to a {@link java.nio.LongBuffer} but handles pointer casts automatically.</p>
*/
public interface Pointer {
/** The pointer size in bytes. Will be 4 on a 32bit JVM and 8 on a 64bit one. */
int POINTER_SIZE = UNSAFE.addressSize();
/** The pointer size power-of-two. Will be 2 on a 32bit JVM and 3 on a 64bit one. */
int POINTER_SHIFT = POINTER_SIZE == 8 ? 3 : 2;
/** The value of {@code sizeof(long)} for the current platform. */
int CLONG_SIZE = POINTER_SIZE == 8 && Util.getOSType() == Util.EnumOS.WINDOWS ? 4 : POINTER_SIZE;
/** The value of {@code sizeof(long)} as a power-of-two. */
int CLONG_SHIFT = CLONG_SIZE == 8 ? 3 : 2;
/** Will be true on a 32bit JVM. */
boolean BITS32 = POINTER_SIZE * 8 == 32;
/** Will be true on a 64bit JVM. */
boolean BITS64 = POINTER_SIZE * 8 == 64;
/**
* Returns the raw pointer address as a {@code long} value.
*
* @return the pointer address
*/
long address();
/** Default {@link Pointer} implementation. */
abstract class Default implements Pointer {
// Removed final due to JDK-8139758. TODO: Restore if the fix is backported to JDK 8.
protected long address;
protected Default(long address) {
if (CHECKS && address == NULL) {
throw new NullPointerException();
}
this.address = address;
}
@Override
public long address() {
return address;
}
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Pointer)) {
return false;
}
Pointer that = (Pointer)o;
return address == that.address();
}
public int hashCode() {
return (int)(address ^ (address >>> 32));
}
@Override
public String toString() {
return String.format("%s pointer [0x%X]", getClass().getSimpleName(), address);
}
}
}
@@ -15,7 +15,6 @@ public class ByteBufferBackedInputStream extends InputStream {
this.buf = buf;
}
@Override
public int read() throws IOException {
if (!buf.hasRemaining()) {
return -1;
@@ -9,65 +9,35 @@ import net.minecraft.util.MathHelper;
import org.joml.Matrix4f;
import org.joml.Vector3d;
import org.joml.Vector3f;
import org.joml.Vector4f;
/**
* Singleton camera instance that tracks the current camera position and orientation.
*/
@Getter
public class Camera {
public static final Camera INSTANCE = new Camera();
final Vector3d pos = new Vector3d();
final BlockPos blockPos = new BlockPos();
float pitch;
float yaw;
EntityLivingBase entity;
boolean thirdPerson;
final float partialTicks;
private final Vector3d pos = new Vector3d();
private final Vector3d entityPos = new Vector3d();
private final BlockPos blockPos = new BlockPos();
private final Vector3f offset = new Vector3f();
private final Matrix4f inverseModelView = new Matrix4f();
private float pitch;
private float yaw;
private EntityLivingBase entity;
private boolean thirdPerson;
private float partialTicks;
private Camera() {
}
/**
* Updates the camera position and orientation based on the current entity and partial ticks.
*
* @param entity The entity to track (usually the render view entity)
* @param partialTicks The partial tick time for interpolation
*/
public void update(EntityLivingBase entity, float partialTicks) {
public Camera(EntityLivingBase entity, float partialTicks) {
this.partialTicks = partialTicks;
final Vector4f offset = new Vector4f(); // third person offset
final Matrix4f inverseModelView = new Matrix4f(RenderingState.INSTANCE.getModelViewMatrix()).invert();
inverseModelView.transform(offset);
final double camX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks + offset.x;
final double camY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks + offset.y;
final double camZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks + offset.z;
this.entity = entity;
// Entity position (interpolated, at eye level)
final double entityX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks;
final double entityY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks;
final double entityZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks;
final double eyeHeight = entity.getEyeHeight();
entityPos.set(entityX, entityY + eyeHeight, entityZ);
thirdPerson = Minecraft.getMinecraft().gameSettings.thirdPersonView > 0;
final double camX, camY, camZ;
offset.set(0, 0, 0);
if (thirdPerson) {
// Third person: use inverse modelview to find camera offset
inverseModelView.set(RenderingState.INSTANCE.getModelViewMatrix()).invert();
inverseModelView.transformPosition(offset);
}
camX = entityX + offset.x;
camY = entityY + eyeHeight + offset.y;
camZ = entityZ + offset.z;
pos.set(camX, camY, camZ);
blockPos.set(MathHelper.floor_double(camX), MathHelper.floor_double(camY), MathHelper.floor_double(camZ));
pitch = entity.cameraPitch;
yaw = entity.rotationYaw;
thirdPerson = Minecraft.getMinecraft().gameSettings.thirdPersonView == 1;
}
}
@@ -40,7 +40,6 @@ public class ChunkPos {
return (long)x & 4294967295L | ((long)z & 4294967295L) << 32;
}
@Override
public int hashCode()
{
final int i = 1664525 * this.x + 1013904223;
@@ -48,7 +47,6 @@ public class ChunkPos {
return i ^ j;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
@@ -1,17 +0,0 @@
package com.gtnewhorizons.angelica.compat.mojang;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.PlayerControllerMP;
public final class GameModeUtil {
private GameModeUtil() {
}
public static boolean isSpectator() {
final PlayerControllerMP controller = Minecraft.getMinecraft().playerController;
if (controller == null) {
return false;
}
return controller.currentGameType.getID() == 3;
}
}
@@ -1,23 +1,10 @@
package com.gtnewhorizons.angelica.compat.mojang;
import com.gtnewhorizons.angelica.compat.ModStatus;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
public enum InteractionHand {
MAIN_HAND,
OFF_HAND;
InteractionHand() {
}
public ItemStack getItemInHand(EntityPlayer player){
if (ModStatus.isBackhandLoaded && this == InteractionHand.OFF_HAND){ // off hand (requires backhand)
return ModStatus.backhandCompat.getOffhandItem(player);
}
else { // main hand
return player.getHeldItem();
}
}
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.compat.mojang;
import com.gtnewhorizons.angelica.compat.lwjgl.MemoryStack;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
@@ -7,8 +8,6 @@ import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import javax.imageio.ImageIO;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import lombok.Getter;
import net.coderbot.iris.Iris;
import org.lwjgl.opengl.GL11;
@@ -59,20 +58,15 @@ public class NativeImage extends BufferedImage {
// final int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_WIDTH);
// final int height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_HEIGHT);
final IntBuffer buffer = MemoryUtilities.memAllocInt(size);
try {
try (final MemoryStack stack = MemoryStack.stackPush()) {
final IntBuffer buffer = stack.mallocInt(size);
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, level, format.glFormat, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
int[] data = new int[size];
buffer.get(data);
setRGB(0, 0, width, height, data, 0, width);
} finally {
MemoryUtilities.memFree(buffer);
}
}
public void writeToFile(File file) throws IOException{
try {
ImageIO.write(this, "png", file);
@@ -110,15 +104,18 @@ public class NativeImage extends BufferedImage {
public enum Format {
RGBA(4, GL11.GL_RGBA),
RGB(3, GL11.GL_RGB);
RGBA(4, GL11.GL_RGBA, BufferedImage.TYPE_INT_ARGB),
RGB(3, GL11.GL_RGB, BufferedImage.TYPE_INT_RGB);
private final int components;
private final int glFormat;
private final int imageType;
Format(int components, int glFormat) {
Format(int components, int glFormat, int imageType) {
this.components = components;
this.glFormat = glFormat;
this.imageType = imageType;
}
}
@@ -0,0 +1,63 @@
package com.gtnewhorizons.angelica.compat.nd;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
public class BufferWriter {
// Adapted from Neodymium
private final ByteBuffer buf;
private final FloatBuffer floatBuffer;
private final ShortBuffer shortBuffer;
private final IntBuffer intBuffer;
public BufferWriter(ByteBuffer buf) {
this.buf = buf;
this.floatBuffer = buf.asFloatBuffer();
this.shortBuffer = buf.asShortBuffer();
this.intBuffer = buf.asIntBuffer();
}
private void incrementPosition(int add) {
buf.position(buf.position() + add);
floatBuffer.position(buf.position() / 4);
shortBuffer.position(buf.position() / 2);
intBuffer.position(buf.position() / 4);
}
public void writeFloat(float x) {
try {
floatBuffer.put(x);
incrementPosition(4);
} catch(Exception e){
e.printStackTrace();
}
}
public void writeInt(int x) {
intBuffer.put(x);
incrementPosition(4);
}
public void writeByte(byte x) {
buf.put(x); // this increments the buffer position by 1
incrementPosition(0);
}
public int position() {
return buf.position();
}
public void writeShort(short s) {
shortBuffer.put(s);
incrementPosition(2);
}
}
@@ -0,0 +1,47 @@
package com.gtnewhorizons.angelica.compat.nd.writers;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
import java.nio.ByteBuffer;
public class ItemVBOQuadWriter implements IWriteQuads {
private boolean direct;
public ItemVBOQuadWriter() {
init(false);
}
public void init(boolean direct) {
// This would need to be re-initialized if the direct vs indirect changes
this.direct = direct;
}
@Override
public void writeQuad(QuadView quad, ByteBuffer buf) {
if(direct) {
writeQuadDirect(quad, buf);
} else {
writeQuadIndirect(quad, buf);
}
}
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
throw new UnsupportedOperationException("Direct mode not supported yet");
}
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
for(int idx = 0; idx < 4; ++idx) {
// Position
buf.putFloat(quad.getX(idx));
buf.putFloat(quad.getY(idx));
buf.putFloat(quad.getZ(idx));
// Texture
buf.putFloat(quad.getTexU(idx));
buf.putFloat(quad.getTexV(idx));
// Normals + Padding
buf.putInt(quad.getNormal(idx));
}
}
}
@@ -0,0 +1,47 @@
package com.gtnewhorizons.angelica.compat.nd.writers;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
import java.nio.ByteBuffer;
public class PositionColorTextureQuadWriter implements IWriteQuads {
private boolean direct;
public PositionColorTextureQuadWriter() {
init(false);
}
public void init(boolean direct) {
// This would need to be re-initialized if the direct vs indirect changes
this.direct = direct;
}
@Override
public void writeQuad(QuadView quad, ByteBuffer buf) {
if(direct) {
writeQuadDirect(quad, buf);
} else {
writeQuadIndirect(quad, buf);
}
}
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
throw new UnsupportedOperationException("Direct mode not supported yet");
}
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
for(int idx = 0; idx < 4; ++idx) {
// Position
buf.putFloat(quad.getX(idx));
buf.putFloat(quad.getY(idx));
buf.putFloat(quad.getZ(idx));
// Color
buf.putInt(quad.getColor(idx));
// Texture
buf.putFloat(quad.getTexU(idx));
buf.putFloat(quad.getTexV(idx));
}
}
}
@@ -0,0 +1,40 @@
package com.gtnewhorizons.angelica.compat.nd.writers;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
import java.nio.ByteBuffer;
public class PositionQuadWriter implements IWriteQuads {
private boolean direct;
public PositionQuadWriter() {
init(false);
}
public void init(boolean direct) {
// This would need to be re-initialized if the direct vs indirect changes
this.direct = direct;
}
@Override
public void writeQuad(QuadView quad, ByteBuffer buf) {
if(direct) {
writeQuadDirect(quad, buf);
} else {
writeQuadIndirect(quad, buf);
}
}
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
throw new UnsupportedOperationException("Direct mode not supported yet");
}
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
for(int idx = 0; idx < 4; ++idx) {
// Position
buf.putFloat(quad.getX(idx));
buf.putFloat(quad.getY(idx));
buf.putFloat(quad.getZ(idx));
}
}
}
@@ -0,0 +1,49 @@
package com.gtnewhorizons.angelica.compat.nd.writers;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
import java.nio.ByteBuffer;
public class PositionTextureColorQuadWriter implements IWriteQuads {
private boolean direct;
public PositionTextureColorQuadWriter() {
init(false);
}
public void init(boolean direct) {
// This would need to be re-initialized if the direct vs indirect changes
this.direct = direct;
}
@Override
public void writeQuad(QuadView quad, ByteBuffer buf) {
if(direct) {
writeQuadDirect(quad, buf);
} else {
writeQuadIndirect(quad, buf);
}
}
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
throw new UnsupportedOperationException("Direct mode not supported yet");
}
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
for(int idx = 0; idx < 4; ++idx) {
// Position
buf.putFloat(quad.getX(idx));
buf.putFloat(quad.getY(idx));
buf.putFloat(quad.getZ(idx));
// Texture
buf.putFloat(quad.getTexU(idx));
buf.putFloat(quad.getTexV(idx));
// Color
buf.putInt(quad.getColor(idx));
}
}
}
@@ -0,0 +1,44 @@
package com.gtnewhorizons.angelica.compat.nd.writers;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
import java.nio.ByteBuffer;
public class PositionTextureQuadWriter implements IWriteQuads {
private boolean direct;
public PositionTextureQuadWriter() {
init(false);
}
public void init(boolean direct) {
// This would need to be re-initialized if the direct vs indirect changes
this.direct = direct;
}
@Override
public void writeQuad(QuadView quad, ByteBuffer buf) {
if(direct) {
writeQuadDirect(quad, buf);
} else {
writeQuadIndirect(quad, buf);
}
}
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
throw new UnsupportedOperationException("Direct mode not supported yet");
}
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
for(int idx = 0; idx < 4; ++idx) {
// Position
buf.putFloat(quad.getX(idx));
buf.putFloat(quad.getY(idx));
buf.putFloat(quad.getZ(idx));
// Texture
buf.putFloat(quad.getTexU(idx));
buf.putFloat(quad.getTexV(idx));
}
}
}
@@ -34,17 +34,10 @@ public class MatrixStack {
public void pop() {
this.matrixStack.removeLast();
}
public boolean isEmpty() {
public boolean clear() {
return this.matrixStack.size() == 1;
}
public void reset() {
while (this.matrixStack.size() > 1) {
this.matrixStack.removeLast();
}
loadIdentity();
}
public void translate(double d, double e, double f) {
final Entry lv = (Entry)this.matrixStack.getLast();
lv.model.translate((float)d, (float)e, (float)f);
@@ -72,6 +72,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
private final MultiPhaseParameters phases;
private final int hash;
private final Optional<RenderLayer> affectedOutline;
private final boolean outline;
private MultiPhase(String name, VertexFormat vertexFormat, int drawMode, int expectedBufferSize, boolean hasCrumbling, boolean translucent, MultiPhaseParameters phases) {
super(name, vertexFormat, drawMode, expectedBufferSize, () -> {
@@ -83,6 +84,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
this.affectedOutline = phases.outlineMode == RenderLayer.OutlineMode.AFFECTS_OUTLINE ? phases.texture.getId().map((arg2) -> {
return getOutline(arg2, phases.cull);
}) : Optional.empty();
this.outline = phases.outlineMode == RenderLayer.OutlineMode.IS_OUTLINE;
this.hash = Objects.hash(new Object[]{super.hashCode(), phases});
}
@@ -95,23 +97,20 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
return this.affectedOutline;
}
@Override
public boolean equals(@Nullable Object object) {
return this == object;
}
@Override
public int hashCode() {
return this.hash;
}
@Override
public String toString() {
return "RenderType[" + this.phases + ']';
}
static {
CACHE = new ObjectOpenCustomHashSet<>(RenderLayer.MultiPhase.HashStrategy.INSTANCE);
CACHE = new ObjectOpenCustomHashSet(RenderLayer.MultiPhase.HashStrategy.INSTANCE);
}
static enum HashStrategy implements Hash.Strategy<MultiPhase> {
@@ -169,7 +168,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
this.phases = ImmutableList.of(this.texture, this.transparency, this.diffuseLighting, this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.fog, this.layering, this.target, this.texturing, this.writeMaskState);
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
@@ -181,12 +179,10 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
}
}
@Override
public int hashCode() {
return Objects.hash(new Object[]{this.phases, this.outlineMode});
}
@Override
public String toString() {
return "CompositeState[" + this.phases + ", outlineProperty=" + this.outlineMode + ']';
}
@@ -297,7 +293,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
this.name = name;
}
@Override
public String toString() {
return this.name;
}
@@ -11,6 +11,7 @@ import org.lwjgl.opengl.GL11;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalDouble;
public abstract class RenderPhase {
@@ -97,7 +98,6 @@ public abstract class RenderPhase {
this.endAction.run();
}
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
@@ -109,12 +109,10 @@ public abstract class RenderPhase {
}
}
@Override
public int hashCode() {
return this.name.hashCode();
}
@Override
public String toString() {
return this.name;
}
@@ -4,21 +4,10 @@ import com.gtnewhorizon.gtnhlib.config.Config;
@Config(modid = "angelica", filename = "angelica-modules")
public class AngelicaConfig {
@Config.Comment("Enable Celeritas terrain rendering")
@Config.Comment("Enable Sodium rendering")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enableCeleritas;
@Config.Comment("Enable multi-threaded chunk building for improved performance [Requires Celeritas]")
@Config.DefaultBoolean(true)
@Config.RequiresWorldRestart
public static boolean enableThreadedChunkBuilding;
@Config.Comment("Number of chunk builder threads. 0 = auto-detect, -1 = use single-threaded fallback")
@Config.DefaultInt(0)
@Config.RangeInt(min = -1, max = 16)
@Config.RequiresWorldRestart
public static int chunkBuilderThreadCount;
public static boolean enableSodium;
@Config.Comment("Enable NotFine Options")
@Config.DefaultBoolean(false)
@@ -28,7 +17,12 @@ public class AngelicaConfig {
@Config.DefaultBoolean(true)
public static boolean enableReesesSodiumOptions;
@Config.Comment("Inject BakedModel rendering into some vanilla blocks")
@Config.Comment("Enable Sodium fluid rendering")
@Config.DefaultBoolean(false)
@Config.RequiresMcRestart
public static boolean enableSodiumFluidRendering;
@Config.Comment("Inject QuadProvider rendering into some vanilla blocks")
@Config.DefaultBoolean(false)
@Config.RequiresMcRestart
public static boolean injectQPRendering;
@@ -38,25 +32,20 @@ public class AngelicaConfig {
@Config.Ignore()
public static boolean enableTestBlocks;
@Config.Comment("Enable Iris Shaders [Requires Celeritas]")
@Config.Comment("Enable Iris Shaders [Requires Sodium]")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enableIris;
@Config.Comment("Enable MCPatcherForge features, still in Alpha. Individual features are toggled in mcpatcher.json")
@Config.DefaultBoolean(true)
@Config.DefaultBoolean(false)
@Config.RequiresMcRestart
public static boolean enableMCPatcherForgeFeatures;
@Config.Comment("Replace cloud renderer with a VBO version.")
@Config.Comment("Replace some vanilla render paths with more optimized versions. Disable if you encounter mixin conflicts.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enableVBOClouds;
@Config.Comment("Uses cached attributes for VBO rendering, resulting in less CPU overhead. Disable if you notice any graphical issues.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enableVAO;
public static boolean enableVBO;
@Config.Comment("Enable NotFine features")
@Config.DefaultBoolean(true)
@@ -144,11 +133,6 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean enableDebugLogging;
@Config.Comment("Enables PBR atlas dumping")
@Config.DefaultBoolean(false)
@Config.Name("Enable PBR Debug")
public static boolean enablePBRDebug;
@Config.Comment("Enable Zoom")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
@@ -159,70 +143,9 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean optimizeInWorldItemRendering;
@Config.Comment("Upper limit for the amount of VBO's to cache for optimized item rendering. Higher number can potentially use more VRAM.")
@Config.DefaultInt(512)
@Config.RangeInt(min = 256, max = 1024)
public static int itemRendererCacheSize;
@Config.Comment("Render distance for the spinning mob inside mod spawners")
@Config.DefaultDouble(16D)
@Config.RangeDouble(min = 16D, max = 64D)
public static double mobSpawnerRenderDistance;
@Config.Comment("Switches to an alternate FPS limiter that gives more stable frametimes, in exchange for slightly " +
"more latency. Will never introduce more than one frame of latency, and has a lower impact at higher framerates.")
@Config.DefaultBoolean(false)
public static boolean sleepBeforeSwap;
@Config.Comment("Allows unicode languages to use an odd gui scale")
@Config.DefaultBoolean(true)
public static boolean removeUnicodeEvenScaling;
@Config.Comment({"Block corners and edges between chunks might have \"cracks\" (various lines/dots) in them.",
"While using \"Compact Vertex Format\" makes the situation even worse.",
"This option fixes it.",
"Requires texture reloading (F3 + T) after changing this option to take effect"})
@Config.DefaultBoolean(true)
public static boolean fixBlockCrack;
@Config.Comment({
"The \"epsilon\" value for the fixBlockCrack option. ",
"Set this a bit higher if you can still see lines/dots between solid blocks in dark areas.",
"May cause intense flickering (z-fighting) between blocks if the value is too high"
})
@Config.RangeDouble(min = 0, max = 0.005)
@Config.DefaultDouble(0.001)
public static double blockCrackFixEpsilon;
@Config.Comment("Block classes that have bugs when rendering with the fixBlockCrack can be put here to avoid manipulating them")
@Config.DefaultStringList({
"net.minecraft.block.BlockCauldron",
"net.minecraft.block.BlockStairs"
})
public static String[] blockCrackFixBlacklist;
@Config.Comment({"Block classes that have render pass other than 0 but still need to be manipulated.",
"Add a block class here if you see flickering (z-fighting) with fixBlockCrack enabled"
})
@Config.DefaultStringList({
"gregtech.common.blocks.BlockOres",
"gregtech.common.blocks.GTBlockOre",
"shukaro.artifice.block.world.BlockOre",
"bartworks.system.material.BWMetaGeneratedOres",
"gtPlusPlus.core.block.base.BlockBaseOre",
})
public static String[] blockCrackFixRenderPassWhitelist__;
@Config.Comment("Register HardcodedCustomUniforms in Iris Shaders. May help with compatibility in certain shader packs")
@Config.DefaultBoolean(false)
public static boolean enableHardcodedCustomUniforms;
@Config.Comment("Renders chunks before neighbors are ready. Improves loading at render distance edges, useful for low render distance servers.")
@Config.DefaultBoolean(false)
public static boolean useVanillaChunkTracking;
@Config.Comment("Disables additional F3 information added by Angelica.")
@Config.DefaultBoolean(false)
public static boolean disableF3Additions;
@Config.Comment("Max amount of display lists to cache for optimized item rendering. Higher number will use more VRAM")
@Config.DefaultInt(256)
@Config.RangeInt(min = 64, max = 1024)
public static int itemRendererDisplayListCacheSize;
}
@@ -6,13 +6,6 @@ import net.minecraft.client.gui.GuiScreen;
public class AngelicaGuiConfig extends SimpleGuiConfig {
public AngelicaGuiConfig(GuiScreen parent) throws ConfigException {
super(parent,
"angelica",
"Angelica",
true,
AngelicaConfig.class,
CompatConfig.class,
FontConfig.class
);
super(parent, AngelicaConfig.class, "angelica", "Angelica");
}
}
@@ -2,7 +2,7 @@ package com.gtnewhorizons.angelica.config;
import com.gtnewhorizon.gtnhlib.config.Config;
@Config(modid = "angelica", filename = "angelica-compat", category = "compat")
@Config(modid = "angelica", filename = "angelica-compat")
public class CompatConfig {
@Config.Comment("Apply fixes to the LOTR mod")
@@ -39,29 +39,4 @@ public class CompatConfig {
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean fixImmersiveEngineering;
@Config.Comment("Apply fixes to Security Craft")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean fixSecurityCraft;
@Config.Comment("Apply tweaks to NTM:Space")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean tweakNTMSpace;
@Config.Comment("Apply fixes to NTM:Space")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean fixNTMSpace;
@Config.Comment("Disables rendering of the planet below the player at high altitudes [Requires tweakNTMSpace]")
@Config.DefaultEnum("ShadersOnly")
public static altitudePlanetRendererState NTMSpace_disableAltitudePlanetRenderer;
public enum altitudePlanetRendererState {
Always,
ShadersOnly,
Never
}
}
@@ -1,80 +0,0 @@
package com.gtnewhorizons.angelica.config;
import com.gtnewhorizon.gtnhlib.config.Config;
@Config(modid = "angelica", category = "customfont")
public class FontConfig {
@Config.Comment("Whether or not to use custom fonts when drawing text.")
@Config.DefaultBoolean(false)
public static boolean enableCustomFont;
@Config.Comment("Name of the primary custom font. Best not to set it from here.")
@Config.DefaultString("(none)")
public static String customFontNamePrimary;
@Config.Comment("Name of the fallback custom font. Best not to set it from here.")
@Config.DefaultString("(none)")
public static String customFontNameFallback;
@Config.Comment("The quality at which custom fonts are rendered, making them less pixelated but increasing memory usage.")
@Config.DefaultInt(30)
@Config.RangeInt(min = 6, max = 72)
public static int customFontQuality;
@Config.Comment("Controls the distance at which the font's shadow is drawn.")
@Config.DefaultFloat(1F)
@Config.RangeFloat(min = 0F, max = 2F)
public static float fontShadowOffset;
@Config.Comment("The number of shadows to be drawn behind text at various offsets.")
@Config.DefaultInt(1)
@Config.RangeInt(min = 1, max = 8)
public static int shadowCopies;
@Config.Comment("The number of bold copies to be drawn behind text at various offsets.")
@Config.DefaultInt(2)
@Config.RangeInt(min = 1, max = 8)
public static int boldCopies;
@Config.Comment("Influences the aspect ratio of each glyph.")
@Config.DefaultFloat(0F)
@Config.RangeFloat(min = -1F, max = 1F)
public static float glyphAspect;
@Config.Comment("Scale of each glyph, whitespace excluded.")
@Config.DefaultFloat(1F)
@Config.RangeFloat(min = 0.1F, max = 3F)
public static float glyphScale;
@Config.Comment("Whitespace scale.")
@Config.DefaultFloat(1F)
@Config.RangeFloat(min = 0.1F, max = 3F)
public static float whitespaceScale;
@Config.Comment("Adds extra spacing between glyphs.")
@Config.DefaultFloat(0F)
@Config.RangeFloat(min = -2F, max = 2F)
public static float glyphSpacing;
@Config.Comment("Controls font antialiasing. 0 = none, 1 = 4x MSAA, 2 = 16x MSAA.")
@Config.DefaultInt(2)
@Config.RangeInt(min = 0, max = 2)
public static int fontAAMode;
@Config.Comment("Affects font antialiasing sample spacing. Higher values increase blur.")
@Config.DefaultInt(7)
@Config.RangeInt(min = 1, max = 24)
public static int fontAAStrength;
@Config.Comment("Custom font scale multiplier, for bugfixes.")
@Config.DefaultFloat(1.5F)
@Config.RangeFloat(min = 0.1F, max = 3F)
public static float customFontScale;
@Config.Comment({"Mods can provide Angelica with a mapping of special characters to either get replaced with " +
"different characters or rendered using the default Unicode font. Setting this to false disables the " +
"replacement, which is useful in cases where custom fonts have direct support for these special characters. ",
"See for additional details: https://github.com/GTNewHorizons/Angelica/issues/1239#issuecomment-3729877936"})
@Config.DefaultBoolean(true)
public static boolean enableGlyphReplacements;
}
@@ -1,158 +0,0 @@
/*
* Adapated from: Beddium for usage in Angelica
*
* Copyright (C) 2025 Ven, FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.gtnewhorizons.angelica.debug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.rendering.celeritas.AngelicaRenderSectionManager;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import lombok.Getter;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ChunkProviderClient;
import net.minecraft.world.chunk.EmptyChunk;
import net.minecraftforge.client.event.RenderGameOverlayEvent;
import net.minecraftforge.common.MinecraftForge;
import org.lwjgl.opengl.GL11;
/**
* Debug overlay that renders a minimap showing chunk loading and rendering state. Toggle via /angelica minimap
*/
public class ChunkDebugMinimap {
private static final ChunkDebugMinimap INSTANCE = new ChunkDebugMinimap();
@Getter
private static volatile boolean enabled = false;
private static final int RANGE = 32; // Chunks in each direction from player
public static synchronized void toggle() {
if (enabled) disable();
else enable();
}
public static synchronized void enable() {
if (enabled) return;
enabled = true;
MinecraftForge.EVENT_BUS.register(INSTANCE);
}
public static synchronized void disable() {
if (!enabled) return;
enabled = false;
MinecraftForge.EVENT_BUS.unregister(INSTANCE);
}
@SubscribeEvent
public void onRenderOverlay(RenderGameOverlayEvent.Text event) {
final Minecraft mc = Minecraft.getMinecraft();
if (mc.theWorld == null || mc.thePlayer == null) return;
final CeleritasWorldRenderer renderer;
try {
renderer = CeleritasWorldRenderer.getInstance();
} catch (IllegalStateException e) {
return; // Renderer not initialized
}
final AngelicaRenderSectionManager manager = renderer.getRenderSectionManager();
if (manager == null) return;
final float partialTicks = event.partialTicks;
final double pX = mc.thePlayer.prevPosX + (mc.thePlayer.posX - mc.thePlayer.prevPosX) * partialTicks;
final double pZ = mc.thePlayer.prevPosZ + (mc.thePlayer.posZ - mc.thePlayer.prevPosZ) * partialTicks;
final int playerChunkX = ((int) Math.floor(pX)) >> 4;
final int playerChunkZ = ((int) Math.floor(pZ)) >> 4;
// Save GL state
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
GLStateManager.glPushMatrix();
// Setup rendering
GLStateManager.glScaled(2, 2, 1);
GLStateManager.glTranslated(32, 32, 0);
GLStateManager.disableLighting();
GLStateManager.disableTexture();
GLStateManager.disableDepthTest();
GLStateManager.enableBlend();
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLStateManager.glBegin(GL11.GL_QUADS);
final ChunkProviderClient chunkProvider = (ChunkProviderClient) mc.theWorld.getChunkProvider();
for (int x = playerChunkX - RANGE; x < playerChunkX + RANGE; x++) {
for (int z = playerChunkZ - RANGE; z < playerChunkZ + RANGE; z++) {
drawChunk(x, z, playerChunkX, playerChunkZ, manager, chunkProvider);
}
}
GL11.glEnd();
// Restore GL state
GLStateManager.enableLighting();
GLStateManager.enableDepthTest();
GLStateManager.disableBlend();
GLStateManager.glPopMatrix();
GLStateManager.glPopAttrib();
}
private void drawChunk(int chunkX, int chunkZ, int playerChunkX, int playerChunkZ, AngelicaRenderSectionManager manager, ChunkProviderClient chunkProvider) {
boolean initialized = false;
float r = 0, g = 0, b = 0;
// Check all 16 sections in the chunk column
for (int y = 0; y < 16; y++) {
if (manager.isSectionBuilt(chunkX, y, chunkZ) && !manager.isSectionVisuallyEmpty(chunkX, y, chunkZ)) {
initialized = true;
g = 1; // Green = built
if (manager.isSectionVisible(chunkX, y, chunkZ)) {
b = 1; // Cyan = visible
break;
}
}
}
if (!initialized) {
// Check if chunk is loaded
final var chunk = chunkProvider.provideChunk(chunkX, chunkZ);
if (chunk instanceof EmptyChunk) {
r = 1; // Red = unloaded
} else {
b = 1; // Blue = loaded but not built
}
}
GL11.glColor4f(r, g, b, 0.8f);
final int xStart = chunkX - playerChunkX;
final int zStart = chunkZ - playerChunkZ;
final int xEnd = xStart + 1;
final int zEnd = zStart + 1;
GLStateManager.glVertex3d(xStart, zStart, 0);
GLStateManager.glVertex3d(xStart, zEnd, 0);
GLStateManager.glVertex3d(xEnd, zEnd, 0);
GLStateManager.glVertex3d(xEnd, zStart, 0);
}
}
@@ -28,10 +28,9 @@ import static org.lwjgl.opengl.GL20.glUniform1;
import static org.lwjgl.opengl.GL20.glUniform1f;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.*;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
import com.gtnewhorizon.gtnhlib.client.renderer.shader.ShaderProgram;
import com.gtnewhorizons.angelica.compat.lwjgl.MemoryStack;
import java.nio.FloatBuffer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
@@ -132,7 +131,7 @@ public abstract class F3Graph {
// Load vertex buffer
vertBuf = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, vertBuf);
try (final MemoryStack stack = stackPush()) {
try (final MemoryStack stack = MemoryStack.stackPush()) {
final FloatBuffer vertices = stack.mallocFloat(VERT_COUNT * VERT_FLOATS);
// Since we use a triangle strip, we only need 4 vertices. The quad extends to the top of the screen so spikes
// don't get truncated. The max height is replaced in the vert shader, no need to be precise.
@@ -1,70 +0,0 @@
package com.gtnewhorizons.angelica.dynamiclights;
import org.jetbrains.annotations.NotNull;
/**
* Calculates the appropriate tick mode for a dynamic light source based on distance from camera and position relative to camera direction.
*/
public final class AdaptiveTickCalculator {
// Distance thresholds in blocks (configurable via settings)
private static int slowDistance = 32; // ~2 chunks
private static int slowerDistance = 64; // ~4 chunks
private static int backgroundDistance = 128; // ~8 chunks
// Squared thresholds for efficient distance comparison
private static int slowDistanceSq = slowDistance * slowDistance;
private static int slowerDistanceSq = slowerDistance * slowerDistance;
private static int backgroundDistanceSq = backgroundDistance * backgroundDistance;
private AdaptiveTickCalculator() {}
/**
* Calculate the appropriate tick mode for a light source.
*/
public static AdaptiveTickMode calculate(@NotNull IDynamicLightSource source, double cameraX, double cameraY, double cameraZ, double lookDirX, double lookDirZ) {
double dx = source.angelica$getDynamicLightX() - cameraX;
double dy = source.angelica$getDynamicLightY() - cameraY;
double dz = source.angelica$getDynamicLightZ() - cameraZ;
double distSq = dx * dx + dy * dy + dz * dz;
// Check if behind camera (dot product of direction to source with look direction)
// Only use X and Z for horizontal check (more stable as player looks up/down)
double dot = dx * lookDirX + dz * lookDirZ;
boolean behindCamera = dot < 0;
// Behind camera always gets BACKGROUND mode
if (behindCamera) {
return AdaptiveTickMode.BACKGROUND;
}
// Distance-based mode selection
if (distSq > backgroundDistanceSq) {
return AdaptiveTickMode.BACKGROUND;
}
if (distSq > slowerDistanceSq) {
return AdaptiveTickMode.SLOWER;
}
if (distSq > slowDistanceSq) {
return AdaptiveTickMode.SLOW;
}
return AdaptiveTickMode.REAL_TIME;
}
public static void setSlowDistance(int distance) {
slowDistance = distance;
slowDistanceSq = distance * distance;
}
public static void setSlowerDistance(int distance) {
slowerDistance = distance;
slowerDistanceSq = distance * distance;
}
public static void setBackgroundDistance(int distance) {
backgroundDistance = distance;
backgroundDistanceSq = distance * distance;
}
}
@@ -1,32 +0,0 @@
package com.gtnewhorizons.angelica.dynamiclights;
/**
* Defines tick modes for adaptive dynamic light updates. Distant or off-screen light sources update less frequently to save CPU.
*/
public enum AdaptiveTickMode {
/** Updates every tick - for nearby, visible light sources */
REAL_TIME(1),
/** Updates every 5 ticks - for light sources at moderate distance */
SLOW(5),
/** Updates every 10 ticks - for light sources at far distance */
SLOWER(10),
/** Updates every 20 ticks - for light sources behind the camera or very far */
BACKGROUND(20);
private final int delay;
AdaptiveTickMode(int delay) {
this.delay = delay;
}
public int getDelay() {
return delay;
}
public boolean shouldTickThisFrame(int worldTick, int sourceHash) {
if (delay <= 1) {
return true;
}
return (worldTick % delay) == Math.abs(sourceHash % delay);
}
}
@@ -1,152 +0,0 @@
package com.gtnewhorizons.angelica.dynamiclights;
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongIterator;
import org.embeddedt.embeddium.impl.render.viewport.Viewport;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Manages deferred chunk rebuilds for dynamic lights with optional frustum culling. Instead of scheduling chunk rebuilds immediately
* when light sources move, this tracks pending rebuilds and only schedules visible chunks.
*/
public class ChunkRebuildManager {
/** Maps packed chunk position to ticks waiting */
private final Long2IntOpenHashMap pendingRebuilds = new Long2IntOpenHashMap();
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
/** Maximum ticks a pending rebuild can wait before being forced through */
private static int maxTicksWaiting = 100;
/** Reusable lists for processVisible - only used from render thread */
private final LongArrayList candidates = new LongArrayList();
private final LongArrayList toRebuild = new LongArrayList();
private final LongArrayList toIncrement = new LongArrayList();
public static void setMaxTicksWaiting(int ticks) {
maxTicksWaiting = Math.max(20, Math.min(ticks, 600)); // Clamp 1-30 seconds (20-600 ticks)
}
public void requestRebuild(int x, int y, int z) {
final long packed = CoordinatePacker.pack(x, y, z);
lock.writeLock().lock();
try {
// Reset wait counter if already pending, otherwise add new entry
pendingRebuilds.put(packed, 0);
} finally {
lock.writeLock().unlock();
}
}
public void processVisible(Viewport viewport, @NotNull IDynamicLightWorldRenderer renderer) {
candidates.clear();
toRebuild.clear();
toIncrement.clear();
// Phase 1: Collect all pending chunks with read lock (fast)
lock.readLock().lock();
try {
if (pendingRebuilds.isEmpty()) {
return;
}
// Collect candidates that might need processing
final var iterator = pendingRebuilds.long2IntEntrySet().fastIterator();
while (iterator.hasNext()) {
final var entry = iterator.next();
final long packed = entry.getLongKey();
final int ticksWaiting = entry.getIntValue();
// Force process if no viewport or waited too long
if (viewport == null || ticksWaiting >= maxTicksWaiting) {
toRebuild.add(packed);
} else {
candidates.add(packed);
}
}
} finally {
lock.readLock().unlock();
}
// Phase 2: Check visibility
for (int i = 0; i < candidates.size(); i++) {
long packed = candidates.getLong(i);
int x = CoordinatePacker.unpackX(packed);
int y = CoordinatePacker.unpackY(packed);
int z = CoordinatePacker.unpackZ(packed);
if (isChunkVisible(viewport, x, y, z)) {
toRebuild.add(packed);
} else {
toIncrement.add(packed);
}
}
// Phase 3: Update map with write lock (brief)
if (!toRebuild.isEmpty() || !toIncrement.isEmpty()) {
lock.writeLock().lock();
try {
// Remove chunks we're going to rebuild
LongIterator rebuildIter = toRebuild.iterator();
while (rebuildIter.hasNext()) {
pendingRebuilds.remove(rebuildIter.nextLong());
}
LongIterator incIter = toIncrement.iterator();
while (incIter.hasNext()) {
long packed = incIter.nextLong();
int current = pendingRebuilds.get(packed);
if (pendingRebuilds.containsKey(packed)) {
pendingRebuilds.put(packed, current + 1);
}
}
} finally {
lock.writeLock().unlock();
}
}
// Phase 4: Schedule rebuilds outside of lock
for (int i = 0; i < toRebuild.size(); i++) {
final long packed = toRebuild.getLong(i);
renderer.scheduleRebuildForChunk(
CoordinatePacker.unpackX(packed),
CoordinatePacker.unpackY(packed),
CoordinatePacker.unpackZ(packed),
false
);
}
}
private boolean isChunkVisible(Viewport viewport, int chunkX, int chunkY, int chunkZ) {
// Convert chunk section coords to block coords
int blockX = chunkX << 4;
int blockY = chunkY << 4;
int blockZ = chunkZ << 4;
// Check if the 16x16x16 chunk section box is visible
return viewport.isBoxVisible(
blockX, blockY, blockZ,
blockX + 16, blockY + 16, blockZ + 16
);
}
public void clear() {
lock.writeLock().lock();
try {
pendingRebuilds.clear();
} finally {
lock.writeLock().unlock();
}
}
public int getPendingCount() {
lock.readLock().lock();
try {
return pendingRebuilds.size();
} finally {
lock.readLock().unlock();
}
}
}
@@ -6,54 +6,43 @@ import com.gtnewhorizon.gtnhlib.blockpos.IBlockPos;
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
import com.gtnewhorizons.angelica.api.IDynamicLightProducer;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.battlegear2.Battlegear2Compat;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfig;
import com.gtnewhorizons.angelica.mixins.interfaces.PrimedEntityAccessor;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer;
import mods.battlegear2.api.core.IBattlePlayer;
import mods.battlegear2.api.core.IInventoryPlayerBattle;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.monster.EntityCreeper;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.client.Minecraft;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.util.MathHelper;
import org.embeddedt.embeddium.impl.render.viewport.Viewport;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xonin.backhand.api.core.BackhandUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Predicate;
public class DynamicLights {
private static DynamicLights instance;
private static IDynamicLightWorldRenderer activeRenderer;
public static DynamicLightsMode Mode = DynamicLightsMode.OFF;
public static boolean ShaderForce = false;
public static boolean FrustumCullingEnabled = true;
public static boolean AdaptiveTickingEnabled = true;
public static final boolean configEnabled = AngelicaConfig.enableDynamicLights;
private static final double MAX_RADIUS = 7.75;
private static final double MAX_RADIUS_SQUARED = MAX_RADIUS * MAX_RADIUS;
private final Set<IDynamicLightSource> dynamicLightSources = new ObjectOpenHashSet<>();
private final ReentrantReadWriteLock lightSourcesLock = new ReentrantReadWriteLock();
private volatile int lightSourceCount = 0;
private final ChunkRebuildManager chunkRebuildManager = new ChunkRebuildManager();
private long lastUpdate = System.currentTimeMillis();
private int lastUpdateCount = 0;
@@ -64,31 +53,19 @@ public class DynamicLights {
}
public static boolean isEnabled() {
return configEnabled &&
return AngelicaConfig.enableDynamicLights &&
Mode.isEnabled() &&
// if shader force is enabled then true
// if not, then true when shaders are not in use
(ShaderForce || !IrisApi.getInstance().isShaderPackInUse());
}
public boolean hasLightSources() {
return lightSourceCount > 0;
}
public static IDynamicLightWorldRenderer getActiveRenderer() {
return activeRenderer;
}
public static void setActiveRenderer(IDynamicLightWorldRenderer renderer) {
activeRenderer = renderer;
}
/**
* Updates all light sources.
*
* @param renderer the renderer
*/
public void updateAll(@NotNull IDynamicLightWorldRenderer renderer) {
public void updateAll(@NotNull SodiumWorldRenderer renderer) {
if (!isEnabled())
return;
@@ -97,43 +74,8 @@ public class DynamicLights {
this.lastUpdate = now;
this.lastUpdateCount = 0;
// Get camera info for adaptive ticking
double cameraX = 0, cameraY = 0, cameraZ = 0;
double lookDirX = 0, lookDirZ = 1;
int worldTick = 0;
if (AdaptiveTickingEnabled) {
Minecraft mc = Minecraft.getMinecraft();
if (mc.theWorld != null && mc.renderViewEntity != null) {
cameraX = mc.renderViewEntity.posX;
cameraY = mc.renderViewEntity.posY + mc.renderViewEntity.getEyeHeight();
cameraZ = mc.renderViewEntity.posZ;
worldTick = (int) (mc.theWorld.getTotalWorldTime() & 0x7FFFFFFF);
// Calculate look direction from yaw (normalized horizontal direction)
float yaw = mc.renderViewEntity.rotationYaw;
double yawRad = Math.toRadians(yaw);
lookDirX = -Math.sin(yawRad);
lookDirZ = Math.cos(yawRad);
}
}
this.lightSourcesLock.readLock().lock();
for (var lightSource : this.dynamicLightSources) {
// Entity type filter
if (!EntityLightConfig.isEntityTypeEnabled(lightSource)) {
continue;
}
// Adaptive ticking filter
if (AdaptiveTickingEnabled) {
AdaptiveTickMode mode = AdaptiveTickCalculator.calculate(
lightSource, cameraX, cameraY, cameraZ, lookDirX, lookDirZ);
if (!mode.shouldTickThisFrame(worldTick, lightSource.hashCode())) {
continue;
}
}
if (lightSource.angelica$updateDynamicLight(renderer))
this.lastUpdateCount++;
}
@@ -147,9 +89,7 @@ public class DynamicLights {
public void addLightSource(IDynamicLightSource lightSource) {
this.lightSourcesLock.writeLock().lock();
if (this.dynamicLightSources.add(lightSource)) {
this.lightSourceCount = this.dynamicLightSources.size();
}
this.dynamicLightSources.add(lightSource);
this.lightSourcesLock.writeLock().unlock();
}
@@ -167,9 +107,8 @@ public class DynamicLights {
it = dynamicLightSources.next();
if (it.equals(lightSource)) {
dynamicLightSources.remove();
this.lightSourceCount = this.dynamicLightSources.size();
if (activeRenderer != null)
lightSource.angelica$scheduleTrackedChunksRebuild(activeRenderer);
if (SodiumWorldRenderer.getInstance() != null)
lightSource.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
break;
}
}
@@ -191,11 +130,10 @@ public class DynamicLights {
it = dynamicLightSources.next();
if (filter.test(it)) {
dynamicLightSources.remove();
this.lightSourceCount = this.dynamicLightSources.size();
if (activeRenderer != null) {
if (SodiumWorldRenderer.getInstance() != null) {
if (it.angelica$getLuminance() > 0)
it.angelica$resetDynamicLight();
it.angelica$scheduleTrackedChunksRebuild(activeRenderer);
it.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
}
break;
}
@@ -215,13 +153,12 @@ public class DynamicLights {
while (dynamicLightSources.hasNext()) {
it = dynamicLightSources.next();
dynamicLightSources.remove();
if (activeRenderer != null) {
if (SodiumWorldRenderer.getInstance() != null) {
if (it.angelica$getLuminance() > 0)
it.angelica$resetDynamicLight();
it.angelica$scheduleTrackedChunksRebuild(activeRenderer);
it.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
}
}
this.lightSourceCount = 0;
this.lightSourcesLock.writeLock().unlock();
}
@@ -266,20 +203,6 @@ public class DynamicLights {
return MathHelper.clamp_double(result, 0, 15);
}
/**
* Gets dynamic light level at an exact world position (for smooth per-vertex lighting).
*/
public double getDynamicLightLevel(double x, double y, double z) {
double result = 0;
this.lightSourcesLock.readLock().lock();
for (var lightSource : this.dynamicLightSources) {
result = maxDynamicLightLevelExact(x, y, z, lightSource, result);
}
this.lightSourcesLock.readLock().unlock();
return MathHelper.clamp_double(result, 0, 15);
}
public double getDynamicLightLevel(@NotNull BlockPos pos) {
return this.getDynamicLightLevel(pos.getX(), pos.getY(), pos.getZ());
}
@@ -306,29 +229,6 @@ public class DynamicLights {
return currentLightLevel;
}
/**
* Calculates dynamic light level at an exact position (no block center offset).
* Used for smooth per-vertex lighting.
*/
public static double maxDynamicLightLevelExact(double x, double y, double z, @NotNull IDynamicLightSource lightSource, double currentLightLevel) {
int luminance = lightSource.angelica$getLuminance();
if (luminance > 0) {
double dx = x - lightSource.angelica$getDynamicLightX();
double dy = y - lightSource.angelica$getDynamicLightY();
double dz = z - lightSource.angelica$getDynamicLightZ();
double distanceSquared = dx * dx + dy * dy + dz * dz;
if (distanceSquared <= MAX_RADIUS_SQUARED) {
double multiplier = 1.0 - Math.sqrt(distanceSquared) / MAX_RADIUS;
double lightLevel = multiplier * (double) luminance;
if (lightLevel > currentLightLevel) {
return lightLevel;
}
}
}
return currentLightLevel;
}
/**
* Returns the dynamic light level generated by the light source at the specified position.
*
@@ -345,51 +245,6 @@ public class DynamicLights {
return this.getLightmapWithDynamicLight(this.getDynamicLightLevel(x, y, z), lightmap);
}
public List<IDynamicLightSource> getSourcesForChunk(int chunkMinX, int chunkMinY, int chunkMinZ) {
// Chunk center
final double centerX = chunkMinX + 8.0;
final double centerY = chunkMinY + 8.0;
final double centerZ = chunkMinZ + 8.0;
// Max distance from chunk center to any corner is sqrt(3) * 8 ≈ 13.86
// Add MAX_RADIUS to get the search radius (13.86 + 7.75)^2 ≈ 467
final double searchRadiusSq = 470.0; // Slightly padded for safety
List<IDynamicLightSource> result = null;
this.lightSourcesLock.readLock().lock();
try {
for (IDynamicLightSource source : this.dynamicLightSources) {
double dx = source.angelica$getDynamicLightX() - centerX;
double dy = source.angelica$getDynamicLightY() - centerY;
double dz = source.angelica$getDynamicLightZ() - centerZ;
double distSq = dx * dx + dy * dy + dz * dz;
if (distSq <= searchRadiusSq) {
if (result == null) {
result = new ArrayList<>();
}
result.add(source);
}
}
} finally {
this.lightSourcesLock.readLock().unlock();
}
return result != null ? result : Collections.emptyList();
}
public double getDynamicLightLevelFromSources(double x, double y, double z, List<IDynamicLightSource> sources) {
if (sources == null || sources.isEmpty()) {
return 0;
}
double result = 0;
for (int i = 0, size = sources.size(); i < size; i++) {
result = maxDynamicLightLevelExact(x, y, z, sources.get(i), result);
}
return MathHelper.clamp_double(result, 0, 15);
}
/**
* Returns the lightmap with combined light levels.
*
@@ -420,7 +275,7 @@ public class DynamicLights {
* @param renderer the renderer
* @param chunkPos the chunk position
*/
public static void scheduleChunkRebuild(@NotNull IDynamicLightWorldRenderer renderer, @NotNull IBlockPos chunkPos) {
public static void scheduleChunkRebuild(@NotNull SodiumWorldRenderer renderer, @NotNull IBlockPos chunkPos) {
scheduleChunkRebuild(renderer, chunkPos.getX(), chunkPos.getY(), chunkPos.getZ());
}
@@ -430,37 +285,14 @@ public class DynamicLights {
* @param renderer the renderer
* @param chunkPos the packed chunk position
*/
public static void scheduleChunkRebuild(@NotNull IDynamicLightWorldRenderer renderer, long chunkPos) {
public static void scheduleChunkRebuild(@NotNull SodiumWorldRenderer renderer, long chunkPos) {
scheduleChunkRebuild(renderer, CoordinatePacker.unpackX(chunkPos), CoordinatePacker.unpackY(chunkPos), CoordinatePacker.unpackZ(chunkPos));
}
public static void scheduleChunkRebuild(@NotNull IDynamicLightWorldRenderer renderer, int x, int y, int z) {
if (FrustumCullingEnabled && instance != null) {
instance.chunkRebuildManager.requestRebuild(x, y, z);
} else {
renderer.scheduleRebuildForChunk(x, y, z, false);
}
}
public static void scheduleChunkRebuildForRemoval(@NotNull IDynamicLightWorldRenderer renderer, int x, int y, int z) {
public static void scheduleChunkRebuild(@NotNull SodiumWorldRenderer renderer, int x, int y, int z) {
renderer.scheduleRebuildForChunk(x, y, z, false);
}
public static void scheduleChunkRebuildForRemoval(@NotNull IDynamicLightWorldRenderer renderer, long chunkPos) {
scheduleChunkRebuildForRemoval(renderer, CoordinatePacker.unpackX(chunkPos), CoordinatePacker.unpackY(chunkPos), CoordinatePacker.unpackZ(chunkPos));
}
public void processChunkRebuilds(@Nullable Viewport viewport) {
if (activeRenderer == null) {
return;
}
chunkRebuildManager.processVisible(viewport, activeRenderer);
}
public ChunkRebuildManager getChunkRebuildManager() {
return chunkRebuildManager;
}
/**
* Updates the tracked chunk sets.
*
@@ -501,7 +333,10 @@ public class DynamicLights {
* @param submergedInWater {@code true} if the stack is submerged in water, else {@code false}
* @return the luminance of the item
*/
public static int getLuminanceFromItemStack(@NotNull ItemStack stack) {
public static int getLuminanceFromItemStack(@NotNull ItemStack stack, boolean submergedInWater) {
// TODO only have certain items not glow in water?
if (submergedInWater) return 0;
Item item = stack.getItem();
if (item instanceof ItemBlock itemBlock) {
Block block = itemBlock.field_150939_a;
@@ -519,53 +354,49 @@ public class DynamicLights {
public static int getLuminanceFromEntity(@NotNull Entity entity) {
if (entity.isBurning()) return 15;
boolean inWater = entity.isInsideOfMaterial(Material.water);
// TODO only have certain items not glow in water?
if (inWater) return 0;
if (entity.fire > 0) return 15;
if (entity instanceof EntityItem item) {
return getLuminanceFromItemStack(item.getEntityItem());
return getLuminanceFromItemStack(item.getEntityItem(), item.isInsideOfMaterial(Material.water));
}
int luminance = 0;
if (entity instanceof EntityLivingBase living) {
int luminance = 0;
boolean inWater = living.isInsideOfMaterial(Material.water);
// check equipment + hand for light (should work for all entities)
ItemStack itemStack;
for (int i = 0; i < 5; i++) {
if ((itemStack = living.getEquipmentInSlot(i)) != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(itemStack));
luminance = Math.max(luminance, getLuminanceFromItemStack(itemStack, inWater));
}
}
if (living instanceof EntityPlayer player) {
if (ModStatus.isBattlegearLoaded &&
player instanceof IBattlePlayer battlePlayer &&
battlePlayer.battlegear2$isBattlemode()
) {
ItemStack offhand = Battlegear2Compat.getBattlegear2Offhand(player);
if (offhand != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand));
}
if (ModStatus.isBattlegearLoaded &&
living instanceof EntityPlayer player &&
player instanceof IBattlePlayer battlePlayer &&
battlePlayer.battlegear2$isBattlemode()
) {
ItemStack offhand = ((IInventoryPlayerBattle) player.inventory).battlegear2$getCurrentOffhandWeapon();
if (offhand != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand, inWater));
}
else if (ModStatus.isBackhandLoaded){
ItemStack offhand = ModStatus.backhandCompat.getOffhandItem(player);
if (offhand != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand));
}
}
else if (ModStatus.isBackhandLoaded && living instanceof EntityPlayer player){
ItemStack offhand = BackhandUtils.getOffhandItem(player);
if (offhand != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand, inWater));
}
}
if (ModStatus.isBaublesLoaded){
var playerBaubles = PlayerHandler.getPlayerBaubles(player);
if (playerBaubles != null){
for (int i = 0; i < playerBaubles.getSizeInventory(); i++){
var stack = playerBaubles.getStackInSlot(i);
if (stack != null){
luminance = Math.max(luminance, getLuminanceFromItemStack(stack));
}
if (ModStatus.isBaublesLoaded && living instanceof EntityPlayer player){
var playerBaubles = PlayerHandler.getPlayerBaubles(player);
if (playerBaubles != null){
for (int i = 0; i < playerBaubles.getSizeInventory(); i++){
var stack = playerBaubles.getStackInSlot(i);
if (stack != null){
luminance = Math.max(luminance, getLuminanceFromItemStack(stack, inWater));
}
}
}
@@ -575,10 +406,7 @@ public class DynamicLights {
}
// TODO: Creepers? TNT? Dusts?
if (entity instanceof PrimedEntityAccessor primedEntity) {
luminance = Math.max(luminance, primedEntity.angelica$getLuminance());
}
return luminance;
return 0;
}
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.dynamiclights;
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@@ -26,13 +27,11 @@ public interface IDynamicLightSource {
double angelica$getDynamicLightZ();
/**
* Returns the dynamic light source world renderer.
* Returns the dynamic light source world.
*
* @return the world renderer instance
* @return the world instance
*/
default IDynamicLightWorldRenderer angelica$getDynamicLightWorld() {
return DynamicLights.getActiveRenderer();
}
SodiumWorldRenderer angelica$getDynamicLightWorld();
/**
* Returns whether the dynamic light is enabled or not.
@@ -73,9 +72,8 @@ public interface IDynamicLightSource {
* Executed at each tick.
*/
void angelica$dynamicLightTick();
default void angelica$updateLights() {}
boolean angelica$updateDynamicLight(@NotNull IDynamicLightWorldRenderer renderer);
boolean angelica$updateDynamicLight(@NotNull SodiumWorldRenderer renderer);
void angelica$scheduleTrackedChunksRebuild(@NotNull IDynamicLightWorldRenderer renderer);
void angelica$scheduleTrackedChunksRebuild(@NotNull SodiumWorldRenderer renderer);
}
@@ -1,17 +0,0 @@
package com.gtnewhorizons.angelica.dynamiclights;
/**
* Common interface for world renderers that support dynamic lights chunk rebuilds.
* Implemented by CeleritasWorldRenderer.
*/
public interface IDynamicLightWorldRenderer {
/**
* Schedules a chunk rebuild for the render section at the given chunk coordinates.
*/
void scheduleRebuildForChunk(int x, int y, int z, boolean important);
/**
* Returns whether this renderer is currently active (has a world loaded).
*/
boolean isActive();
}
@@ -1,187 +0,0 @@
package com.gtnewhorizons.angelica.dynamiclights.config;
import com.gtnewhorizon.gtnhlib.concurrent.cas.CasMap;
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightSource;
import cpw.mods.fml.common.registry.EntityRegistry;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public final class EntityLightConfig {
private static final Logger LOGGER = LogManager.getLogger("Angelica/DynamicLights");
private static final Set<String> disabledClassNames = ConcurrentHashMap.newKeySet();
private static final CasMap<Class<?>, Boolean> enabledCache = new CasMap<>();
private static File configFile;
private static boolean initialized = false;
private EntityLightConfig() {}
public static void init(@NotNull File configDir) {
if (initialized) return;
configFile = new File(configDir, "angelica-dynamiclights-entities.txt");
load();
initialized = true;
}
public static boolean isEntityTypeEnabled(@NotNull IDynamicLightSource source) {
if (!initialized) return true;
return isEntityTypeEnabled(source.getClass());
}
public static boolean isEntityTypeEnabled(@NotNull Class<?> entityClass) {
if (!initialized) return true;
Boolean cached = enabledCache.get(entityClass);
if (cached != null) {
return cached;
}
final boolean enabled = !disabledClassNames.contains(entityClass.getName());
Boolean existing = enabledCache.putIfAbsent(entityClass, enabled);
return existing != null ? existing : enabled;
}
public static void setEntityTypeEnabled(@NotNull Class<?> entityClass, boolean enabled) {
enabledCache.put(entityClass, enabled);
final String className = entityClass.getName();
if (enabled) {
disabledClassNames.remove(className);
} else {
disabledClassNames.add(className);
}
}
@SuppressWarnings("unchecked")
public static List<EntityTypeEntry> getAllEntityTypes() {
List<EntityTypeEntry> entries = new ArrayList<>();
Set<String> seenClasses = new HashSet<>();
Map<String, Class<? extends Entity>> stringToClass = EntityList.stringToClassMapping;
for (Map.Entry<String, Class<? extends Entity>> entry : stringToClass.entrySet()) {
String entityId = entry.getKey();
Class<?> entityClass = entry.getValue();
String className = entityClass.getName();
if (seenClasses.contains(className)) continue;
seenClasses.add(className);
String modId = getModIdForEntity(entityClass, entityId);
String displayName = getDisplayName(entityId, entityClass);
boolean enabled = !disabledClassNames.contains(className);
entries.add(new EntityTypeEntry(displayName, className, modId, enabled));
}
// Sort by mod ID, then by display name
entries.sort((a, b) -> {
int modCompare = a.getModId().compareToIgnoreCase(b.getModId());
if (modCompare != 0) return modCompare;
return a.getDisplayName().compareToIgnoreCase(b.getDisplayName());
});
return entries;
}
@SuppressWarnings("unchecked")
private static String getModIdForEntity(Class<?> entityClass, String entityId) {
// Check EntityRegistry for mod entity entries
if (Entity.class.isAssignableFrom(entityClass)) {
EntityRegistry.EntityRegistration registration = EntityRegistry.instance()
.lookupModSpawn((Class<? extends Entity>) entityClass, false);
if (registration != null && registration.getContainer() != null) {
return registration.getContainer().getModId();
}
}
// Check if it's a vanilla entity (no dot in entity ID)
if (!entityId.contains(".")) {
return "minecraft";
}
// Extract mod ID from entity ID (format: modid.EntityName)
int dotIndex = entityId.indexOf('.');
if (dotIndex > 0) {
return entityId.substring(0, dotIndex);
}
return "unknown";
}
private static String getDisplayName(String entityId, Class<?> entityClass) {
// Use the simple class name, removing package
String simpleName = entityClass.getSimpleName();
if (simpleName.isEmpty()) {
// Anonymous class, use entity ID
return entityId;
}
return simpleName;
}
public static void load() {
if (configFile == null || !configFile.exists()) return;
disabledClassNames.clear();
enabledCache.clear();
try (BufferedReader reader = new BufferedReader(new FileReader(configFile))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) continue;
// Lines are entity class names that are DISABLED
disabledClassNames.add(line);
}
LOGGER.info("Loaded {} disabled entity types from config", disabledClassNames.size());
} catch (IOException e) {
LOGGER.warn("Failed to load entity light config: {}", e.getMessage());
}
}
public static void save() {
if (configFile == null) return;
try (BufferedWriter writer = new BufferedWriter(new FileWriter(configFile))) {
writer.write("# Angelica Dynamic Lights - Disabled Entity Types");
writer.newLine();
writer.write("# Each line is a fully qualified class name of an entity type that is DISABLED.");
writer.newLine();
writer.write("# Remove a line to re-enable dynamic lighting for that entity type.");
writer.newLine();
writer.newLine();
List<String> sorted = new ArrayList<>(disabledClassNames);
Collections.sort(sorted);
for (String className : sorted) {
writer.write(className);
writer.newLine();
}
LOGGER.info("Saved {} disabled entity types to config", disabledClassNames.size());
} catch (IOException e) {
LOGGER.warn("Failed to save entity light config: {}", e.getMessage());
}
}
}
@@ -1,20 +0,0 @@
package com.gtnewhorizons.angelica.dynamiclights.config;
import me.jellysquid.mods.sodium.client.gui.options.storage.OptionStorage;
public class EntityLightConfigStorage implements OptionStorage<EntityLightConfigStorage> {
public static final EntityLightConfigStorage INSTANCE = new EntityLightConfigStorage();
private EntityLightConfigStorage() {}
@Override
public EntityLightConfigStorage getData() {
return this;
}
@Override
public void save() {
EntityLightConfig.save();
}
}
@@ -1,42 +0,0 @@
package com.gtnewhorizons.angelica.dynamiclights.config;
import org.jetbrains.annotations.NotNull;
public class EntityTypeEntry {
private final String displayName;
private final String className;
private final String modId;
private boolean enabled;
public EntityTypeEntry(@NotNull String displayName, @NotNull String className, @NotNull String modId, boolean enabled) {
this.displayName = displayName;
this.className = className;
this.modId = modId;
this.enabled = enabled;
}
public String getDisplayName() {
return displayName;
}
public String getClassName() {
return className;
}
public String getModId() {
return modId;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public String toString() {
return displayName + " (" + modId + ") [" + (enabled ? "ON" : "OFF") + "]";
}
}
@@ -1,52 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import org.embeddedt.embeddium.impl.render.chunk.fog.FogService;
import org.embeddedt.embeddium.impl.render.chunk.shader.ChunkFogMode;
public class AngelicaFogService implements FogService {
private final float[] fogColorArray = new float[4];
@Override
public float getFogEnd() {
return GLStateManager.getFogState().getEnd();
}
@Override
public float getFogStart() {
return GLStateManager.getFogState().getStart();
}
@Override
public float getFogDensity() {
return GLStateManager.getFogState().getDensity();
}
@Override
public int getFogShapeIndex() {
return 0; // 1.7.10 only has one fog shape
}
@Override
public float getFogCutoff() {
return getFogEnd();
}
@Override
public float[] getFogColor() {
final var fogState = GLStateManager.getFogState();
final var color = fogState.getFogColor();
fogColorArray[0] = (float) color.x;
fogColorArray[1] = (float) color.y;
fogColorArray[2] = (float) color.z;
fogColorArray[3] = fogState.getFogAlpha();
return fogColorArray;
}
@Override
public ChunkFogMode getFogMode() {
if (!GLStateManager.getFogMode().isEnabled()) {
return ChunkFogMode.NONE;
}
return ChunkFogMode.fromGLMode(GLStateManager.getFogState().getFogMode());
}
}
@@ -1,369 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.mitchej123.glsm.GLStateManagerService;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import java.nio.IntBuffer;
public class AngelicaGLStateManagerService implements GLStateManagerService {
private IntBuffer intArrayBuffer = BufferUtils.createIntBuffer(16);
private IntBuffer getIntArrayBuffer(int size) {
if (intArrayBuffer.capacity() < size) {
intArrayBuffer = BufferUtils.createIntBuffer(size);
}
intArrayBuffer.clear();
return intArrayBuffer;
}
@Override
public int getPriority() {
return 100;
}
// ===================== QUERY OPERATIONS =====================
@Override
public int glGetInteger(int pname) {
return GLStateManager.glGetInteger(pname);
}
@Override
public String glGetString(int pname) {
return GL11.glGetString(pname);
}
// ===================== FRAMEBUFFER OPERATIONS =====================
@Override
public int glGenFramebuffers() {
return GL30.glGenFramebuffers();
}
@Override
public void glDeleteFramebuffers(int framebuffer) {
GL30.glDeleteFramebuffers(framebuffer);
}
@Override
public void glBindFramebuffer(int target, int framebuffer) {
GL30.glBindFramebuffer(target, framebuffer);
}
@Override
public int glCheckFramebufferStatus(int target) {
return GL30.glCheckFramebufferStatus(target);
}
@Override
public void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
GL30.glFramebufferTexture2D(target, attachment, textarget, texture, level);
}
// ===================== SHADER OPERATIONS =====================
@Override
public int glCreateShader(int type) {
return GL20.glCreateShader(type);
}
@Override
public void glCompileShader(int shader) {
GL20.glCompileShader(shader);
}
@Override
public int glGetShaderi(int shader, int pname) {
return GL20.glGetShaderi(shader, pname);
}
@Override
public String glGetShaderInfoLog(int shader, int maxLength) {
return GL20.glGetShaderInfoLog(shader, maxLength);
}
@Override
public void glDeleteShader(int shader) {
GL20.glDeleteShader(shader);
}
@Override
public int glCreateProgram() {
return GL20.glCreateProgram();
}
@Override
public void glAttachShader(int program, int shader) {
GL20.glAttachShader(program, shader);
}
@Override
public void glLinkProgram(int program) {
GL20.glLinkProgram(program);
}
@Override
public int glGetProgrami(int program, int pname) {
return GL20.glGetProgrami(program, pname);
}
@Override
public void glUseProgram(int program) {
GLStateManager.glUseProgram(program);
}
@Override
public void glDeleteProgram(int program) {
GL20.glDeleteProgram(program);
}
// ===================== UNIFORM/ATTRIBUTE OPERATIONS =====================
@Override
public int glGetUniformLocation(int program, CharSequence name) {
return GL20.glGetUniformLocation(program, name);
}
@Override
public void glUniform1i(int location, int value) {
GL20.glUniform1i(location, value);
}
@Override
public int glGetAttribLocation(int program, CharSequence name) {
return GL20.glGetAttribLocation(program, name);
}
@Override
public void glBindAttribLocation(int program, int index, CharSequence name) {
GL20.glBindAttribLocation(program, index, name);
}
// ===================== VAO OPERATIONS =====================
@Override
public int glGenVertexArrays() {
return GL30.glGenVertexArrays();
}
@Override
public void glBindVertexArray(int array) {
GL30.glBindVertexArray(array);
}
@Override
public void glDeleteVertexArrays(int array) {
GL30.glDeleteVertexArrays(array);
}
// ===================== BUFFER OPERATIONS =====================
@Override
public int glGenBuffers() {
return GL15.glGenBuffers();
}
@Override
public void glBindBuffer(int target, int buffer) {
GL15.glBindBuffer(target, buffer);
}
@Override
public void glDeleteBuffers(int buffer) {
GL15.glDeleteBuffers(buffer);
}
// ===================== TEXTURE OPERATIONS =====================
@Override
public int glGenTextures() {
return GL11.glGenTextures();
}
@Override
public void glGenTextures(int[] textures) {
IntBuffer buffer = getIntArrayBuffer(textures.length);
GL11.glGenTextures(buffer);
buffer.get(textures);
}
@Override
public void glDeleteTextures(int texture) {
GLStateManager.glDeleteTextures(texture);
}
@Override
public void glDeleteTextures(int[] textures) {
IntBuffer buffer = getIntArrayBuffer(textures.length);
buffer.put(textures);
buffer.flip();
GLStateManager.glDeleteTextures(buffer);
}
@Override
public void glActiveTexture(int texture) {
GLStateManager.glActiveTexture(texture);
}
@Override
public int glGetTexLevelParameteri(int target, int level, int pname) {
return GLStateManager.glGetTexLevelParameteri(target, level, pname);
}
@Override
public int glGetTexLevelParameter(int target, int level, int pname) {
return GLStateManager.glGetTexLevelParameteri(target, level, pname);
}
@Override
public void glCopyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
GL11.glCopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
}
@Override
public void glPixelStorei(int pname, int param) {
GLStateManager.glPixelStorei(pname, param);
}
// ===================== STATE OPERATIONS =====================
@Override
public void enableCullFace() {
GLStateManager.enableCull();
}
@Override
public void disableCullFace() {
GLStateManager.disableCull();
}
@Override
public void enableBlend() {
GLStateManager.enableBlend();
}
@Override
public void disableBlend() {
GLStateManager.disableBlend();
}
@Override
public void glBlendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
GLStateManager.tryBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
}
@Override
public void enableDepthTest() {
GLStateManager.enableDepthTest();
}
@Override
public void disableDepthTest() {
GLStateManager.disableDepthTest();
}
@Override
public void glDepthFunc(int func) {
GLStateManager.glDepthFunc(func);
}
@Override
public void glDepthMask(boolean flag) {
GLStateManager.glDepthMask(flag);
}
@Override
public void glViewport(int x, int y, int width, int height) {
GLStateManager.glViewport(x, y, width, height);
}
@Override
public void glColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
GLStateManager.glColorMask(red, green, blue, alpha);
}
@Override
public void glClearColor(float red, float green, float blue, float alpha) {
GLStateManager.glClearColor(red, green, blue, alpha);
}
@Override
public void glClear(int mask) {
GLStateManager.glClear(mask);
}
// ===================== MOJANG ADDITIONS =====================
@Override
public void clear(int mask, boolean checkError) {
GLStateManager.glClear(mask);
}
@Override
public void bindTexture(int texture) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
}
@Override
public int getActiveTexture() {
return GLStateManager.getActiveTextureUnit() + GL13.GL_TEXTURE0;
}
@Override
public int getActiveTextureAccessor() {
return GLStateManager.getActiveTextureUnit();
}
@Override
public int getBoundTexture(int internalUnit) {
return GLStateManager.getBoundTextureForServerState(internalUnit);
}
@Override
public int getActiveBoundTexture() {
return GLStateManager.getBoundTextureForServerState();
}
@Override
public int getViewportWidth() {
return GLStateManager.getViewportState().width;
}
@Override
public int getViewportHeight() {
return GLStateManager.getViewportState().height;
}
@Override
public boolean getDepthStateMask() {
return GLStateManager.getDepthState().isEnabled();
}
@Override
public boolean isBlendEnabled() {
return GLStateManager.getBlendMode().isEnabled();
}
@Override
public void setBoundTexture(int unit, int texture) {
int currentUnit = GLStateManager.getActiveTextureUnit();
if (currentUnit != unit) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
}
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
if (currentUnit != unit) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + currentUnit);
}
}
@Override
public int getTextureBinding(int unit) {
return GLStateManager.getBoundTextureForServerState(unit);
}
}
@@ -1,244 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.mitchej123.glsm.RenderSystemService;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
public class AngelicaRenderSystemService implements RenderSystemService {
// Shader state (Mojang-specific, tracked here)
private final int[] shaderTextures = new int[16];
private final float[] shaderColor = {1f, 1f, 1f, 1f};
private final float[] shaderFogColor = {0f, 0f, 0f, 1f};
private float shaderFogStart = 0f;
private float shaderFogEnd = 0f;
private int fogShape = 0;
private float shaderLineWidth = 1f;
private final Matrix4f projectionMatrix = new Matrix4f();
private final FloatBuffer projectionMatrixBuffer = BufferUtils.createFloatBuffer(16);
@Override
public int getPriority() {
return 100;
}
// ===================== TEXTURE OPERATIONS =====================
@Override
public void glActiveTexture(int texture) {
GLStateManager.glActiveTexture(texture);
}
@Override
public void bindTexture(int texture) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
}
// ===================== STATE OPERATIONS =====================
@Override
public void enableCullFace() {
GLStateManager.enableCull();
}
@Override
public void disableCullFace() {
GLStateManager.disableCull();
}
@Override
public void enableBlend() {
GLStateManager.enableBlend();
}
@Override
public void disableBlend() {
GLStateManager.disableBlend();
}
@Override
public void defaultBlendFunc() {
GLStateManager.defaultBlendFunc();
}
@Override
public void blendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
GLStateManager.tryBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
}
@Override
public void setUnknownBlendState() {
// No-op: Angelica tracks blend state accurately
}
@Override
public void enableDepthTest() {
GLStateManager.enableDepthTest();
}
@Override
public void disableDepthTest() {
GLStateManager.disableDepthTest();
}
@Override
public void depthFunc(int depthFunc) {
GLStateManager.glDepthFunc(depthFunc);
}
@Override
public void depthMask(boolean flag) {
GLStateManager.glDepthMask(flag);
}
@Override
public void glViewport(int x, int y, int width, int height) {
GLStateManager.glViewport(x, y, width, height);
}
@Override
public void glClearColor(float red, float green, float blue, float alpha) {
GLStateManager.glClearColor(red, green, blue, alpha);
}
@Override
public void clear(int mask, boolean checkError) {
GLStateManager.glClear(mask);
}
// ===================== UNIFORM OPERATIONS =====================
@Override
public void glUniform1i(int location, int value) {
GL20.glUniform1i(location, value);
}
@Override
public void glUniformMatrix3(int location, boolean transpose, FloatBuffer value) {
GL20.glUniformMatrix3(location, transpose, value);
}
@Override
public void glUniformMatrix4(int location, boolean transpose, FloatBuffer value) {
GL20.glUniformMatrix4(location, transpose, value);
}
// ===================== THREAD ASSERTIONS =====================
@Override
public void assertOnRenderThread() {
// No-op: 1.7.10 is single-threaded for rendering
}
@Override
public void assertOnRenderThreadOrInit() {
// No-op: 1.7.10 is single-threaded for rendering
}
// ===================== SHADER STATE =====================
@Override
public void setShaderTexture(int shaderTexture, int textureId) {
if (shaderTexture >= 0 && shaderTexture < 16) {
shaderTextures[shaderTexture] = textureId;
}
}
@Override
public int getShaderTexture(int shaderTexture) {
return (shaderTexture >= 0 && shaderTexture < 16) ? shaderTextures[shaderTexture] : 0;
}
@Override
public void setShaderColor(float red, float green, float blue, float alpha) {
shaderColor[0] = red;
shaderColor[1] = green;
shaderColor[2] = blue;
shaderColor[3] = alpha;
}
@Override
public float[] getShaderColor() {
return shaderColor.clone();
}
@Override
public void setShaderLineWidth(float lineWidth) {
shaderLineWidth = lineWidth;
}
@Override
public float getShaderLineWidth() {
return shaderLineWidth;
}
// ===================== FOG STATE =====================
@Override
public void setShaderFogColor(float red, float green, float blue, float alpha) {
shaderFogColor[0] = red;
shaderFogColor[1] = green;
shaderFogColor[2] = blue;
shaderFogColor[3] = alpha;
}
@Override
public float[] getShaderFogColor() {
return shaderFogColor.clone();
}
@Override
public void setShaderFogStart(float start) {
shaderFogStart = start;
}
@Override
public float getShaderFogStart() {
return shaderFogStart;
}
@Override
public void setShaderFogEnd(float end) {
shaderFogEnd = end;
}
@Override
public float getShaderFogEnd() {
return shaderFogEnd;
}
@Override
public void setFogShape(int shape) {
fogShape = shape;
}
@Override
public int getFogShape() {
return fogShape;
}
// ===================== PROJECTION MATRIX =====================
@Override
public Matrix4f getProjectionMatrix() {
return new Matrix4f(projectionMatrix);
}
@Override
public void setProjectionMatrixOrth(Matrix4f m) {
projectionMatrix.set(m);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
projectionMatrix.get(projectionMatrixBuffer);
projectionMatrixBuffer.rewind();
GLStateManager.glLoadMatrix(projectionMatrixBuffer);
}
@Override
public void setProjectionMatrixOrigin(Matrix4f m) {
setProjectionMatrixOrth(m);
}
}
@@ -1,238 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizons.angelica.glsm.recording.CommandBuffer;
import com.gtnewhorizons.angelica.glsm.recording.CommandBufferProcessor;
import com.gtnewhorizons.angelica.glsm.recording.CommandBufferProcessor.BufferTransformOptimizer;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* Builds final command buffers from raw command buffers and compiled VBO data.
* Transforms are already collapsed to MultMatrix during recording; the optimizer
* tracks transform state to emit deltas when draws need a different transform.
*/
public final class CommandBufferBuilder {
private static final Logger LOGGER = LogManager.getLogger("CommandBufferBuilder");
private CommandBufferBuilder() {}
/** Build with transform delta optimization and merged draw ranges. */
public static void buildOptimizedFromRawBuffer(
CommandBuffer rawBuffer,
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
final CompiledLineBuffer compiledLineBuffer,
final Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers,
VertexBuffer[] ownedVbos,
int glListId,
CommandBuffer finalBuffer) {
// Build VBO index map for efficient lookup (no boxing)
final Object2IntMap<VertexBuffer> vboIndexMap = new Object2IntOpenHashMap<>();
vboIndexMap.defaultReturnValue(-1);
for (int i = 0; i < ownedVbos.length; i++) {
vboIndexMap.put(ownedVbos[i], i);
}
// Collect all merged draw ranges sorted by command index
final List<DrawRangeWithBuffer> allRanges = collectDrawRanges(compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers);
// Buffer optimizer state
final BufferTransformOptimizer opt = new BufferTransformOptimizer();
rawBuffer.resetRead();
int cmdIndex = 0;
int rangeIndex = 0;
while (rawBuffer.hasRemaining()) {
// Emit draw ranges at this command position
while (rangeIndex < allRanges.size() && allRanges.get(rangeIndex).range().commandIndex() == cmdIndex) {
emitDrawRangeToBuffer(allRanges.get(rangeIndex++), opt, finalBuffer, vboIndexMap);
}
// Read and process the command
final int opcode = rawBuffer.readInt();
CommandBufferProcessor.processCommandForOptimization(opcode, rawBuffer, opt, finalBuffer);
cmdIndex++;
}
// Emit remaining draw ranges at end of command stream
while (rangeIndex < allRanges.size()) {
emitDrawRangeToBuffer(allRanges.get(rangeIndex++), opt, finalBuffer, vboIndexMap);
}
// Emit residual transform if not identity
if (!opt.isIdentity()) {
opt.emitPendingTransform(finalBuffer);
}
}
/** Debug build: no optimizations, 1:1 draw commands. */
public static void buildUnoptimizedFromRawBuffer(
CommandBuffer rawBuffer,
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
CompiledLineBuffer compiledLineBuffer,
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers,
VertexBuffer[] ownedVbos,
CommandBuffer finalBuffer) {
// Build VBO index map for efficient lookup (no boxing)
final Object2IntMap<VertexBuffer> vboIndexMap = new Object2IntOpenHashMap<>();
vboIndexMap.defaultReturnValue(-1);
for (int i = 0; i < ownedVbos.length; i++) {
vboIndexMap.put(ownedVbos[i], i);
}
// Collect per-draw ranges (no merging) sorted by command index
final List<DrawRangeWithBuffer> allRanges = collectDrawRangesUnoptimized(compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers);
rawBuffer.resetRead();
int cmdIndex = 0;
int rangeIndex = 0;
while (rawBuffer.hasRemaining()) {
// Emit draw ranges at this command position
while (rangeIndex < allRanges.size() && allRanges.get(rangeIndex).range().commandIndex() == cmdIndex) {
final DrawRangeWithBuffer drb = allRanges.get(rangeIndex++);
final int vboIdx = vboIndexMap.getInt(drb.vbo());
if (vboIdx >= 0) {
finalBuffer.writeDrawRange(vboIdx, drb.range().startVertex(), drb.range().vertexCount(), drb.hasBrightness());
DisplayListManager.trackDrawRangeSource(drb.source().toString());
}
}
// Copy the command directly
CommandBufferProcessor.copyCommand(rawBuffer, finalBuffer);
cmdIndex++;
}
// Emit remaining draw ranges at end of command stream
while (rangeIndex < allRanges.size()) {
final DrawRangeWithBuffer drb = allRanges.get(rangeIndex++);
final int vboIdx = vboIndexMap.getInt(drb.vbo());
if (vboIdx >= 0) {
finalBuffer.writeDrawRange(vboIdx, drb.range().startVertex(), drb.range().vertexCount(), drb.hasBrightness());
DisplayListManager.trackDrawRangeSource(drb.source().toString());
}
}
}
/** Collect merged draw ranges, sorted by command index. */
static List<DrawRangeWithBuffer> collectDrawRanges(
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
CompiledLineBuffer compiledLineBuffer,
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers) {
return collectDrawRangesInternal(compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers, true);
}
/** Collect unmerged draw ranges for debug mode. */
static List<DrawRangeWithBuffer> collectDrawRangesUnoptimized(
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
CompiledLineBuffer compiledLineBuffer,
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers) {
return collectDrawRangesInternal(compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers, false);
}
private static List<DrawRangeWithBuffer> collectDrawRangesInternal(
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
CompiledLineBuffer compiledLineBuffer,
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers,
boolean useMerged) {
final List<DrawRangeWithBuffer> allRanges = new ArrayList<>();
// Add quad ranges
final CompiledFormatBuffer[] quadBufferArray = compiledQuadBuffers.values().toArray(new CompiledFormatBuffer[0]);
for (int b = 0; b < quadBufferArray.length; b++) {
final CompiledFormatBuffer compiledBuffer = quadBufferArray[b];
final DrawRange[] ranges = useMerged ? compiledBuffer.mergedRanges() : compiledBuffer.perDrawRanges();
for (int i = 0; i < ranges.length; i++) {
allRanges.add(new DrawRangeWithBuffer(ranges[i], compiledBuffer.vbo(), compiledBuffer.flags(),
DrawRangeWithBuffer.DrawSource.TESSELLATOR_QUADS));
}
}
// Add immediate mode line ranges
// Lines use a minimal flags (color only, no texture/brightness/normals)
if (compiledLineBuffer != null) {
final CapturingTessellator.Flags lineFlags = new CapturingTessellator.Flags(false, false, true, false);
final DrawRange[] ranges = useMerged ? compiledLineBuffer.mergedRanges() : compiledLineBuffer.perDrawRanges();
for (int i = 0; i < ranges.length; i++) {
allRanges.add(new DrawRangeWithBuffer(ranges[i], compiledLineBuffer.vbo(), lineFlags,
DrawRangeWithBuffer.DrawSource.IMMEDIATE_MODE_LINES));
}
}
// Add tessellator primitive ranges (lines and triangles) - grouped by format
if (compiledPrimitiveBuffers != null && !compiledPrimitiveBuffers.isEmpty()) {
final CompiledPrimitiveBuffers[] primBufferArray = compiledPrimitiveBuffers.values().toArray(new CompiledPrimitiveBuffers[0]);
for (int p = 0; p < primBufferArray.length; p++) {
final CompiledPrimitiveBuffers primBuffers = primBufferArray[p];
final CapturingTessellator.Flags primFlags = primBuffers.flags();
// Tessellator lines
if (primBuffers.hasLines()) {
final DrawRange[] ranges = useMerged ? primBuffers.lineMergedRanges() : primBuffers.linePerDrawRanges();
final VertexBuffer vbo = primBuffers.lineVbo();
for (int i = 0; i < ranges.length; i++) {
allRanges.add(new DrawRangeWithBuffer(ranges[i], vbo, primFlags,
DrawRangeWithBuffer.DrawSource.TESSELLATOR_LINES));
}
}
// Tessellator triangles
if (primBuffers.hasTriangles()) {
final DrawRange[] ranges = useMerged ? primBuffers.triangleMergedRanges() : primBuffers.trianglePerDrawRanges();
final VertexBuffer vbo = primBuffers.triangleVbo();
for (int i = 0; i < ranges.length; i++) {
allRanges.add(new DrawRangeWithBuffer(ranges[i], vbo, primFlags,
DrawRangeWithBuffer.DrawSource.TESSELLATOR_TRIANGLES));
}
}
}
}
allRanges.sort(Comparator.comparingInt(r -> r.range().commandIndex()));
return allRanges;
}
/** Emit draw range with delta transform. Uses writeDrawRangeRestore for immediate mode. */
private static void emitDrawRangeToBuffer(
DrawRangeWithBuffer drb,
BufferTransformOptimizer opt,
CommandBuffer out,
Object2IntMap<VertexBuffer> vboIndexMap) {
// Emit transform to reach the draw's expected transform
opt.emitTransformTo(out, drb.range().transform());
// Write the draw range command
final int vboIdx = vboIndexMap.getInt(drb.vbo());
if (vboIdx >= 0) {
final var restoreData = drb.range().restoreData();
if (restoreData != null) {
// Immediate mode draw with attribute restoration
final var flags = drb.flags();
out.writeDrawRangeRestore(
vboIdx, drb.range().startVertex(), drb.range().vertexCount(),
drb.hasBrightness(), flags.hasColor, flags.hasNormals, flags.hasTexture,
restoreData.lastColorR(), restoreData.lastColorG(),
restoreData.lastColorB(), restoreData.lastColorA(),
restoreData.lastNormalX(), restoreData.lastNormalY(), restoreData.lastNormalZ(),
restoreData.lastTexCoordS(), restoreData.lastTexCoordT()
);
} else {
// Regular tessellator draw (no restoration needed)
out.writeDrawRange(vboIdx, drb.range().startVertex(), drb.range().vertexCount(), drb.hasBrightness());
}
DisplayListManager.trackDrawRangeSource(drb.source().toString());
}
}
}
@@ -1,23 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
/**
* A compiled VBO with its associated draw ranges.
* All ranges reference vertices within this single VBO.
* <p>
* Contains two range arrays:
* <ul>
* <li><b>mergedRanges</b>: For optimized path - consecutive same-transform draws merged</li>
* <li><b>perDrawRanges</b>: For unoptimized path - 1:1 with original draws</li>
* </ul>
*/
@Desugar
public record CompiledFormatBuffer(
VertexBuffer vbo,
CapturingTessellator.Flags flags,
DrawRange[] mergedRanges,
DrawRange[] perDrawRanges
) {}
@@ -1,21 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
/**
* A compiled VBO containing all line geometry with associated draw ranges.
* All ranges reference vertices within this single VBO.
* <p>
* Contains two range arrays:
* <ul>
* <li><b>mergedRanges</b>: For optimized path - consecutive same-transform draws merged</li>
* <li><b>perDrawRanges</b>: For unoptimized path - 1:1 with original draws</li>
* </ul>
*/
@Desugar
public record CompiledLineBuffer(
VertexBuffer vbo,
DrawRange[] mergedRanges,
DrawRange[] perDrawRanges
) {}
@@ -1,31 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
/**
* Compiled VBOs containing tessellator primitives (lines and triangles) for a single vertex format.
* Separate VBOs are used for lines (GL_LINES) and triangles (GL_TRIANGLES) since they have different draw modes.
* <p>
* Contains range arrays for both optimized (merged) and unoptimized (per-draw) paths.
* Similar to CompiledFormatBuffer but handles primitives instead of quads.
*/
@Desugar
public record CompiledPrimitiveBuffers(
CapturingTessellator.Flags flags,
VertexBuffer lineVbo,
DrawRange[] lineMergedRanges,
DrawRange[] linePerDrawRanges,
VertexBuffer triangleVbo,
DrawRange[] triangleMergedRanges,
DrawRange[] trianglePerDrawRanges
) {
public boolean hasLines() {
return lineVbo != null;
}
public boolean hasTriangles() {
return triangleVbo != null;
}
}
File diff suppressed because it is too large. Load diff
@@ -1,25 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
import org.joml.Matrix4f;
/**
* Represents a vertex range within a shared VBO, along with its transform.
* Used during format-based batching to track where each draw's vertices are stored.
*
* <p>The restoreData field holds last vertex attributes for GL state restoration
* after VBO rendering (immediate mode draws only). Null for tessellator draws.
*/
@Desugar
public record DrawRange(
int startVertex,
int vertexCount,
Matrix4f transform,
int commandIndex,
AccumulatedDraw.RestoreData restoreData
) {
DrawRange(int startVertex, int vertexCount, Matrix4f transform, int commandIndex) {
this(startVertex, vertexCount, transform, commandIndex, null);
}
}
@@ -1,43 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
/**
* Helper record pairing a DrawRange with its VBO and vertex format flags.
* Used for sorting ranges by command index while retaining VBO reference.
* Works for both quad ranges (from CompiledFormatBuffer) and line ranges (from CompiledLineBuffer).
*/
@Desugar
record DrawRangeWithBuffer(
DrawRange range,
VertexBuffer vbo,
CapturingTessellator.Flags flags,
DrawSource source
) {
boolean hasBrightness() {
return flags != null && flags.hasBrightness;
}
/**
* Source of a draw command for debug logging.
*/
enum DrawSource {
TESSELLATOR_QUADS("tessellator quads"),
TESSELLATOR_LINES("tessellator lines"),
TESSELLATOR_TRIANGLES("tessellator triangles"),
IMMEDIATE_MODE_LINES("immediate mode lines");
private final String description;
DrawSource(String description) {
this.description = description;
}
@Override
public String toString() {
return description;
}
}
}
@@ -2,17 +2,14 @@ package com.gtnewhorizons.angelica.glsm;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -22,66 +19,24 @@ public class Feature {
GL13.GL_MULTISAMPLE_BIT, GL11.GL_PIXEL_MODE_BIT, GL11.GL_POINT_BIT, GL11.GL_POLYGON_BIT, GL11.GL_POLYGON_STIPPLE_BIT, GL11.GL_SCISSOR_BIT,
GL11.GL_STENCIL_BUFFER_BIT, GL11.GL_TEXTURE_BIT, GL11.GL_TRANSFORM_BIT, GL11.GL_VIEWPORT_BIT };
static final Int2ObjectMap<List<IStateStack<?>>> maskToFeaturesMap = new Int2ObjectOpenHashMap<>();
static final Int2ObjectMap<IStateStack<?>[]> maskToNonBooleanStacksMap = new Int2ObjectOpenHashMap<>();
static final Int2ObjectMap<Set<IStateStack<?>>> maskToFeaturesMap = new Int2ObjectOpenHashMap<>();
static List<IStateStack<?>> maskToFeatures(int mask) {
static Set<IStateStack<?>> maskToFeatures(int mask) {
if(maskToFeaturesMap.containsKey(mask)) {
return maskToFeaturesMap.get(mask);
}
final Set<IStateStack<?>> features = new HashSet<>();
for(int attrib : Feature.supportedAttribs) {
if((mask & attrib) == attrib) {
features.addAll(getFeatures(attrib));
}
}
final List<IStateStack<?>> asList = new ArrayList<>(features);
maskToFeaturesMap.put(mask, asList);
return asList;
}
/**
* Returns only non-BooleanStateStack instances for the given mask.
* These use traditional push/pop without global depth tracking.
*/
static IStateStack<?>[] maskToNonBooleanStacks(int mask) {
IStateStack<?>[] cached = maskToNonBooleanStacksMap.get(mask);
if (cached != null) {
return cached;
}
final List<IStateStack<?>> all = maskToFeatures(mask);
final List<IStateStack<?>> nonBooleans = new ArrayList<>();
for (int i = 0; i < all.size(); i++) {
final IStateStack<?> stack = all.get(i);
if (!(stack instanceof BooleanStateStack)) {
nonBooleans.add(stack);
}
}
cached = nonBooleans.toArray(new IStateStack<?>[0]);
maskToNonBooleanStacksMap.put(mask, cached);
return cached;
maskToFeaturesMap.put(mask, features);
return features;
}
private static final Map<Integer, Set<IStateStack<?>>> attribToFeatures = new HashMap<>();
/**
* Helper method to add all texture gen states (S, T, R, Q) for all texture units to a collection.
* Used by both GL_ENABLE_BIT and GL_TEXTURE_BIT.
*/
private static void addTextureGenStates(Set<IStateStack<?>> collection) {
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
collection.add(GLStateManager.textures.getTexGenSStates(i));
collection.add(GLStateManager.textures.getTexGenTStates(i));
collection.add(GLStateManager.textures.getTexGenRStates(i));
collection.add(GLStateManager.textures.getTexGenQStates(i));
}
}
static {
attribToFeatures.put(GL11.GL_COLOR_BUFFER_BIT, ImmutableSet.of(
GLStateManager.alphaTest // GL_ALPHA_TEST enable bit
@@ -90,10 +45,10 @@ public class Feature {
, GLStateManager.blendState // Blending source and destination functions
// Constant blend color
// Blending equation
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP enable bit
, GLStateManager.ditherState // GL_DITHER enable bit
, GLStateManager.drawBuffer // GL_DRAW_BUFFER setting
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP enable bit
// GL_DITHER enable bit
// GL_DRAW_BUFFER setting
// GL_COLOR_LOGIC_OP enable bit
// GL_INDEX_LOGIC_OP enable bit
// Logic op function
, GLStateManager.colorMask // Color-mode and index-mode writemasks
, GLStateManager.clearColor // Color-mode and index-mode clear values
@@ -119,15 +74,14 @@ public class Feature {
final HashSet<IStateStack<?>> enableBits = new HashSet<>(ImmutableSet.of(
GLStateManager.alphaTest // GL_ALPHA_TEST flag
, GLStateManager.autoNormalState // GL_AUTO_NORMAL flag
// GL_AUTO_NORMAL flag
, GLStateManager.blendMode // GL_BLEND flag
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP flag
// Enable bits for the user-definable clipping planes
, GLStateManager.colorMaterial // GL_COLOR_MATERIAL
, GLStateManager.cullState // GL_CULL_FACE flag
, GLStateManager.depthTest // GL_DEPTH_TEST flag
, GLStateManager.ditherState // GL_DITHER flag
// GL_DITHER flag
, GLStateManager.fogMode // GL_FOG flag
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP flag
, GLStateManager.lightStates[0] // GL_LIGHT0
, GLStateManager.lightStates[1] // GL_LIGHT1
, GLStateManager.lightStates[2] // GL_LIGHT2
@@ -137,57 +91,36 @@ public class Feature {
, GLStateManager.lightStates[6] // GL_LIGHT6
, GLStateManager.lightStates[7] // GL_LIGHT7
, GLStateManager.lightingState // GL_LIGHTING flag
, GLStateManager.lineSmoothState // GL_LINE_SMOOTH flag
, GLStateManager.lineStippleState // GL_LINE_STIPPLE flag
, GLStateManager.map1Color4State // GL_MAP1_COLOR_4
, GLStateManager.map1IndexState // GL_MAP1_INDEX
, GLStateManager.map1NormalState // GL_MAP1_NORMAL
, GLStateManager.map1TextureCoord1State // GL_MAP1_TEXTURE_COORD_1
, GLStateManager.map1TextureCoord2State // GL_MAP1_TEXTURE_COORD_2
, GLStateManager.map1TextureCoord3State // GL_MAP1_TEXTURE_COORD_3
, GLStateManager.map1TextureCoord4State // GL_MAP1_TEXTURE_COORD_4
, GLStateManager.map1Vertex3State // GL_MAP1_VERTEX_3
, GLStateManager.map1Vertex4State // GL_MAP1_VERTEX_4
, GLStateManager.map2Color4State // GL_MAP2_COLOR_4
, GLStateManager.map2IndexState // GL_MAP2_INDEX
, GLStateManager.map2NormalState // GL_MAP2_NORMAL
, GLStateManager.map2TextureCoord1State // GL_MAP2_TEXTURE_COORD_1
, GLStateManager.map2TextureCoord2State // GL_MAP2_TEXTURE_COORD_2
, GLStateManager.map2TextureCoord3State // GL_MAP2_TEXTURE_COORD_3
, GLStateManager.map2TextureCoord4State // GL_MAP2_TEXTURE_COORD_4
, GLStateManager.map2Vertex3State // GL_MAP2_VERTEX_3
, GLStateManager.map2Vertex4State // GL_MAP2_VERTEX_4
, GLStateManager.multisampleState // GL_MULTISAMPLE flag
, GLStateManager.normalizeState // GL_NORMALIZE flag
, GLStateManager.pointSmoothState // GL_POINT_SMOOTH flag
, GLStateManager.polygonOffsetPointState // GL_POLYGON_OFFSET_POINT flag
, GLStateManager.polygonOffsetLineState // GL_POLYGON_OFFSET_LINE flag
, GLStateManager.polygonOffsetFillState // GL_POLYGON_OFFSET_FILL flag
, GLStateManager.polygonSmoothState // GL_POLYGON_SMOOTH flag
, GLStateManager.polygonStippleState // GL_POLYGON_STIPPLE flag
, GLStateManager.rescaleNormalState // GL_RESCALE_NORMAL flag
, GLStateManager.sampleAlphaToCoverageState // GL_SAMPLE_ALPHA_TO_COVERAGE flag
, GLStateManager.sampleAlphaToOneState // GL_SAMPLE_ALPHA_TO_ONE flag
, GLStateManager.sampleCoverageState // GL_SAMPLE_COVERAGE flag
// GL_LINE_SMOOTH flag
// GL_LINE_STIPPLE flag
// GL_INDEX_LOGIC_OP flag
// GL_COLOR_LOGIC_OP flag
// GL_MAP1_x where x is a map type
// GL_MAP2_x where x is a map type
// GL_MULTISAMPLE flag
// GL_NORMALIZE flag
// GL_POINT_SMOOTH flag
// GL_POLYGON_OFFSET_LINE flag
// GL_POLYGON_OFFSET_FILL flag
// GL_POLYGON_OFFSET_POINT flag
// GL_POLYGON_SMOOTH flag
// GL_POLYGON_STIPPLE flag
// GL_SAMPLE_ALPHA_TO_COVERAGE flag
// GL_SAMPLE_ALPHA_TO_ONE flag
// GL_SAMPLE_COVERAGE flag
, GLStateManager.scissorTest // GL_SCISSOR_TEST flag
, GLStateManager.stencilTest // GL_STENCIL_TEST flag
// GL_STENCIL_TEST flag
// GL_TEXTURE_1D flag
// GL_TEXTURE_2D flag - Below
// GL_TEXTURE_3D flag
// Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q
));
// Enable bits for the user-definable clipping planes
for(int i = 0; i < GLStateManager.clipPlaneStates.length; i++) {
enableBits.add(GLStateManager.clipPlaneStates[i]);
}
// GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D flags
// GL_TEXTURE_2D flag
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
enableBits.add(GLStateManager.textures.getTexture1DStates(i));
enableBits.add(GLStateManager.textures.getTextureUnitStates(i));
enableBits.add(GLStateManager.textures.getTexture3DStates(i));
}
// Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q
addTextureGenStates(enableBits);
attribToFeatures.put(GL11.GL_ENABLE_BIT, enableBits);
attribToFeatures.put(GL11.GL_EVAL_BIT, ImmutableSet.of(
// GL_MAP1_x enable bits, where x is a map type
@@ -248,9 +181,10 @@ public class Feature {
, GLStateManager.shadeModelState // GL_SHADE_MODEL setting
));
attribToFeatures.put(GL11.GL_LINE_BIT, ImmutableSet.of(
GLStateManager.lineSmoothState // GL_LINE_SMOOTH flag
, GLStateManager.lineStippleState // GL_LINE_STIPPLE enable bit
, GLStateManager.lineState // Line stipple pattern, repeat counter, and width
// GL_LINE_SMOOTH flag
// GL_LINE_STIPPLE enable bit
// Line stipple pattern and repeat counter
// Line width
));
attribToFeatures.put(GL11.GL_LIST_BIT, ImmutableSet.of(
// GL_LIST_BASE setting
@@ -275,17 +209,21 @@ public class Feature {
// GL_READ_BUFFER setting
));
attribToFeatures.put(GL11.GL_POINT_BIT, ImmutableSet.of(
GLStateManager.pointSmoothState // GL_POINT_SMOOTH flag
, GLStateManager.pointState // Point size
// GL_POINT_SMOOTH flag
// Point size
));
attribToFeatures.put(GL11.GL_POLYGON_BIT, ImmutableSet.of(
GLStateManager.cullState // GL_CULL_FACE enable bit
, GLStateManager.polygonSmoothState // GL_POLYGON_SMOOTH flag
, GLStateManager.polygonStippleState // GL_POLYGON_STIPPLE enable bit
, GLStateManager.polygonOffsetFillState // GL_POLYGON_OFFSET_FILL flag
, GLStateManager.polygonOffsetLineState // GL_POLYGON_OFFSET_LINE flag
, GLStateManager.polygonOffsetPointState // GL_POLYGON_OFFSET_POINT flag
, GLStateManager.polygonState // GL_CULL_FACE_MODE, GL_FRONT_FACE, GL_POLYGON_MODE, GL_POLYGON_OFFSET_FACTOR/UNITS
// GL_CULL_FACE_MODE value
// GL_FRONT_FACE indicator
// GL_POLYGON_MODE setting
// GL_POLYGON_SMOOTH flag
// GL_POLYGON_STIPPLE enable bit
// GL_POLYGON_OFFSET_FILL flag
// GL_POLYGON_OFFSET_LINE flag
// GL_POLYGON_OFFSET_POINT flag
// GL_POLYGON_OFFSET_FACTOR
// GL_POLYGON_OFFSET_UNITS
));
attribToFeatures.put(GL11.GL_POLYGON_STIPPLE_BIT, ImmutableSet.of(
// Polygon stipple pattern
@@ -295,8 +233,12 @@ public class Feature {
// Scissor box
));
attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of(
GLStateManager.stencilTest // GL_STENCIL_TEST enable bit
, GLStateManager.stencilState // Stencil function, ref, mask, ops, writemask, clear value
// GL_STENCIL_TEST enable bit
// Stencil function and reference value
// Stencil value mask
// Stencil fail, pass, and depth buffer pass actions
// Stencil buffer clear value
// Stencil buffer writemask
));
final Set<IStateStack<?>> textureAttribs = new HashSet<>(ImmutableSet.of(
GLStateManager.activeTextureUnit // Active texture unit
@@ -320,16 +262,13 @@ public class Feature {
// textureAttribs.add(GLStateManager.textures.getInfo(i))
}
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q
addTextureGenStates(textureAttribs);
attribToFeatures.put(GL11.GL_TEXTURE_BIT, textureAttribs);
attribToFeatures.put(GL11.GL_TRANSFORM_BIT, ImmutableSet.of(
// Coefficients of the six clipping planes
// Enable bits for the user-definable clipping planes
GLStateManager.matrixMode
, GLStateManager.normalizeState // GL_NORMALIZE flag
// GL_NORMALIZE flag
, GLStateManager.rescaleNormalState // GL_RESCALE_NORMAL flag
));
attribToFeatures.put(GL11.GL_VIEWPORT_BIT, ImmutableSet.of(
@@ -1,106 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadViewMutable;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
import org.joml.Matrix4f;
import java.util.ArrayList;
import java.util.List;
/**
* Accumulates geometry for a single vertex format during display list compilation.
* Tracks merged ranges (same-transform draws combined) and per-draw ranges (1:1).
*/
class FormatBuffer {
final CapturingTessellator.Flags flags;
final List<ModelQuadViewMutable> allQuads = new ArrayList<>();
// Merged ranges for optimized path (consecutive same-transform draws combined)
final List<DrawRange> mergedRanges = new ArrayList<>();
// Per-draw ranges for unoptimized path (1:1 with input draws)
final List<DrawRange> perDrawRanges = new ArrayList<>();
int currentVertexOffset = 0;
// Track the current range being built for merging consecutive same-transform draws
private int pendingStartVertex = -1;
private int pendingVertexCount = 0;
private Matrix4f pendingTransform = null;
private int pendingCommandIndex = -1;
private int pendingGeneration = -1;
private int pendingStateGen = -1;
// Track last restore data for merged draws (uses the LAST draw's data)
private AccumulatedDraw.RestoreData pendingRestoreData = null;
FormatBuffer(CapturingTessellator.Flags flags) {
this.flags = flags;
}
/** Add a draw, tracking both merged and per-draw ranges. */
void addDraw(AccumulatedDraw draw) {
final int vertexCount = draw.quads.size() * 4; // 4 vertices per quad
// Always track per-draw range for unoptimized path (includes restoreData)
perDrawRanges.add(new DrawRange(
currentVertexOffset, vertexCount, draw.transform, draw.commandIndex, draw.restoreData
));
// Merge logic for optimized path:
// - same matrixGeneration = same matrix transform context
// - same stateGeneration = no state commands (draw barriers) between draws
boolean canMerge = pendingGeneration != -1
&& pendingGeneration == draw.matrixGeneration
&& pendingStateGen == draw.stateGeneration;
if (canMerge) {
// Extend the pending range
pendingVertexCount += vertexCount;
// Update restoreData to the LAST draw's data (used after merged draw completes)
pendingRestoreData = draw.restoreData;
} else {
// Flush pending range if any
flushPendingRange();
// Start new pending range
pendingStartVertex = currentVertexOffset;
pendingVertexCount = vertexCount;
pendingTransform = new Matrix4f(draw.transform);
pendingCommandIndex = draw.commandIndex;
pendingGeneration = draw.matrixGeneration;
pendingStateGen = draw.stateGeneration;
pendingRestoreData = draw.restoreData;
}
allQuads.addAll(draw.quads);
currentVertexOffset += vertexCount;
}
private void flushPendingRange() {
if (pendingGeneration != -1) {
mergedRanges.add(new DrawRange(
pendingStartVertex, pendingVertexCount, pendingTransform, pendingCommandIndex, pendingRestoreData
));
pendingTransform = null;
pendingGeneration = -1;
pendingStateGen = -1;
pendingRestoreData = null;
}
}
/** Compile accumulated quads into a VBO. */
CompiledFormatBuffer finish() {
// Flush any remaining pending range
flushPendingRange();
VertexBuffer vbo = DisplayListManager.compileQuads(allQuads, flags);
return new CompiledFormatBuffer(
vbo,
flags,
mergedRanges.toArray(new DrawRange[0]),
perDrawRanges.toArray(new DrawRange[0])
);
}
}
@@ -9,16 +9,15 @@ import org.lwjgl.opengl.AMDDebugOutput;
import org.lwjgl.opengl.AMDDebugOutputCallback;
import org.lwjgl.opengl.ARBDebugOutput;
import org.lwjgl.opengl.ARBDebugOutputCallback;
import org.lwjgl.opengl.EXTBlendColor;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.KHRDebug;
import org.lwjgl.opengl.KHRDebugCallback;
import java.io.PrintStream;
import java.util.function.Consumer;
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
import static org.lwjgl.opengl.ARBDebugOutput.glDebugMessageCallbackARB;
@@ -32,9 +31,20 @@ public final class GLDebug {
LOGGER.warn("setupDebugMessageCallback called from non-main thread!");
return 0;
}
return setupDebugMessageCallbackImpl();
return setupDebugMessageCallback(System.err);
}
private static void trace(Consumer<String> output) {
/*
* We can not just use a fixed stacktrace element offset, because some methods
* are intercepted and some are not. So, check the package name.
*/
StackTraceElement[] elems = filterStackTrace(new Throwable(), 4).getStackTrace();
for (StackTraceElement ste : elems) {
output.accept(ste.toString());
}
}
public static Throwable filterStackTrace(Throwable throwable, int offset) {
StackTraceElement[] elems = throwable.getStackTrace();
StackTraceElement[] filtered = new StackTraceElement[elems.length];
@@ -52,48 +62,36 @@ public final class GLDebug {
return throwable;
}
private static String buildStackTrace() {
StackTraceElement[] elems = filterStackTrace(new Throwable(), 4).getStackTrace();
StringBuilder sb = new StringBuilder();
for (StackTraceElement elem : elems) {
sb.append("\n\t").append(elem.toString());
}
return sb.toString();
}
public static void printTrace(PrintStream stream) {
trace(new Consumer<String>() {
boolean first = true;
private static void logDebugMessage(int id, String source, String type, String severity, String message) {
String fullMessage = String.format("[GL] %s %s (0x%X) from %s: %s%s", severity, type, id, source, message, buildStackTrace());
if ("HIGH".equals(severity)) {
LOGGER.error(fullMessage);
} else if ("MEDIUM".equals(severity)) {
LOGGER.warn(fullMessage);
} else {
LOGGER.info(fullMessage);
}
}
private static void logDebugMessageAMD(int id, String category, String severity, String message) {
String fullMessage = String.format("[GL] %s %s (0x%X): %s%s", severity, category, id, message, buildStackTrace());
if ("HIGH".equals(severity)) {
LOGGER.error(fullMessage);
} else if ("MEDIUM".equals(severity)) {
LOGGER.warn(fullMessage);
} else {
LOGGER.info(fullMessage);
}
public void accept(String str) {
if (first) {
printDetail(stream, "Stacktrace", str);
first = false;
} else {
printDetailLine(stream, "Stacktrace", str);
}
}
});
}
/**
* Sets up debug callbacks
* @return 0 for failure, 1 for success, 2 for restart required.
*/
private static int setupDebugMessageCallbackImpl() {
public static int setupDebugMessageCallback(PrintStream stream) {
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);
KHRDebugCallback proc = new KHRDebugCallback((source, type, id, severity, message) -> {
stream.println("[LWJGL] OpenGL debug message");
printDetail(stream, "ID", String.format("0x%X", id));
printDetail(stream, "Source", getDebugSource(source));
printDetail(stream, "Type", getDebugType(type));
printDetail(stream, "Severity", getDebugSeverity(severity));
printDetail(stream, "Message", message);
printTrace(stream);
});
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);
@@ -101,9 +99,6 @@ public final class GLDebug {
GL43.glDebugMessageControl(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_NOTIFICATION, null, false);
GL43.glDebugMessageCallback(proc);
// Enable synchronous debug output so errors are reported immediately with accurate stack traces
GL11.glEnable(GL43.GL_DEBUG_OUTPUT_SYNCHRONOUS);
if ((GL11.glGetInteger(GL30.GL_CONTEXT_FLAGS) & GL43.GL_CONTEXT_FLAG_DEBUG_BIT) == 0) {
LOGGER.warn("[GL] Warning: A non-debug context may not produce any debug output.");
GL11.glEnable(GL43.GL_DEBUG_OUTPUT);
@@ -112,22 +107,30 @@ public final class GLDebug {
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);
ARBDebugOutputCallback proc = new ARBDebugOutputCallback((source, type, id, severity, message) -> {
stream.println("[LWJGL] ARB_debug_output message");
printDetail(stream, "ID", String.format("0x%X", id));
printDetail(stream, "Source", getSourceARB(source));
printDetail(stream, "Type", getTypeARB(type));
printDetail(stream, "Severity", getSeverityARB(severity));
printDetail(stream, "Message", message);
printTrace(stream);
});
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);
AMDDebugOutputCallback proc = new AMDDebugOutputCallback((id, category, severity, message) -> {
stream.println("[LWJGL] AMD_debug_output message");
printDetail(stream, "ID", String.format("0x%X", id));
printDetail(stream, "Category", getCategoryAMD(category));
printDetail(stream, "Severity", getSeverityAMD(severity));
printDetail(stream, "Message", message);
printTrace(stream);
});
AMDDebugOutput.glDebugMessageEnableAMD(0, GL43.GL_DEBUG_SEVERITY_HIGH, null, true);
AMDDebugOutput.glDebugMessageEnableAMD(0, GL43.GL_DEBUG_SEVERITY_MEDIUM, null, false);
@@ -163,6 +166,17 @@ public final class GLDebug {
}
}
private static void printDetail(PrintStream stream, String type, String message) {
stream.printf("\t%s: %s\n", type, message);
}
private static void printDetailLine(PrintStream stream, String type, String message) {
stream.append(" ");
for (int i = 0; i < type.length(); i++) {
stream.append(" ");
}
stream.append(message).append("\n");
}
private static String getDebugSource(int source) {
return switch (source) {
@@ -255,304 +269,6 @@ public final class GLDebug {
};
}
public static String getMatrixModeName(int mode) {
return switch (mode) {
case GL11.GL_MODELVIEW -> "MODELVIEW";
case GL11.GL_PROJECTION -> "PROJECTION";
case GL11.GL_TEXTURE -> "TEXTURE";
case GL11.GL_COLOR -> "COLOR";
default -> String.format("0x%X", mode);
};
}
public static String getCapabilityName(int cap) {
return switch (cap) {
case GL11.GL_ALPHA_TEST -> "ALPHA_TEST";
case GL11.GL_BLEND -> "BLEND";
case GL11.GL_COLOR_MATERIAL -> "COLOR_MATERIAL";
case GL11.GL_CULL_FACE -> "CULL_FACE";
case GL11.GL_DEPTH_TEST -> "DEPTH_TEST";
case GL11.GL_FOG -> "FOG";
case GL11.GL_LIGHTING -> "LIGHTING";
case GL11.GL_LINE_SMOOTH -> "LINE_SMOOTH";
case GL11.GL_NORMALIZE -> "NORMALIZE";
case GL11.GL_POINT_SMOOTH -> "POINT_SMOOTH";
case GL11.GL_POLYGON_OFFSET_FILL -> "POLYGON_OFFSET_FILL";
case GL11.GL_POLYGON_OFFSET_LINE -> "POLYGON_OFFSET_LINE";
case GL11.GL_POLYGON_SMOOTH -> "POLYGON_SMOOTH";
case GL12.GL_RESCALE_NORMAL -> "RESCALE_NORMAL";
case GL11.GL_SCISSOR_TEST -> "SCISSOR_TEST";
case GL11.GL_STENCIL_TEST -> "STENCIL_TEST";
case GL11.GL_TEXTURE_1D -> "TEXTURE_1D";
case GL11.GL_TEXTURE_2D -> "TEXTURE_2D";
case GL11.GL_LIGHT0 -> "LIGHT0";
case GL11.GL_LIGHT1 -> "LIGHT1";
case GL11.GL_LIGHT2 -> "LIGHT2";
case GL11.GL_LIGHT3 -> "LIGHT3";
case GL11.GL_LIGHT4 -> "LIGHT4";
case GL11.GL_LIGHT5 -> "LIGHT5";
case GL11.GL_LIGHT6 -> "LIGHT6";
case GL11.GL_LIGHT7 -> "LIGHT7";
default -> String.format("0x%X", cap);
};
}
public static String getComparisonFuncName(int func) {
return switch (func) {
case GL11.GL_NEVER -> "NEVER";
case GL11.GL_LESS -> "LESS";
case GL11.GL_EQUAL -> "EQUAL";
case GL11.GL_LEQUAL -> "LEQUAL";
case GL11.GL_GREATER -> "GREATER";
case GL11.GL_NOTEQUAL -> "NOTEQUAL";
case GL11.GL_GEQUAL -> "GEQUAL";
case GL11.GL_ALWAYS -> "ALWAYS";
default -> String.format("0x%X", func);
};
}
public static String getBlendFactorName(int factor) {
return switch (factor) {
case GL11.GL_ZERO -> "ZERO";
case GL11.GL_ONE -> "ONE";
case GL11.GL_SRC_COLOR -> "SRC_COLOR";
case GL11.GL_ONE_MINUS_SRC_COLOR -> "ONE_MINUS_SRC_COLOR";
case GL11.GL_DST_COLOR -> "DST_COLOR";
case GL11.GL_ONE_MINUS_DST_COLOR -> "ONE_MINUS_DST_COLOR";
case GL11.GL_SRC_ALPHA -> "SRC_ALPHA";
case GL11.GL_ONE_MINUS_SRC_ALPHA -> "ONE_MINUS_SRC_ALPHA";
case GL11.GL_DST_ALPHA -> "DST_ALPHA";
case GL11.GL_ONE_MINUS_DST_ALPHA -> "ONE_MINUS_DST_ALPHA";
case EXTBlendColor.GL_CONSTANT_COLOR_EXT -> "CONSTANT_COLOR";
case EXTBlendColor.GL_ONE_MINUS_CONSTANT_COLOR_EXT -> "ONE_MINUS_CONSTANT_COLOR";
case EXTBlendColor.GL_CONSTANT_ALPHA_EXT -> "CONSTANT_ALPHA";
case EXTBlendColor.GL_ONE_MINUS_CONSTANT_ALPHA_EXT -> "ONE_MINUS_CONSTANT_ALPHA";
case GL11.GL_SRC_ALPHA_SATURATE -> "SRC_ALPHA_SATURATE";
default -> String.format("0x%X", factor);
};
}
public static String getShadeModelName(int mode) {
return switch (mode) {
case GL11.GL_FLAT -> "FLAT";
case GL11.GL_SMOOTH -> "SMOOTH";
default -> String.format("0x%X", mode);
};
}
public static String getTextureTargetName(int target) {
return switch (target) {
case GL11.GL_TEXTURE_1D -> "TEXTURE_1D";
case GL11.GL_TEXTURE_2D -> "TEXTURE_2D";
case GL12.GL_TEXTURE_3D -> "TEXTURE_3D";
case GL13.GL_TEXTURE_CUBE_MAP -> "TEXTURE_CUBE_MAP";
default -> String.format("0x%X", target);
};
}
public static String getTexturePnameName(int pname) {
return switch (pname) {
case GL11.GL_TEXTURE_MIN_FILTER -> "TEXTURE_MIN_FILTER";
case GL11.GL_TEXTURE_MAG_FILTER -> "TEXTURE_MAG_FILTER";
case GL11.GL_TEXTURE_WRAP_S -> "TEXTURE_WRAP_S";
case GL11.GL_TEXTURE_WRAP_T -> "TEXTURE_WRAP_T";
case GL12.GL_TEXTURE_WRAP_R -> "TEXTURE_WRAP_R";
case GL12.GL_TEXTURE_MIN_LOD -> "TEXTURE_MIN_LOD";
case GL12.GL_TEXTURE_MAX_LOD -> "TEXTURE_MAX_LOD";
case GL12.GL_TEXTURE_BASE_LEVEL -> "TEXTURE_BASE_LEVEL";
case GL12.GL_TEXTURE_MAX_LEVEL -> "TEXTURE_MAX_LEVEL";
case GL11.GL_TEXTURE_BORDER_COLOR -> "TEXTURE_BORDER_COLOR";
case GL11.GL_TEXTURE_PRIORITY -> "TEXTURE_PRIORITY";
case GL14.GL_TEXTURE_LOD_BIAS -> "TEXTURE_LOD_BIAS";
case GL14.GL_GENERATE_MIPMAP -> "GENERATE_MIPMAP";
default -> String.format("0x%X", pname);
};
}
public static String getTextureFormatName(int format) {
return switch (format) {
case GL11.GL_RED -> "RED";
case GL11.GL_GREEN -> "GREEN";
case GL11.GL_BLUE -> "BLUE";
case GL11.GL_ALPHA -> "ALPHA";
case GL11.GL_RGB -> "RGB";
case GL11.GL_RGBA -> "RGBA";
case GL11.GL_LUMINANCE -> "LUMINANCE";
case GL11.GL_LUMINANCE_ALPHA -> "LUMINANCE_ALPHA";
case GL12.GL_BGR -> "BGR";
case GL12.GL_BGRA -> "BGRA";
case GL11.GL_DEPTH_COMPONENT -> "DEPTH_COMPONENT";
case GL11.GL_STENCIL_INDEX -> "STENCIL_INDEX";
case GL30.GL_DEPTH_STENCIL -> "DEPTH_STENCIL";
// Internal formats
case GL11.GL_RGB8 -> "RGB8";
case GL11.GL_RGBA8 -> "RGBA8";
case GL11.GL_ALPHA8 -> "ALPHA8";
case GL11.GL_LUMINANCE8 -> "LUMINANCE8";
case GL11.GL_LUMINANCE8_ALPHA8 -> "LUMINANCE8_ALPHA8";
default -> String.format("0x%X", format);
};
}
public static String getDataTypeName(int type) {
return switch (type) {
case GL11.GL_UNSIGNED_BYTE -> "UNSIGNED_BYTE";
case GL11.GL_BYTE -> "BYTE";
case GL11.GL_UNSIGNED_SHORT -> "UNSIGNED_SHORT";
case GL11.GL_SHORT -> "SHORT";
case GL11.GL_UNSIGNED_INT -> "UNSIGNED_INT";
case GL11.GL_INT -> "INT";
case GL11.GL_FLOAT -> "FLOAT";
case GL11.GL_DOUBLE -> "DOUBLE";
case GL12.GL_UNSIGNED_BYTE_3_3_2 -> "UNSIGNED_BYTE_3_3_2";
case GL12.GL_UNSIGNED_SHORT_4_4_4_4 -> "UNSIGNED_SHORT_4_4_4_4";
case GL12.GL_UNSIGNED_SHORT_5_5_5_1 -> "UNSIGNED_SHORT_5_5_5_1";
case GL12.GL_UNSIGNED_INT_8_8_8_8 -> "UNSIGNED_INT_8_8_8_8";
case GL12.GL_UNSIGNED_INT_10_10_10_2 -> "UNSIGNED_INT_10_10_10_2";
default -> String.format("0x%X", type);
};
}
public static String getLightName(int light) {
return switch (light) {
case GL11.GL_LIGHT0 -> "LIGHT0";
case GL11.GL_LIGHT1 -> "LIGHT1";
case GL11.GL_LIGHT2 -> "LIGHT2";
case GL11.GL_LIGHT3 -> "LIGHT3";
case GL11.GL_LIGHT4 -> "LIGHT4";
case GL11.GL_LIGHT5 -> "LIGHT5";
case GL11.GL_LIGHT6 -> "LIGHT6";
case GL11.GL_LIGHT7 -> "LIGHT7";
default -> String.format("0x%X", light);
};
}
public static String getLightPnameName(int pname) {
return switch (pname) {
case GL11.GL_AMBIENT -> "AMBIENT";
case GL11.GL_DIFFUSE -> "DIFFUSE";
case GL11.GL_SPECULAR -> "SPECULAR";
case GL11.GL_POSITION -> "POSITION";
case GL11.GL_SPOT_DIRECTION -> "SPOT_DIRECTION";
case GL11.GL_SPOT_EXPONENT -> "SPOT_EXPONENT";
case GL11.GL_SPOT_CUTOFF -> "SPOT_CUTOFF";
case GL11.GL_CONSTANT_ATTENUATION -> "CONSTANT_ATTENUATION";
case GL11.GL_LINEAR_ATTENUATION -> "LINEAR_ATTENUATION";
case GL11.GL_QUADRATIC_ATTENUATION -> "QUADRATIC_ATTENUATION";
default -> String.format("0x%X", pname);
};
}
public static String getMaterialPnameName(int pname) {
return switch (pname) {
case GL11.GL_AMBIENT -> "AMBIENT";
case GL11.GL_DIFFUSE -> "DIFFUSE";
case GL11.GL_SPECULAR -> "SPECULAR";
case GL11.GL_EMISSION -> "EMISSION";
case GL11.GL_SHININESS -> "SHININESS";
case GL11.GL_AMBIENT_AND_DIFFUSE -> "AMBIENT_AND_DIFFUSE";
case GL11.GL_COLOR_INDEXES -> "COLOR_INDEXES";
default -> String.format("0x%X", pname);
};
}
public static String getFaceName(int face) {
return switch (face) {
case GL11.GL_FRONT -> "FRONT";
case GL11.GL_BACK -> "BACK";
case GL11.GL_FRONT_AND_BACK -> "FRONT_AND_BACK";
default -> String.format("0x%X", face);
};
}
public static String getColorMaterialModeName(int mode) {
return switch (mode) {
case GL11.GL_AMBIENT -> "AMBIENT";
case GL11.GL_DIFFUSE -> "DIFFUSE";
case GL11.GL_SPECULAR -> "SPECULAR";
case GL11.GL_EMISSION -> "EMISSION";
case GL11.GL_AMBIENT_AND_DIFFUSE -> "AMBIENT_AND_DIFFUSE";
default -> String.format("0x%X", mode);
};
}
public static String getFogPnameName(int pname) {
return switch (pname) {
case GL11.GL_FOG_MODE -> "FOG_MODE";
case GL11.GL_FOG_DENSITY -> "FOG_DENSITY";
case GL11.GL_FOG_START -> "FOG_START";
case GL11.GL_FOG_END -> "FOG_END";
case GL11.GL_FOG_INDEX -> "FOG_INDEX";
case GL11.GL_FOG_COLOR -> "FOG_COLOR";
default -> String.format("0x%X", pname);
};
}
public static String getLightModelPnameName(int pname) {
return switch (pname) {
case GL11.GL_LIGHT_MODEL_AMBIENT -> "LIGHT_MODEL_AMBIENT";
case GL11.GL_LIGHT_MODEL_LOCAL_VIEWER -> "LIGHT_MODEL_LOCAL_VIEWER";
case GL11.GL_LIGHT_MODEL_TWO_SIDE -> "LIGHT_MODEL_TWO_SIDE";
case GL12.GL_LIGHT_MODEL_COLOR_CONTROL -> "LIGHT_MODEL_COLOR_CONTROL";
default -> String.format("0x%X", pname);
};
}
public static String getClearMaskString(int mask) {
StringBuilder sb = new StringBuilder();
boolean first = true;
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
sb.append("COLOR");
first = false;
}
if ((mask & GL11.GL_DEPTH_BUFFER_BIT) != 0) {
if (!first) sb.append("|");
sb.append("DEPTH");
first = false;
}
if ((mask & GL11.GL_STENCIL_BUFFER_BIT) != 0) {
if (!first) sb.append("|");
sb.append("STENCIL");
first = false;
}
if ((mask & GL11.GL_ACCUM_BUFFER_BIT) != 0) {
if (!first) sb.append("|");
sb.append("ACCUM");
}
return sb.toString();
}
public static String getCullFaceName(int mode) {
return switch (mode) {
case GL11.GL_FRONT -> "FRONT";
case GL11.GL_BACK -> "BACK";
case GL11.GL_FRONT_AND_BACK -> "FRONT_AND_BACK";
default -> String.format("0x%X", mode);
};
}
public static String getLogicOpName(int opcode) {
return switch (opcode) {
case GL11.GL_CLEAR -> "CLEAR";
case GL11.GL_AND -> "AND";
case GL11.GL_AND_REVERSE -> "AND_REVERSE";
case GL11.GL_COPY -> "COPY";
case GL11.GL_AND_INVERTED -> "AND_INVERTED";
case GL11.GL_NOOP -> "NOOP";
case GL11.GL_XOR -> "XOR";
case GL11.GL_OR -> "OR";
case GL11.GL_NOR -> "NOR";
case GL11.GL_EQUIV -> "EQUIV";
case GL11.GL_INVERT -> "INVERT";
case GL11.GL_OR_REVERSE -> "OR_REVERSE";
case GL11.GL_COPY_INVERTED -> "COPY_INVERTED";
case GL11.GL_OR_INVERTED -> "OR_INVERTED";
case GL11.GL_NAND -> "NAND";
case GL11.GL_SET -> "SET";
default -> String.format("0x%X", opcode);
};
}
private static DebugState debugState;
private interface DebugState {
File diff suppressed because it is too large. Load diff
@@ -1,134 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedLineDraw;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* Accumulates line geometry during display list compilation.
* All line draws are collected here, then compiled into a single VBO.
* <p>
* Lines all use the same format (POSITION_COLOR), so no format-based grouping needed.
* <p>
* Tracks two types of ranges:
* <ul>
* <li><b>mergedRanges</b>: Consecutive same-transform draws merged for optimized path</li>
* <li><b>perDrawRanges</b>: One range per input draw for unoptimized path (1:1 with addDraw calls)</li>
* </ul>
*/
class LineBuffer {
final List<ImmediateModeRecorder.LineVertex> allVertices = new ArrayList<>();
// Merged ranges for optimized path (consecutive same-transform draws combined)
final List<DrawRange> mergedRanges = new ArrayList<>();
// Per-draw ranges for unoptimized path (1:1 with input draws)
final List<DrawRange> perDrawRanges = new ArrayList<>();
int currentVertexOffset = 0;
// Track the current range being built for merging consecutive same-transform draws
private int pendingStartVertex = -1;
private int pendingVertexCount = 0;
private Matrix4f pendingTransform = null;
private int pendingCommandIndex = -1;
private int pendingStateGen = -1;
/**
* Add a line draw to this buffer.
* Tracks both merged (for optimized) and per-draw (for unoptimized) ranges.
*/
void addDraw(AccumulatedLineDraw draw) {
int vertexCount = draw.lines.size(); // Each LineVertex is one vertex, lines come in pairs
// Always track per-draw range for unoptimized path
perDrawRanges.add(new DrawRange(currentVertexOffset, vertexCount, draw.transform, draw.commandIndex));
// Merge logic for optimized path:
// - same transform
// - same stateGeneration = no state commands (draw barriers) between draws
boolean canMerge = pendingTransform != null
&& pendingTransform.equals(draw.transform)
&& pendingStateGen == draw.stateGeneration;
if (canMerge) {
// Extend the pending range
pendingVertexCount += vertexCount;
} else {
// Flush pending range if any
flushPendingRange();
// Start new pending range
pendingStartVertex = currentVertexOffset;
pendingVertexCount = vertexCount;
pendingTransform = new Matrix4f(draw.transform);
pendingCommandIndex = draw.commandIndex;
pendingStateGen = draw.stateGeneration;
}
allVertices.addAll(draw.lines);
currentVertexOffset += vertexCount;
}
private void flushPendingRange() {
if (pendingTransform != null) {
mergedRanges.add(new DrawRange(pendingStartVertex, pendingVertexCount, pendingTransform, pendingCommandIndex));
pendingTransform = null;
pendingStateGen = -1;
}
}
/**
* Compile all accumulated line vertices into a single VBO.
* @return The compiled buffer with VBO and both range types, or null if empty
*/
CompiledLineBuffer finish() {
if (allVertices.isEmpty()) {
return null;
}
// Flush any remaining pending range
flushPendingRange();
VertexBuffer vbo = compileLinesToVBO(allVertices);
return new CompiledLineBuffer(
vbo,
mergedRanges.toArray(new DrawRange[0]),
perDrawRanges.toArray(new DrawRange[0])
);
}
/**
* Compile line vertices into a VertexBuffer (VAO or VBO).
* Lines use POSITION_COLOR format (x, y, z, color).
*/
private static VertexBuffer compileLinesToVBO(List<ImmediateModeRecorder.LineVertex> lines) {
final VertexFormat format = DefaultVertexFormat.POSITION_COLOR;
final VertexBuffer vao = VAOManager.createVAO(format, GL11.GL_LINES);
// POSITION_COLOR format: 3 floats (12 bytes) + 4 bytes color = 16 bytes per vertex
final int vertexSize = format.getVertexSize();
final ByteBuffer buffer = org.lwjgl.BufferUtils.createByteBuffer(vertexSize * lines.size());
for (ImmediateModeRecorder.LineVertex v : lines) {
buffer.putFloat(v.x);
buffer.putFloat(v.y);
buffer.putFloat(v.z);
buffer.putInt(v.color);
}
buffer.flip();
vao.upload(buffer);
return vao;
}
}
@@ -1,12 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
/**
* Result of building an optimized display list.
* Contains both the commands and the shared VBOs that need lifecycle management.
*/
@Desugar
record OptimizedListResult(DisplayListCommand[] commands, VertexBuffer[] ownedVbos) {}
@@ -10,23 +10,14 @@ 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.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;
@@ -39,17 +30,8 @@ import java.nio.IntBuffer;
public class RenderSystem {
private static final Logger LOGGER = LogManager.getLogger("RenderSystem");
private static DSAAccess dsaState;
private static boolean hasMultibind;
private static boolean supportsCompute;
private static boolean supportsImageLoadStore;
private static boolean supportsSSBO;
private static boolean supportsBufferStorage;
private static boolean supportsClearTexture;
private static boolean supportsSamplerObjects;
private static int maxImageUnits;
private static int maxSSBOBindings;
// Sampler object state tracking (null if unsupported)
private static int[] samplers;
private RenderSystem() {}
@@ -59,6 +41,7 @@ public class RenderSystem {
dsaState = new DSACore();
LOGGER.info("OpenGL 4.5 detected, enabling DSA.");
}
hasMultibind = GLStateManager.capabilities.OpenGL45;
} catch (NoSuchFieldError ignored) {}
try {
@@ -72,47 +55,11 @@ public class RenderSystem {
LOGGER.info("No DSA support detected, falling back to legacy OpenGL.");
}
try {
hasMultibind |= GLStateManager.capabilities.GL_ARB_multi_bind;
} catch (NoSuchFieldError ignored) {}
supportsCompute = supportsCompute();
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)];
}
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);
}
public static void generateMipmaps(int texture, int mipmapTarget) {
@@ -124,8 +71,8 @@ public class RenderSystem {
}
public static void texImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
GL11.glBindTexture(target, texture);
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
}
public static void uniformMatrix4fv(int location, boolean transpose, FloatBuffer matrix) {
@@ -133,7 +80,7 @@ public class RenderSystem {
}
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);
GL11.glCopyTexImage2D(target, level, internalFormat, x, y, width, height, border);
}
public static void uniform1f(int location, float v0) {
@@ -156,8 +103,6 @@ public class RenderSystem {
GL20.glUniform3f(location, v0, v1, v2);
}
public static void uniform3i(int location, int v0, int v1, int v2) { GL20.glUniform3i(location, v0, v1, v2); }
public static void uniform4f(int location, float v0, float v1, float v2, float v3) {
GL20.glUniform4f(location, v0, v1, v2, v3);
}
@@ -190,33 +135,6 @@ public class RenderSystem {
dsaState.texParameterf(texture, target, pname, param);
}
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
}
public static void texImage1D(int texture, int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
GL11.glBindTexture(target, texture);
GL11.glTexImage1D(target, level, internalformat, width, border, format, type, pixels);
}
public static void texImage3D(int texture, int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
GL11.glBindTexture(target, texture);
GL12.glTexImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
}
public static String getProgramInfoLog(int program) {
return GL20.glGetProgramInfoLog(program, GL20.glGetProgrami(program, GL20.GL_INFO_LOG_LENGTH));
}
@@ -265,10 +183,6 @@ public class RenderSystem {
return dsaState.getTexParameteri(texture, target, pname);
}
public static int getTexLevelParameteri(int texture, int level, int pname) {
return dsaState.getTexLevelParameteri(texture, level, pname);
}
public static void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format) {
if (GLStateManager.capabilities.OpenGL42) {
GL42.glBindImageTexture(unit, texture, level, layered, layer, access, format);
@@ -278,27 +192,13 @@ public class RenderSystem {
}
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;
if (GLStateManager.capabilities.OpenGL42) {
return GL11.glGetInteger(GL42.GL_MAX_IMAGE_UNITS);
} else if (GLStateManager.capabilities.GL_EXT_shader_image_load_store) {
return GL11.glGetInteger(EXTShaderImageLoadStore.GL_MAX_IMAGE_UNITS_EXT);
} else {
return 0;
}
}
public static void getProgramiv(int program, int value, IntBuffer storage) {
@@ -313,14 +213,6 @@ public class RenderSystem {
GL43.glDispatchCompute(workGroups.x, workGroups.y, workGroups.z);
}
public static void dispatchComputeIndirect(long offset) {
GL43.glDispatchComputeIndirect(offset);
}
public static void bindBuffer(int target, int buffer) {
GL15.glBindBuffer(target, buffer);
}
public static void memoryBarrier(int barriers) {
if (supportsCompute) {
GL42.glMemoryBarrier(barriers);
@@ -347,23 +239,19 @@ public class RenderSystem {
dsaState.bindTextureToUnit(unit, texture);
}
public static void bindTextureToUnit(int target, int unit, int texture) {
dsaState.bindTextureToUnit(target, unit, texture);
}
public static final FloatBuffer PROJECTION_MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
public static void setupProjectionMatrix(Matrix4f matrix) {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPushMatrix();
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glPushMatrix();
matrix.get(0, PROJECTION_MATRIX_BUFFER);
GLStateManager.glLoadMatrix(PROJECTION_MATRIX_BUFFER);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glLoadMatrix(PROJECTION_MATRIX_BUFFER);
GL11.glMatrixMode(GL11.GL_MODELVIEW);
}
public static void restoreProjectionMatrix() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPopMatrix();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GL11.glMatrixMode(GL11.GL_PROJECTION);
GL11.glPopMatrix();
GL11.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) {
@@ -386,100 +274,4 @@ public class RenderSystem {
return false;
}
}
public static int createBuffers() {
return GL15.glGenBuffers();
}
public static void bindBufferBase(int target, int index, int buffer) {
GL30.glBindBufferBase(target, index, buffer);
}
public static void bufferStorage(int target, long size, int flags) {
if (GLStateManager.capabilities.OpenGL44) {
GL44.glBufferStorage(target, size, flags);
} else if (GLStateManager.capabilities.GL_ARB_buffer_storage) {
ARBBufferStorage.glBufferStorage(target, size, flags);
} else {
// Fallback: use mutable storage
GL15.glBufferData(target, size, GL15.GL_DYNAMIC_DRAW);
}
}
public static void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, int[] data) {
if (GLStateManager.capabilities.OpenGL43) {
GL43.glClearBufferSubData(target, internalFormat, offset, size, format, type, (ByteBuffer) null);
} else if (GLStateManager.capabilities.GL_ARB_clear_buffer_object) {
ARBClearBufferObject.glClearBufferSubData(target, internalFormat, offset, size, format, type, (ByteBuffer) null);
}
}
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 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,246 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.line.ModelLine;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.primitive.ModelPrimitiveView;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.tri.ModelTriangle;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedPrimitiveDraw;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* Accumulates tessellator primitives (lines, triangles) for a single vertex format during display list compilation.
* Similar to FormatBuffer but handles ModelPrimitiveView instances instead of quads.
* <p>
* Unlike LineBuffer which handles ImmediateModeRecorder.LineVertex (fixed 16-byte: pos+color),
* this handles ModelLine/ModelTriangle from the tessellator with format-optimized vertex size.
* <p>
* Primitives are separated by draw mode (GL_LINES, GL_TRIANGLES) into separate VBOs.
*/
class TessellatorPrimitiveBuffer {
final CapturingTessellator.Flags flags;
// Accumulated primitives separated by type
final List<ModelLine> allLines = new ArrayList<>();
final List<ModelTriangle> allTriangles = new ArrayList<>();
// Ranges for lines
final List<DrawRange> lineMergedRanges = new ArrayList<>();
final List<DrawRange> linePerDrawRanges = new ArrayList<>();
int currentLineVertexOffset = 0;
private int pendingLineStartVertex = -1;
private int pendingLineVertexCount = 0;
private Matrix4f pendingLineTransform = null;
private int pendingLineCommandIndex = -1;
private int pendingLineStateGen = -1;
// Ranges for triangles
final List<DrawRange> triangleMergedRanges = new ArrayList<>();
final List<DrawRange> trianglePerDrawRanges = new ArrayList<>();
int currentTriangleVertexOffset = 0;
private int pendingTriangleStartVertex = -1;
private int pendingTriangleVertexCount = 0;
private Matrix4f pendingTriangleTransform = null;
private int pendingTriangleCommandIndex = -1;
private int pendingTriangleStateGen = -1;
TessellatorPrimitiveBuffer(CapturingTessellator.Flags flags) {
this.flags = flags;
}
/**
* Add a primitive draw to this buffer.
* Separates lines and triangles, tracking ranges for both merged and per-draw paths.
*/
void addDraw(AccumulatedPrimitiveDraw draw) {
final List<ModelPrimitiveView> primitives = draw.primitives;
final int size = primitives.size();
// Single pass: count and collect primitives by type
int lineCount = 0;
int triangleCount = 0;
for (int i = 0; i < size; i++) {
final ModelPrimitiveView prim = primitives.get(i);
if (prim instanceof ModelLine ml) {
allLines.add(ml);
lineCount++;
} else if (prim instanceof ModelTriangle mt) {
allTriangles.add(mt);
triangleCount++;
}
}
// Process lines
if (lineCount > 0) {
final int lineVertexCount = lineCount * 2; // 2 vertices per line
linePerDrawRanges.add(new DrawRange(currentLineVertexOffset, lineVertexCount, draw.transform, draw.commandIndex));
// Merge logic for optimized path:
// - same transform
// - same stateGeneration = no state commands (draw barriers) between draws
boolean canMerge = pendingLineTransform != null
&& pendingLineTransform.equals(draw.transform)
&& pendingLineStateGen == draw.stateGeneration;
if (canMerge) {
pendingLineVertexCount += lineVertexCount;
} else {
flushPendingLineRange();
pendingLineStartVertex = currentLineVertexOffset;
pendingLineVertexCount = lineVertexCount;
pendingLineTransform = new Matrix4f(draw.transform);
pendingLineCommandIndex = draw.commandIndex;
pendingLineStateGen = draw.stateGeneration;
}
currentLineVertexOffset += lineVertexCount;
}
// Process triangles
if (triangleCount > 0) {
final int triangleVertexCount = triangleCount * 3; // 3 vertices per triangle
trianglePerDrawRanges.add(new DrawRange(currentTriangleVertexOffset, triangleVertexCount, draw.transform, draw.commandIndex));
// Merge logic for optimized path:
// - same transform
// - same stateGeneration = no state commands (draw barriers) between draws
boolean canMerge = pendingTriangleTransform != null
&& pendingTriangleTransform.equals(draw.transform)
&& pendingTriangleStateGen == draw.stateGeneration;
if (canMerge) {
pendingTriangleVertexCount += triangleVertexCount;
} else {
flushPendingTriangleRange();
pendingTriangleStartVertex = currentTriangleVertexOffset;
pendingTriangleVertexCount = triangleVertexCount;
pendingTriangleTransform = new Matrix4f(draw.transform);
pendingTriangleCommandIndex = draw.commandIndex;
pendingTriangleStateGen = draw.stateGeneration;
}
currentTriangleVertexOffset += triangleVertexCount;
}
}
private void flushPendingLineRange() {
if (pendingLineTransform != null) {
lineMergedRanges.add(new DrawRange(pendingLineStartVertex, pendingLineVertexCount, pendingLineTransform, pendingLineCommandIndex));
pendingLineTransform = null;
pendingLineStateGen = -1;
}
}
private void flushPendingTriangleRange() {
if (pendingTriangleTransform != null) {
triangleMergedRanges.add(new DrawRange(pendingTriangleStartVertex, pendingTriangleVertexCount, pendingTriangleTransform, pendingTriangleCommandIndex));
pendingTriangleTransform = null;
pendingTriangleStateGen = -1;
}
}
/**
* Compile all accumulated primitives into VBOs using optimal vertex format.
* @return The compiled buffers with separate VBOs for lines and triangles, or null if empty
*/
CompiledPrimitiveBuffers finish() {
flushPendingLineRange();
flushPendingTriangleRange();
if (allLines.isEmpty() && allTriangles.isEmpty()) {
return null;
}
// Use optimal format based on flags (same logic as quads)
final VertexFormat format = DisplayListManager.selectOptimalFormat(flags);
final VertexBuffer lineVbo = allLines.isEmpty() ? null : compileLinesToVBO(format);
final VertexBuffer triangleVbo = allTriangles.isEmpty() ? null : compileTrianglesToVBO(format);
return new CompiledPrimitiveBuffers(
flags,
lineVbo,
lineMergedRanges.toArray(new DrawRange[0]),
linePerDrawRanges.toArray(new DrawRange[0]),
triangleVbo,
triangleMergedRanges.toArray(new DrawRange[0]),
trianglePerDrawRanges.toArray(new DrawRange[0])
);
}
private VertexBuffer compileLinesToVBO(VertexFormat format) {
final int vertexSize = format.getVertexSize();
final int size = allLines.size();
final ByteBuffer buffer = BufferUtils.createByteBuffer(vertexSize * size * 2);
for (int i = 0; i < size; i++) {
writePrimitiveToBuffer(allLines.get(i), buffer);
}
buffer.flip();
final VertexBuffer vbo = VAOManager.createVAO(format, GL11.GL_LINES);
vbo.upload(buffer);
return vbo;
}
private VertexBuffer compileTrianglesToVBO(VertexFormat format) {
final int vertexSize = format.getVertexSize();
final int size = allTriangles.size();
final ByteBuffer buffer = BufferUtils.createByteBuffer(vertexSize * size * 3);
for (int i = 0; i < size; i++) {
writePrimitiveToBuffer(allTriangles.get(i), buffer);
}
buffer.flip();
final VertexBuffer vbo = VAOManager.createVAO(format, GL11.GL_TRIANGLES);
vbo.upload(buffer);
return vbo;
}
/**
* Write a primitive's vertices to the buffer using the format specified by flags.
* Only writes attributes that are present in the format.
*/
private void writePrimitiveToBuffer(ModelPrimitiveView prim, ByteBuffer buffer) {
final int vertexCount = prim.getVertexCount();
final boolean hasTexture = flags.hasTexture;
final boolean hasBrightness = flags.hasBrightness;
final boolean hasColor = flags.hasColor;
final boolean hasNormals = flags.hasNormals;
for (int i = 0; i < vertexCount; i++) {
// Position (always present) - 12 bytes
buffer.putFloat(prim.getX(i));
buffer.putFloat(prim.getY(i));
buffer.putFloat(prim.getZ(i));
// Color (4 bytes, ABGR) - if present
if (hasColor) {
buffer.putInt(prim.getColor(i));
}
// Texture (2 floats = 8 bytes) - if present
if (hasTexture) {
buffer.putFloat(prim.getTexU(i));
buffer.putFloat(prim.getTexV(i));
}
// Light/Brightness (4 bytes, packed) - if present
if (hasBrightness) {
buffer.putInt(prim.getLight(i));
}
// Normal (4 bytes, packed) - if present
if (hasNormals) {
buffer.putInt(prim.getForgeNormal(i));
}
}
}
}
@@ -1,136 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.MultMatrixCmd;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
/**
* Helper class for tracking and collapsing matrix transforms during optimization.
* Implements the transform accumulation and emission strategy:
* <ul>
* <li>accumulated: total relative transform (what GL matrix should be)</li>
* <li>lastEmitted: what we've emitted (what GL matrix is)</li>
* <li>stack: saved accumulated values at Push points</li>
* </ul>
*/
class TransformOptimizer {
private static final Logger LOGGER = LogManager.getLogger("TransformOptimizer");
private final Matrix4f accumulated = new Matrix4f(); // Current accumulated transform
private final Matrix4f lastEmitted = new Matrix4f(); // Last emitted transform
private final Deque<Matrix4f> stack = new ArrayDeque<>(); // For Push/Pop
private final int listId; // For logging
private boolean absoluteMatrixFlag = false; // True after LoadMatrix, cleared by LoadIdentity emit
TransformOptimizer(int listId) {
this.listId = listId;
accumulated.identity();
lastEmitted.identity();
}
Matrix4f getAccumulated() {
return accumulated;
}
void loadIdentity() {
accumulated.identity();
}
void pushTransform() {
stack.push(new Matrix4f(accumulated));
}
void popTransform() {
if (!stack.isEmpty()) {
final Matrix4f popped = stack.pop();
accumulated.set(popped);
// After Pop, GL state is restored to the pushed value so lastEmitted should also reflect this
lastEmitted.set(accumulated);
} else {
LOGGER.warn("[TransformOptimizer] list={} Pop with empty stack - resetting to identity", listId);
accumulated.identity();
lastEmitted.identity();
}
}
boolean isIdentity() {
return DisplayListManager.isIdentity(accumulated);
}
/**
* Check if there's a pending transform that hasn't been emitted yet.
*/
boolean hasPendingTransform() {
return !accumulated.equals(lastEmitted);
}
/**
* Emit a MultMatrix command if accumulated differs from lastEmitted.
* Updates lastEmitted to match accumulated after emission.
*/
void emitPendingTransform(List<DisplayListCommand> output) {
if (!accumulated.equals(lastEmitted)) {
emitTransformTo(output, accumulated);
}
}
/**
* Emit a MultMatrix command to make GL state match a target transform.
* Used when a draw expects a specific transform that may differ from accumulated.
* Updates lastEmitted to match target after emission.
*
* @param output The command list to emit to
* @param target The target transform to reach
*/
void emitTransformTo(List<DisplayListCommand> output, Matrix4f target) {
if (target.equals(lastEmitted)) {
// Already at target - nothing to emit
return;
}
// Calculate delta: what we need to multiply to go from lastEmitted to target
// delta = inverse(lastEmitted) * target
// But for simplicity, if lastEmitted is identity, just emit target
if (DisplayListManager.isIdentity(lastEmitted)) {
// Simple case: just emit target
output.add(MultMatrixCmd.create(target));
} else {
// Need to emit delta: inv(lastEmitted) * target
final Matrix4f delta = new Matrix4f(lastEmitted).invert().mul(target);
output.add(MultMatrixCmd.create(delta));
}
lastEmitted.set(target);
}
/**
* Check if a draw's expected transform differs from what we've emitted.
*/
boolean needsTransformForDraw(Matrix4f drawTransform) {
return !drawTransform.equals(lastEmitted);
}
/**
* Mark that the GL matrix is at an absolute value (after LoadMatrix).
* Subsequent LoadIdentity commands must be emitted to actually reset GL to identity.
*/
void markAbsoluteMatrix() {
absoluteMatrixFlag = true;
}
/**
* Check if the GL matrix is at an absolute value and clear the flag.
* Used to determine if LoadIdentity needs to emit.
* @return true if LoadMatrix was called and not yet reset by LoadIdentity
*/
boolean checkAndClearAbsoluteMatrix() {
boolean was = absoluteMatrixFlag;
absoluteMatrixFlag = false;
return was;
}
}
@@ -1,27 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
/// Matches
public enum Vendor {
AMD("amd"),
INTEL("intel"),
MESA("mesa"),
NVIDIA("nvidia"),
UNKNOWN("");
final String[] names;
Vendor(String... names) {
this.names = names;
}
public static Vendor getVendor(String vendorString) {
for (var v : values()) {
for (var name : v.names) {
if (vendorString.contains(name)) return v;
}
}
// Shouldn't be needed as "" matches everything, but sanity checks good.
return UNKNOWN;
}
}
@@ -1,11 +0,0 @@
package com.gtnewhorizons.angelica.glsm.compat;
/**
* Simple holder for fog color values captured from vanilla rendering.
* Used by terrain renderers to access fog state.
*/
public class FogHelper {
public static float red;
public static float green;
public static float blue;
}
@@ -1,34 +1,22 @@
package com.gtnewhorizons.angelica.glsm.debug;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.ITextureObject;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
// Adapted from https://github.com/makamys/CoreTweaks/blob/master/src/main/java/makamys/coretweaks/util/OpenGLDebugging.java;
// originally from https://github.com/TheGreyGhost/MinecraftByExample/blob/1-8final/src/main/java/minecraftbyexample/usefultools/OpenGLdebugging.java
@@ -37,13 +25,12 @@ public class OpenGLDebugging {
public static final Logger LOGGER = LogManager.getLogger("gldumper");
private static final Consumer<String> LINE_LOGGER = LOGGER::debug;
private static final Set<String> seenErrors = new HashSet<>();
public static final String GL_IS_ENABLED = "glIsEnabled()";
public static final String GL_GET_BOOLEANV = "glGetBooleanv()";
public static final String GL_GET_INTEGERV = "glGetIntegerv()";
public static final String GL_GET_FLOATV = "glGetFloatv()";
public static class GLproperty {
public class GLproperty {
public GLproperty(int glConstant, String name, String description, String category, String fetchCommand) {
this.gLConstant = glConstant;
@@ -458,250 +445,30 @@ public class OpenGLDebugging {
}
}
public static String getGLSMErrorLog() {
return getGLSMErrorLog(null, false, false);
}
// Re-entrancy guard - getGLSMErrorLog calls glActiveTexture which could trigger checkGLSMOnce
private static boolean inCheck = false;
/**
* Enhanced GLSM error logging with context, stack trace, and first-occurrence filtering.
*
* @param context Optional context string to include in error messages (e.g., "During rendering")
* @param logStacktrace If true, includes the current stack trace in the error output
* @param onlyFirstOccurrence If true, only logs each unique error once
* @return Error log string if mismatches found, null otherwise
*/
public static String getGLSMErrorLog(String context, boolean logStacktrace, boolean onlyFirstOccurrence) {
if (inCheck) return null;
if (!GLStateManager.isCachingEnabled() || GLStateManager.isRecordingDisplayList()) {
return null;
}
inCheck = true;
try {
String group = "checkGLSM" + (context == null ? "" : " " + context);
GLDebug.pushGroup(group);
final StringBuilder error = new StringBuilder();
public static void checkGLSM() {
boolean mismatch = false;
if (context != null && !context.isEmpty()) {
error.append("[").append(context).append("] ");
}
for (int i = 0; i < instance.propertyList.length; ++i) {
final GLproperty gLProperty = instance.propertyList[i];
if (gLProperty.fetchCommand.equals(GL_GET_FLOATV) && gLProperty.name.endsWith("_MATRIX")) {
if(gLProperty.fetchCommand.equals(GL_GET_FLOATV) && gLProperty.name.endsWith("_MATRIX")) {
final Matrix4f cached = gLProperty.getAsMatrix4f(true);
final Matrix4f uncached = gLProperty.getAsMatrix4f(false);
// Skip comparison if either matrix is degenerate (contains NaN/Infinity) these represent invalid GL state from bad parameters, not cache bugs
if (isMatrixDegenerate(cached) || isMatrixDegenerate(uncached)) continue;
if (!matricesEquivalent(cached, uncached, 0.001f)) {
final String errorKey = "MATRIX:" + gLProperty.name;
if (!onlyFirstOccurrence || seenErrors.add(errorKey)) {
final String errorMsg = "GLSM mismatch: " + gLProperty.name + "\ncached:\n" + cached + "uncached:\n" + uncached;
LOGGER.error(errorMsg);
error.append(errorMsg).append('\n');
mismatch = true;
}
// Some precision issues due to RAD vs degrees, and translated (vs f)
if (!cached.equals(uncached, 0.001f)) {
LOGGER.error("GLSM mismatch: " + gLProperty.name + "\ncached:\n" + cached + "uncached:\n" + uncached);
mismatch = true;
}
} else {
final String cached = gLProperty.getAsString(true);
final String uncached = gLProperty.getAsString(false);
if (!cached.equals(uncached)) {
final String errorKey = "PROPERTY:" + gLProperty.name + ":" + cached + ":" + uncached;
if (!onlyFirstOccurrence || seenErrors.add(errorKey)) {
error.append("GLSM mismatch: ").append(gLProperty.name).append(" cached: ").append(cached).append(" uncached: ").append(uncached);
error.append('\n');
mismatch = true;
}
}
}
}
final TextureUnitArray textureUnits = GLStateManager.getTextures();
final int currentActiveTexture = GLStateManager.getActiveTextureUnit();
// Check GL_ACTIVE_TEXTURE
final int glActiveUnit = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE) - GL13.GL_TEXTURE0;
if (glActiveUnit != currentActiveTexture) {
final String errorKey = "ACTIVE_TEXTURE:" + currentActiveTexture + ":" + glActiveUnit;
if (!onlyFirstOccurrence || seenErrors.add(errorKey)) {
error.append("GLSM mismatch: GL_ACTIVE_TEXTURE cached: ").append(currentActiveTexture)
.append(" uncached: ").append(glActiveUnit).append('\n');
mismatch = true;
}
}
// Check per-unit texture state
for (int i = 0; i < textureUnits.textureMatricies.length; i++) {
GL13.glActiveTexture(GL13.GL_TEXTURE0 + i);
// Check GL_TEXTURE_2D enabled
final boolean cachedEnabled = textureUnits.getTextureUnitStates(i).isEnabled();
final boolean uncachedEnabled = GL11.glIsEnabled(GL11.GL_TEXTURE_2D);
if (cachedEnabled != uncachedEnabled) {
final String errorKey = "TEXTURE_2D:" + i + ":" + cachedEnabled + ":" + uncachedEnabled;
if (!onlyFirstOccurrence || seenErrors.add(errorKey)) {
error.append("GLSM mismatch: GL_TEXTURE_2D cached: ").append(cachedEnabled)
.append(" uncached: ").append(uncachedEnabled)
.append(", texture unit: GL_TEXTURE").append(i).append('\n');
mismatch = true;
}
}
// Check GL_TEXTURE_BINDING_2D
final int cachedBinding = textureUnits.getTextureUnitBindings(i).getBinding();
final int uncachedBinding = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
if (cachedBinding != uncachedBinding) {
final String errorKey = "TEXTURE_BINDING_2D:" + i + ":" + cachedBinding + ":" + uncachedBinding;
if (!onlyFirstOccurrence || seenErrors.add(errorKey)) {
error.append("GLSM mismatch: GL_TEXTURE_BINDING_2D cached: ").append(cachedBinding)
.append(" uncached: ").append(uncachedBinding)
.append(", texture unit: GL_TEXTURE").append(i).append('\n');
LOGGER.error("GLSM mismatch: " + gLProperty.name + " cached: " + cached + " uncached: " + uncached);
mismatch = true;
}
}
}
GL13.glActiveTexture(GL13.GL_TEXTURE0 + currentActiveTexture);
// Add stack trace if requested
if (mismatch && logStacktrace) {
error.append("\nStack trace:\n");
final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
for (int i = 2; i < stackTrace.length; i++) {
error.append(" at ").append(stackTrace[i].toString()).append('\n');
}
}
GLDebug.popGroup();
return mismatch ? error.toString() : null;
} finally {
inCheck = false;
}
}
public static boolean checkGLSMOnce(String context) {
final String output = getGLSMErrorLog(context, true, true);
if (output != null) {
// Only log on actual mismatch
LOGGER.error("[{}] MISMATCH thread={}\n{}", context, Thread.currentThread().getName(), output);
return false;
}
return true;
}
/**
* Check if a matrix contains any non-finite values (NaN or Infinity).
* Such matrices represent invalid/degenerate GL state (e.g., zero near/far in projection).
*/
private static boolean isMatrixDegenerate(Matrix4f matrix) {
for (int col = 0; col < 4; col++) {
for (int row = 0; row < 4; row++) {
if (!Float.isFinite(matrix.get(col, row))) {
return true;
}
}
}
return false;
}
/**
* Compare two matrices with tolerance for NaN/Infinity.
* Treats NaN == NaN and Infinity == Infinity as equal (both are degenerate states).
* For finite values, uses epsilon comparison.
*/
private static boolean matricesEquivalent(Matrix4f a, Matrix4f b, float epsilon) {
// Compare all 16 elements
for (int col = 0; col < 4; col++) {
for (int row = 0; row < 4; row++) {
final float aVal = a.get(col, row);
final float bVal = b.get(col, row);
// If both are non-finite (NaN or Infinity), consider them equal
if (!Float.isFinite(aVal) && !Float.isFinite(bVal)) {
// Both non-finite: check if both NaN or both same Infinity
if (Float.isNaN(aVal) && Float.isNaN(bVal)) continue;
if (Float.isInfinite(aVal) && Float.isInfinite(bVal)) {
// Check same sign of infinity
if ((aVal > 0) == (bVal > 0)) continue;
}
return false; // Different non-finite values
}
// If one is finite and other is not, they differ
if (Float.isFinite(aVal) != Float.isFinite(bVal)) {
return false;
}
// Both finite: use epsilon comparison
if (Math.abs(aVal - bVal) > epsilon) {
return false;
}
}
}
return true;
}
public static void checkGLSM() {
final String output = getGLSMErrorLog();
if (output == null) {
if (!mismatch) {
LOGGER.info("GLSM state matches!");
} else {
LOGGER.error(output);
}
}
public static void copyTexture(ResourceLocation resource, File output) {
final Object object = Minecraft.getMinecraft().renderEngine.mapTextureObjects.get(resource);
if (object != null) {
copyTexture(((ITextureObject) object).getGlTextureId(), output);
}
}
public static void copyTexture(int textureID, File output) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, textureID);
int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH);
int height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT);
BufferedImage bufferedimage = new BufferedImage(width, height, 1);
final int[] pixelValues = ((DataBufferInt) bufferedimage.getRaster().getDataBuffer()).getData();
IntBuffer pixelBuffer = BufferUtils.createIntBuffer(pixelValues.length);
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer);
pixelBuffer.get(pixelValues);
// Flip texture
final int halfHeight = height / 2;
final int[] rowBuffer = new int[width];
for (int y = 0; y < halfHeight; y++) {
final int top = y * width;
final int bottom = (height - 1 - y) * width;
System.arraycopy(pixelValues, top, rowBuffer, 0, width);
System.arraycopy(pixelValues, bottom, pixelValues, top, width);
System.arraycopy(rowBuffer, 0, pixelValues, bottom, width);
}
File dir = output.getParentFile();
dir.mkdirs();
copyBufferedImageToFile(bufferedimage, output);
bufferedimage.flush();
}
private static void copyBufferedImageToFile(BufferedImage image, File file) {
try {
ImageIO.write(image, "png", file);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -4,7 +4,6 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.ARBDirectStateAccess;
import org.lwjgl.opengl.GL45;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
@@ -15,29 +14,6 @@ public class DSAARB extends DSAUnsupported {
ARBDirectStateAccess.glGenerateTextureMipmap(texture);
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
// Note: DSA glTextureImage2D doesn't exist in ARB_direct_state_access
// We need to use glTextureStorage2D + glTextureSubImage2D, or fall back to bind-based approach
// For simplicity, use the fallback which binds, uploads, and restores
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
}
@Override
public void texParameteri(int texture, int target, int pname, int param) {
if(!GLStateManager.updateTexParameteriCache(target, texture, pname, param)) return;
@@ -72,11 +48,6 @@ public class DSAARB extends DSAUnsupported {
return GLStateManager.getTexParameterOrDefault(texture, pname, () -> ARBDirectStateAccess.glGetTextureParameteri(texture, pname));
}
@Override
public int getTexLevelParameteri(int texture, int level, int pname) {
return ARBDirectStateAccess.glGetTextureLevelParameteri(texture, level, pname);
}
@Override
public void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
ARBDirectStateAccess.glCopyTextureSubImage2D(destTexture, i, i1, i2, i3, i4, width, height);
@@ -84,7 +55,7 @@ public class DSAARB extends DSAUnsupported {
@Override
public void bindTextureToUnit(int unit, int texture) {
if(GLStateManager.getBoundTextureForServerState(unit) == texture) return;
if(GLStateManager.getBoundTexture(unit) == texture) return;
if (texture == 0) {
super.bindTextureToUnit(unit, texture);
@@ -94,11 +65,6 @@ public class DSAARB extends DSAUnsupported {
}
}
@Override
public void bindTextureToUnit(int target, int unit, int texture) {
bindTextureToUnit(unit, texture);
}
@Override
public int bufferStorage(int target, FloatBuffer data, int usage) {
final int buffer = GL45.glCreateBuffers();
@@ -126,19 +92,4 @@ public class DSAARB extends DSAUnsupported {
public int createTexture(int target) {
return ARBDirectStateAccess.glCreateTextures(target);
}
@Override
public void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
ARBDirectStateAccess.glTextureStorage1D(texture, levels, internalFormat, width);
}
@Override
public void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
ARBDirectStateAccess.glTextureStorage2D(texture, levels, internalFormat, width, height);
}
@Override
public void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
ARBDirectStateAccess.glTextureStorage3D(texture, levels, internalFormat, width, height, depth);
}
}
@@ -1,18 +1,11 @@
package com.gtnewhorizons.angelica.glsm.dsa;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
public interface DSAAccess {
void generateMipmaps(int texture, int target);
// Texture image upload - DSA allows specifying texture directly without binding
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels);
void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels);
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels);
void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels);
void texParameteri(int texture, int target, int pname, int param);
void texParameterf(int texture, int target, int pname, float param);
void texParameteriv(int texture, int target, int pname, IntBuffer params);
@@ -23,12 +16,9 @@ public interface DSAAccess {
int getTexParameteri(int texture, int target, int pname);
int getTexLevelParameteri(int texture, int level, int pname);
void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height);
void bindTextureToUnit(int unit, int texture);
void bindTextureToUnit(int target, int unit, int texture);
int bufferStorage(int target, FloatBuffer data, int usage);
@@ -38,9 +28,4 @@ public interface DSAAccess {
int createFramebuffer();
int createTexture(int target);
// Texture storage methods for custom images
void textureStorage1D(int texture, int target, int levels, int internalFormat, int width);
void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height);
void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth);
}
@@ -2,16 +2,13 @@ package com.gtnewhorizons.angelica.glsm.dsa;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import net.minecraft.client.renderer.OpenGlHelper;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL42;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
@@ -23,39 +20,6 @@ public class DSAUnsupported implements DSAAccess {
GL30.glGenerateMipmap(target);
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
// Get what cache thinks is bound - we'll restore to this to keep cache/GL in sync
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
GL11.glBindTexture(target, texture);
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
GL11.glBindTexture(target, cachedBinding); // Restore to cache value
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
GL11.glBindTexture(target, texture);
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
GL11.glBindTexture(target, cachedBinding);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
GL11.glBindTexture(target, texture);
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
GL11.glBindTexture(target, cachedBinding);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
final int cachedBinding = GLStateManager.getBoundTextureForServerState();
GL11.glBindTexture(target, texture);
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
GL11.glBindTexture(target, cachedBinding);
}
@Override
public void texParameteri(int texture, int target, int pname, int param) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
@@ -76,13 +40,13 @@ public class DSAUnsupported implements DSAAccess {
@Override
public void readBuffer(int framebuffer, int buffer) {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
OpenGlHelper.func_153171_g/*glBindFramebuffer*/(GL30.GL_FRAMEBUFFER, framebuffer);
GL11.glReadBuffer(buffer);
}
@Override
public void drawBuffers(int framebuffer, IntBuffer buffers) {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
OpenGlHelper.func_153171_g/*glBindFramebuffer*/(GL30.GL_FRAMEBUFFER, framebuffer);
GL20.glDrawBuffers(buffers);
}
@@ -92,18 +56,9 @@ public class DSAUnsupported implements DSAAccess {
return GLStateManager.glGetTexParameteri(target, pname);
}
@Override
public int getTexLevelParameteri(int texture, int level, int pname) {
final int previous = GLStateManager.getBoundTextureForServerState();
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);
final int result = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, pname);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previous);
return result;
}
@Override
public void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
final int previous = GLStateManager.getBoundTextureForServerState();
final int previous = GLStateManager.getBoundTexture();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, destTexture);
GL11.glCopyTexSubImage2D(target, i, i1, i2, i3, i4, width, height);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, previous);
@@ -115,12 +70,6 @@ public class DSAUnsupported implements DSAAccess {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
}
@Override
public void bindTextureToUnit(int target, int unit, int texture) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
GLStateManager.glBindTexture(target, texture);
}
@Override
public int bufferStorage(int target, FloatBuffer data, int usage) {
final int buffer = GL15.glGenBuffers();
@@ -134,21 +83,21 @@ public class DSAUnsupported implements DSAAccess {
@Override
public void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2,
int bufferChoice, int filter) {
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, source);
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, dest);
OpenGlHelper.func_153171_g/*glBindFramebuffer*/(GL30.GL_READ_FRAMEBUFFER, source);
OpenGlHelper.func_153171_g/*glBindFramebuffer*/(GL30.GL_DRAW_FRAMEBUFFER, dest);
GL30.glBlitFramebuffer(offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
}
@Override
public void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
GLStateManager.glBindFramebuffer(fbtarget, fb);
OpenGlHelper.func_153171_g/*glBindFramebuffer*/(fbtarget, fb);
GL30.glFramebufferTexture2D(fbtarget, attachment, target, texture, levels);
}
@Override
public int createFramebuffer() {
final int framebuffer = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
final int framebuffer = OpenGlHelper.func_153165_e/*glGenFramebuffers*/();
OpenGlHelper.func_153171_g/*glBindFramebuffer*/(GL30.GL_FRAMEBUFFER, framebuffer);
return framebuffer;
}
@@ -158,28 +107,4 @@ public class DSAUnsupported implements DSAAccess {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
return texture;
}
@Override
public void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
final int previous = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_1D);
GL11.glBindTexture(target, texture);
GL42.glTexStorage1D(target, levels, internalFormat, width);
GL11.glBindTexture(target, previous);
}
@Override
public void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
final int previous = (target == GL11.GL_TEXTURE_2D) ? GLStateManager.getBoundTextureForServerState() : GL11.glGetInteger(GL31.GL_TEXTURE_BINDING_RECTANGLE);
GL11.glBindTexture(target, texture);
GL42.glTexStorage2D(target, levels, internalFormat, width, height);
GL11.glBindTexture(target, previous);
}
@Override
public void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
final int previous = GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D);
GL11.glBindTexture(target, texture);
GL42.glTexStorage3D(target, levels, internalFormat, width, height, depth);
GL11.glBindTexture(target, previous);
}
}
@@ -1,66 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadViewMutable;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil;
import org.joml.Matrix4f;
import java.util.List;
/**
* Represents an accumulated draw call during display list compilation.
* Matrix-as-Data Architecture: Stores untransformed vertices + captured matrix.
* The matrix will be applied at runtime, not baked into vertices.
*/
public class AccumulatedDraw {
public final List<ModelQuadViewMutable> quads;
public final Matrix4f transform;
public final CapturingTessellator.Flags flags;
public final int commandIndex; // Position in command list for state tracking
public final int matrixGeneration; // For batching: same generation = same transform context
public final int stateGeneration; // Same value = no state commands between draws, can merge
/**
* Last vertex restoration data for immediate mode draws.
* Null for tessellator draws (no restoration needed).
*/
public final RestoreData restoreData;
/**
* Holds last vertex attribute values for GL state restoration after VBO draw.
* Used to sync GLSM cache with actual GL state after VBO rendering.
*/
@Desugar
public record RestoreData(
float lastColorR, float lastColorG, float lastColorB, float lastColorA,
float lastNormalX, float lastNormalY, float lastNormalZ,
float lastTexCoordS, float lastTexCoordT) {}
public AccumulatedDraw(List<ModelQuadViewMutable> quads, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex, int matrixGeneration) {
this(quads, transform, flags, commandIndex, matrixGeneration, 0, null);
}
public AccumulatedDraw(List<ModelQuadViewMutable> quads, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex, int matrixGeneration, int stateGeneration) {
this(quads, transform, flags, commandIndex, matrixGeneration, stateGeneration, null);
}
public AccumulatedDraw(List<ModelQuadViewMutable> quads, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex, int matrixGeneration, int stateGeneration, RestoreData restoreData) {
// Deep copy quads - pooled quads are reused after callback
this.quads = ModelQuadUtil.deepCopyQuads(quads);
this.transform = new Matrix4f(transform); // Snapshot for runtime application
this.flags = flags;
this.commandIndex = commandIndex;
this.matrixGeneration = matrixGeneration;
this.stateGeneration = stateGeneration;
this.restoreData = restoreData;
}
@Override
public String toString() {
return String.format("AccumulatedDraw[quads=%d, cmdIdx=%d, matrixGen=%d, stateGen=%d, flags=C%dT%dL%dN%d, restore=%s]",
quads.size(), commandIndex, matrixGeneration, stateGeneration, flags.hasColor ? 1 : 0, flags.hasTexture ? 1 : 0, flags.hasBrightness ? 1 : 0, flags.hasNormals ? 1 : 0,
restoreData != null ? "yes" : "no");
}
}
Loaded 100 of 917 files, more files were not shown because too many files have changed in this diff. Show more