Compare commits

..
6 Commits
1027 changed files with 26518 additions and 54138 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
+13 -29
View File
@@ -7,49 +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
## Core Profile
Targets GL 3.3+ core profile
* FFP emulation via generated GLSL shaders
* Display list emulation
* Software matrix stacks and state management through GLSM
* GL_QUADS → GL_TRIANGLES conversion
* Automatic transformation of mod shaders that use compat-only GLSL builtins (`gl_ModelViewMatrix`, `texture2D`, etc.)
## **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
@@ -64,16 +55,16 @@ Targets GL 3.3+ core profile
* The [Iris](https://github.com/IrisShaders/Iris) Team: coderbot, IMS212, Justsnoopy30, FoundationGames
* Asek3 for the Iris Forge port [Oculus](https://github.com/Asek3/Oculus/tree/839ce8eca8cf0c4f6b7a1322b906e5c99125ca69)
* Backported code under the LGPL
* Sodium / Celeritas
* Sodium
* JellySquid & the CaffeineMC team, for making Sodium in the first place
* Embeddedt for Celeritas used under the LGPL 3.0
* Embeddedt for [Embeddium](https://github.com/embeddedt/embeddium/tree/dc59ca357c25beefd6288f0d1d40b4cd8e670ab8) under the [LGPL 3.0](https://github.com/embeddedt/embeddium/blob/16.x/forge/LICENSE)
* Reese's Sodium Options
* FlashyReese for [Reese's Sodium Options](https://github.com/FlashyReese/reeses-sodium-options/tree/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b) under the [MIT license](https://github.com/FlashyReese/reeses-sodium-options/blob/aa4ea67047cf53e54976e93c6bb1ab5ba16b142b/LICENSE.txt)
* Neodymium
* Makamys for [Neodymium](https://github.com/makamys/Neodymium) - Various Tessellator -> Quad functionality taken under the LGPL 3
* MCPatcherForge
* Original [MCPatcher](https://bitbucket.org/prupe/mcpatcher/src/master/): Paul Rupe
* [Forge version](https://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
@@ -91,10 +82,3 @@ Targets GL 3.3+ core profile
* Code from both glsl-transformation-lib and Monocle taken under their respective LGPL 3.0 licenses
* FalseTweaks
* FalsePattern for [FalseTweaks](https://github.com/falsepattern/falsetweaks) - Item Rendering Optimizations taken from FalseTweaks under the LGPL 3.0 license
* Beddium
* Venn and Falsepattern for [Beddium](https://github.com/vfx-dev/Beddium) - MinimapRenderer, SafeChunkTracker, and some debug commands adapted under the LGPL 3.0 License
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) — Code ported and/or inspired from Mesa - under the MIT licensed
* Display list ID allocator ported from [util_idalloc](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/util/u_idalloc.c) (Copyright 2017 Valve Corporation)
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
* Fog parameter optimization inspired by st_nir_lower_fog.c (Copyright 2023 Google LLC) and prog_statevars.c (Copyright 1999-2007 Brian Paul)
+2 -37
View File
@@ -1,16 +1,9 @@
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
plugins {
id("com.gtnewhorizons.gtnhconvention")
}
minecraft {
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true")
extraRunJvmArguments.add("-Dangelica.dumpClass=true")
// extraRunJvmArguments.add("-Dorg.lwjgl.util.Debug=true")
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
//extraRunJvmArguments.add("-Dangelica.redirectorLogspam=true")
}
tasks.test {
@@ -18,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())
}
}
@@ -45,24 +31,3 @@ tasks.register<Copy>("copyDependencies") {
from(configurations.default)
into("testDependencies")
}
val embedOnly: Configuration by configurations
// Downgrade embedOnly jars for the tests
val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) {
inputCollection = embedOnly
}
tasks.test {
dependsOn(downgradeEmbedOnlyForTest)
classpath = classpath
.minus(embedOnly)
.plus(files(downgradeEmbedOnlyForTest.map { it.outputCollection }))
}
tasks.shadowJar {
from(embedOnly.map(::zipTree))
relocate("com.mitchej123", "com.mitchej123")
relocate("org.embeddedt", "org.embeddedt")
}
+32 -54
View File
@@ -46,60 +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)
testRuntimeClasspath.extendsFrom(embedOnly)
}
// Exclude self-reference from all configurations - prevents transitive Angelica deps from resolving against this project
configurations.all {
exclude group: "com.github.GTNewHorizons", module: "Angelica"
}
dependencies {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.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.36: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-26.g5ee7d96-GTNH") {
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 }
@@ -109,36 +94,29 @@ dependencies {
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev")
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5593_H261"))
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:campfirebackport-387444:4611675"))
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
compileOnly(rfg.deobf("curse.maven:et-futurum-requiem-441392:5873837"))
// For testing alternative splash screens — https://github.com/MalTeeez/ModernSplash/releases
// devOnlyNonPublishable("com.github.MalTeeez:modernsplash:1.7.10-1.2.4:dev")
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")
// Better Crashes
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.1-GTNH:dev")
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/cd495626d2ee49a75447e3fdc6afb287/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e1118b4c7c2858d28154d086dbff010e/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/59a9771cad43219260d9aac9a8ec4d6a/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5ee947fbfb70bc347d8d531e3a578c4/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/a4c09dd2e2d7079373d30e524bbc2829/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/8e1d9ee5d0f13e442218f6884a306da1/redirect
toolchainVersion=25
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+9 -5
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.
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -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 -18
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 {
@@ -22,18 +32,5 @@ repositories {
includeGroup 'org.taumc'
}
}
ivy {
name = 'modernsplash-github'
url = 'https://github.com/MalTeeez/ModernSplash/releases/download/'
patternLayout {
artifact '[revision]/[module]-[revision](-[classifier]).[ext]'
}
content {
includeModule 'com.github.MalTeeez', 'modernsplash'
}
metadataSources {
artifact()
}
}
mavenLocal()
}
+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.23")
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);
}
}
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica;
import com.gtnewhorizons.angelica.common.BlockError;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.config.ConfigMigrator;
import com.gtnewhorizons.angelica.proxy.CommonProxy;
import com.gtnewhorizons.angelica.utils.AnimationMode;
import com.gtnewhorizons.angelica.utils.ManagedEnum;
@@ -11,39 +10,17 @@ import cpw.mods.fml.common.SidedProxy;
import cpw.mods.fml.common.event.FMLInitializationEvent;
import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
@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");
private static SodiumGameOptions CONFIG;
public static SodiumGameOptions options() {
if (CONFIG == null) {
CONFIG = SodiumGameOptions.load(ConfigMigrator.handleConfigMigration("angelica-options.json"));
}
return CONFIG;
}
@SidedProxy(clientSide = "com.gtnewhorizons.angelica.proxy.ClientProxy", serverSide = "com.gtnewhorizons.angelica.proxy.CommonProxy")
public static CommonProxy proxy;
@@ -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,35 +1,25 @@
package com.gtnewhorizons.angelica.client.font;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IndexBuffer;
import com.gtnewhorizon.gtnhlib.util.font.GlyphReplacements;
import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.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.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.IntBuffer;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
/**
* A batching replacement for {@code FontRenderer}
*
@@ -39,190 +29,131 @@ 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;
protected int[] charWidth = new int[256];
/** Array of the start/end column (in upper/lower nibble) for every glyph in the /font directory. */
protected byte[] glyphWidth;
/**
* Array of RGB triplets defining the 16 standard chat colors followed by 16 darker version of the same colors for
* drop shadows.
*/
private final int[] colorCode;
private int[] colorCode;
/** Location of the primary font atlas to bind. */
protected final ResourceLocation locationFontTexture;
/** 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 mvpMatrixLocation;
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, 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;
//noinspection deprecation
fontShaderId = FontAAShader.getProgram().getProgramId();
AAMode = GL20.glGetUniformLocation(fontShaderId, "aaMode");
AAStrength = GL20.glGetUniformLocation(fontShaderId, "strength");
mvpMatrixLocation = GL20.glGetUniformLocation(fontShaderId, "u_MVPMatrix");
if (ebo == null) {
ebo = new IndexBuffer();
vbo = GL15.glGenBuffers();
allocateBuffers();
}
}
// === Batched rendering
private static final int INITIAL_BATCH_SIZE = 2048;
private static final ResourceLocation DUMMY_RESOURCE_LOCATION = new ResourceLocation("angelica$dummy",
"this is invalid!");
// Layout in data:
// [v, v, t, t, c, c, c, c, tb, tb, tb, tb]
// v, t and tb are floats, c is bytes; 36 bytes total
private static final int VERTEX_SIZE = 36;
private static int rawCapacity = INITIAL_BATCH_SIZE * VERTEX_SIZE;
private static long vertexDataAddress = nmemAllocChecked(rawCapacity);
// OpenGL objects (static, can be used between multiple BatchingFontRenderer)
private static int fontVAO = 0;
private static int vbo;
private static IndexBuffer ebo;
private int batchDepth = 0;
private int vertexDataPos = 0;
private int vtxWriterIndex = 0;
private int idxWriterIndex = 0;
private static final int INITIAL_BATCH_SIZE = 256;
private static final ResourceLocation DUMMY_RESOURCE_LOCATION = new ResourceLocation("angelica$dummy",
"this is invalid!");
private FloatBuffer batchVtxPositions = BufferUtils.createFloatBuffer(INITIAL_BATCH_SIZE * 2);
private ByteBuffer batchVtxColors = BufferUtils.createByteBuffer(INITIAL_BATCH_SIZE * 4);
private FloatBuffer batchVtxTexCoords = BufferUtils.createFloatBuffer(INITIAL_BATCH_SIZE * 2);
private IntBuffer batchIndices = BufferUtils.createIntBuffer(INITIAL_BATCH_SIZE / 2 * 3);
private final ObjectArrayList<FontDrawCmd> batchCommands = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
private final ObjectArrayList<FontDrawCmd> batchCommandPool = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
private int blendSrcRGB = GL11.GL_SRC_ALPHA;
private int blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
private void allocateBuffers() {
populateEBO(rawCapacity / VERTEX_SIZE);
}
private void populateEBO(int capacity) {
final int quadCount = capacity * 6;
final ByteBuffer data = memAlloc(quadCount * 6 * 2);
long ptr = memAddress0(data);
for (int i = 0; i < quadCount; i++) {
int base = (i * 4);
// triangle 1
memPutShort(ptr, (short) base);
memPutShort(ptr + 2, (short) (base + 1));
memPutShort(ptr + 4, (short) (base + 2));
// triangle 2
memPutShort(ptr + 6, (short) (base + 2));
memPutShort(ptr + 8, (short) (base + 1));
memPutShort(ptr + 10, (short) (base + 3));
ptr += 12;
/** */
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 = 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 = CompatMemoryUtil.memReallocDirect(batchIndices, newIdxCap);
}
ebo.upload(data);
memFree(data);
}
private void ensureCapacity() {
if (vertexDataPos + (4 * VERTEX_SIZE) > rawCapacity) {
rawCapacity *= 2;
vertexDataAddress = nmemReallocChecked(vertexDataAddress, rawCapacity);
allocateBuffers();
}
}
private void pushVtx(float x, float y, int rgba, float u, float v, float uMin, float uMax, float vMin, float vMax) {
final long ptr = vertexDataAddress + vertexDataPos;
// v, v
memPutFloat(ptr, x);
memPutFloat(ptr + 4, y);
// t, t
memPutFloat(ptr + 8, u);
memPutFloat(ptr + 12, v);
// c, c, c, c
final int idx = vtxWriterIndex;
final int idx2 = idx * 2;
final int idx4 = idx * 4;
batchVtxPositions.put(idx2, x);
batchVtxPositions.put(idx2 + 1, y);
// 0xAARRGGBB
memPutByte(ptr + 16, (byte) ((rgba >> 16) & 0xFF));
memPutByte(ptr + 17, (byte) ((rgba >> 8) & 0xFF));
memPutByte(ptr + 18, (byte) (rgba & 0xFF));
memPutByte(ptr + 19, (byte) ((rgba >> 24) & 0xFF));
// tb, tb, tb, tb
memPutFloat(ptr + 20, uMin);
memPutFloat(ptr + 24, uMax);
memPutFloat(ptr + 28, vMin);
memPutFloat(ptr + 32, vMax);
vertexDataPos += VERTEX_SIZE;
batchVtxColors.put(idx4, (byte) ((rgba >> 16) & 0xFF));
batchVtxColors.put(idx4 + 1, (byte) ((rgba >> 8) & 0xFF));
batchVtxColors.put(idx4 + 2, (byte) (rgba & 0xFF));
batchVtxColors.put(idx4 + 3, (byte) ((rgba >> 24) & 0xFF));
batchVtxTexCoords.put(idx2, u);
batchVtxTexCoords.put(idx2 + 1, v);
vtxWriterIndex++;
}
private void pushUntexRect(float x, float y, float w, float h, int rgba) {
ensureCapacity();
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();
final int vtxId = vtxWriterIndex;
pushVtx(x, y, rgba, 0, 0);
pushVtx(x, y + h, rgba, 0, 0);
pushVtx(x + w, y, rgba, 0, 0);
pushVtx(x + w, y + h, rgba, 0, 0);
pushQuadIdx(vtxId);
}
private void pushTexRect(float x, float y, float w, float h, float itOff, int rgba, float uStart, float vStart, float uSz, float vSz) {
ensureCapacity();
pushVtx(x + itOff, y, rgba, uStart, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x - itOff, y + h, rgba, uStart, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x + itOff + w, y, rgba, uStart + uSz, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x - itOff + w, y + h, rgba, uStart + uSz, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
pushQuadIdx();
}
private void pushQuadIdx() {
private int pushQuadIdx(int startV) {
final int idx = idxWriterIndex;
batchIndices.put(idx, startV);
batchIndices.put(idx + 1, startV + 1);
batchIndices.put(idx + 2, startV + 2);
//
batchIndices.put(idx + 3, startV + 2);
batchIndices.put(idx + 4, startV + 1);
batchIndices.put(idx + 5, startV + 3);
idxWriterIndex += 6;
return idx;
}
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture, boolean isUnicode) {
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;
@@ -238,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);
}
@@ -246,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
@@ -294,6 +223,9 @@ public class BatchingFontRenderer {
* allow for easier optimizing of blocks of font rendering code.
*/
public void beginBatch() {
if (batchDepth == Integer.MAX_VALUE) {
throw new StackOverflowError("More than Integer.MAX_VALUE nested font rendering batch operations");
}
batchDepth++;
}
@@ -309,23 +241,7 @@ public class BatchingFontRenderer {
}
}
private static final Matrix4f scratchMvp = new Matrix4f();
private int fontAAModeLast = -1;
private int fontAAStrengthLast = -1;
private void flushBatch() {
if (vertexDataPos == 0) {
clearBatch();
return;
}
// Upload first (to reduce stalls)
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
streamUpload(vertexDataAddress, vertexDataPos);
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
// Sort&Draw
batchCommands.sort(FontDrawCmd.DRAW_ORDER_COMPARATOR);
@@ -337,51 +253,16 @@ 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);
GLStateManager.glUseProgram(fontShaderId);
if (FontConfig.fontAAMode != fontAAModeLast) {
fontAAModeLast = FontConfig.fontAAMode;
GL20.glUniform1i(AAMode, FontConfig.fontAAMode);
}
if (FontConfig.fontAAStrength != fontAAStrengthLast) {
fontAAStrengthLast = FontConfig.fontAAStrength;
GL20.glUniform1f(AAStrength, FontConfig.fontAAStrength / 120.f);
}
try (MemoryStack stack = stackPush()) {
final FloatBuffer mvpBuf = stack.mallocFloat(16);
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), scratchMvp);
scratchMvp.get(mvpBuf);
GL20.glUniformMatrix4(mvpMatrixLocation, false, mvpBuf);
}
if (fontVAO == 0) {
fontVAO = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(fontVAO);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
ebo.bind();
// position
GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, VERTEX_SIZE, 0);
GL20.glEnableVertexAttribArray(0);
// texcoords
GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, VERTEX_SIZE, 8);
GL20.glEnableVertexAttribArray(1);
// color
GL20.glVertexAttribPointer(2, 4, GL11.GL_UNSIGNED_BYTE, true, VERTEX_SIZE, 16);
GL20.glEnableVertexAttribArray(2);
// tex bounds
GL20.glVertexAttribPointer(3, 4, GL11.GL_FLOAT, false, VERTEX_SIZE, 20);
GL20.glEnableVertexAttribArray(3);
}
GLStateManager.glBindVertexArray(fontVAO);
GL11.glTexCoordPointer(2, 0, batchVtxTexCoords);
GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GL11.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 0, batchVtxColors);
GL11.glEnableClientState(GL11.GL_COLOR_ARRAY);
GL11.glVertexPointer(2, 0, batchVtxPositions);
GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
// Use plain for loop to avoid allocations
final FontDrawCmd[] cmdsData = batchCommands.elements();
@@ -400,14 +281,14 @@ public class BatchingFontRenderer {
}
lastTexture = cmd.texture;
}
GL11.glDrawElements(GL11.GL_TRIANGLES, cmd.idxCount, GL11.GL_UNSIGNED_SHORT, (long) cmd.startVtx * 2L);
batchIndices.limit(cmd.startVtx + cmd.idxCount);
batchIndices.position(cmd.startVtx);
GL11.glDrawElements(GL11.GL_TRIANGLES, batchIndices);
}
GLStateManager.glUseProgram(prevProgram);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GL11.glDisableClientState(GL11.GL_COLOR_ARRAY);
GL11.glDisableClientState(GL11.GL_VERTEX_ARRAY);
if (isTextureEnabledBefore) {
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
@@ -416,21 +297,13 @@ public class BatchingFontRenderer {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, boundTextureBefore);
}
clearBatch();
}
private void clearBatch() {
// Clear for the next batch
batchCommandPool.addAll(batchCommands);
batchCommands.clear();
vertexDataPos = 0;
vtxWriterIndex = 0;
idxWriterIndex = 0;
}
private static void streamUpload(long address, int bytes) {
final ByteBuffer data = memByteBuffer(address, bytes);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, bytes, GL15.GL_STREAM_DRAW);
GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, 0, data);
batchIndices.limit(batchIndices.capacity());
batchIndices.position(0);
}
// === Actual text mesh generation
@@ -439,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; // §
@@ -473,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 {
@@ -495,15 +354,10 @@ public class BatchingFontRenderer {
boolean curStrikethrough = false;
boolean curUnderline = false;
float glyphScaleY = getGlyphScaleY();
float glyphScaleX = getGlyphScaleX();
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;
@@ -515,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) {
@@ -525,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;
}
@@ -572,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) * glyphScaleX;
final float glyphW = fontProvider.getGlyphW(chr) * glyphScaleX;
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;
@@ -662,9 +575,9 @@ public class BatchingFontRenderer {
strikethroughStartX,
strikethroughY,
strikethroughEndX - strikethroughStartX,
glyphScaleY,
1.0f,
curColor);
pushDrawCmd(ulIdx, 6, null, false);
pushDrawCmd(ulIdx, 6, null);
}
} finally {
@@ -673,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,291 +0,0 @@
package com.gtnewhorizons.angelica.client.font;
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)")) {
this.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);
this.font = null;
return;
}
this.font = availableFonts[fontPos].deriveFont(this.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) {
this.currentFontQuality = FontConfig.customFontQuality;
this.font = FontStrategist.getAvailableFonts()[fontID].deriveFont(this.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);
}
}
this.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) {
int atlasChars = 0;
for (int i = 0; i < ATLAS_SIZE; i++) {
final char ch = (char) (i + ATLAS_SIZE * this.id);
if (font.canDisplay(ch)) { atlasChars++; }
}
if (atlasChars == 0) { return; }
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics();
g2d.dispose();
final int atlasTilesX = (int) Math.ceil(Math.sqrt(atlasChars) * 1.5f);
final int atlasTilesY = (int) Math.ceil((double) atlasChars / atlasTilesX);
final float charSeparator = currentFontQuality / 3f;
int rowWidth = 0;
int maxRowWidth = 0;
atlasChars = 0;
for (int i = 0; i < ATLAS_SIZE; i++) {
if (atlasChars % atlasTilesX == 0) {
maxRowWidth = Math.max(maxRowWidth, rowWidth);
rowWidth = 0;
}
final char ch = (char)(i + ATLAS_SIZE * this.id);
if (font.canDisplay(ch)) {
rowWidth += (int) (charSeparator + fm.charWidth(ch));
atlasChars++;
}
}
maxRowWidth = Math.max(maxRowWidth, rowWidth);
final int lineHeight = fm.getHeight();
final float desc = fm.getDescent();
final int imageWidth = (int) (maxRowWidth + charSeparator);
final int imageHeight = (int) ((charSeparator + lineHeight) * atlasTilesY + charSeparator);
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) charSeparator; // 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) charSeparator;
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 + charSeparator) * (tileY + 1) - desc);
final float uStart = (float) (imgX - inset * charAspectRatio) / imageWidth;
final float vStart = ((lineHeight + charSeparator) * (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;
this.glyphData[i] = new GlyphData(uStart, vStart, xAdvance, glyphW, uSz, vSz);
imgX += (int) (charWidth + charSeparator);
tileX++;
}
g2d.dispose();
try {
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 = this.fontAtlases[id];
if (fa == null) {
fa = new FontAtlas(id);
fa.construct(this.font);
this.fontAtlases[id] = fa;
}
return fa;
}
@Override
public boolean isGlyphAvailable(char chr) {
if (this.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,109 +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 {
MCFONT_ASCII_MAP.defaultReturnValue((short) -1);
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
MCFONT_ASCII_MAP.put(MCFONT_CHARS.charAt(i), i);
}
}
public int lookupMcFontPosition(char ch) {
return MCFONT_ASCII_MAP.get(ch);
}
@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,113 +0,0 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.FontConfig;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ResourceLocation;
import java.io.IOException;
import java.io.InputStream;
public final class FontProviderUnicode implements FontProvider {
private FontProviderUnicode() {
try {
InputStream inputstream = Minecraft.getMinecraft().getResourceManager()
.getResource(new ResourceLocation("font/glyph_sizes.bin")).getInputStream();
//noinspection ResultOfMethodCallIgnored
inputstream.read(this.glyphWidth);
}
catch (IOException ioexception) {
throw new RuntimeException(ioexception);
}
}
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];
private final byte[] glyphWidth = new byte[65536];
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,126 +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);
}
/**
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 com.gtnewhorizons.angelica.glsm.GLStateManager;
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 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);
}
GLStateManager.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,11 +1,14 @@
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;
import net.minecraft.world.chunk.NibbleArray;
import net.minecraft.world.chunk.storage.ExtendedBlockStorage;
import ru.fewizz.idextender.Hooks;
public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
public boolean hasSky;
@@ -16,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) {
@@ -30,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) {
@@ -38,36 +41,42 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
System.arraycopy(((IExtendedBlockStorageMixin)(Object)storage).getBlock16BMetaArray(), 0, block16BMetaArray, 0, block16BMetaArray.length);
}
}
else if (ModStatus.isOldNEIDLoaded){
final short[] blockLSBArray = Hooks.get(this);
System.arraycopy(Hooks.get(storage), 0, blockLSBArray, 0, blockLSBArray.length);
// getBlockMSBArray is nuked in asm version
arrayLen = blockLSBArray.length;
}
else {
final byte[] blockLSBArray = this.getBlockLSBArray();
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,18 +1,24 @@
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");
/**
* Mixin Version
*/
public static boolean isNEIDLoaded;
/**
* ASM Version
*/
public static boolean isOldNEIDLoaded;
public static boolean isBetterCrashesLoaded;
public static boolean isNEIDLoaded;
public static boolean isNEIDMetadataExtended;
public static boolean isLotrLoaded;
public static boolean isChunkAPILoaded;
@@ -24,27 +30,28 @@ public class ModStatus {
public static boolean isThaumcraftLoaded;
public static boolean isThaumicHorizonsLoaded;
public static boolean isBaublesLoaded;
public static boolean isFluidLoggedLoaded;
public static void preInit() {
if (Loader.isModLoaded("backhand")) {
isBackhandLoaded = BackhandReflectionCompat.isBackhandLoaded();
}
public static void preInit(){
isBetterCrashesLoaded = Loader.isModLoaded("bettercrashes");
isNEIDLoaded = Loader.isModLoaded("neid");
isOldNEIDLoaded = Loader.isModLoaded("notenoughIDs");
isLotrLoaded = Loader.isModLoaded("lotr");
isChunkAPILoaded = Loader.isModLoaded("chunkapi");
isEIDBiomeLoaded = Loader.isModLoaded("endlessids_biome");
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){
@@ -54,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,20 +1,10 @@
package com.gtnewhorizons.angelica.compat.mojang;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
public enum InteractionHand {
MAIN_HAND,
OFF_HAND;
public ItemStack getItemInHand(EntityPlayer player) {
if (ModStatus.isBackhandLoaded && this == InteractionHand.OFF_HAND) { // off hand (requires backhand)
return BackhandReflectionCompat.getOffhandItem(player);
} else { // main hand
return player.getHeldItem();
}
InteractionHand() {
}
}
@@ -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;
}
@@ -1,7 +1,7 @@
package com.gtnewhorizons.angelica.compat.toremove;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import me.jellysquid.mods.sodium.client.SodiumClientMod;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.renderer.texture.TextureMap;
@@ -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;
}
@@ -147,7 +145,7 @@ public abstract class RenderPhase {
FOG = new Fog("fog", () -> {
// Unclear what this should do
// BackgroundRenderer.setFogBlack(), also levelFogColor()
AngelicaMod.LOGGER.debug("Fog - Not setting level fog color");
SodiumClientMod.LOGGER.debug("Fog - Not setting level fog color");
GLStateManager.enableFog();
}, GLStateManager::disableFog);
BLACK_FOG = new Fog("black_fog", () -> {
@@ -156,7 +154,7 @@ public abstract class RenderPhase {
}, () -> {
// Unclear what this should do
// BackgroundRenderer.setFogBlack(), also levelFogColor()
AngelicaMod.LOGGER.debug("Fog - Not setting level fog color");
SodiumClientMod.LOGGER.debug("Fog - Not setting level fog color");
GLStateManager.disableFog();
});
MAIN_TARGET = new Target("main_target", () -> {
@@ -164,7 +162,7 @@ public abstract class RenderPhase {
});
OUTLINE_TARGET = new Target("outline_target", () -> {
// TODO: Sodium
AngelicaMod.LOGGER.debug("NOT enabling the entity outline framebuffer");
SodiumClientMod.LOGGER.debug("NOT enabling the entity outline framebuffer");
//MinecraftClient.getInstance().worldRenderer.getEntityOutlinesFramebuffer().beginWrite(false);
}, () -> {
Minecraft.getMinecraft().getFramebuffer().bindFramebuffer(false);
@@ -172,7 +170,7 @@ public abstract class RenderPhase {
TRANSLUCENT_TARGET = new Target("translucent_target", () -> {
if (Minecraft.isFancyGraphicsEnabled()) {
// TODO: Sodium
AngelicaMod.LOGGER.debug("NOT enabling the translucent framebuffer");
SodiumClientMod.LOGGER.debug("NOT enabling the translucent framebuffer");
// MinecraftClient.getInstance().worldRenderer.getTranslucentFramebuffer().beginWrite(false);
}
@@ -345,7 +343,7 @@ public abstract class RenderPhase {
super("lightmap", () -> {
if (lightmap) {
// TODO: Sodium - LightmapTextureManager
AngelicaMod.LOGGER.debug("Lightmap - enable (not implemented)");
SodiumClientMod.LOGGER.debug("Lightmap - enable (not implemented)");
// throw new RuntimeException("Not Implemented Yet");
// MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().enable();
@@ -354,7 +352,7 @@ public abstract class RenderPhase {
}, () -> {
if (lightmap) {
// TODO: Sodium - LightmapTextureManager
AngelicaMod.LOGGER.debug("Lightmap - disable (not implemented)");
SodiumClientMod.LOGGER.debug("Lightmap - disable (not implemented)");
// throw new RuntimeException("Not Implemented Yet");
// MinecraftClient.getInstance().gameRenderer.getLightmapTextureManager().disable();
}
@@ -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,30 +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 main menu panorama with modern equivalent.")
@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 enablePanoramaBlurShader;
@Config.Comment("Replace cloud renderer with a VBO version.")
@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)
@@ -93,16 +77,12 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean enableHudCachingEventTransformer;
@Config.Comment("Enable HUD Caching at runtime. Requires enableHudCaching to be on at startup. [Experimental]")
@Config.DefaultBoolean(false)
public static boolean hudCachingActive;
@Config.Comment("The amount of frames to wait before updating the HUD elements. [Experimental]")
@Config.DefaultInt(20)
@Config.RangeInt(min = 1, max = 60)
public static int hudCachingFPS = 20;
@Config.Comment("Batch drawScreen fonts")
@Config.Comment("Batch drawScreen fonts [Experimental]")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enableFontRenderer;
@@ -117,7 +97,7 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean optimizeWorldUpdateLight;
@Config.Comment("Optimize Texture Animations. [From Hodgepodge]")
@Config.Comment("Speedup Animations. [From Hodgepodge]")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean speedupAnimations;
@@ -153,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
@@ -168,91 +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",
"org.pfaa.geologica.block.BrokenGeoBlock",
"org.pfaa.geologica.block.BrickGeoBlock",
})
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("Define IS_IRIS in shader macros.")
@Config.DefaultBoolean(true)
public static boolean defineIsIris;
@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("Replaces various FFP uploads with statically allocated VBO's.")
@Config.DefaultBoolean(true)
public static boolean replaceFFPUploads;
@Config.Comment("Pinned OpenGL version an integer (e.g. 46, 41, 33). 0 = auto-detect. [33, 46]. (Disable with disableGLVersionPinning=true)")
@Config.DefaultInt(0)
@Config.RangeInt(min = 0, max = 46)
@Config.RequiresMcRestart
public static int pinnedGLVersion;
@Config.Comment("Disable automatic GL version pinning. When true, always probes from highest on every launch.")
@Config.DefaultBoolean(false)
@Config.RequiresMcRestart
public static boolean disableGLVersionPinning;
@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,160 +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.client.renderer.Tessellator;
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);
final Tessellator tess = Tessellator.instance;
tess.startDrawingQuads();
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(tess, x, z, playerChunkX, playerChunkZ, manager, chunkProvider);
}
}
tess.draw();
// Restore GL state
GLStateManager.enableLighting();
GLStateManager.enableDepthTest();
GLStateManager.disableBlend();
GLStateManager.glPopMatrix();
GLStateManager.glPopAttrib();
}
private void drawChunk(Tessellator tess, 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
}
}
tess.setColorRGBA_F(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;
tess.addVertex(xStart, zStart, 0);
tess.addVertex(xStart, zEnd, 0);
tess.addVertex(xEnd, zEnd, 0);
tess.addVertex(xEnd, zStart, 0);
}
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.debug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.Tessellator;
@@ -11,31 +10,31 @@ import org.lwjgl.opengl.GL11;
public class F3Direction {
public static void renderWorldDirectionsEvent(Minecraft mc, RenderGameOverlayEvent.Pre event) {
if (mc.gameSettings.showDebugInfo && mc.gameSettings.thirdPersonView == 0) {
final ScaledResolution scaledresolution = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
final int width = scaledresolution.getScaledWidth();
final int height = scaledresolution.getScaledHeight();
ScaledResolution scaledresolution = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
int width = scaledresolution.getScaledWidth();
int height = scaledresolution.getScaledHeight();
GLStateManager.glPushMatrix();
GL11.glPushMatrix();
GLStateManager.glTranslatef((float) (width / 2), (float) (height / 2), -90);
GL11.glTranslatef((float) (width / 2), (float) (height / 2), -90);
final Entity entity = mc.renderViewEntity;
GLStateManager.glRotatef(entity.prevRotationPitch + (entity.rotationPitch - entity.prevRotationPitch) * event.partialTicks, -1.0F, 0.0F, 0.0F);
GLStateManager.glRotatef(entity.prevRotationYaw + (entity.rotationYaw - entity.prevRotationYaw) * event.partialTicks, 0.0F, 1.0F, 0.0F);
Entity entity = mc.renderViewEntity;
GL11.glRotatef(entity.prevRotationPitch + (entity.rotationPitch - entity.prevRotationPitch) * event.partialTicks, -1.0F, 0.0F, 0.0F);
GL11.glRotatef(entity.prevRotationYaw + (entity.rotationYaw - entity.prevRotationYaw) * event.partialTicks, 0.0F, 1.0F, 0.0F);
GLStateManager.glScalef(-1.0F, -1.0F, -1.0F);
GL11.glScalef(-1.0F, -1.0F, -1.0F);
renderWorldDirections();
GLStateManager.glPopMatrix();
GL11.glPopMatrix();
}
}
public static void renderWorldDirections() {
GLStateManager.disableTexture();
GLStateManager.glDepthMask(false);
final Tessellator tessellator = Tessellator.instance;
GL11.glDisable(GL11.GL_TEXTURE_2D);
GL11.glDepthMask(false);
Tessellator tessellator = Tessellator.instance;
GLStateManager.glLineWidth(2.0F);
GL11.glLineWidth(2.0F);
tessellator.startDrawing(GL11.GL_LINES);
//X
@@ -55,8 +54,8 @@ public class F3Direction {
tessellator.draw();
GLStateManager.glLineWidth(1.0F);
GLStateManager.glDepthMask(true);
GLStateManager.enableTexture();
GL11.glLineWidth(1.0F);
GL11.glDepthMask(true);
GL11.glEnable(GL11.GL_TEXTURE_2D);
}
}
@@ -1,33 +1,43 @@
package com.gtnewhorizons.angelica.debug;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
import static org.lwjgl.opengl.GL11.GL_ALPHA_TEST;
import static org.lwjgl.opengl.GL11.GL_BLEND;
import static org.lwjgl.opengl.GL11.GL_CULL_FACE;
import static org.lwjgl.opengl.GL11.GL_DEPTH_TEST;
import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.opengl.GL11.GL_VERTEX_ARRAY;
import static org.lwjgl.opengl.GL11.glBlendFunc;
import static org.lwjgl.opengl.GL11.glColor4f;
import static org.lwjgl.opengl.GL11.glDisable;
import static org.lwjgl.opengl.GL11.glDisableClientState;
import static org.lwjgl.opengl.GL11.glDrawArrays;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glEnableClientState;
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL15.glBufferData;
import static org.lwjgl.opengl.GL15.glGenBuffers;
import static org.lwjgl.opengl.GL20.glDisableVertexAttribArray;
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
import static org.lwjgl.opengl.GL20.glUniform1;
import static org.lwjgl.opengl.GL20.glUniform1f;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
import com.gtnewhorizon.gtnhlib.client.renderer.shader.ShaderProgram;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.compat.lwjgl.MemoryStack;
import java.nio.FloatBuffer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL30;
import java.nio.FloatBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush;
import static com.gtnewhorizons.angelica.glsm.GLStateManager.glBindBuffer;
import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
import static org.lwjgl.opengl.GL15.glBufferData;
import static org.lwjgl.opengl.GL15.glGenBuffers;
import static org.lwjgl.opengl.GL20.glEnableVertexAttribArray;
import static org.lwjgl.opengl.GL20.glUniform1;
import static org.lwjgl.opengl.GL20.glUniform1f;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
public abstract class F3Graph {
private static final int NUM_SAMPLES = 240;
@@ -52,7 +62,6 @@ public abstract class F3Graph {
private int uHeadIdx;
private int uSamples;
private int vertBuf;
private int vao;
private final ResourceLocation texture;
private final float pxPerNs;
private final boolean left;
@@ -107,8 +116,8 @@ public abstract class F3Graph {
"shaders/debug_graph.frag.glsl");
shader.use();
// Attribute location is declared in shader via layout(location = 0)
aPos = 0;
// Register attributes
aPos = shader.getAttribLocation("pos");
// Register uniforms
uFBWidth = shader.getUniformLocation("fbWidth");
@@ -119,12 +128,10 @@ public abstract class F3Graph {
final int uPxPerNs = shader.getUniformLocation("pxPerNs");
final int uLeft = shader.getUniformLocation("left");
vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
// 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.
@@ -138,13 +145,7 @@ public abstract class F3Graph {
glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW);
}
// Configure vertex attribute in the VAO
glVertexAttribPointer(aPos, VERT_FLOATS, GL_FLOAT, false, VERT_FLOATS * 4, 0);
glEnableVertexAttribArray(aPos);
glBindBuffer(GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
final Minecraft mc = Minecraft.getMinecraft();
final ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
@@ -184,21 +185,21 @@ public abstract class F3Graph {
// Setup GL state
final Tessellator tess = Tessellator.instance;
GLStateManager.disableDepthTest();
GLStateManager.enableAlphaTest();
GLStateManager.enableBlend();
GLStateManager.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_DEPTH_TEST);
glEnable(GL_ALPHA_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Draw background
GLStateManager.disableTexture();
GLStateManager.glColor4f(0x50 / 255.0F, 0x50 / 255.0F, 0x50 / 255.0F, 0x90 / 255.0F);
glDisable(GL_TEXTURE_2D);
glColor4f(0x50 / 255.0F, 0x50 / 255.0F, 0x50 / 255.0F, 0x90 / 255.0F);
tess.startDrawingQuads();
for (int i = 0; i < 4; ++i) {
tess.addVertex(getVertX(sr, i), getVertY(sr, i), 0);
}
tess.draw();
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
GLStateManager.enableTexture();
glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
glEnable(GL_TEXTURE_2D);
// Draw samples
@@ -211,16 +212,21 @@ public abstract class F3Graph {
glUniform1i(uHeadIdx, samplesHead);
glUniform1(uSamples, sampleBuf);
GLStateManager.glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vertBuf);
glEnableVertexAttribArray(aPos);
glVertexAttribPointer(aPos, VERT_FLOATS, GL_FLOAT, false, VERT_FLOATS * 4, 0);
glEnableClientState(GL_VERTEX_ARRAY);
// Left side graphs are inverted, so temporarily disable culling
if (!left) GLStateManager.disableCull();
if (!left) glDisable(GL_CULL_FACE);
GLStateManager.glDrawArrays(GL_TRIANGLE_STRIP, 0, VERT_COUNT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, VERT_COUNT);
if (!left) GLStateManager.enableCull();
if (!left) glEnable(GL_CULL_FACE);
GLStateManager.glBindVertexArray(0);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableVertexAttribArray(aPos);
glBindBuffer(GL_ARRAY_BUFFER, 0);
ShaderProgram.clear();
@@ -235,9 +241,9 @@ public abstract class F3Graph {
tess.draw();
// Reset GL state
GLStateManager.enableDepthTest();
GLStateManager.disableAlphaTest();
GLStateManager.disableBlend();
glEnable(GL_DEPTH_TEST);
glDisable(GL_ALPHA_TEST);
glDisable(GL_BLEND);
// Draw text
// Ensure running counters are reset on first sample
@@ -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,55 +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.backhand.BackhandReflectionCompat;
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;
@@ -65,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;
@@ -98,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++;
}
@@ -148,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();
}
@@ -168,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;
}
}
@@ -192,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;
}
@@ -216,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();
}
@@ -267,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());
}
@@ -307,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.
*
@@ -346,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.
*
@@ -421,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());
}
@@ -431,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.
*
@@ -502,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;
@@ -520,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 = BackhandReflectionCompat.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));
}
}
}
@@ -576,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,370 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
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 UniversalVAO.genVertexArrays();
}
@Override
public void glBindVertexArray(int array) {
GLStateManager.glBindVertexArray(array);
}
@Override
public void glDeleteVertexArrays(int array) {
GLStateManager.glDeleteVertexArrays(array);
}
// ===================== BUFFER OPERATIONS =====================
@Override
public int glGenBuffers() {
return GL15.glGenBuffers();
}
@Override
public void glBindBuffer(int target, int buffer) {
GLStateManager.glBindBuffer(target, buffer);
}
@Override
public void glDeleteBuffers(int buffer) {
GLStateManager.glDeleteBuffers(buffer);
}
// ===================== TEXTURE OPERATIONS =====================
@Override
public int glGenTextures() {
return GL11.glGenTextures();
}
@Override
public void glGenTextures(int[] textures) {
final 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) {
final 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) {
final 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,67 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
import com.gtnewhorizons.angelica.glsm.recording.CommandBuffer;
import com.gtnewhorizons.angelica.glsm.recording.CommandBufferProcessor;
import java.util.List;
/**
* Builds final command buffers from raw command buffers and compiled VBO data.
* Bridges the gap between VBO compilation (glsm types) and buffer processing (recording types).
*/
public final class CommandBufferBuilder {
private CommandBufferBuilder() {}
/**
* Build commands with transform collapsing optimization.
* Accumulates MODELVIEW transforms and emits MultMatrix at barriers (draws, CallList).
* Uses mergedRanges where consecutive same-transform draws are combined.
*/
public static void buildFromRawBuffer(
CommandBuffer rawBuffer,
List<AccumulatedDraw> accumulatedDraws,
CommandBuffer finalBuffer) {
rawBuffer.resetRead();
int cmdIndex = 0;
int rangeIndex = 0;
AccumulatedDraw draw;
while (rawBuffer.hasRemaining()) {
// Read and process the command
CommandBufferProcessor.copyCommand(rawBuffer, finalBuffer);
// Emit draw ranges at this command position
while (rangeIndex < accumulatedDraws.size() && (draw = accumulatedDraws.get(rangeIndex)).commandIndex == cmdIndex) {
emitDrawRangeToBuffer(draw, finalBuffer, rangeIndex);
rangeIndex++;
}
cmdIndex++;
}
while (rangeIndex < accumulatedDraws.size()) {
emitDrawRangeToBuffer(accumulatedDraws.get(rangeIndex), finalBuffer, rangeIndex);
rangeIndex++;
}
}
/**
* Emit a draw range to the output buffer, with transform if needed.
*/
private static void emitDrawRangeToBuffer(
AccumulatedDraw draw,
CommandBuffer out,
int vboIdx) {
if (draw.restoreData != null) {
out.writeDrawRangeRestore(vboIdx, draw.restoreData);
return;
}
// Write the draw range command
out.writeDrawRange(vboIdx);
}
}
@@ -1,460 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import net.minecraft.launchwrapper.Launch;
import org.taumc.glsl.ShaderParser;
import org.taumc.glsl.Transformer;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
/**
* Compat transformation for mod shaders running under core profile.
*
* <p>Handles:
* <ul>
* <li>Matrix builtin replacement (gl_ModelViewMatrix, gl_ProjectionMatrix, etc.)</li>
* <li>Vertex attribute replacement (gl_Vertex, gl_Color, gl_MultiTexCoord0/1, gl_Normal)</li>
* <li>gl_TexCoord[N] varying array → per-index in/out declarations</li>
* <li>Texture function renames (texture2D → texture, etc.)</li>
* <li>Fragment output handling (gl_FragColor → layout-qualified out declarations)</li>
* <li>Fog builtins (gl_Fog, gl_FogFragCoord)</li>
* <li>gl_FrontColor → local variable in vertex shaders</li>
* <li>shadow2D/shadow2DLod → texture/textureLod with vec4 wrapping</li>
* <li>Version upgrade to 330 core minimum</li>
* <li>Reserved word pre-parse renaming (texture-as-variable, sample, etc.)</li>
* </ul>
*/
public class CompatShaderTransformer {
private static final Pattern VERSION_PATTERN =
Pattern.compile("#version\\s+(\\d+)(?:\\s+(\\w+))?");
private static final Pattern DEFINE_PATTERN =
Pattern.compile("^\\s*#\\s*define\\s+.+$", Pattern.MULTILINE);
/** Compat builtins that trigger AST transformation. */
private static final Set<String> COMPAT_BUILTINS = Set.of(
"gl_ModelView", "gl_Projection", "gl_NormalMatrix", "gl_TextureMatrix",
"gl_FragColor", "gl_Fog", "gl_FrontColor", "gl_Color",
"gl_Vertex", "gl_MultiTexCoord", "gl_TexCoord", "gl_Normal", "ftransform",
"texture2D", "texture3D", "texelFetch2D", "texelFetch3D", "textureSize2D",
"shadow2D", "gl_FrontLightModelProduct",
"gl_LightSource", "gl_FrontMaterial"
);
private static final Pattern NEEDS_TRANSFORM_PATTERN = Pattern.compile(
String.join("|", COMPAT_BUILTINS) + "|\\b(?:attribute|varying)\\b"
);
private static final Map<String, String> MATRIX_RENAMES = Map.of(
"gl_ModelViewMatrix", "angelica_ModelViewMatrix",
"gl_ModelViewMatrixInverse", "angelica_ModelViewMatrixInverse",
"gl_ProjectionMatrix", "angelica_ProjectionMatrix",
"gl_ProjectionMatrixInverse", "angelica_ProjectionMatrixInverse",
"gl_NormalMatrix", "angelica_NormalMatrix"
);
private static final Path DUMP_DIR;
private static final AtomicInteger dumpCounter = new AtomicInteger(0);
static {
boolean isDev = false;
try {
final Object deobfEnv = Launch.blackboard != null ? Launch.blackboard.get("fml.deobfuscatedEnvironment") : null;
isDev = Boolean.TRUE.equals(deobfEnv);
} catch (Exception ignored) {}
DUMP_DIR = (isDev || Boolean.parseBoolean(System.getProperty("angelica.compat.dumpShaders", "false")))
? Paths.get("compat_shaders") : null;
}
private static final int CACHE_SIZE = 32;
private record CacheKey(String source, boolean isFragment) {}
private static final Map<CacheKey, String> cache = Collections.synchronizedMap(
new LinkedHashMap<>(32, 0.75f, true) {
@Override protected boolean removeEldestEntry(Map.Entry<CacheKey, String> eldest) {
return size() > CACHE_SIZE;
}
});
public static void clearCache() {
cache.clear();
}
/**
* Transform a mod shader source for core profile compatibility.
*/
public static String transform(String source, boolean isFragment) {
final boolean needsTransform = needsTransformation(source);
String result;
if (!needsTransform) {
result = fixupVersion(source);
} else {
final CacheKey key = new CacheKey(source, isFragment);
final String cached = cache.get(key);
if (cached != null) {
dumpShader(source, cached, isFragment, needsTransform);
return cached;
}
try {
result = transformInternal(source, isFragment);
cache.put(key, result);
} catch (Exception e) {
LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
result = fixupVersion(source);
}
}
dumpShader(source, result, isFragment, needsTransform);
return result;
}
private static boolean needsTransformation(String source) {
// Shaders already at 330+ core don't need compat transformation
final Matcher vm = VERSION_PATTERN.matcher(source);
if (vm.find()) {
final int version = Integer.parseInt(vm.group(1));
if (version >= 330 && "core".equals(vm.group(2))) return false;
}
return NEEDS_TRANSFORM_PATTERN.matcher(source).find();
}
private static String transformInternal(String source, boolean isFragment) {
final Matcher versionMatcher = VERSION_PATTERN.matcher(source);
int declaredVersion = 110;
if (versionMatcher.find()) {
declaredVersion = Integer.parseInt(versionMatcher.group(1));
}
final int targetVersion = Math.max(declaredVersion, 330);
// Pre-parse reserved word renaming — prevents ANTLR parse failures
source = GlslTransformUtils.replaceTexture(source);
source = GlslTransformUtils.renameReservedWords(source, targetVersion);
final ShaderParser.ParsedShader parsedShader = ShaderParser.parseShader(source);
final Transformer transformer = new Transformer(parsedShader.full());
injectMatrixUniforms(transformer);
transformFog(transformer, isFragment, source);
// gl_FrontLightModelProduct.sceneColor → angelica_SceneColor uniform
if (source.contains("gl_FrontLightModelProduct")) {
transformer.injectVariable("uniform vec4 angelica_SceneColor;");
transformer.replaceExpression("gl_FrontLightModelProduct.sceneColor", "angelica_SceneColor");
}
// gl_LightSource[i] → struct + uniform array (both via injectFunction to keep struct before uniform)
if (source.contains("gl_LightSource")) {
transformer.injectFunction(
"struct angelica_LightSourceParameters {"
+ "vec4 ambient;vec4 diffuse;vec4 specular;vec4 position;vec4 halfVector;"
+ "vec3 spotDirection;float spotExponent;float spotCutoff;float spotCosCutoff;"
+ "float constantAttenuation;float linearAttenuation;float quadraticAttenuation;"
+ "};");
transformer.injectFunction("uniform angelica_LightSourceParameters angelica_LightSource[2];");
transformer.rename("gl_LightSource", "angelica_LightSource");
}
// gl_FrontMaterial → struct + uniform (both via injectFunction to keep struct before uniform)
if (source.contains("gl_FrontMaterial")) {
transformer.injectFunction(
"struct angelica_MaterialParameters {"
+ "vec4 emission;vec4 ambient;vec4 diffuse;vec4 specular;float shininess;"
+ "};");
transformer.injectFunction("uniform angelica_MaterialParameters angelica_FrontMaterial;");
transformer.rename("gl_FrontMaterial", "angelica_FrontMaterial");
}
// gl_FrontColor (vertex) → gl_Color (fragment) varying chain
// Vertex side is unconditional: fragment may read gl_Color without vertex writing gl_FrontColor.
// Uses angelica_ prefix to avoid colliding with user-declared varyings (e.g. "v_Color").
if (!isFragment) {
transformer.injectVariable("out vec4 angelica_FrontColor;");
transformer.rename("gl_FrontColor", "angelica_FrontColor");
transformer.prependMain("angelica_FrontColor = vec4(1.0);");
// Vertex attributes — replaces removed FFP vertex inputs with explicit in declarations
transformVertexAttributes(transformer, source);
} else {
if (source.contains("gl_Color")) {
transformer.injectVariable("in vec4 angelica_FrontColor;");
transformer.rename("gl_Color", "angelica_FrontColor");
}
}
// gl_TexCoord[N] varying array → per-index in/out declarations
final Set<Integer> texCoordIndices = new HashSet<>();
transformer.renameArray("gl_TexCoord", "angelica_TexCoord", texCoordIndices);
for (Integer i : texCoordIndices) {
final String qualifier = isFragment ? "in" : "out";
transformer.injectVariable(qualifier + " vec4 angelica_TexCoord" + i + ";");
}
// Fragment output handling + alpha test discard
if (isFragment) {
transformFragmentOutputs(transformer);
}
// texture-as-variable collision handling
if (transformer.containsCall("texture") && transformer.hasVariable("texture")) {
transformer.rename("texture", "gtexture");
}
if (transformer.hasVariable("angelica_renamed_texture")) {
transformer.rename("angelica_renamed_texture", "gtexture");
}
transformer.renameFunctionCall(GlslTransformUtils.TEXTURE_RENAMES);
transformer.renameAndWrapShadow("shadow2D", "texture");
transformer.renameAndWrapShadow("shadow2DLod", "textureLod");
final String versionDirective = "#version " + targetVersion + " core\n";
final String extensions = VERSION_PATTERN.matcher(GlslTransformUtils.getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
// Preserve #define directives
final StringBuilder defines = new StringBuilder();
final Matcher defineMatcher = DEFINE_PATTERN.matcher(source);
while (defineMatcher.find()) {
defines.append(defineMatcher.group().trim()).append('\n');
}
final String header = versionDirective + (extensions.isEmpty() ? "" : "\n" + extensions) + (defines.isEmpty() ? "" : "\n" + defines);
final StringBuilder result = new StringBuilder();
transformer.mutateTree(tree -> result.append(GlslTransformUtils.getFormattedShader(tree, header)));
// Restore pre-parse renames
String output = GlslTransformUtils.restoreReservedWords(result.toString());
// Core profile: attribute → in, varying → out (vertex) / in (fragment)
output = fixupQualifiers(output, isFragment);
return output;
}
/**
* Inject matrix uniforms and rename compat builtins.
*/
private static void injectMatrixUniforms(Transformer transformer) {
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrix;");
transformer.injectVariable("uniform mat4 angelica_ModelViewMatrixInverse;");
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrix;");
transformer.injectVariable("uniform mat4 angelica_ProjectionMatrixInverse;");
transformer.injectVariable("uniform mat3 angelica_NormalMatrix;");
transformer.injectVariable("uniform mat4 angelica_LightmapTextureMatrix;");
transformer.rename(MATRIX_RENAMES);
// Expression replacements
transformer.replaceExpression("gl_ModelViewProjectionMatrix", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix)");
transformer.replaceExpression("gl_TextureMatrix[0]", "mat4(1.0)");
transformer.replaceExpression("gl_TextureMatrix[1]", "angelica_LightmapTextureMatrix");
transformer.replaceExpression("gl_TextureMatrix", "mat4[8](mat4(1.0), angelica_LightmapTextureMatrix, mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0), mat4(1.0))");
}
/**
* Transform fragment outputs for core profile.
*/
private static void transformFragmentOutputs(Transformer transformer) {
if (transformer.containsCall("gl_FragColor")) {
transformer.replaceExpression("gl_FragColor", "gl_FragData[0]");
}
final Set<Integer> found = new HashSet<>();
transformer.renameArray("gl_FragData", "angelica_FragData", found);
for (Integer i : found) {
transformer.injectVariable("layout (location = " + i + ") out vec4 angelica_FragData" + i + ";");
}
// Core profile: GL_ALPHA_TEST is removed - inject runtime discard using GLSM-tracked alpha reference (uploaded by CompatUniformManager).
if (found.contains(0)) {
transformer.injectVariable("uniform float angelica_currentAlphaTest;");
transformer.appendMain("if (angelica_FragData0.a <= angelica_currentAlphaTest) discard;");
}
}
private static void transformFog(Transformer transformer, boolean isFragment, String source) {
// Vertex side is unconditional: fragment may read gl_FogFragCoord without vertex writing it
transformer.rename("gl_FogFragCoord", "angelica_FogFragCoord");
if (!isFragment) {
transformer.injectVariable("out float angelica_FogFragCoord;");
transformer.prependMain("angelica_FogFragCoord = 0.0;");
} else {
if (source.contains("gl_FogFragCoord")) {
transformer.injectVariable("in float angelica_FogFragCoord;");
}
}
transformer.rename("gl_Fog", "angelica_Fog");
transformer.injectVariable("uniform float angelica_FogDensity;");
transformer.injectVariable("uniform float angelica_FogStart;");
transformer.injectVariable("uniform float angelica_FogEnd;");
transformer.injectVariable("uniform vec4 angelica_FogColor;");
transformer.injectFunction("struct angelica_FogParameters {vec4 color;float density;float start;float end;float scale;};");
transformer.injectFunction(
"angelica_FogParameters angelica_Fog = angelica_FogParameters("
+ "angelica_FogColor, angelica_FogDensity, angelica_FogStart, angelica_FogEnd, "
+ "1.0 / (angelica_FogEnd - angelica_FogStart));");
}
/**
* Replace removed FFP vertex attributes with explicit {@code in} declarations at core profile attribute locations.
*/
private static void transformVertexAttributes(Transformer transformer, String source) {
if (source.contains("gl_Vertex") || source.contains("ftransform")) {
transformer.injectVariable("layout(location = 0) in vec4 angelica_Vertex;");
transformer.rename("gl_Vertex", "angelica_Vertex");
}
// gl_Color in vertex shaders is the per-vertex color attribute, distinct from the fragment gl_Color (interpolated gl_FrontColor) handled above
if (source.contains("gl_Color")) {
transformer.injectVariable("layout(location = 1) in vec4 angelica_Color;");
transformer.rename("gl_Color", "angelica_Color");
}
if (source.contains("gl_MultiTexCoord0")) {
transformer.injectVariable("layout(location = 2) in vec4 angelica_MultiTexCoord0;");
transformer.rename("gl_MultiTexCoord0", "angelica_MultiTexCoord0");
}
if (source.contains("gl_MultiTexCoord1")) {
transformer.injectVariable("layout(location = 3) in vec4 angelica_MultiTexCoord1;");
transformer.rename("gl_MultiTexCoord1", "angelica_MultiTexCoord1");
}
if (source.contains("gl_Normal")) {
transformer.injectVariable("layout(location = 4) in vec3 angelica_Normal;");
transformer.rename("gl_Normal", "angelica_Normal");
}
if (source.contains("ftransform")) {
transformer.replaceExpression("ftransform()", "(angelica_ProjectionMatrix * angelica_ModelViewMatrix * angelica_Vertex)");
}
}
private static void dumpShader(String original, String transformed, boolean isFragment, boolean wasTransformed) {
if (DUMP_DIR == null) return;
final int id = dumpCounter.getAndIncrement();
final String suffix = isFragment ? ".frag.glsl" : ".vert.glsl";
try {
Files.createDirectories(DUMP_DIR);
// Capture caller info for identification
final String caller = identifyCaller();
final String header = "// Compat shader dump #" + id + " (" + (isFragment ? "fragment" : "vertex") + ")"
+ "\n// Transformed: " + wasTransformed
+ "\n// Caller: " + caller + "\n\n";
Files.writeString(DUMP_DIR.resolve(id + "_original" + suffix), header + original, StandardCharsets.UTF_8);
Files.writeString(DUMP_DIR.resolve(id + "_transformed" + suffix), header + transformed, StandardCharsets.UTF_8);
} catch (IOException e) {
LOGGER.warn("Failed to dump compat shader: {}", e.getMessage());
}
}
private static String identifyCaller() {
for (StackTraceElement frame : Thread.currentThread().getStackTrace()) {
final String cls = frame.getClassName();
if (!cls.startsWith("com.gtnewhorizons.angelica.glsm.")
&& !cls.startsWith("java.")
&& !cls.equals("org.lwjgl.opengl.GL20")) {
return cls + "." + frame.getMethodName() + ":" + frame.getLineNumber();
}
}
return "unknown";
}
private static final Pattern ATTRIBUTE_PATTERN = Pattern.compile("\\battribute\\b");
private static final Pattern VARYING_PATTERN = Pattern.compile("\\bvarying\\b");
/** Replace legacy storage qualifiers removed in core profile. Safe on AST-serialized output (no comments). */
public static String fixupQualifiers(String source, boolean isFragment) {
source = ATTRIBUTE_PATTERN.matcher(source).replaceAll("in");
source = VARYING_PATTERN.matcher(source).replaceAll(isFragment ? "in" : "out");
return source;
}
// Patterns for parsing fragment shader in declarations to generate passthrough vertex shaders
private static final Pattern FRAG_IN_COLOR = Pattern.compile("\\bin\\s+vec4\\s+angelica_FrontColor\\b");
private static final Pattern FRAG_IN_TEXCOORD = Pattern.compile("\\bin\\s+vec4\\s+angelica_TexCoord(\\d+)\\b");
private static final Pattern FRAG_IN_FOGCOORD = Pattern.compile("\\bin\\s+float\\s+angelica_FogFragCoord\\b");
/**
* Generate a passthrough vertex shader for a fragment-only program.
*
* @param fragmentSource the transformed fragment shader source
* @return a complete vertex shader source string
*/
public static String generatePassthroughVertexShader(String fragmentSource) {
final StringBuilder sb = new StringBuilder(512);
sb.append("#version 330 core\n\n");
// Always have position
sb.append("layout(location = 0) in vec4 a_Position;\n");
// Matrix uniforms for position transform
sb.append("uniform mat4 angelica_ModelViewMatrix;\n");
sb.append("uniform mat4 angelica_ProjectionMatrix;\n");
final StringBuilder varyings = new StringBuilder();
final StringBuilder assignments = new StringBuilder();
if (FRAG_IN_COLOR.matcher(fragmentSource).find()) {
sb.append("layout(location = 1) in vec4 a_Color;\n");
varyings.append("out vec4 angelica_FrontColor;\n");
assignments.append(" angelica_FrontColor = a_Color;\n");
}
final Matcher texCoordMatcher = FRAG_IN_TEXCOORD.matcher(fragmentSource);
final Set<Integer> texCoordIndices = new HashSet<>();
while (texCoordMatcher.find()) {
texCoordIndices.add(Integer.parseInt(texCoordMatcher.group(1)));
}
for (int i : texCoordIndices) {
// PRIMARY_UV=2, SECONDARY_UV=3. Only indices 0 and 1 are supported; higher indices collide at location 3.
final int location = i == 0 ? 2 : 3;
sb.append("layout(location = ").append(location).append(") in vec4 a_TexCoord").append(i).append(";\n");
varyings.append("out vec4 angelica_TexCoord").append(i).append(";\n");
assignments.append(" angelica_TexCoord").append(i).append(" = a_TexCoord").append(i).append(";\n");
}
if (FRAG_IN_FOGCOORD.matcher(fragmentSource).find()) {
varyings.append("out float angelica_FogFragCoord;\n");
assignments.append(" angelica_FogFragCoord = abs((angelica_ModelViewMatrix * a_Position).z);\n");
}
sb.append(varyings);
sb.append("\nvoid main() {\n");
sb.append(" gl_Position = angelica_ProjectionMatrix * angelica_ModelViewMatrix * a_Position;\n");
sb.append(assignments);
sb.append("}\n");
return sb.toString();
}
/** Ensure #version is at least 330 core, strip 'compatibility' profile. */
private static String fixupVersion(String source) {
final Matcher m = VERSION_PATTERN.matcher(source);
if (!m.find()) {
return "#version 330 core\n" + source;
}
final int version = Integer.parseInt(m.group(1));
final String profile = m.group(2);
if (version >= 330 && !"compatibility".equals(profile)) {
if ("core".equals(profile)) return source;
return m.replaceFirst("#version " + version + " core");
}
final int targetVersion = Math.max(version, 330);
return m.replaceFirst("#version " + targetVersion + " core");
}
}
@@ -1,411 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.LightStateStack;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
/**
* Per-program compat uniform location cache and upload manager.
*
* <p>Manages {@code angelica_*} uniforms injected by {@link CompatShaderTransformer} and {@code iris_*} matrix uniforms used by Iris/celeritas shader programs.
*/
public class CompatUniformManager {
// Uniform location indices within the cached array (package-private for tests)
static final int LOC_MODELVIEW = 0;
static final int LOC_MODELVIEW_INVERSE = 1;
static final int LOC_PROJECTION = 2;
static final int LOC_PROJECTION_INVERSE = 3;
static final int LOC_NORMAL = 4;
static final int LOC_LIGHTMAP_TEXTURE_MATRIX = 5;
static final int LOC_FOG_DENSITY = 6;
static final int LOC_FOG_START = 7;
static final int LOC_FOG_END = 8;
static final int LOC_FOG_COLOR = 9;
static final int LOC_IRIS_MODELVIEW = 10;
static final int LOC_IRIS_PROJECTION = 11;
static final int LOC_IRIS_NORMAL = 12;
static final int LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX = 13;
static final int LOC_ALPHA_TEST_REF = 14;
static final int LOC_SCENE_COLOR = 15;
static final int LOC_CLIP_PLANES = 16;
static final int LOC_CLIP_PLANES_ENABLED = 17;
// Light source fields: 12 per light × 2 lights = 24 locations
static final int LIGHT_FIELDS = 12;
static final int LF_AMBIENT = 0, LF_DIFFUSE = 1, LF_SPECULAR = 2, LF_POSITION = 3;
static final int LF_HALF_VECTOR = 4, LF_SPOT_DIRECTION = 5, LF_SPOT_EXPONENT = 6;
static final int LF_SPOT_CUTOFF = 7, LF_SPOT_COS_CUTOFF = 8;
static final int LF_CONSTANT_ATTEN = 9, LF_LINEAR_ATTEN = 10, LF_QUADRATIC_ATTEN = 11;
static final int LOC_LIGHT_BASE = 18;
// Material fields: 5 locations
static final int MF_EMISSION = 0, MF_AMBIENT = 1, MF_DIFFUSE = 2, MF_SPECULAR = 3, MF_SHININESS = 4;
static final int MAT_FIELDS = 5;
static final int LOC_MAT_BASE = LOC_LIGHT_BASE + 2 * LIGHT_FIELDS; // 42
static final int LOC_COUNT = LOC_MAT_BASE + MAT_FIELDS; // 47
private static final String[] LIGHT_FIELD_NAMES = {
"ambient", "diffuse", "specular", "position", "halfVector",
"spotDirection", "spotExponent", "spotCutoff", "spotCosCutoff",
"constantAttenuation", "linearAttenuation", "quadraticAttenuation",
};
private static final String[] MAT_FIELD_NAMES = {
"emission", "ambient", "diffuse", "specular", "shininess",
};
private static final String[] UNIFORM_NAMES;
static {
UNIFORM_NAMES = new String[LOC_COUNT];
UNIFORM_NAMES[LOC_MODELVIEW] = "angelica_ModelViewMatrix";
UNIFORM_NAMES[LOC_MODELVIEW_INVERSE] = "angelica_ModelViewMatrixInverse";
UNIFORM_NAMES[LOC_PROJECTION] = "angelica_ProjectionMatrix";
UNIFORM_NAMES[LOC_PROJECTION_INVERSE] = "angelica_ProjectionMatrixInverse";
UNIFORM_NAMES[LOC_NORMAL] = "angelica_NormalMatrix";
UNIFORM_NAMES[LOC_LIGHTMAP_TEXTURE_MATRIX] = "angelica_LightmapTextureMatrix";
UNIFORM_NAMES[LOC_FOG_DENSITY] = "angelica_FogDensity";
UNIFORM_NAMES[LOC_FOG_START] = "angelica_FogStart";
UNIFORM_NAMES[LOC_FOG_END] = "angelica_FogEnd";
UNIFORM_NAMES[LOC_FOG_COLOR] = "angelica_FogColor";
UNIFORM_NAMES[LOC_IRIS_MODELVIEW] = "iris_ModelViewMatrix";
UNIFORM_NAMES[LOC_IRIS_PROJECTION] = "iris_ProjectionMatrix";
UNIFORM_NAMES[LOC_IRIS_NORMAL] = "iris_NormalMatrix";
UNIFORM_NAMES[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] = "iris_LightmapTextureMatrix";
UNIFORM_NAMES[LOC_ALPHA_TEST_REF] = "angelica_currentAlphaTest";
UNIFORM_NAMES[LOC_SCENE_COLOR] = "angelica_SceneColor";
UNIFORM_NAMES[LOC_CLIP_PLANES] = "angelica_ClipPlane[0]";
UNIFORM_NAMES[LOC_CLIP_PLANES_ENABLED] = "angelica_ClipPlanesEnabled";
for (int li = 0; li < 2; li++) {
for (int fi = 0; fi < LIGHT_FIELDS; fi++) {
UNIFORM_NAMES[LOC_LIGHT_BASE + li * LIGHT_FIELDS + fi] =
"angelica_LightSource[" + li + "]." + LIGHT_FIELD_NAMES[fi];
}
}
for (int fi = 0; fi < MAT_FIELDS; fi++) {
UNIFORM_NAMES[LOC_MAT_BASE + fi] = "angelica_FrontMaterial." + MAT_FIELD_NAMES[fi];
}
}
/** Per-program cached uniform locations. Maps program ID → int[LOC_COUNT]. */
private static final Int2ObjectOpenHashMap<int[]> programLocations = new Int2ObjectOpenHashMap<>();
// Reusable NIO buffers for upload
private static final FloatBuffer mat4Buf = BufferUtils.createFloatBuffer(16);
private static final FloatBuffer mat3Buf = BufferUtils.createFloatBuffer(9);
private static final FloatBuffer vec4Buf = BufferUtils.createFloatBuffer(4);
private static final FloatBuffer clipPlaneBuf = BufferUtils.createFloatBuffer(32); // 8 planes * vec4
private static final Matrix3f normalMatrix = new Matrix3f();
private static final Matrix4f scratchMatrix = new Matrix4f();
private static final float LIGHTMAP_SCALE = 1.0f / 256.0f;
private static final FloatBuffer lightmapMatrixBuf;
static {
lightmapMatrixBuf = BufferUtils.createFloatBuffer(16);
new Matrix4f().scale(LIGHTMAP_SCALE).translate(8.0f, 8.0f, 8.0f).get(lightmapMatrixBuf);
}
// Dirty tracking: skip uploads when state hasn't changed and program is the same
private static int lastProgram = -1;
private static int lastMvGen = -1;
private static int lastProjGen = -1;
private static int lastTexMatGen = -1;
private static int lastFragmentGen = -1;
private static int lastLightingGen = -1;
private static int lastClipPlaneGen = -1;
private CompatUniformManager() {}
public static void onLinkProgram(int program) {
int[] locs = new int[LOC_COUNT];
boolean hasAny = false;
for (int i = 0; i < LOC_COUNT; i++) {
locs[i] = GL20.glGetUniformLocation(program, UNIFORM_NAMES[i]);
if (locs[i] != -1) hasAny = true;
}
if (hasAny) {
programLocations.put(program, locs);
LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
}
}
public static void onUseProgram(int program) {
if (program == 0) return;
int[] locs = programLocations.get(program);
if (locs == null) return;
final boolean programChanged = program != lastProgram;
lastProgram = program;
// Matrix uniforms — skip if generation unchanged and same program
final int mvGen = GLStateManager.mvGeneration;
final int projGen = GLStateManager.projGeneration;
final int texMatGen = GLStateManager.texMatrixGeneration;
final boolean mvChanged = programChanged || mvGen != lastMvGen;
final boolean projChanged = programChanged || projGen != lastProjGen;
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
if (mvChanged || projChanged || texMatChanged) {
uploadMatrices(locs, mvChanged, projChanged, texMatChanged);
lastMvGen = mvGen;
lastProjGen = projGen;
lastTexMatGen = texMatGen;
}
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
final int fragGen = GLStateManager.fragmentGeneration;
if (programChanged || fragGen != lastFragmentGen) {
lastFragmentGen = fragGen;
uploadFragmentUniforms(locs);
}
// Lighting-derived uniforms (scene color, light sources, material)
final int litGen = GLStateManager.lightingGeneration;
if (programChanged || litGen != lastLightingGen) {
lastLightingGen = litGen;
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
uploadLightSources(locs);
uploadMaterial(locs);
}
// Clip plane equations + enabled bool — uploaded when enable state or equations change
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
final int cpGen = GLStateManager.clipPlaneGeneration;
if (programChanged || cpGen != lastClipPlaneGen) {
lastClipPlaneGen = cpGen;
uploadClipPlanes(locs);
}
}
}
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
if (mvChanged) {
// ModelView
if (locs[LOC_MODELVIEW] != -1 || locs[LOC_IRIS_MODELVIEW] != -1) {
mv.get(mat4Buf);
if (locs[LOC_MODELVIEW] != -1) {
GL20.glUniformMatrix4(locs[LOC_MODELVIEW], false, mat4Buf);
}
if (locs[LOC_IRIS_MODELVIEW] != -1) {
GL20.glUniformMatrix4(locs[LOC_IRIS_MODELVIEW], false, mat4Buf);
}
}
// ModelView Inverse
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
mv.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
GL20.glUniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
}
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
mv.normal(normalMatrix);
normalMatrix.get(mat3Buf);
if (locs[LOC_NORMAL] != -1) {
GL20.glUniformMatrix3(locs[LOC_NORMAL], false, mat3Buf);
}
if (locs[LOC_IRIS_NORMAL] != -1) {
GL20.glUniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
}
}
}
if (projChanged) {
// Projection
if (locs[LOC_PROJECTION] != -1 || locs[LOC_IRIS_PROJECTION] != -1) {
proj.get(mat4Buf);
if (locs[LOC_PROJECTION] != -1) {
GL20.glUniformMatrix4(locs[LOC_PROJECTION], false, mat4Buf);
}
if (locs[LOC_IRIS_PROJECTION] != -1) {
GL20.glUniformMatrix4(locs[LOC_IRIS_PROJECTION], false, mat4Buf);
}
}
// Projection Inverse
if (locs[LOC_PROJECTION_INVERSE] != -1) {
proj.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
GL20.glUniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
}
}
if (texMatChanged) {
// Iris lightmap texture matrix (constant)
if (locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] != -1) {
GL20.glUniformMatrix4(locs[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX], false, lightmapMatrixBuf);
}
// angelica_LightmapTextureMatrix — actual GLSM texture unit 1 matrix (set by enableLightmap())
if (locs[LOC_LIGHTMAP_TEXTURE_MATRIX] != -1) {
GLStateManager.getTextures().getTextureUnitMatrix(1).get(mat4Buf);
GL20.glUniformMatrix4(locs[LOC_LIGHTMAP_TEXTURE_MATRIX], false, mat4Buf);
}
}
}
private static void uploadFragmentUniforms(int[] locs) {
// Fog uniforms
FogStateStack fog = GLStateManager.getFogState();
if (locs[LOC_FOG_DENSITY] != -1) {
GL20.glUniform1f(locs[LOC_FOG_DENSITY], Math.max(0.0f, fog.getDensity()));
}
if (locs[LOC_FOG_START] != -1) {
GL20.glUniform1f(locs[LOC_FOG_START], fog.getStart());
}
if (locs[LOC_FOG_END] != -1) {
GL20.glUniform1f(locs[LOC_FOG_END], fog.getEnd());
}
if (locs[LOC_FOG_COLOR] != -1) {
vec4Buf.clear();
vec4Buf.put((float) fog.getFogColor().x).put((float) fog.getFogColor().y)
.put((float) fog.getFogColor().z).put(fog.getFogAlpha());
vec4Buf.flip();
GL20.glUniform4(locs[LOC_FOG_COLOR], vec4Buf);
}
// Alpha test reference — core profile replacement for GL_ALPHA_TEST
// Return -1.0f for ALWAYS or disabled so `alpha <= ref` never discards.
if (locs[LOC_ALPHA_TEST_REF] != -1) {
final float ref;
if (!GLStateManager.getAlphaTest().isEnabled()
|| GLStateManager.getAlphaState().getFunction() == GL11.GL_ALWAYS) {
ref = -1.0f;
} else {
ref = GLStateManager.getAlphaState().getReference();
}
GL20.glUniform1f(locs[LOC_ALPHA_TEST_REF], ref);
}
}
/**
* Scene color = emission + ambient * lightModel.ambient, alpha = diffuse.a
* Same computation as {@code Uniforms.uploadLightingPreComputed()}.
*/
private static void uploadSceneColor(int[] locs) {
final MaterialState mat = GLStateManager.getFrontMaterial();
final LightModelState lm = GLStateManager.getLightModel();
vec4Buf.clear();
vec4Buf.put(mat.emission.x + mat.ambient.x * lm.ambient.x)
.put(mat.emission.y + mat.ambient.y * lm.ambient.y)
.put(mat.emission.z + mat.ambient.z * lm.ambient.z)
.put(mat.diffuse.w);
vec4Buf.flip();
GL20.glUniform4(locs[LOC_SCENE_COLOR], vec4Buf);
}
private static void uploadLightSources(int[] locs) {
// Early exit: if the first light field isn't present, no lighting uniforms exist
if (locs[LOC_LIGHT_BASE] == -1) return;
final LightStateStack[] lights = GLStateManager.getLightDataStates();
for (int li = 0; li < 2; li++) {
final int base = LOC_LIGHT_BASE + li * LIGHT_FIELDS;
final LightState light = lights[li];
if (locs[base + LF_AMBIENT] != -1)
GL20.glUniform4f(locs[base + LF_AMBIENT], light.ambient.x, light.ambient.y, light.ambient.z, light.ambient.w);
if (locs[base + LF_DIFFUSE] != -1)
GL20.glUniform4f(locs[base + LF_DIFFUSE], light.diffuse.x, light.diffuse.y, light.diffuse.z, light.diffuse.w);
if (locs[base + LF_SPECULAR] != -1)
GL20.glUniform4f(locs[base + LF_SPECULAR], light.specular.x, light.specular.y, light.specular.z, light.specular.w);
if (locs[base + LF_POSITION] != -1)
GL20.glUniform4f(locs[base + LF_POSITION], light.position.x, light.position.y, light.position.z, light.position.w);
if (locs[base + LF_HALF_VECTOR] != -1) {
// halfVector = normalize(normalize(position.xyz) + eye), eye=(0,0,1) for infinite viewer
float hx, hy, hz;
if (light.position.w == 0.0f) {
float len = (float) Math.sqrt(light.position.x * light.position.x
+ light.position.y * light.position.y + light.position.z * light.position.z);
if (len > 0) {
hx = light.position.x / len;
hy = light.position.y / len;
hz = light.position.z / len + 1.0f;
} else {
hx = 0; hy = 0; hz = 1;
}
} else {
hx = 0; hy = 0; hz = 1;
}
float hlen = (float) Math.sqrt(hx * hx + hy * hy + hz * hz);
if (hlen > 0) { hx /= hlen; hy /= hlen; hz /= hlen; }
GL20.glUniform4f(locs[base + LF_HALF_VECTOR], hx, hy, hz, 0.0f);
}
if (locs[base + LF_SPOT_DIRECTION] != -1)
GL20.glUniform3f(locs[base + LF_SPOT_DIRECTION], light.spotDirection.x, light.spotDirection.y, light.spotDirection.z);
if (locs[base + LF_SPOT_EXPONENT] != -1)
GL20.glUniform1f(locs[base + LF_SPOT_EXPONENT], light.spotExponent);
if (locs[base + LF_SPOT_CUTOFF] != -1)
GL20.glUniform1f(locs[base + LF_SPOT_CUTOFF], light.spotCutoff);
if (locs[base + LF_SPOT_COS_CUTOFF] != -1)
GL20.glUniform1f(locs[base + LF_SPOT_COS_CUTOFF], light.spotCosCutoff);
if (locs[base + LF_CONSTANT_ATTEN] != -1)
GL20.glUniform1f(locs[base + LF_CONSTANT_ATTEN], light.constantAttenuation);
if (locs[base + LF_LINEAR_ATTEN] != -1)
GL20.glUniform1f(locs[base + LF_LINEAR_ATTEN], light.linearAttenuation);
if (locs[base + LF_QUADRATIC_ATTEN] != -1)
GL20.glUniform1f(locs[base + LF_QUADRATIC_ATTEN], light.quadraticAttenuation);
}
}
private static void uploadMaterial(int[] locs) {
// Early exit: if the first material field isn't present, no material uniforms exist
if (locs[LOC_MAT_BASE] == -1) return;
final MaterialState mat = GLStateManager.getFrontMaterial();
if (locs[LOC_MAT_BASE + MF_EMISSION] != -1)
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_EMISSION], mat.emission.x, mat.emission.y, mat.emission.z, mat.emission.w);
if (locs[LOC_MAT_BASE + MF_AMBIENT] != -1)
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_AMBIENT], mat.ambient.x, mat.ambient.y, mat.ambient.z, mat.ambient.w);
if (locs[LOC_MAT_BASE + MF_DIFFUSE] != -1)
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_DIFFUSE], mat.diffuse.x, mat.diffuse.y, mat.diffuse.z, mat.diffuse.w);
if (locs[LOC_MAT_BASE + MF_SPECULAR] != -1)
GL20.glUniform4f(locs[LOC_MAT_BASE + MF_SPECULAR], mat.specular.x, mat.specular.y, mat.specular.z, mat.specular.w);
if (locs[LOC_MAT_BASE + MF_SHININESS] != -1)
GL20.glUniform1f(locs[LOC_MAT_BASE + MF_SHININESS], mat.shininess);
}
private static void uploadClipPlanes(int[] locs) {
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
GL20.glUniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLStateManager.anyClipPlaneEnabled() ? 1 : 0);
}
if (locs[LOC_CLIP_PLANES] != -1) {
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf);
}
clipPlaneBuf.flip();
GL20.glUniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
}
}
public static void onDeleteProgram(int program) {
programLocations.remove(program);
}
public static boolean hasProgram(int program) {
return programLocations.containsKey(program);
}
public static int[] getLocations(int program) {
return programLocations.get(program);
}
}
@@ -1,187 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
/*
* Ported from Mesa's util_idalloc (src/util/u_idalloc.c).
*
* Original copyright and license:
*
* Copyright 2017 Valve Corporation
* All Rights Reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sub license, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial portions
* of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Original author: Samuel Pitoiset <[email protected]>
*/
/**
* Bitmap ID allocator for display list IDs.
*
* <p>Bitmap-based: each bit in the {@code data} array represents one ID.
* Bit set = allocated, bit clear = free. ID 0 is reserved (never returned).
*/
public class DisplayListIDAllocator {
private int[] data;
private int numElements; // number of uint32 words allocated
private int numSetElements; // highest word index with any bit set + 1
private int lowestFreeIdx; // optimization: skip fully-allocated words
public DisplayListIDAllocator() {
this(32); // 1024 IDs initially
}
public DisplayListIDAllocator(int initialWords) {
if (initialWords < 1) initialWords = 1;
this.data = new int[initialWords];
this.numElements = initialWords;
this.numSetElements = 0;
this.lowestFreeIdx = 0;
// Reserve ID 0 (never returned), matching Mesa's skip_zero pattern
data[0] = 1;
numSetElements = 1;
}
public int allocRange(int count) {
if (count <= 0) throw new IllegalArgumentException("count must be > 0");
if (count == 1) return allocSingle();
return allocMultiple(count);
}
private int allocSingle() {
for (int i = lowestFreeIdx; i < numElements; i++) {
if (data[i] == 0xFFFFFFFF) continue;
int bit = Integer.numberOfTrailingZeros(~data[i]);
data[i] |= 1 << bit;
lowestFreeIdx = i;
numSetElements = Math.max(numSetElements, i + 1);
return i * 32 + bit;
}
// No slots available, grow. Record old end, then mark first bit there.
int oldNumElements = numElements;
resize(Math.max(numElements, 1) * 2);
lowestFreeIdx = oldNumElements;
data[oldNumElements] |= 1;
numSetElements = Math.max(numSetElements, oldNumElements + 1);
return oldNumElements * 32;
}
private int allocMultiple(int num) {
int numWords = divRoundUp(num, 32);
int base = findFreeBlock(lowestFreeIdx);
boolean found = false;
while (!found) {
int i;
for (i = base; i < numElements && i - base < numWords && data[i] == 0; i++)
;
if (i - base == numWords) {
found = true;
} else if (i == numElements) {
// Hit end — base is valid: data[base..end] are zero, resize extends the run into newly zeroed words.
resize(numElements * 2 + numWords);
found = true;
} else {
// Non-zero word at i, skip past it
base = i + 1;
}
}
// Mark bits as used
int fullWords = num / 32;
int remainder = num % 32;
for (int i = base; i < base + fullWords; i++) {
data[i] = 0xFFFFFFFF;
}
if (remainder != 0) {
data[base + fullWords] |= (1 << remainder) - 1;
}
if (lowestFreeIdx == base) {
lowestFreeIdx = base + num / 32;
}
numSetElements = Math.max(numSetElements, base + numWords);
return base * 32;
}
private int findFreeBlock(int start) {
for (int i = start; i < numElements; i++) {
if (data[i] == 0) return i;
}
return numElements;
}
public void free(int id) {
if (id <= 0) return; // ID 0 is reserved
int idx = id / 32;
if (idx >= numElements) return;
lowestFreeIdx = Math.min(idx, lowestFreeIdx);
data[idx] &= ~(1 << (id % 32));
if (numSetElements == idx + 1) {
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
numSetElements--;
}
}
}
public void freeRange(int base, int count) {
if (count <= 0 || base <= 0) return;
int startIdx = base / 32;
if (startIdx >= numElements) return;
int end = base + count;
for (int id = base; id < end; id++) {
int idx = id / 32;
if (idx >= numElements) break;
data[idx] &= ~(1 << (id % 32));
}
lowestFreeIdx = Math.min(startIdx, lowestFreeIdx);
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
numSetElements--;
}
}
public boolean isAllocated(int id) {
if (id <= 0) return false;
int idx = id / 32;
if (idx >= numSetElements) return false;
return (data[idx] & (1 << (id % 32))) != 0;
}
private void resize(int newNumElements) {
if (newNumElements <= numElements) return;
int[] newData = new int[newNumElements];
System.arraycopy(data, 0, newData, 0, numElements);
data = newData;
numElements = newNumElements;
}
private static int divRoundUp(int a, int b) {
return (a + b - 1) / b;
}
}
File diff suppressed because it is too large. Load diff
@@ -2,18 +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.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -23,85 +19,45 @@ 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
, GLStateManager.alphaState // Alpha test function and reference value
, GLStateManager.blendMode // GL_BLEND enable bit
, GLStateManager.blendState // Blending source/destination functions, equation, blend color
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP enable bit
, GLStateManager.ditherState // GL_DITHER enable bit
, GLStateManager.drawBuffer // GL_DRAW_BUFFER setting
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP enable bit
, GLStateManager.logicOpMode // Logic op function
, GLStateManager.blendState // Blending source and destination functions
// Constant blend color
// Blending equation
// GL_DITHER enable bit
// GL_DRAW_BUFFER setting
// GL_COLOR_LOGIC_OP enable bit
// GL_INDEX_LOGIC_OP enable bit
// Logic op function
, GLStateManager.colorMask // Color-mode and index-mode writemasks
, GLStateManager.clearColor // Color-mode and index-mode clear values
));
attribToFeatures.put(GL11.GL_CURRENT_BIT, ImmutableSet.of(
GLStateManager.color // Current RGBA color
, ShaderManager.getNormalStack() // Current normal vector
, ShaderManager.getTexCoordStack() // Current texture coordinates
// Current color index
// Current normal vector
// Current texture coordinates
// Current raster position
// GL_CURRENT_RASTER_POSITION_VALID flag
// RGBA color associated with current raster position
@@ -118,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
@@ -136,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
@@ -247,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
@@ -274,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
@@ -294,12 +233,15 @@ 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
// GL_TEXTURE_ENV_MODE + GL_TEXTURE_ENV_COLOR — now per-unit in TexEnvState (added below)
// Enable bits for the four texture coordinates
// Border color for each texture image
@@ -307,28 +249,26 @@ public class Feature {
// Magnification function for each texture image
// Texture coordinates and wrap mode for each texture image
// Color and mode for each texture environment
// Enable bits GL_TEXTURE_GEN_x, x is S, T, R, and Q
// GL_TEXTURE_GEN_MODE setting for S, T, R, and Q
// glTexGen plane equations for S, T, R, and Q
// Current texture bindings (for example, GL_TEXTURE_BINDING_2D) - Below
));
// Current Texture Bindings - GL_TEXTURE_BINDING_2D + per-unit TexEnvState
// Current Texture Bindings - GL_TEXTURE_BINDING_2D
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
textureAttribs.add(GLStateManager.textures.getTextureUnitBindings(i));
textureAttribs.add(GLStateManager.textures.getTexEnvState(i));
// textureAttribs.add(GLStateManager.textures.getInfo(i))
}
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q
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,359 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import com.gtnewhorizons.angelica.loading.AngelicaTweaker;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
/**
* Software emulation of GL feedback mode
*/
public class FeedbackManager {
private static final int FB_3D = 0x01;
private static final int FB_4D = 0x02;
private static final int FB_COLOR = 0x04;
private static final int FB_TEXTURE = 0x08;
private static int renderMode = GL11.GL_RENDER;
private static FloatBuffer feedbackBuffer;
private static int feedbackBufferSize;
private static int feedbackMask;
private static int feedbackCount;
private static final Matrix4f mvpMatrix = new Matrix4f();
private static final Vector4f clipVec = new Vector4f();
private static ByteBuffer readbackBuf = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder());
private static ByteBuffer indexReadbackBuf = ByteBuffer.allocateDirect(4 * 1024).order(ByteOrder.nativeOrder());
@FunctionalInterface
private interface IndexReader {
int read(int i);
}
public static boolean isFeedbackMode() {
return renderMode == GL11.GL_FEEDBACK;
}
public static void glFeedbackBuffer(int type, FloatBuffer buffer) {
if (renderMode == GL11.GL_FEEDBACK) {
AngelicaTweaker.LOGGER.error("glFeedbackBuffer called while already in feedback mode");
return;
}
feedbackBuffer = buffer;
feedbackBufferSize = buffer.remaining();
feedbackMask = computeMask(type);
feedbackCount = 0;
}
public static int glRenderMode(int mode) {
int result = switch (renderMode) {
case GL11.GL_FEEDBACK -> (feedbackCount > feedbackBufferSize) ? -1 : feedbackCount;
case GL11.GL_RENDER, GL11.GL_SELECT -> 0;
default -> 0;
};
if (mode == GL11.GL_SELECT) {
AngelicaTweaker.LOGGER.warn("glRenderMode(GL_SELECT): selection mode not emulated");
}
feedbackCount = 0;
renderMode = mode;
return result;
}
public static void glPassThrough(float token) {
if (renderMode == GL11.GL_FEEDBACK) {
feedbackToken(GL11.GL_PASS_THROUGH_TOKEN);
feedbackToken(token);
}
}
private static void feedbackToken(float token) {
if (feedbackCount < feedbackBufferSize) {
feedbackBuffer.put(feedbackBuffer.position() + feedbackCount, token);
}
feedbackCount++;
}
private static void feedbackVertex(float winX, float winY, float winZ, float winW) {
feedbackToken(winX);
feedbackToken(winY);
if ((feedbackMask & FB_3D) != 0) feedbackToken(winZ);
if ((feedbackMask & FB_4D) != 0) feedbackToken(winW);
if ((feedbackMask & FB_COLOR) != 0) {
feedbackToken(1f);
feedbackToken(1f);
feedbackToken(1f);
feedbackToken(1f);
}
if ((feedbackMask & FB_TEXTURE) != 0) {
feedbackToken(0f);
feedbackToken(0f);
feedbackToken(0f);
feedbackToken(1f);
}
}
private static void transformAndEmitVertex(float x, float y, float z) {
clipVec.set(x, y, z, 1.0f);
mvpMatrix.transform(clipVec);
final float w = clipVec.w;
final float ndcX = clipVec.x / w;
final float ndcY = clipVec.y / w;
final float ndcZ = clipVec.z / w;
final ViewportState vp = GLStateManager.getViewportState();
final float winX = vp.x + vp.width * (ndcX + 1.0f) * 0.5f;
final float winY = vp.y + vp.height * (ndcY + 1.0f) * 0.5f;
final float winZ = (float) ((vp.depthRangeFar - vp.depthRangeNear) * ndcZ
+ (vp.depthRangeFar + vp.depthRangeNear)) * 0.5f;
final float winW = 1.0f / w;
feedbackVertex(winX, winY, winZ, winW);
}
private static int positionStride() {
final int s = VertexAttribState.get(0).effectiveStride();
return (s != 0) ? s : 12;
}
private static long positionOffset() {
return VertexAttribState.get(0).offset;
}
public static void processDrawArrays(int mode, int first, int count) {
computeMVP();
final int stride = positionStride();
final long posOffset = positionOffset();
final int byteCount = count * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, (long) first * stride + posOffset, readbackBuf);
emitAllPrimitives(mode, count, i -> i, stride);
}
public static void processDrawElements(int mode, int indexCount, int type, long eboOffset) {
computeMVP();
final int stride = positionStride();
final int elementSize = VertexAttribState.Attrib.glTypeSizeBytes(type);
final int indexByteCount = indexCount * elementSize;
ensureIndexReadbackCapacity(indexByteCount);
indexReadbackBuf.clear().limit(indexByteCount);
GL15.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboOffset, indexReadbackBuf);
final ByteBuffer indexBuf = indexReadbackBuf;
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
for (int i = 0; i < indexCount; i++) {
final int idx = ImmediateModeRecorder.readIndex(indexBuf, type, 0, i);
if (idx < minIdx) minIdx = idx;
if (idx > maxIdx) maxIdx = idx;
}
final long posOffset = positionOffset();
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
final int base = minIdx;
emitAllPrimitives(mode, indexCount, i -> ImmediateModeRecorder.readIndex(indexBuf, type, 0, i) - base, stride);
}
public static void processDrawElements(int mode, ByteBuffer indices) {
final int basePos = indices.position();
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFF);
}
public static void processDrawElements(int mode, ShortBuffer indices) {
final int basePos = indices.position();
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i) & 0xFFFF);
}
public static void processDrawElements(int mode, IntBuffer indices) {
final int basePos = indices.position();
processDrawElementsImpl(mode, indices.remaining(), i -> indices.get(basePos + i));
}
public static void processDrawElements(int mode, int count, int type, ByteBuffer indices) {
final int pos = indices.position();
final IndexReader reader = switch (type) {
case GL11.GL_UNSIGNED_BYTE -> i -> indices.get(pos + i) & 0xFF;
case GL11.GL_UNSIGNED_SHORT -> i -> indices.getShort(pos + i * 2) & 0xFFFF;
case GL11.GL_UNSIGNED_INT -> i -> indices.getInt(pos + i * 4);
default -> i -> 0;
};
processDrawElementsImpl(mode, count, reader);
}
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
computeMVP();
final int stride = positionStride();
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
for (int i = 0; i < count; i++) {
final int idx = reader.read(i);
if (idx < minIdx) minIdx = idx;
if (idx > maxIdx) maxIdx = idx;
}
readVerticesFromVBO(minIdx, maxIdx, stride);
final int base = minIdx;
emitAllPrimitives(mode, count, i -> reader.read(i) - base, stride);
}
private static void computeMVP() {
mvpMatrix.set(GLStateManager.getProjectionMatrix()).mul(GLStateManager.getModelViewMatrix());
}
private static int computeMask(int type) {
return switch (type) {
case GL11.GL_2D -> 0;
case GL11.GL_3D -> FB_3D;
case GL11.GL_3D_COLOR -> FB_3D | FB_COLOR;
case GL11.GL_3D_COLOR_TEXTURE -> FB_3D | FB_COLOR | FB_TEXTURE;
case GL11.GL_4D_COLOR_TEXTURE -> FB_3D | FB_4D | FB_COLOR | FB_TEXTURE;
default -> 0;
};
}
private static void ensureReadbackCapacity(int bytes) {
if (readbackBuf.capacity() < bytes) {
readbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
}
}
private static void ensureIndexReadbackCapacity(int bytes) {
if (indexReadbackBuf.capacity() < bytes) {
indexReadbackBuf = ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
}
}
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride) {
final long posOffset = positionOffset();
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
GL15.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
}
private static void emitVertex(int vertexIndex, int stride) {
final float x = readbackBuf.getFloat(vertexIndex * stride);
final float y = readbackBuf.getFloat(vertexIndex * stride + 4);
final float z = readbackBuf.getFloat(vertexIndex * stride + 8);
transformAndEmitVertex(x, y, z);
}
private static void emitAllPrimitives(int mode, int count, IndexReader reader, int stride) {
switch (mode) {
case GL11.GL_POINTS:
for (int i = 0; i < count; i++) {
feedbackToken(GL11.GL_POINT_TOKEN);
emitVertex(reader.read(i), stride);
}
break;
case GL11.GL_LINES:
for (int i = 0; i + 1 < count; i += 2) {
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
break;
case GL11.GL_LINE_STRIP:
for (int i = 0; i + 1 < count; i++) {
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
break;
case GL11.GL_LINE_LOOP:
for (int i = 0; i + 1 < count; i++) {
feedbackToken(i == 0 ? GL11.GL_LINE_RESET_TOKEN : GL11.GL_LINE_TOKEN);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
if (count > 2) {
feedbackToken(GL11.GL_LINE_TOKEN);
emitVertex(reader.read(count - 1), stride);
emitVertex(reader.read(0), stride);
}
break;
case GL11.GL_TRIANGLES:
for (int i = 0; i + 2 < count; i += 3) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(3);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i + 2), stride);
}
break;
case GL11.GL_TRIANGLE_STRIP:
for (int i = 0; i + 2 < count; i++) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(3);
if ((i & 1) == 0) {
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
} else {
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i), stride);
}
emitVertex(reader.read(i + 2), stride);
}
break;
case GL11.GL_TRIANGLE_FAN:
for (int i = 1; i + 1 < count; i++) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(3);
emitVertex(reader.read(0), stride);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
}
break;
case GL11.GL_QUADS:
for (int i = 0; i + 3 < count; i += 4) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(4);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i + 2), stride);
emitVertex(reader.read(i + 3), stride);
}
break;
default:
throw new UnsupportedOperationException("Feedback mode: unsupported primitive mode 0x" + Integer.toHexString(mode));
}
}
static void reset() {
renderMode = GL11.GL_RENDER;
feedbackBuffer = null;
feedbackBufferSize = 0;
feedbackMask = 0;
feedbackCount = 0;
}
}
@@ -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,355 +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);
};
}
public static String getFramebufferStatusName(int status) {
return switch (status) {
case GL30.GL_FRAMEBUFFER_COMPLETE -> "COMPLETE";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT -> "INCOMPLETE_ATTACHMENT";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT -> "INCOMPLETE_MISSING_ATTACHMENT";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER -> "INCOMPLETE_DRAW_BUFFER";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER -> "INCOMPLETE_READ_BUFFER";
case GL30.GL_FRAMEBUFFER_UNSUPPORTED -> "UNSUPPORTED";
case GL30.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE -> "INCOMPLETE_MULTISAMPLE";
case 0 -> "NO_FRAMEBUFFER_BOUND";
default -> String.format("UNKNOWN(0x%X)", status);
};
}
public static String getInternalFormatName(int format) {
return switch (format) {
// Basic
case GL11.GL_RGBA -> "RGBA";
case GL11.GL_RGB -> "RGB";
// 8-bit normalized
case GL30.GL_R8 -> "R8";
case GL30.GL_RG8 -> "RG8";
case GL11.GL_RGB8 -> "RGB8";
case GL11.GL_RGBA8 -> "RGBA8";
// 8-bit signed normalized
case 0x8F94 -> "R8_SNORM";
case 0x8F95 -> "RG8_SNORM";
case 0x8F96 -> "RGB8_SNORM";
case 0x8F97 -> "RGBA8_SNORM";
// 16-bit normalized
case GL30.GL_R16 -> "R16";
case GL30.GL_RG16 -> "RG16";
case GL11.GL_RGB16 -> "RGB16";
case GL11.GL_RGBA16 -> "RGBA16";
// 16-bit float
case GL30.GL_R16F -> "R16F";
case GL30.GL_RG16F -> "RG16F";
case GL30.GL_RGB16F -> "RGB16F";
case GL30.GL_RGBA16F -> "RGBA16F";
// 32-bit float
case GL30.GL_R32F -> "R32F";
case GL30.GL_RG32F -> "RG32F";
case GL30.GL_RGB32F -> "RGB32F";
case GL30.GL_RGBA32F -> "RGBA32F";
// Packed
case GL30.GL_R11F_G11F_B10F -> "R11F_G11F_B10F";
case GL11.GL_RGB10_A2 -> "RGB10_A2";
default -> String.format("0x%X", format);
};
}
private static DebugState debugState;
private interface DebugState {
@@ -620,7 +285,6 @@ public final class GLDebug {
private int depth = 0;
private static final int maxDepth = GL11.glGetInteger(KHRDebug.GL_MAX_DEBUG_GROUP_STACK_DEPTH);
private static final int maxNameLength = GL11.glGetInteger(KHRDebug.GL_MAX_LABEL_LENGTH);
private final String[] groupStack = new String[maxDepth + 1];
@Override
public void nameObject(int id, int object, String name) {
@@ -629,28 +293,20 @@ public final class GLDebug {
@Override
public void pushGroup(String name) {
if (depth + 1 > maxDepth) {
final StringBuilder sb = new StringBuilder();
sb.append("Debug group stack overflow (maxDepth=").append(maxDepth).append("), current stack:\n");
for (int i = 0; i < depth; i++) {
sb.append(" [").append(i).append("] ").append(groupStack[i]).append('\n');
}
sb.append(" -> ").append(name).append(" (rejected)");
throw new RuntimeException(sb.toString());
}
groupStack[depth] = name;
KHRDebug.glPushDebugGroup(KHRDebug.GL_DEBUG_SOURCE_APPLICATION, ID, name);
depth++;
if (depth > maxDepth) {
throw new RuntimeException("Stack overflow");
}
KHRDebug.glPushDebugGroup(KHRDebug.GL_DEBUG_SOURCE_APPLICATION, ID, name);
}
@Override
public void popGroup() {
if (depth - 1 < 0) {
depth--;
if (depth < 0) {
throw new RuntimeException("Stack underflow");
}
KHRDebug.glPopDebugGroup();
depth--;
groupStack[depth] = null;
}
@Override
File diff suppressed because it is too large. Load diff
@@ -1,111 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Shared GLSL shader transformation utilities.
*/
public class GlslTransformUtils {
private static final String RENAMED_PREFIX = "angelica_renamed_";
/** Texture function renames */
public static final Map<String, String> TEXTURE_RENAMES = Map.ofEntries(
Map.entry("texture2D", "texture"),
Map.entry("texture3D", "texture"),
Map.entry("texture2DLod", "textureLod"),
Map.entry("texture3DLod", "textureLod"),
Map.entry("texture2DProj", "textureProj"),
Map.entry("texture3DProj", "textureProj"),
Map.entry("texture2DGrad", "textureGrad"),
Map.entry("texture2DGradARB", "textureGrad"),
Map.entry("texture3DGrad", "textureGrad"),
Map.entry("texelFetch2D", "texelFetch"),
Map.entry("texelFetch3D", "texelFetch"),
Map.entry("textureSize2D", "textureSize")
);
private static final Pattern TEXTURE_PATTERN = Pattern.compile("\\btexture\\s*\\(|(\\btexture\\b)");
/** Reserved words added in later GLSL versions that may appear as identifiers in older shaders. */
private record ReservedWordRename(Pattern pattern, String replacement) {}
private static final Map<Integer, List<ReservedWordRename>> VERSIONED_RESERVED_WORDS = Map.of(
400, List.of(new ReservedWordRename(Pattern.compile("\\bsample\\b"), RENAMED_PREFIX + "sample"))
);
public static String replaceTexture(String input) {
final Matcher matcher = TEXTURE_PATTERN.matcher(input);
final StringBuilder builder = new StringBuilder();
while (matcher.find()) {
if (matcher.group(1) != null) {
matcher.appendReplacement(builder, RENAMED_PREFIX + "texture");
} else {
matcher.appendReplacement(builder, Matcher.quoteReplacement(matcher.group(0)));
}
}
matcher.appendTail(builder);
return builder.toString();
}
public static String renameReservedWords(String source, int targetVersion) {
for (var entry : VERSIONED_RESERVED_WORDS.entrySet()) {
if (targetVersion < entry.getKey()) {
for (var rename : entry.getValue()) {
source = rename.pattern().matcher(source).replaceAll(rename.replacement());
}
}
}
return source;
}
public static String restoreReservedWords(String source) {
source = source.replace(RENAMED_PREFIX + "texture", "texture");
source = source.replace(RENAMED_PREFIX + "sample", "sample");
return source;
}
public static String getFormattedShader(ParseTree tree, String header) {
final StringBuilder sb = new StringBuilder(header + "\n");
final String[] tabHolder = {""};
getFormattedShader(tree, sb, tabHolder);
return sb.toString();
}
private static void getFormattedShader(ParseTree tree, StringBuilder stringBuilder, String[] tabHolder) {
if (tree instanceof TerminalNode) {
final String text = tree.getText();
if (text.equals("<EOF>")) {
return;
}
if (text.equals("#")) {
stringBuilder.append("\n#");
return;
}
stringBuilder.append(text);
if (text.equals("{")) {
stringBuilder.append(" \n\t");
tabHolder[0] = "\t";
}
if (text.equals("}")) {
if (stringBuilder.length() >= 2) {
stringBuilder.deleteCharAt(stringBuilder.length() - 2);
}
tabHolder[0] = "";
stringBuilder.append(" \n");
} else {
stringBuilder.append(text.equals(";") ? " \n" + tabHolder[0] : " ");
}
} else {
for (int i = 0; i < tree.getChildCount(); ++i) {
getFormattedShader(tree.getChild(i), stringBuilder, tabHolder);
}
}
}
}
@@ -1,294 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetShort;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutShort;
/**
* Shared quad-to-triangle EBO for core profile GL_QUADS emulation.
*
* <p>Maintains a single growable element buffer with pre-computed indices mapping
* GL_QUADS vertex order to GL_TRIANGLES: {@code (0,1,3, 1,2,3)} per quad.
* Uses {@code GL_LAST_VERTEX_CONVENTION} winding (Mesa {@code do_quad} LAST path)
* so both emitted triangles share the quad's provoking vertex (v3).
*/
public final class QuadConverter {
public static final int INDEX_TYPE = GL11.GL_UNSIGNED_INT;
private static int eboId;
private static int maxQuads;
/** Scratch EBO for converted quad-element draws (drawQuadElementsAsTriangles). */
private static int scratchEboId;
private static int scratchEboCapacity; // in bytes
private QuadConverter() {}
private static void ensureCapacity(int quadCount) {
if (quadCount <= maxQuads) return;
// Round up to power of 2 for amortized growth
int newMaxQuads = Math.max(256, maxQuads);
while (newMaxQuads < quadCount) {
newMaxQuads *= 2;
}
if (eboId == 0) {
eboId = GL15.glGenBuffers();
}
// Generate index data: 6 ints per quad
final ByteBuffer indexData = memAlloc(newMaxQuads * 6 * 4);
long ptr = memAddress0(indexData);
for (int i = 0; i < newMaxQuads; i++) {
final int base = i * 4;
// GL_LAST_VERTEX_CONVENTION: both triangles share v3 as provoking vertex
// Triangle 1: 0, 1, 3
memPutInt(ptr, base);
memPutInt(ptr + 4, base + 1);
memPutInt(ptr + 8, base + 3);
// Triangle 2: 1, 2, 3
memPutInt(ptr + 12, base + 1);
memPutInt(ptr + 16, base + 2);
memPutInt(ptr + 20, base + 3);
ptr += 24;
}
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indexData, GL15.GL_STATIC_DRAW);
memFree(indexData);
maxQuads = newMaxQuads;
}
/**
* Convert a GL_QUADS glDrawArrays call to indexed GL_TRIANGLES.
* Binds the shared EBO to the current VAO and issues glDrawElements.
*
* @param first first vertex index (must be aligned to quad boundary, i.e. multiple of 4)
* @param vertexCount number of vertices (must be multiple of 4)
*/
public static void drawQuadsAsTriangles(int first, int vertexCount) {
assert first % 4 == 0 : "QuadConverter: first (" + first + ") must be a multiple of 4";
assert vertexCount % 4 == 0 : "QuadConverter: vertexCount (" + vertexCount + ") must be a multiple of 4";
final int quadCount = vertexCount / 4;
final int prevEbo = GLStateManager.getBoundEBO();
ensureCapacity(first / 4 + quadCount);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
final long indexOffset = (long) (first / 4) * 6 * 4;
GL11.glDrawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
}
/**
* Upload converted triangle indices to the scratch EBO and draw.
* Saves/restores the caller's EBO binding so subsequent indexed draws
* (or glGetBufferSubData reads) still see the original EBO.
*
* @param dst off-heap buffer with triangle index data (freed by this method)
* @param triIndexCount number of triangle indices
* @param indexType GL_UNSIGNED_INT or GL_UNSIGNED_SHORT
* @param bytesPerIndex bytes per index element
*/
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
final int needed = triIndexCount * bytesPerIndex;
final int prevEbo = GLStateManager.getBoundEBO();
if (scratchEboId == 0) {
scratchEboId = GL15.glGenBuffers();
}
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
if (needed > scratchEboCapacity) {
// Power-of-2 growth — allocate full capacity, upload actual data
int newCap = Math.max(4096, scratchEboCapacity);
while (newCap < needed) newCap *= 2;
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
scratchEboCapacity = newCap;
}
GL15.glBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
GL11.glDrawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
memFree(dst);
}
/**
* Convert an indexed GL_QUADS draw to GL_TRIANGLES by reading the source EBO,
* building a triangle index buffer, and issuing the converted draw.
*
* @param indexCount number of quad indices (must be multiple of 4)
* @param type GL_UNSIGNED_INT, GL_UNSIGNED_SHORT, or GL_UNSIGNED_BYTE
* @param offset byte offset into the currently bound EBO
*/
public static void drawQuadElementsAsTriangles(int indexCount, int type, long offset) {
if (indexCount == 0) return;
assert indexCount % 4 == 0 : "QuadConverter: indexCount must be multiple of 4";
final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6;
final int bytesPerIndex = VertexAttribState.Attrib.glTypeSizeBytes(type);
// Read source indices from caller's EBO
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
GL15.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
final long srcAddr = memAddress0(src);
// Allocate output as GL_UNSIGNED_INT
final ByteBuffer dst = memAlloc(triIndexCount * 4);
final long dstAddr = memAddress0(dst);
for (int i = 0; i < quadCount; i++) {
final int a, b, c, d;
switch (type) {
case GL11.GL_UNSIGNED_INT -> {
final long p = srcAddr + (long) i * 16;
a = memGetInt(p); b = memGetInt(p + 4); c = memGetInt(p + 8); d = memGetInt(p + 12);
}
case GL11.GL_UNSIGNED_SHORT -> {
final long p = srcAddr + (long) i * 8;
a = memGetShort(p) & 0xFFFF; b = memGetShort(p + 2) & 0xFFFF;
c = memGetShort(p + 4) & 0xFFFF; d = memGetShort(p + 6) & 0xFFFF;
}
default /* GL_UNSIGNED_BYTE */ -> {
final long p = srcAddr + (long) i * 4;
a = memGetByte(p) & 0xFF; b = memGetByte(p + 1) & 0xFF;
c = memGetByte(p + 2) & 0xFF; d = memGetByte(p + 3) & 0xFF;
}
}
// GL_LAST_VERTEX_CONVENTION: (a,b,d), (b,c,d)
final long o = (long) i * 24;
memPutInt(dstAddr + o, a);
memPutInt(dstAddr + o + 4, b);
memPutInt(dstAddr + o + 8, d);
memPutInt(dstAddr + o + 12, b);
memPutInt(dstAddr + o + 16, c);
memPutInt(dstAddr + o + 20, d);
}
memFree(src);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
* Convert a client-side IntBuffer of quad indices to triangles.
*/
public static void drawQuadElementsAsTriangles(IntBuffer indices) {
final int indexCount = indices.remaining();
if (indexCount == 0) return;
assert indexCount % 4 == 0;
final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6;
final ByteBuffer dst = memAlloc(triIndexCount * 4);
final long dstAddr = memAddress0(dst);
final int basePos = indices.position();
for (int i = 0; i < quadCount; i++) {
final int base = basePos + i * 4;
final int a = indices.get(base), b = indices.get(base + 1), c = indices.get(base + 2), d = indices.get(base + 3);
final long o = (long) i * 24;
memPutInt(dstAddr + o, a); memPutInt(dstAddr + o + 4, b); memPutInt(dstAddr + o + 8, d);
memPutInt(dstAddr + o + 12, b); memPutInt(dstAddr + o + 16, c); memPutInt(dstAddr + o + 20, d);
}
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
* Convert a client-side ShortBuffer of quad indices to triangles.
*/
public static void drawQuadElementsAsTriangles(ShortBuffer indices) {
final int indexCount = indices.remaining();
if (indexCount == 0) return;
assert indexCount % 4 == 0;
final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6;
final ByteBuffer dst = memAlloc(triIndexCount * 2);
final long dstAddr = memAddress0(dst);
final int basePos = indices.position();
for (int i = 0; i < quadCount; i++) {
final int base = basePos + i * 4;
final short a = indices.get(base), b = indices.get(base + 1), c = indices.get(base + 2), d = indices.get(base + 3);
final long o = (long) i * 12;
memPutShort(dstAddr + o, a); memPutShort(dstAddr + o + 2, b); memPutShort(dstAddr + o + 4, d);
memPutShort(dstAddr + o + 6, b); memPutShort(dstAddr + o + 8, c); memPutShort(dstAddr + o + 10, d);
}
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
}
/**
* Convert a client-side ByteBuffer of quad indices (type-agnostic) to triangles.
*/
public static void drawQuadElementsAsTriangles(int count, int type, ByteBuffer indices) {
if (count == 0) return;
assert count % 4 == 0;
final int quadCount = count / 4;
final int triIndexCount = quadCount * 6;
final long srcAddr = memAddress0(indices) + indices.position();
final ByteBuffer dst = memAlloc(triIndexCount * 4);
final long dstAddr = memAddress0(dst);
for (int i = 0; i < quadCount; i++) {
final int a, b, c, d;
switch (type) {
case GL11.GL_UNSIGNED_INT -> {
final long p = srcAddr + (long) i * 16;
a = memGetInt(p); b = memGetInt(p + 4); c = memGetInt(p + 8); d = memGetInt(p + 12);
}
case GL11.GL_UNSIGNED_SHORT -> {
final long p = srcAddr + (long) i * 8;
a = memGetShort(p) & 0xFFFF; b = memGetShort(p + 2) & 0xFFFF;
c = memGetShort(p + 4) & 0xFFFF; d = memGetShort(p + 6) & 0xFFFF;
}
case GL11.GL_UNSIGNED_BYTE -> {
final long p = srcAddr + (long) i * 4;
a = memGetByte(p) & 0xFF; b = memGetByte(p + 1) & 0xFF;
c = memGetByte(p + 2) & 0xFF; d = memGetByte(p + 3) & 0xFF;
}
default -> { memFree(dst); return; }
}
final long o = (long) i * 24;
memPutInt(dstAddr + o, a); memPutInt(dstAddr + o + 4, b); memPutInt(dstAddr + o + 8, d);
memPutInt(dstAddr + o + 12, b); memPutInt(dstAddr + o + 16, c); memPutInt(dstAddr + o + 20, d);
}
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
* Clean up the shared EBO.
*/
public static void destroy() {
if (eboId != 0) {
GL15.glDeleteBuffers(eboId);
eboId = 0;
maxQuads = 0;
}
if (scratchEboId != 0) {
GL15.glDeleteBuffers(scratchEboId);
scratchEboId = 0;
scratchEboCapacity = 0;
}
}
}
@@ -1,37 +1,23 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.dsa.DSAAccess;
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
import com.gtnewhorizons.angelica.glsm.dsa.DSAAccess;
import com.gtnewhorizons.angelica.glsm.dsa.DSACore;
import com.gtnewhorizons.angelica.glsm.dsa.DSAEXT;
import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import net.coderbot.iris.gl.shader.StandardMacros;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Vector3i;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBBufferStorage;
import org.lwjgl.opengl.ARBClearBufferObject;
import org.lwjgl.opengl.ARBClearTexture;
import org.lwjgl.opengl.ARBShaderImageLoadStore;
import org.lwjgl.opengl.ARBShaderStorageBufferObject;
import org.lwjgl.opengl.EXTShaderImageLoadStore;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL42;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.GL44;
import org.lwjgl.opengl.NVXGpuMemoryInfo;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
@@ -44,29 +30,18 @@ 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 supportsTesselation;
private static boolean supportsSamplerObjects;
private static int maxImageUnits;
private static int maxSSBOBindings;
private static int maxGlslVersion;
private static boolean supportsGpuShader4;
// Sampler object state tracking (null if unsupported)
private static int[] samplers;
private RenderSystem() {}
public static void initRenderer() {
try {
if (GLStateManager.capabilities.OpenGL45) {
dsaState = (Runtime.version().feature() > 8 && GLStateManager.capabilities.GL_EXT_direct_state_access) ? new DSAEXT() : new DSACore();
dsaState = new DSACore();
LOGGER.info("OpenGL 4.5 detected, enabling DSA.");
}
hasMultibind = GLStateManager.capabilities.OpenGL45;
} catch (NoSuchFieldError ignored) {}
try {
@@ -80,60 +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();
supportsTesselation = GLStateManager.capabilities.GL_ARB_tessellation_shader || GLStateManager.capabilities.OpenGL40;
supportsImageLoadStore = GLStateManager.capabilities.OpenGL42 || GLStateManager.capabilities.GL_ARB_shader_image_load_store || GLStateManager.capabilities.GL_EXT_shader_image_load_store;
supportsSSBO = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_shader_storage_buffer_object;
supportsBufferStorage = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_buffer_storage;
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
// Cache maximum image units
if (supportsImageLoadStore) {
if (GLStateManager.capabilities.OpenGL42) {
maxImageUnits = GL11.glGetInteger(GL42.GL_MAX_IMAGE_UNITS);
} else if (GLStateManager.capabilities.GL_ARB_shader_image_load_store) {
maxImageUnits = GL11.glGetInteger(ARBShaderImageLoadStore.GL_MAX_IMAGE_UNITS);
} else {
maxImageUnits = GL11.glGetInteger(EXTShaderImageLoadStore.GL_MAX_IMAGE_UNITS_EXT);
}
} else {
maxImageUnits = 0;
}
// Cache maximum SSBO bindings
if (supportsSSBO) {
if (GLStateManager.capabilities.OpenGL43) {
maxSSBOBindings = GL11.glGetInteger(GL43.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
} else {
maxSSBOBindings = GL11.glGetInteger(ARBShaderStorageBufferObject.GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS);
}
} else {
maxSSBOBindings = 0;
}
// Check for sampler objects support (GL 3.3+ or ARB extension)
supportsSamplerObjects = GLStateManager.capabilities.OpenGL33
|| GLStateManager.capabilities.GL_ARB_sampler_objects;
if (supportsSamplerObjects) {
samplers = new int[GL11.glGetInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)];
}
maxGlslVersion = Integer.parseInt(StandardMacros.getGlVersion(GL20.GL_SHADING_LANGUAGE_VERSION));
supportsGpuShader4 = GLStateManager.capabilities.GL_EXT_gpu_shader4;
LOGGER.info("Max GLSL version: {}, GPU Shader4: {}", maxGlslVersion, supportsGpuShader4);
LOGGER.info("Image Load/Store: {}, Max Image Units: {}", supportsImageLoadStore, maxImageUnits);
LOGGER.info("SSBO: {}, Max SSBO Bindings: {}", supportsSSBO, maxSSBOBindings);
LOGGER.info("Buffer Storage: {}, Clear Texture: {}, Sampler Objects: {}", supportsBufferStorage, supportsClearTexture, supportsSamplerObjects);
if (GLStateManager.capabilities.OpenGL32) {
final int profileMask = GL11.glGetInteger(GL32.GL_CONTEXT_PROFILE_MASK);
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
ShaderManager.getInstance().enable();
}
}
}
public static void generateMipmaps(int texture, int mipmapTarget) {
@@ -145,11 +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) {
GLStateManager.glBindTexture(target, texture);
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
if (target == GL11.GL_TEXTURE_2D && level == 0) {
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
}
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) {
@@ -157,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) {
@@ -180,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);
}
@@ -214,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));
}
@@ -289,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);
@@ -302,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) {
@@ -337,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) {
GLStateManager.glBindBuffer(target, buffer);
}
public static void memoryBarrier(int barriers) {
if (supportsCompute) {
GL42.glMemoryBarrier(barriers);
@@ -357,12 +225,10 @@ public class RenderSystem {
public static void disableBufferBlend(int buffer) {
GL30.glDisablei(GL11.GL_BLEND, buffer);
GLStateManager.getBlendMode().setUnknownState();
}
public static void enableBufferBlend(int buffer) {
GL30.glEnablei(GL11.GL_BLEND, buffer);
GLStateManager.getBlendMode().setUnknownState();
}
public static void blendFuncSeparatei(int buffer, int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) {
@@ -373,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) {
@@ -412,122 +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) {
final ByteBuffer buf = BufferUtils.createByteBuffer(data.length * 4);
buf.asIntBuffer().put(data);
if (GLStateManager.capabilities.OpenGL43) {
GL43.glClearBufferSubData(target, internalFormat, offset, size, format, type, buf);
} else if (GLStateManager.capabilities.GL_ARB_clear_buffer_object) {
ARBClearBufferObject.glClearBufferSubData(target, internalFormat, offset, size, format, type, buf);
}
}
public static void deleteBuffers(int buffer) {
GL15.glDeleteBuffers(buffer);
}
public static long getVRAM() {
if (GLStateManager.capabilities.GL_NVX_gpu_memory_info) {
return GL11.glGetInteger(NVXGpuMemoryInfo.GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX) * 1024L;
} else {
// Default to 4GB if we can't query VRAM
return 4294967296L;
}
}
public static void clearTexImage(int texture, int target, int level, int format, int type) {
if (GLStateManager.capabilities.OpenGL44) {
GL44.glClearTexImage(texture, level, format, type, (ByteBuffer) null);
} else {
ARBClearTexture.glClearTexImage(texture, level, format, type, (ByteBuffer) null);
}
}
public static void textureStorage1D(int texture, int target, int levels, int internalFormat, int width) {
dsaState.textureStorage1D(texture, target, levels, internalFormat, width);
}
public static void textureStorage2D(int texture, int target, int levels, int internalFormat, int width, int height) {
dsaState.textureStorage2D(texture, target, levels, internalFormat, width, height);
}
public static void textureStorage3D(int texture, int target, int levels, int internalFormat, int width, int height, int depth) {
dsaState.textureStorage3D(texture, target, levels, internalFormat, width, height, depth);
}
public static int getMaxGlslVersion() {
return maxGlslVersion;
}
public static boolean supportsGpuShader4() {
return supportsGpuShader4;
}
public static boolean supportsSnormFormats() {
return GLStateManager.capabilities.OpenGL31;
}
public static boolean supportsPackedFloatRenderable() {
return GLStateManager.capabilities.OpenGL30 && maxGlslVersion >= 130;
}
public static boolean supportsTesselation() {
return supportsTesselation;
}
public static boolean supportsSamplerObjects() {
return supportsSamplerObjects;
}
public static int genSampler() {
if (!supportsSamplerObjects) return 0;
return GL33.glGenSamplers();
}
public static void destroySampler(int sampler) {
if (!supportsSamplerObjects || sampler == 0) return;
GL33.glDeleteSamplers(sampler);
}
public static void samplerParameteri(int sampler, int pname, int param) {
if (!supportsSamplerObjects || sampler == 0) return;
GL33.glSamplerParameteri(sampler, pname, param);
}
public static void bindSamplerToUnit(int unit, int sampler) {
if (!supportsSamplerObjects) return;
if (samplers[unit] == sampler) return;
GL33.glBindSampler(unit, sampler);
samplers[unit] = sampler;
}
public static void unbindAllSamplers() {
if (!supportsSamplerObjects) return;
for (int i = 0; i < samplers.length; i++) {
if (samplers[i] != 0) {
GL33.glBindSampler(i, 0);
samplers[i] = 0;
}
}
}
}
@@ -1,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,142 +0,0 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.Cylinder;
import static org.lwjgl.opengl.GL11.GL_LINES;
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_INSIDE;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
/**
* Core-profile compatible replacement for {@link Cylinder}.
*/
@SuppressWarnings("unused")
public class AngelicaCylinder extends Cylinder {
private static final float PI = (float) Math.PI;
private void emitNormal(float x, float y, float z) {
final float mag = (float) Math.sqrt(x * x + y * y + z * z);
if (mag > 0.00001F) {
x /= mag;
y /= mag;
z /= mag;
}
GLStateManager.glNormal3f(x, y, z);
}
private void emitTexCoord(float x, float y) {
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
}
@Override
public void draw(float baseRadius, float topRadius, float height, int slices, int stacks) {
final float da, dr, dz, nz, nsign;
float r, x, y, z;
nsign = (super.orientation == GLU_INSIDE) ? -1.0f : 1.0f;
da = 2.0f * PI / slices;
dr = (topRadius - baseRadius) / stacks;
dz = height / stacks;
nz = (baseRadius - topRadius) / height;
if (super.drawStyle == GLU_POINT) {
GLStateManager.glBegin(GL_POINTS);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
z = 0.0f;
r = baseRadius;
for (int j = 0; j <= stacks; j++) {
GLStateManager.glVertex3f(x * r, y * r, z);
z += dz;
r += dr;
}
}
GLStateManager.glEnd();
} else if (super.drawStyle == GLU_LINE || super.drawStyle == GLU_SILHOUETTE) {
// Draw rings
if (super.drawStyle == GLU_LINE) {
z = 0.0f;
r = baseRadius;
for (int j = 0; j <= stacks; j++) {
GLStateManager.glBegin(GL_LINE_LOOP);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * r, y * r, z);
}
GLStateManager.glEnd();
z += dz;
r += dr;
}
} else {
// draw one ring at each end
if (baseRadius != 0.0) {
GLStateManager.glBegin(GL_LINE_LOOP);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * baseRadius, y * baseRadius, 0.0f);
}
GLStateManager.glEnd();
GLStateManager.glBegin(GL_LINE_LOOP);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * topRadius, y * topRadius, height);
}
GLStateManager.glEnd();
}
}
// draw length lines
GLStateManager.glBegin(GL_LINES);
for (int i = 0; i < slices; i++) {
x = cos(i * da);
y = sin(i * da);
emitNormal(x * nsign, y * nsign, nz * nsign);
GLStateManager.glVertex3f(x * baseRadius, y * baseRadius, 0.0f);
GLStateManager.glVertex3f(x * topRadius, y * topRadius, height);
}
GLStateManager.glEnd();
} else if (super.drawStyle == GLU_FILL) {
final float ds = 1.0f / slices;
final float dt = 1.0f / stacks;
float t = 0.0f;
z = 0.0f;
r = baseRadius;
for (int j = 0; j < stacks; j++) {
float s = 0.0f;
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int i = 0; i <= slices; i++) {
final float angle = (i == slices) ? 0.0f : i * da;
x = sin(angle);
y = cos(angle);
emitNormal(x * nsign, y * nsign, nz * nsign);
emitTexCoord(s, t);
GLStateManager.glVertex3f(x * r, y * r, z);
emitNormal(x * nsign, y * nsign, nz * nsign);
emitTexCoord(s, t + dt);
GLStateManager.glVertex3f(x * (r + dr), y * (r + dr), z + dz);
s += ds;
}
GLStateManager.glEnd();
r += dr;
t += dt;
z += dz;
}
}
}
}
@@ -1,134 +0,0 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.Disk;
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_NONE;
import static org.lwjgl.util.glu.GLU.GLU_OUTSIDE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
/**
* Core-profile compatible replacement for {@link Disk}.
*/
@SuppressWarnings("unused")
public class AngelicaDisk extends Disk {
private static final float PI = (float) Math.PI;
private void emitTexCoord(float x, float y) {
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
}
@Override
public void draw(float innerRadius, float outerRadius, int slices, int loops) {
final float da, dr;
if (super.normals != GLU_NONE) {
GLStateManager.glNormal3f(0.0f, 0.0f, (super.orientation == GLU_OUTSIDE) ? 1.0f : -1.0f);
}
da = 2.0f * PI / slices;
dr = (outerRadius - innerRadius) / loops;
switch (super.drawStyle) {
case GLU_FILL -> {
final float dtc = 2.0f * outerRadius;
float sa, ca;
float r1 = innerRadius;
for (int l = 0; l < loops; l++) {
final float r2 = r1 + dr;
if (super.orientation == GLU_OUTSIDE) {
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int s = 0; s <= slices; s++) {
final float a = (s == slices) ? 0.0f : s * da;
sa = sin(a);
ca = cos(a);
emitTexCoord(0.5f + sa * r2 / dtc, 0.5f + ca * r2 / dtc);
GLStateManager.glVertex2f(r2 * sa, r2 * ca);
emitTexCoord(0.5f + sa * r1 / dtc, 0.5f + ca * r1 / dtc);
GLStateManager.glVertex2f(r1 * sa, r1 * ca);
}
GLStateManager.glEnd();
} else {
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int s = slices; s >= 0; s--) {
final float a = (s == slices) ? 0.0f : s * da;
sa = sin(a);
ca = cos(a);
emitTexCoord(0.5f - sa * r2 / dtc, 0.5f + ca * r2 / dtc);
GLStateManager.glVertex2f(r2 * sa, r2 * ca);
emitTexCoord(0.5f - sa * r1 / dtc, 0.5f + ca * r1 / dtc);
GLStateManager.glVertex2f(r1 * sa, r1 * ca);
}
GLStateManager.glEnd();
}
r1 = r2;
}
}
case GLU_LINE -> {
/* draw loops */
for (int l = 0; l <= loops; l++) {
final float r = innerRadius + l * dr;
GLStateManager.glBegin(GL_LINE_LOOP);
for (int s = 0; s < slices; s++) {
final float a = s * da;
GLStateManager.glVertex2f(r * sin(a), r * cos(a));
}
GLStateManager.glEnd();
}
/* draw spokes */
for (int s = 0; s < slices; s++) {
final float a = s * da;
final float x = sin(a);
final float y = cos(a);
GLStateManager.glBegin(GL_LINE_STRIP);
for (int l = 0; l <= loops; l++) {
final float r = innerRadius + l * dr;
GLStateManager.glVertex2f(r * x, r * y);
}
GLStateManager.glEnd();
}
}
case GLU_POINT -> {
GLStateManager.glBegin(GL_POINTS);
for (int s = 0; s < slices; s++) {
final float a = s * da;
final float x = sin(a);
final float y = cos(a);
for (int l = 0; l <= loops; l++) {
final float r = innerRadius * l * dr;
GLStateManager.glVertex2f(r * x, r * y);
}
}
GLStateManager.glEnd();
}
case GLU_SILHOUETTE -> {
if (innerRadius != 0.0) {
GLStateManager.glBegin(GL_LINE_LOOP);
for (float a = 0.0f; a < 2.0 * PI; a += da) {
final float x = innerRadius * sin(a);
final float y = innerRadius * cos(a);
GLStateManager.glVertex2f(x, y);
}
GLStateManager.glEnd();
}
GLStateManager.glBegin(GL_LINE_LOOP);
for (float a = 0; a < 2.0f * PI; a += da) {
final float x = outerRadius * sin(a);
final float y = outerRadius * cos(a);
GLStateManager.glVertex2f(x, y);
}
GLStateManager.glEnd();
}
default -> {
}
}
}
}
@@ -1,244 +0,0 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.PartialDisk;
import static com.gtnewhorizons.angelica.loading.AngelicaTweaker.LOGGER;
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_FAN;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_FLAT;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_OUTSIDE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
import static org.lwjgl.util.glu.GLU.GLU_SMOOTH;
/**
* Core-profile compatible replacement for {@link PartialDisk}.
*/
@SuppressWarnings("unused")
public class AngelicaPartialDisk extends PartialDisk {
private static final float PI = (float) Math.PI;
private static final int CACHE_SIZE = 240;
private final float[] sinCache = new float[CACHE_SIZE];
private final float[] cosCache = new float[CACHE_SIZE];
@Override
public void draw(float innerRadius, float outerRadius, int slices, int loops, float startAngle, float sweepAngle) {
final float deltaRadius, angleOffset;
float angle, sintemp, costemp, radiusLow, radiusHigh, texLow = 0, texHigh = 0;
final int slices2, finish;
if (slices >= CACHE_SIZE) slices = CACHE_SIZE - 1;
if (slices < 2 || loops < 1 || outerRadius <= 0.0f || innerRadius < 0.0f || innerRadius > outerRadius) {
LOGGER.warn("PartialDisk: GLU_INVALID_VALUE (slices={}, loops={}, innerRadius={}, outerRadius={})", slices, loops, innerRadius, outerRadius);
return;
}
if (sweepAngle < -360.0f) sweepAngle = 360.0f;
if (sweepAngle > 360.0f) sweepAngle = 360.0f;
if (sweepAngle < 0) {
startAngle += sweepAngle;
sweepAngle = -sweepAngle;
}
slices2 = (sweepAngle == 360.0f) ? slices : slices + 1;
/* Compute length (needed for normal calculations) */
deltaRadius = outerRadius - innerRadius;
angleOffset = startAngle / 180.0f * PI;
for (int i = 0; i <= slices; i++) {
angle = angleOffset + ((PI * sweepAngle) / 180.0f) * i / slices;
sinCache[i] = sin(angle);
cosCache[i] = cos(angle);
}
if (sweepAngle == 360.0f) {
sinCache[slices] = sinCache[0];
cosCache[slices] = cosCache[0];
}
switch (super.normals) {
case GLU_FLAT, GLU_SMOOTH -> GLStateManager.glNormal3f(0.0f, 0.0f, (super.orientation == GLU_OUTSIDE) ? 1.0f : -1.0f);
default -> {
}
}
switch (super.drawStyle) {
case GLU_FILL -> {
if (innerRadius == .0f) {
finish = loops - 1;
/* Triangle strip for inner polygons */
GLStateManager.glBegin(GL_TRIANGLE_FAN);
if (super.textureFlag) {
GLStateManager.glTexCoord2f(0.5f, 0.5f);
}
GLStateManager.glVertex3f(0.0f, 0.0f, 0.0f);
radiusLow = outerRadius - deltaRadius * ((float) (loops - 1) / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
if (super.orientation == GLU_OUTSIDE) {
for (int i = slices; i >= 0; i--) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
} else {
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
}
GLStateManager.glEnd();
} else {
finish = loops;
}
for (int j = 0; j < finish; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
radiusHigh = outerRadius - deltaRadius * ((float) (j + 1) / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
texHigh = radiusHigh / outerRadius / 2;
}
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.orientation == GLU_OUTSIDE) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texHigh * sinCache[i] + 0.5f, texHigh * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusHigh * sinCache[i], radiusHigh * cosCache[i], 0.0f);
} else {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texHigh * sinCache[i] + 0.5f, texHigh * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusHigh * sinCache[i], radiusHigh * cosCache[i], 0.0f);
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
}
GLStateManager.glEnd();
}
}
case GLU_POINT -> {
GLStateManager.glBegin(GL_POINTS);
for (int i = 0; i < slices2; i++) {
sintemp = sinCache[i];
costemp = cosCache[i];
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
}
}
GLStateManager.glEnd();
}
case GLU_LINE -> {
if (innerRadius == outerRadius) {
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(sinCache[i] / 2 + 0.5f, cosCache[i] / 2 + 0.5f);
}
GLStateManager.glVertex3f(innerRadius * sinCache[i], innerRadius * cosCache[i], 0.0f);
}
GLStateManager.glEnd();
break;
}
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
GLStateManager.glEnd();
}
for (int i = 0; i < slices2; i++) {
sintemp = sinCache[i];
costemp = cosCache[i];
GLStateManager.glBegin(GL_LINE_STRIP);
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
}
GLStateManager.glEnd();
}
}
case GLU_SILHOUETTE -> {
if (sweepAngle < 360.0f) {
for (int i = 0; i <= slices; i += slices) {
sintemp = sinCache[i];
costemp = cosCache[i];
GLStateManager.glBegin(GL_LINE_STRIP);
for (int j = 0; j <= loops; j++) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sintemp, radiusLow * costemp, 0.0f);
}
GLStateManager.glEnd();
}
}
for (int j = 0; j <= loops; j += loops) {
radiusLow = outerRadius - deltaRadius * ((float) j / loops);
if (super.textureFlag) {
texLow = radiusLow / outerRadius / 2;
}
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= slices; i++) {
if (super.textureFlag) {
GLStateManager.glTexCoord2f(texLow * sinCache[i] + 0.5f, texLow * cosCache[i] + 0.5f);
}
GLStateManager.glVertex3f(radiusLow * sinCache[i], radiusLow * cosCache[i], 0.0f);
}
GLStateManager.glEnd();
if (innerRadius == outerRadius) break;
}
}
default -> {
}
}
}
}
@@ -1,183 +0,0 @@
package com.gtnewhorizons.angelica.glsm.compat.lwjgl;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.util.glu.Sphere;
import static org.lwjgl.opengl.GL11.GL_LINE_LOOP;
import static org.lwjgl.opengl.GL11.GL_LINE_STRIP;
import static org.lwjgl.opengl.GL11.GL_POINTS;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_FAN;
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
import static org.lwjgl.util.glu.GLU.GLU_FILL;
import static org.lwjgl.util.glu.GLU.GLU_INSIDE;
import static org.lwjgl.util.glu.GLU.GLU_LINE;
import static org.lwjgl.util.glu.GLU.GLU_NONE;
import static org.lwjgl.util.glu.GLU.GLU_POINT;
import static org.lwjgl.util.glu.GLU.GLU_SILHOUETTE;
/**
* Core-profile compatible replacement for {@link Sphere}.
*/
@SuppressWarnings("unused")
public class AngelicaSphere extends Sphere {
private static final float PI = (float) Math.PI;
private void emitNormal(float x, float y, float z) {
final float mag = (float) Math.sqrt(x * x + y * y + z * z);
if (mag > 0.00001F) {
x /= mag;
y /= mag;
z /= mag;
}
GLStateManager.glNormal3f(x, y, z);
}
private void emitTexCoord(float x, float y) {
if (super.textureFlag) GLStateManager.glTexCoord2f(x, y);
}
@Override
public void draw(float radius, int slices, int stacks) {
final float drho, dtheta, ds, dt, nsign;
float rho, theta, x, y, z, s, t;
final int imin, imax;
final boolean normals;
normals = super.normals != GLU_NONE;
nsign = (super.orientation == GLU_INSIDE) ? -1.0f : 1.0f;
drho = PI / stacks;
dtheta = 2.0f * PI / slices;
if (super.drawStyle == GLU_FILL) {
if (!super.textureFlag) {
// draw +Z end as a triangle fan
GLStateManager.glBegin(GL_TRIANGLE_FAN);
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
GLStateManager.glVertex3f(0.0f, 0.0f, nsign * radius);
for (int j = 0; j <= slices; j++) {
theta = (j == slices) ? 0.0f : j * dtheta;
x = -sin(theta) * sin(drho);
y = cos(theta) * sin(drho);
z = nsign * cos(drho);
if (normals) {
emitNormal(x * nsign, y * nsign, z * nsign);
}
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
ds = 1.0f / slices;
dt = 1.0f / stacks;
t = 1.0f;
if (super.textureFlag) {
imin = 0;
imax = stacks;
} else {
imin = 1;
imax = stacks - 1;
}
// draw intermediate stacks as triangle strips (GL_QUAD_STRIP removed in core profile)
for (int i = imin; i < imax; i++) {
rho = i * drho;
GLStateManager.glBegin(GL_TRIANGLE_STRIP);
s = 0.0f;
for (int j = 0; j <= slices; j++) {
theta = (j == slices) ? 0.0f : j * dtheta;
x = -sin(theta) * sin(rho);
y = cos(theta) * sin(rho);
z = nsign * cos(rho);
if (normals) {
emitNormal(x * nsign, y * nsign, z * nsign);
}
emitTexCoord(s, t);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
x = -sin(theta) * sin(rho + drho);
y = cos(theta) * sin(rho + drho);
z = nsign * cos(rho + drho);
if (normals) {
emitNormal(x * nsign, y * nsign, z * nsign);
}
emitTexCoord(s, t - dt);
s += ds;
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
t -= dt;
}
if (!super.textureFlag) {
// draw -Z end as a triangle fan
GLStateManager.glBegin(GL_TRIANGLE_FAN);
GLStateManager.glNormal3f(0.0f, 0.0f, -1.0f);
GLStateManager.glVertex3f(0.0f, 0.0f, -radius * nsign);
rho = PI - drho;
s = 1.0f;
for (int j = slices; j >= 0; j--) {
theta = (j == slices) ? 0.0f : j * dtheta;
x = -sin(theta) * sin(rho);
y = cos(theta) * sin(rho);
z = nsign * cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
s -= ds;
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
} else if (super.drawStyle == GLU_LINE || super.drawStyle == GLU_SILHOUETTE) {
// draw stack lines
for (int i = 1; i < stacks; i++) {
rho = i * drho;
GLStateManager.glBegin(GL_LINE_LOOP);
for (int j = 0; j < slices; j++) {
theta = j * dtheta;
x = cos(theta) * sin(rho);
y = sin(theta) * sin(rho);
z = cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
// draw slice lines
for (int j = 0; j < slices; j++) {
theta = j * dtheta;
GLStateManager.glBegin(GL_LINE_STRIP);
for (int i = 0; i <= stacks; i++) {
rho = i * drho;
x = cos(theta) * sin(rho);
y = sin(theta) * sin(rho);
z = cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
GLStateManager.glEnd();
}
} else if (super.drawStyle == GLU_POINT) {
// top and bottom-most points
GLStateManager.glBegin(GL_POINTS);
if (normals) GLStateManager.glNormal3f(0.0f, 0.0f, nsign);
GLStateManager.glVertex3f(0.0f, 0.0f, radius);
if (normals) GLStateManager.glNormal3f(0.0f, 0.0f, -nsign);
GLStateManager.glVertex3f(0.0f, 0.0f, -radius);
// loop over stacks
for (int i = 1; i < stacks - 1; i++) {
rho = i * drho;
for (int j = 0; j < slices; j++) {
theta = j * dtheta;
x = cos(theta) * sin(rho);
y = sin(theta) * sin(rho);
z = cos(rho);
if (normals) emitNormal(x * nsign, y * nsign, z * nsign);
GLStateManager.glVertex3f(x * radius, y * radius, z * radius);
}
}
GLStateManager.glEnd();
}
}
}
@@ -1,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,32 +14,8 @@ 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) {
param = GLStateManager.remapTexClamp(pname, param);
if(!GLStateManager.updateTexParameteriCache(target, texture, pname, param)) return;
ARBDirectStateAccess.glTextureParameteri(texture, pname, param);
@@ -48,7 +23,6 @@ public class DSAARB extends DSAUnsupported {
@Override
public void texParameterf(int texture, int target, int pname, float param) {
param = GLStateManager.remapTexClamp(pname, param);
if(!GLStateManager.updateTexParameterfCache(target, texture, pname, param)) return;
ARBDirectStateAccess.glTextureParameterf(texture, pname, param);
@@ -56,7 +30,6 @@ public class DSAARB extends DSAUnsupported {
@Override
public void texParameteriv(int texture, int target, int pname, IntBuffer params) {
GLStateManager.remapTexClampBuffer(pname, params);
ARBDirectStateAccess.glTextureParameter(texture, pname, params);
}
@@ -75,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);
@@ -87,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);
@@ -97,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();
@@ -129,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);
}
Loaded 100 of 1027 files, more files were not shown because too many files have changed in this diff. Show more