Compare commits

..
569 changed files with 9093 additions and 25739 deletions

No files matched your search

+2
View File
@@ -36,3 +36,5 @@ addon.local.gradle
addon.local.gradle.kts
addon.late.local.gradle
addon.late.local.gradle.kts
/*.csv
!gl_functions.csv
+8 -9
View File
@@ -7,41 +7,40 @@
* It's a cute angle -mitchej123
* It's the angel that pulls you out of OptiFine's hell -Roadhog360
## **Beta Quality - Here there be Dragons**
* Shaders - Beta
## **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.8.21
* [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 0.4.5 or above
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 - Threading issues, use 1.3.0+
* Neodymium - Untested, but unlikely to be compatible. (Chunk Meshing is definitely incompatible, other features might work depending on config)
* SmoothFont - Equivalent functionality added
# Credits
* ShadersMod
@@ -65,7 +64,7 @@
* Makamys for [Neodymium](https://github.com/makamys/Neodymium) - Various Tessellator -> Quad functionality taken under the LGPL 3
* MCPatcherForge
* Original [MCPatcher](https://bitbucket.org/prupe/mcpatcher/src/master/): Paul Rupe
* [Forge version](https://github.com/mist475/MCPatcherForge): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
* [Forge version](https://bitbucket.org/prupe/mcpatcher/src/master/): Mist475 under LGPL 3.0 with default ctm textures for glass and bookshelves by DarkBum under CC-BY-SA
* HUDCaching
* Created by [Moulberry](https://github.com/Moulberry/MCHUDCaching), backported from [Patcher](https://github.com/Sk1erLLC/Patcher) mod by Alexdoru under the CC-BY-SA-NC 4.0 license
* Angelica
-13
View File
@@ -4,13 +4,6 @@ plugins {
minecraft {
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true")
extraRunJvmArguments.add("-Dangelica.dumpClass=true")
/* extraRunJvmArguments.addAll(
"-Dlegacy.debugClassLoading=true",
"-Dlegacy.debugClassLoadingFiner=true",
"-Dlegacy.debugClassLoadingSave=true")*/
//extraRunJvmArguments.add("-Dangelica.redirectorLogspam=true")
}
tasks.test {
@@ -18,14 +11,8 @@ 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}")
+25 -23
View File
@@ -49,16 +49,19 @@ configurations {
}
dependencies {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.14")
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.34") { transitive = false }
annotationProcessor("org.projectlombok:lombok:1.18.34")
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.8.21: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-4.g6b42bca") {
exclude module: "antlr4" // we only want to shadow the runtime module
@@ -67,20 +70,21 @@ dependencies {
compileOnly "org.apache.ant:ant:1.8.2"
// 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 }
@@ -90,18 +94,15 @@ dependencies {
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev") // Mixin Version
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.6:dev") // Mixin Version
compileOnly("com.github.GTNewHorizons:NotEnoughIds-Legacy:1.4.7:dev") // ASM Version
compileOnly(rfg.deobf("curse.maven:ntm-space-1121235:6974420"))
compileOnly(rfg.deobf("curse.maven:campfirebackport-387444:4611675"))
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
compileOnly("com.github.GTNewHorizons:HoloInventory:2.4.13-GTNH:dev")
runtimeOnlyNonPublishable(rfg.deobf("CoreTweaks:CoreTweaks:0.3.3.2"))
// For testing alternative splash screens, can be sourced from https://github.com/MalTeeez/ModernSplash/releases
//runtimeOnly(rfg.deobf(files("dependencies/modernsplash-1.7.10-1.2.2-dev.jar")))
// Hodgepodge
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
@@ -110,11 +111,12 @@ dependencies {
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.0-GTNH:dev")
// Tests
testImplementation(platform('org.junit:junit-bom:5.11.4'))
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")
testAnnotationProcessor("com.github.bsideup.jabel:jabel-javac-plugin:1.0.1")
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
devOnlyNonPublishable("com.github.javaparser:javaparser-symbol-solver-core:3.26.2") {
exclude module: "guava"
}
}
+2084
View File
File diff suppressed because it is too large. Load diff
+3 -11
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.
@@ -96,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!
@@ -153,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.
@@ -174,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.
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-8.14.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+3 -2
View File
@@ -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
@@ -205,7 +206,7 @@ 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.
-7
View File
@@ -8,13 +8,6 @@ repositories {
includeGroup 'com.falsepattern'
}
}
maven {
name = 'mega'
url = 'https://mvn.falsepattern.com/gtmega_releases/'
content {
includeGroup 'mega'
}
}
exclusiveContent {
forRepository {
ivy {
+1 -1
View File
@@ -16,5 +16,5 @@ pluginManagement {
}
plugins {
id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.48'
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,7 +2,6 @@ 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;
@@ -10,7 +9,6 @@ import java.util.Set;
@LateMixin
public class AngelicaLateMixins implements ILateMixinLoader {
@Override
public String getMixinConfig() {
return "mixins.angelica.late.json";
@@ -18,6 +16,7 @@ public class AngelicaLateMixins implements ILateMixinLoader {
@Override
public List<String> getMixins(Set<String> loadedMods) {
return IMixins.getLateMixins(Mixins.class, loadedMods);
return Mixins.getLateMixins(loadedMods);
}
}
@@ -1,7 +1,5 @@
package com.gtnewhorizons.angelica;
import static com.gtnewhorizons.angelica.AngelicaMod.MOD_ID;
import com.gtnewhorizons.angelica.common.BlockError;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.proxy.CommonProxy;
@@ -14,22 +12,15 @@ import cpw.mods.fml.common.event.FMLPostInitializationEvent;
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
@Mod(
modid = MOD_ID,
modid = "angelica",
name = "Angelica",
version = Tags.VERSION,
dependencies = """
required-after:gtnhlib@[0.8.21,);\
before:lwjgl3ify@[1.5.3,);\
after:hodgepodge@[2.4.4,);\
after:CodeChickenCore@[1.2.0,);\
after:archaicfix@[0.7.0,);\
""",
dependencies = " before:lwjgl3ify@[1.5.3,);" + " after:hodgepodge@[2.4.4,);" + " after:CodeChickenCore@[1.2.0,);"
+ " after:archaicfix@[0.7.0,);" + " required-after:gtnhlib@[0.4.2,);",
acceptedMinecraftVersions = "[1.7.10]",
acceptableRemoteVersions = "*",
guiFactory = "com.gtnewhorizons.angelica.config.AngelicaGuiConfigFactory")
public class AngelicaMod {
public static final String MOD_ID = "angelica";
@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.IPatchedTextureAtlasSprite;
import com.gtnewhorizons.angelica.mixins.interfaces.ITexturesCache;
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 IPatchedTextureAtlasSprite patchedSprite) {
patchedSprite.markNeedsAnimationUpdate();
}
}
}
@@ -1,31 +1,25 @@
package com.gtnewhorizons.angelica.client.font;
import com.google.common.collect.ImmutableSet;
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 cpw.mods.fml.client.SplashProgress;
import it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.coderbot.iris.gl.program.Program;
import net.coderbot.iris.gl.program.ProgramBuilder;
import jss.util.RandomXoshiro256StarStar;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.MathHelper;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.io.IOUtils;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
/**
* A batching replacement for {@code FontRenderer}
*
@@ -35,6 +29,8 @@ public class BatchingFontRenderer {
/** The underlying FontRenderer object that's being accelerated */
protected FontRenderer underlying;
/** Cached locations for each unicode page atlas */
private final ResourceLocation[] unicodePageLocations;
/** Array of width of all the characters in default.png */
protected int[] charWidth = new int[256];
/** Array of the start/end column (in upper/lower nibble) for every glyph in the /font directory. */
@@ -46,59 +42,50 @@ public class BatchingFontRenderer {
private int[] colorCode;
/** Location of the primary font atlas to bind. */
protected final ResourceLocation locationFontTexture;
/** The RenderEngine used to load and setup glyph textures. */
private final TextureManager renderEngine;
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
private final int AAMode;
private final int AAStrength;
private final int texBoundAttrLocation;
private final int fontShaderId;
/** The full list of characters present in the default Minecraft font, excluding the Unicode font */
@SuppressWarnings("UnnecessaryUnicodeEscape")
private static final String MCFONT_CHARS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
final boolean isSGA;
final boolean isSplash;
private static final short[] MCFONT_ASCII_LUT = new short[512];
private static final Char2ShortOpenHashMap MCFONT_UNI_LUT = new Char2ShortOpenHashMap();
private static class FontAAShader {
private static Program fontShader = null;
public static Program getProgram() {
if (fontShader == null) {
String vsh, fsh;
try {
fsh = new String(IOUtils.toByteArray(Objects.requireNonNull(FontAAShader.class.getResourceAsStream("/assets/angelica/shaders/fontFilter.fsh"))), StandardCharsets.UTF_8);
vsh = new String(IOUtils.toByteArray(Objects.requireNonNull(FontAAShader.class.getResourceAsStream("/assets/angelica/shaders/fontFilter.vsh"))), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new RuntimeException(e);
}
ProgramBuilder builder = ProgramBuilder.begin("fontFilter", vsh, null, fsh, ImmutableSet.of(0));
fontShader = builder.build();
static {
Arrays.fill(MCFONT_ASCII_LUT, (short) -1);
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
char ch = MCFONT_CHARS.charAt(i);
if (ch < MCFONT_ASCII_LUT.length) {
MCFONT_ASCII_LUT[ch] = i;
} else {
MCFONT_UNI_LUT.put(ch, i);
}
return fontShader;
}
}
public BatchingFontRenderer(FontRenderer underlying, int[] charWidth, byte[] glyphWidth,
int[] colorCode, ResourceLocation locationFontTexture) {
public static int lookupMcFontPosition(char ch) {
if (ch < MCFONT_ASCII_LUT.length) {
return MCFONT_ASCII_LUT[ch];
} else {
return MCFONT_UNI_LUT.getOrDefault(ch, (short) -1);
}
}
public BatchingFontRenderer(FontRenderer underlying, ResourceLocation[] unicodePageLocations, int[] charWidth,
byte[] glyphWidth, int[] colorCode, ResourceLocation locationFontTexture, TextureManager renderEngine) {
this.underlying = underlying;
this.unicodePageLocations = unicodePageLocations;
this.charWidth = charWidth;
this.glyphWidth = glyphWidth;
this.colorCode = colorCode;
this.locationFontTexture = locationFontTexture;
this.renderEngine = renderEngine;
for (int i = 0; i < 64; i++) {
batchCommandPool.add(new FontDrawCmd());
}
this.isSGA = Objects.equals(this.locationFontTexture.getResourcePath(), "textures/font/ascii_sga.png");
this.isSplash = FontStrategist.isSplashFontRendererActive(underlying);
FontProviderMC.get(this.isSGA).charWidth = this.charWidth;
FontProviderMC.get(this.isSGA).locationFontTexture = this.locationFontTexture;
FontProviderUnicode.get().glyphWidth = this.glyphWidth;
//noinspection deprecation
fontShaderId = FontAAShader.getProgram().getProgramId();
AAMode = GL20.glGetUniformLocation(fontShaderId, "aaMode");
AAStrength = GL20.glGetUniformLocation(fontShaderId, "strength");
texBoundAttrLocation = GL20.glGetAttribLocation(fontShaderId, "texBounds");
}
// === Batched rendering
@@ -110,29 +97,24 @@ public class BatchingFontRenderer {
private static final int INITIAL_BATCH_SIZE = 256;
private static final ResourceLocation DUMMY_RESOURCE_LOCATION = new ResourceLocation("angelica$dummy",
"this is invalid!");
private FloatBuffer batchVtxPositions = memAllocFloat(INITIAL_BATCH_SIZE * 2);
private ByteBuffer batchVtxColors = memAlloc(INITIAL_BATCH_SIZE * 4);
private FloatBuffer batchVtxTexCoords = memAllocFloat(INITIAL_BATCH_SIZE * 2);
private IntBuffer batchIndices = memAllocInt(INITIAL_BATCH_SIZE / 2 * 3);
private FloatBuffer batchVtxTexBounds = memAllocFloat(INITIAL_BATCH_SIZE * 4);
private FloatBuffer batchVtxPositions = BufferUtils.createFloatBuffer(INITIAL_BATCH_SIZE * 2);
private ByteBuffer batchVtxColors = BufferUtils.createByteBuffer(INITIAL_BATCH_SIZE * 4);
private FloatBuffer batchVtxTexCoords = BufferUtils.createFloatBuffer(INITIAL_BATCH_SIZE * 2);
private IntBuffer batchIndices = BufferUtils.createIntBuffer(INITIAL_BATCH_SIZE / 2 * 3);
private final ObjectArrayList<FontDrawCmd> batchCommands = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
private final ObjectArrayList<FontDrawCmd> batchCommandPool = ObjectArrayList.wrap(new FontDrawCmd[64], 0);
private int blendSrcRGB = GL11.GL_SRC_ALPHA;
private int blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA;
/** */
private void pushVtx(float x, float y, int rgba, float u, float v, float uMin, float uMax, float vMin, float vMax) {
private void pushVtx(float x, float y, int rgba, float u, float v) {
final int oldCap = batchVtxPositions.capacity() / 2;
if (vtxWriterIndex >= oldCap) {
final int newCap = oldCap * 2;
batchVtxPositions = memRealloc(batchVtxPositions, newCap * 2);
batchVtxColors = memRealloc(batchVtxColors, newCap * 4);
batchVtxTexCoords = memRealloc(batchVtxTexCoords, newCap * 2);
batchVtxTexBounds = memRealloc(batchVtxTexBounds, newCap * 4);
batchVtxPositions = CompatMemoryUtil.memReallocDirect(batchVtxPositions, newCap * 2);
batchVtxColors = CompatMemoryUtil.memReallocDirect(batchVtxColors, newCap * 4);
batchVtxTexCoords = CompatMemoryUtil.memReallocDirect(batchVtxTexCoords, newCap * 2);
final int oldIdxCap = batchIndices.capacity();
final int newIdxCap = oldIdxCap * 2;
batchIndices = memRealloc(batchIndices, newIdxCap);
batchIndices = CompatMemoryUtil.memReallocDirect(batchIndices, newIdxCap);
}
final int idx = vtxWriterIndex;
final int idx2 = idx * 2;
@@ -146,28 +128,15 @@ public class BatchingFontRenderer {
batchVtxColors.put(idx4 + 3, (byte) ((rgba >> 24) & 0xFF));
batchVtxTexCoords.put(idx2, u);
batchVtxTexCoords.put(idx2 + 1, v);
batchVtxTexBounds.put(idx4, uMin);
batchVtxTexBounds.put(idx4 + 1, uMax);
batchVtxTexBounds.put(idx4 + 2, vMin);
batchVtxTexBounds.put(idx4 + 3, vMax);
vtxWriterIndex++;
}
private void pushUntexRect(float x, float y, float w, float h, int rgba) {
final int vtxId = vtxWriterIndex;
pushVtx(x, y, rgba, 0, 0, 0, 0, 0, 0);
pushVtx(x, y + h, rgba, 0, 0, 0, 0, 0, 0);
pushVtx(x + w, y, rgba, 0, 0, 0, 0, 0, 0);
pushVtx(x + w, y + h, rgba, 0, 0, 0, 0, 0, 0);
pushQuadIdx(vtxId);
}
private void pushTexRect(float x, float y, float w, float h, float itOff, int rgba, float uStart, float vStart, float uSz, float vSz) {
final int vtxId = vtxWriterIndex;
pushVtx(x + itOff, y, rgba, uStart, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x - itOff, y + h, rgba, uStart, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x + itOff + w, y, rgba, uStart + uSz, vStart, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x - itOff + w, y + h, rgba, uStart + uSz, vStart + vSz, uStart, uStart + uSz, vStart, vStart + vSz);
pushVtx(x, y, rgba, 0, 0);
pushVtx(x, y + h, rgba, 0, 0);
pushVtx(x + w, y, rgba, 0, 0);
pushVtx(x + w, y + h, rgba, 0, 0);
pushQuadIdx(vtxId);
}
@@ -184,7 +153,7 @@ public class BatchingFontRenderer {
return idx;
}
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture, boolean isUnicode) {
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture) {
if (!batchCommands.isEmpty()) {
final FontDrawCmd lastCmd = batchCommands.get(batchCommands.size() - 1);
final int prevEndVtx = lastCmd.startVtx + lastCmd.idxCount;
@@ -200,7 +169,7 @@ public class BatchingFontRenderer {
}
}
final FontDrawCmd cmd = batchCommandPool.pop();
cmd.reset(startIdx, idxCount, texture, isUnicode);
cmd.reset(startIdx, idxCount, texture);
batchCommands.add(cmd);
}
@@ -208,14 +177,12 @@ public class BatchingFontRenderer {
public int startVtx;
public int idxCount;
public boolean isUnicode;
public ResourceLocation texture;
public void reset(int startVtx, int vtxCount, ResourceLocation texture, boolean isUnicode) {
public void reset(int startVtx, int vtxCount, ResourceLocation texture) {
this.startVtx = startVtx;
this.idxCount = vtxCount;
this.texture = texture;
this.isUnicode = isUnicode;
}
@Override
@@ -274,12 +241,7 @@ public class BatchingFontRenderer {
}
}
int lastActiveProgram;
int fontAAModeLast = -1;
int fontAAStrengthLast = -1;
private void flushBatch() {
final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM);
// Sort&Draw
batchCommands.sort(FontDrawCmd.DRAW_ORDER_COMPARATOR);
@@ -291,26 +253,9 @@ public class BatchingFontRenderer {
GLStateManager.enableTexture();
GLStateManager.enableAlphaTest();
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(blendSrcRGB, blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glShadeModel(GL11.GL_FLAT);
final boolean canUseAA = FontConfig.fontAAMode != 0 && prevProgram == 0;
if (canUseAA) {
GL20.glVertexAttribPointer(texBoundAttrLocation, 4, false, 0, batchVtxTexBounds);
GL20.glEnableVertexAttribArray(texBoundAttrLocation);
lastActiveProgram = prevProgram;
GLStateManager.glUseProgram(fontShaderId);
if (FontConfig.fontAAMode != fontAAModeLast) {
fontAAModeLast = FontConfig.fontAAMode;
GL20.glUniform1i(AAMode, FontConfig.fontAAMode);
}
if (FontConfig.fontAAStrength != fontAAStrengthLast) {
fontAAStrengthLast = FontConfig.fontAAStrength;
GL20.glUniform1f(AAStrength, FontConfig.fontAAStrength / 120.f);
}
}
GL13.glClientActiveTexture(GL13.GL_TEXTURE0);
GL11.glTexCoordPointer(2, 0, batchVtxTexCoords);
GL11.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GL11.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 0, batchVtxColors);
@@ -319,9 +264,6 @@ public class BatchingFontRenderer {
GL11.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GL11.glDisableClientState(GL11.GL_NORMAL_ARRAY);
GL11.glNormal3f(0.0f, 0.0f, 1.0f);
// Use plain for loop to avoid allocations
final FontDrawCmd[] cmdsData = batchCommands.elements();
final int cmdsSize = batchCommands.size();
@@ -341,13 +283,8 @@ public class BatchingFontRenderer {
}
batchIndices.limit(cmd.startVtx + cmd.idxCount);
batchIndices.position(cmd.startVtx);
GL11.glDrawElements(GL11.GL_TRIANGLES, batchIndices);
}
if (canUseAA) {
GLStateManager.glUseProgram(lastActiveProgram);
GL20.glDisableVertexAttribArray(texBoundAttrLocation);
}
GL11.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GL11.glDisableClientState(GL11.GL_COLOR_ARRAY);
@@ -375,28 +312,17 @@ public class BatchingFontRenderer {
return (what >= fromInclusive) && (what <= toInclusive);
}
public boolean forceDefaults() {
return this.isSGA || this.isSplash;
}
public float getGlyphScaleX() {
return forceDefaults() ? 1 : (float) (FontConfig.glyphScale * Math.pow(2, FontConfig.glyphAspect)) * (FontStrategist.customFontInUse ? 1.5f : 1);
}
public float getGlyphScaleY() {
return forceDefaults() ? 1 : (float) (FontConfig.glyphScale / Math.pow(2, FontConfig.glyphAspect)) * (FontStrategist.customFontInUse ? 1.5f : 1);
}
public float getGlyphSpacing() {
return forceDefaults() ? 0 : FontConfig.glyphSpacing;
}
public float getWhitespaceScale() {
return forceDefaults() ? 1 : FontConfig.whitespaceScale;
}
public float getShadowOffset() {
return forceDefaults() ? 1 : FontConfig.fontShadowOffset;
private ResourceLocation getUnicodePageLocation(int page) {
final ResourceLocation lookup = unicodePageLocations[page];
if (lookup == null) {
final ResourceLocation rl = new ResourceLocation(String.format(
"textures/font/unicode_page_%02x.png",
page));
unicodePageLocations[page] = rl;
return rl;
} else {
return lookup;
}
}
private static final char FORMATTING_CHAR = 167; // §
@@ -409,9 +335,6 @@ public class BatchingFontRenderer {
}
final int shadowColor = (color & 0xfcfcfc) >> 2 | color & 0xff000000;
FontProviderMC.get(this.isSGA).charWidth = this.charWidth;
FontProviderMC.get(this.isSGA).locationFontTexture = this.locationFontTexture;
this.beginBatch();
float curX = anchorX;
try {
@@ -431,14 +354,10 @@ public class BatchingFontRenderer {
boolean curStrikethrough = false;
boolean curUnderline = false;
final float glyphScaleY = getGlyphScaleY();
final float heightNorth = anchorY + (underlying.FONT_HEIGHT - 1.0f) * (0.5f - glyphScaleY / 2);
final float heightSouth = (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY;
final float underlineY = heightNorth + (underlying.FONT_HEIGHT - 1.0f) * glyphScaleY;
final float underlineY = anchorY + underlying.FONT_HEIGHT - 1.0f;
float underlineStartX = 0.0f;
float underlineEndX = 0.0f;
final float strikethroughY = heightNorth + ((float) (underlying.FONT_HEIGHT / 2) - 1.0f) * glyphScaleY;
final float strikethroughY = anchorY + (float) (underlying.FONT_HEIGHT / 2) - 1.0F;
float strikethroughStartX = 0.0f;
float strikethroughEndX = 0.0f;
@@ -450,8 +369,8 @@ public class BatchingFontRenderer {
if (curUnderline && underlineStartX != underlineEndX) {
final int ulIdx = idxWriterIndex;
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
pushDrawCmd(ulIdx, 6, null, false);
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, 1.0f, curColor);
pushDrawCmd(ulIdx, 6, null);
underlineStartX = underlineEndX;
}
if (curStrikethrough && strikethroughStartX != strikethroughEndX) {
@@ -460,9 +379,9 @@ public class BatchingFontRenderer {
strikethroughStartX,
strikethroughY,
strikethroughEndX - strikethroughStartX,
glyphScaleY,
1.0f,
curColor);
pushDrawCmd(ulIdx, 6, null, false);
pushDrawCmd(ulIdx, 6, null);
strikethroughStartX = strikethroughEndX;
}
@@ -507,73 +426,148 @@ public class BatchingFontRenderer {
continue;
}
if (curRandom) {
chr = FontProviderMC.get(this.isSGA).getRandomReplacement(chr);
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);
}
FontProvider fontProvider = FontStrategist.getFontProvider(this, chr, FontConfig.enableCustomFont, unicodeFlag);
final float shadowOffset = unicodeFlag ? 0.5F : 1.0F;
// Check ASCII space, NBSP, NNBSP
if (chr == ' ' || chr == '\u00A0' || chr == '\u202F') {
curX += 4 * this.getWhitespaceScale();
curX += 4;
continue;
}
final float uStart = fontProvider.getUStart(chr);
final float vStart = fontProvider.getVStart(chr);
final float xAdvance = fontProvider.getXAdvance(chr) * getGlyphScaleX();
final float glyphW = fontProvider.getGlyphW(chr) * getGlyphScaleX();
final float uSz = fontProvider.getUSize(chr);
final float vSz = fontProvider.getVSize(chr);
final float uStart;
final float vStart;
final float xAdvance;
final float glyphW;
final float uSz;
final float vSz;
final float itOff = curItalic ? 1.0F : 0.0F; // italic offset
final float shadowOffset = fontProvider.getShadowOffset();
final int shadowCopies = FontConfig.shadowCopies;
final int boldCopies = FontConfig.boldCopies;
final ResourceLocation texture = fontProvider.getTexture(chr);
final ResourceLocation texture;
if (lutIndex == -1 || unicodeFlag) {
if (glyphWidth[chr] == 0) {
continue;
}
// Draw unicode char
final int uniPage = chr / 256;
texture = getUnicodePageLocation(uniPage);
final int startColumn = this.glyphWidth[chr] >>> 4;
final int endColumn = this.glyphWidth[chr] & 15;
final float startColumnF = (float) startColumn;
final float endColumnF = (float) (endColumn + 1);
uStart = ((float) (chr % 16 * 16) + startColumnF) / 256.0f;
vStart = ((float) ((chr & 255) / 16 * 16)) / 256.0f;
final float chrWidth = endColumnF - startColumnF - 0.02F;
glyphW = chrWidth / 2.0f + 1.0f;
xAdvance = (endColumnF - startColumnF) / 2.0F + 1.0F;
uSz = chrWidth / 256.0f;
vSz = 15.98f / 256.0f;
} else {
// Draw "ASCII" char
uStart = ((lutIndex % 16) * 8) / 128.0F;
vStart = (float) ((lutIndex / 16) * 8) / 128.0F;
xAdvance = this.charWidth[lutIndex];
if (xAdvance == 0) {
continue;
}
glyphW = xAdvance - 0.01F;
uSz = (glyphW - 1.0F) / 128.0F;
vSz = 7.99F / 128.0F;
texture = locationFontTexture;
}
final int vtxId = vtxWriterIndex;
final int idxId = idxWriterIndex;
if (enableShadow) {
for (int n = 1; n <= shadowCopies; n++) {
final float shadowOffsetPart = shadowOffset * ((float) n / shadowCopies);
pushTexRect(curX + shadowOffsetPart, heightNorth + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz);
int vtxCount = 0;
if (curBold) {
pushTexRect(curX + 2.0f * shadowOffsetPart, heightNorth + shadowOffsetPart, glyphW - 1.0f, heightSouth, itOff, curShadowColor, uStart, vStart, uSz, vSz);
}
if (enableShadow) {
pushVtx(curX + itOff + shadowOffset, anchorY + shadowOffset, curShadowColor, uStart, vStart);
pushVtx(
curX - itOff + shadowOffset,
anchorY + 7.99F + shadowOffset,
curShadowColor,
uStart,
vStart + vSz);
pushVtx(curX + glyphW - 1.0F + itOff + shadowOffset,
anchorY + shadowOffset,
curShadowColor,
uStart + uSz,
vStart);
pushVtx(curX + glyphW - 1.0F - itOff + shadowOffset,
anchorY + 7.99F + shadowOffset,
curShadowColor,
uStart + uSz,
vStart + vSz);
pushQuadIdx(vtxId + vtxCount);
vtxCount += 4;
if (curBold) {
final float shadowOffset2 = 2.0f * shadowOffset;
pushVtx(curX + itOff + shadowOffset2, anchorY + shadowOffset, curShadowColor, uStart, vStart);
pushVtx(
curX - itOff + shadowOffset2,
anchorY + 7.99F + shadowOffset,
curShadowColor,
uStart,
vStart + vSz);
pushVtx(
curX + glyphW - 1.0F + itOff + shadowOffset2,
anchorY + shadowOffset,
curShadowColor,
uStart + uSz,
vStart);
pushVtx(curX + glyphW - 1.0F - itOff + shadowOffset2,
anchorY + 7.99F + shadowOffset,
curShadowColor,
uStart + uSz,
vStart + vSz);
pushQuadIdx(vtxId + vtxCount);
vtxCount += 4;
}
}
pushTexRect(curX, heightNorth, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz);
pushVtx(curX + itOff, anchorY, curColor, uStart, vStart);
pushVtx(curX - itOff, anchorY + 7.99F, curColor, uStart, vStart + vSz);
pushVtx(curX + glyphW - 1.0F + itOff, anchorY, curColor, uStart + uSz, vStart);
pushVtx(curX + glyphW - 1.0F - itOff, anchorY + 7.99F, curColor, uStart + uSz, vStart + vSz);
pushQuadIdx(vtxId + vtxCount);
vtxCount += 4;
if (curBold) {
for (int n = 1; n <= boldCopies; n++) {
final float shadowOffsetPart = shadowOffset * ((float) n / boldCopies);
pushTexRect(curX + shadowOffsetPart, heightNorth, glyphW - 1.0f, heightSouth, itOff, curColor, uStart, vStart, uSz, vSz);
}
pushVtx(shadowOffset + curX + itOff, anchorY, curColor, uStart, vStart);
pushVtx(shadowOffset + curX - itOff, anchorY + 7.99F, curColor, uStart, vStart + vSz);
pushVtx(shadowOffset + curX + glyphW - 1.0F + itOff, anchorY, curColor, uStart + uSz, vStart);
pushVtx(shadowOffset + curX + glyphW - 1.0F - itOff,
anchorY + 7.99F,
curColor,
uStart + uSz,
vStart + vSz);
pushQuadIdx(vtxId + vtxCount);
vtxCount += 4;
}
/*
Vertex-per-char counts for different configurations
default: 4
shadow only: 4(1 + shadowCopies)
bold only: 4(1 + boldCopies)
both: 4(1 + 2 * shadowCopies + boldCopies)
*/
int charCount = 1;
if (enableShadow) { charCount += shadowCopies * (curBold ? 2 : 1); }
if (curBold) { charCount += boldCopies; }
final int vtxCount = 4 * charCount;
pushDrawCmd(idxId, vtxCount / 2 * 3, texture, chr > 255);
curX += (xAdvance + (curBold ? shadowOffset : 0.0f)) + getGlyphSpacing();
pushDrawCmd(idxId, vtxCount / 2 * 3, texture);
curX += xAdvance + (curBold ? shadowOffset : 0.0f);
underlineEndX = curX;
strikethroughEndX = curX;
}
if (curUnderline && underlineStartX != underlineEndX) {
final int ulIdx = idxWriterIndex;
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
pushDrawCmd(ulIdx, 6, null, false);
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, 1.0f, curColor);
pushDrawCmd(ulIdx, 6, null);
}
if (curStrikethrough && strikethroughStartX != strikethroughEndX) {
final int ulIdx = idxWriterIndex;
@@ -581,9 +575,9 @@ public class BatchingFontRenderer {
strikethroughStartX,
strikethroughY,
strikethroughEndX - strikethroughStartX,
glyphScaleY,
1.0f,
curColor);
pushDrawCmd(ulIdx, 6, null, false);
pushDrawCmd(ulIdx, 6, null);
}
} finally {
@@ -592,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,20 +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);
}
@@ -1,300 +0,0 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizon.gtnhlib.config.ConfigurationManager;
import com.gtnewhorizons.angelica.config.FontConfig;
import lombok.Setter;
import lombok.Value;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.ITextureObject;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.util.ResourceLocation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.imageio.ImageIO;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Objects;
public final class FontProviderCustom implements FontProvider {
public static final Logger LOGGER = LogManager.getLogger("Angelica");
public static final String FONT_DIR = "fonts/custom/";
static final int ATLAS_SIZE = 128;
static final int ATLAS_COUNT = 512;
private final byte id; // 0 - primary font, 1 - fallback font
private FontAtlas[] fontAtlases = new FontAtlas[ATLAS_COUNT];
private float currentFontQuality = FontConfig.customFontQuality;
@Setter
private Font font;
private FontProviderCustom(byte id) {
this.id = id;
Font[] availableFonts = FontStrategist.getAvailableFonts();
if (availableFonts.length == 0) {
LOGGER.fatal("There seem to be no fonts available on this system! Disabling custom font and throwing an exception in an attempt to restore the session.");
FontConfig.enableCustomFont = false;
ConfigurationManager.save(FontConfig.class);
throw new RuntimeException();
}
String myFontName = switch (this.id) {
case 0 -> FontConfig.customFontNamePrimary;
case 1 -> FontConfig.customFontNameFallback;
default -> null;
};
if (Objects.equals(myFontName, "(none)")) {
font = null;
return;
}
int fontPos = -1;
for (int i = 0; i < availableFonts.length; i++) {
if (Objects.equals(myFontName, availableFonts[i].getFontName())) {
fontPos = i;
break;
}
}
if (fontPos == -1) {
LOGGER.info("Could not find previously set font \"{}\". ", myFontName);
font = null;
return;
}
font = availableFonts[fontPos].deriveFont(currentFontQuality);
}
private static class InstLoader {
static final FontProviderCustom instance0 = new FontProviderCustom((byte)0);
static final FontProviderCustom instance1 = new FontProviderCustom((byte)1);
}
public static FontProviderCustom getPrimary() { return InstLoader.instance0; }
public static FontProviderCustom getFallback() { return InstLoader.instance1; }
public void reloadFont(int fontID) {
currentFontQuality = FontConfig.customFontQuality;
font = FontStrategist.getAvailableFonts()[fontID].deriveFont(currentFontQuality);
File[] files = new File(getFontDir()).listFiles();
if (files != null) {
for (File f : files) {
if (!Files.isSymbolicLink(f.toPath())) {
f.delete();
}
}
}
TextureManager tm = Minecraft.getMinecraft().getTextureManager();
Map mapTextureObjects = tm.mapTextureObjects;
for (int i = 0; i < ATLAS_COUNT; i++) {
ResourceLocation key = new ResourceLocation(getAtlasResourceName(i));
if (mapTextureObjects.containsKey(key)) {
ITextureObject obj = (ITextureObject) mapTextureObjects.get(key);
TextureUtil.deleteTexture(obj.getGlTextureId());
mapTextureObjects.remove(key);
}
}
fontAtlases = new FontAtlas[ATLAS_COUNT];
}
private String getFontDir() {
return FONT_DIR + "f" + this.id + "/";
}
private String getAtlasFilename(int atlasId) {
return "f" + this.id + "p" + atlasId;
}
String getAtlasResourceName(int atlasId) {
return "minecraft:angelica_c" + getAtlasFilename(atlasId);
}
String getAtlasFullPath(int atlasId) {
return getFontDir() + getAtlasFilename(atlasId) + ".png";
}
@Value
private class GlyphData {
float uStart;
float vStart;
float xAdvance;
float glyphW;
float uSz;
float vSz;
}
private class FontAtlas {
GlyphData[] glyphData = new GlyphData[ATLAS_SIZE];
private ResourceLocation texture;
private final int id;
FontAtlas(int id) {
this.id = id;
}
void construct(Font font) {
float separator = currentFontQuality / 3f;
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics();
g2d.dispose();
int width = 0;
int actualChars = 0;
for (int i = 0; i < ATLAS_SIZE; i++) {
final char ch = (char)(i + ATLAS_SIZE * this.id);
if (font.canDisplay(ch)) {
width += (int) (separator + fm.charWidth(ch));
actualChars++;
}
}
if (actualChars == 0) {
return;
}
final int atlasTilesX = (int) Math.ceil(Math.sqrt(actualChars) * 1.5f);
final int atlasTilesY = (int) Math.ceil((double) actualChars / atlasTilesX);
LOGGER.info("constructing custom font atlas (ID {}) of nominal size {} chars, actual: {}, {} rows by {} columns",
this.id, ATLAS_SIZE, actualChars, atlasTilesX, atlasTilesY);
width = 0;
actualChars = 0;
int maxRowWidth = 0;
for (int i = 0; i < ATLAS_SIZE; i++) {
if (actualChars % atlasTilesX == 0) {
maxRowWidth = Math.max(maxRowWidth, width);
width = 0;
}
final char ch = (char)(i + ATLAS_SIZE * this.id);
if (font.canDisplay(ch)) {
width += (int) (separator + fm.charWidth(ch));
actualChars++;
}
}
maxRowWidth = Math.max(maxRowWidth, width);
final int lineHeight = fm.getHeight();
final float desc = fm.getDescent();
final int imageWidth = (int) (maxRowWidth + separator);
final int imageHeight = (int) ((separator + lineHeight) * atlasTilesY + separator);
image = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
g2d = image.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setFont(font);
fm = g2d.getFontMetrics();
int tileX = 0, tileY = 0; // position in atlas tiles
int imgX = (int) separator; // position in pixels
for (int i = 0; i < ATLAS_SIZE; i++) {
final char ch = (char)(i + ATLAS_SIZE * this.id);
if (!font.canDisplay(ch)) { continue; }
if (tileX >= atlasTilesX) {
tileX = 0;
imgX = (int) separator;
tileY++;
}
final int charWidth = fm.charWidth(ch);
final float charAspectRatio = (float) charWidth / lineHeight;
final float inset = currentFontQuality / 16;
g2d.drawString(Character.toString(ch), imgX, (lineHeight + separator) * (tileY + 1) - desc);
final float uStart = (float) (imgX - inset * charAspectRatio) / imageWidth;
final float vStart = ((lineHeight + separator) * (tileY + 1) - lineHeight - inset) / imageHeight;
final float xAdvance = charAspectRatio * 8 * charWidth / (charWidth + 2 * inset * charAspectRatio);
final float glyphW = charAspectRatio * 8 + 1;
final float uSz = (float) (charWidth + 2 * inset * charAspectRatio) / imageWidth;
final float vSz = (float) (lineHeight + 2 * inset) / imageHeight;
glyphData[i] = new GlyphData(uStart, vStart, xAdvance, glyphW, uSz, vSz);
imgX += (int) (charWidth + separator);
tileX++;
}
g2d.dispose();
try {
LOGGER.info("writing custom font atlas texture ({}x{} px) to {}", imageWidth, imageHeight, getAtlasFullPath(this.id));
Files.createDirectories(Paths.get(getFontDir()));
ImageIO.write(image, "png", new File(getAtlasFullPath(this.id)));
} catch (IOException e) {
e.printStackTrace();
}
this.texture = new ResourceLocation(getAtlasResourceName(this.id));
}
}
private FontAtlas getAtlas(char chr) {
int id = chr / ATLAS_SIZE;
FontAtlas fa = fontAtlases[id];
if (fa == null) {
fa = new FontAtlas(id);
fa.construct(font);
}
fontAtlases[id] = fa;
return fa;
}
@Override
public boolean isGlyphAvailable(char chr) {
if (font == null) { return false; }
return (getAtlas(chr).glyphData[chr % ATLAS_SIZE] != null);
}
@Override
public char getRandomReplacement(char chr) {
return chr;
}
@Override
public float getUStart(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].uStart;
}
@Override
public float getVStart(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].vStart;
}
@Override
public float getXAdvance(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].xAdvance;
}
@Override
public float getGlyphW(char chr) {
return getAtlas(chr).glyphData[chr % ATLAS_SIZE].glyphW;
}
@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;
}
}
@@ -1,106 +0,0 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.FontConfig;
import it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
import jss.util.RandomXoshiro256StarStar;
import net.minecraft.util.ResourceLocation;
public final class FontProviderMC implements FontProvider {
/**
* The full list of characters present in the default Minecraft font, excluding the Unicode font
*/
@SuppressWarnings("UnnecessaryUnicodeEscape")
private static final String MCFONT_CHARS = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u2302\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000";
private FontProviderMC() {}
private static class InstLoader {
static final FontProviderMC instance = new FontProviderMC();
static final FontProviderMC instanceSGA = new FontProviderMC();
}
public static FontProviderMC get(boolean isSGA) {
return (isSGA ? InstLoader.instanceSGA : InstLoader.instance);
}
private static final Char2ShortOpenHashMap MCFONT_ASCII_MAP = new Char2ShortOpenHashMap();
ResourceLocation locationFontTexture = null;
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
public int[] charWidth;
static {
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
MCFONT_ASCII_MAP.put(MCFONT_CHARS.charAt(i), i);
}
}
public int lookupMcFontPosition(char ch) {
if (isGlyphAvailable(ch)) {
return MCFONT_ASCII_MAP.get(ch);
}
return -1;
}
@Override
public boolean isGlyphAvailable(char chr) {
return MCFONT_ASCII_MAP.containsKey(chr);
}
public char getRandomReplacement(char chr){
int lutIndex = lookupMcFontPosition(chr);
if (lutIndex != -1) {
int randomReplacementIndex;
do {
randomReplacementIndex = fontRandom.nextInt(this.charWidth.length);
} while (this.charWidth[lutIndex] != this.charWidth[randomReplacementIndex]);
lutIndex = randomReplacementIndex;
return MCFONT_CHARS.charAt(lutIndex);
}
return chr;
}
@Override
public float getUStart(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return ((lutIndex % 16) * 8) / 128.0F;
}
@Override
public float getVStart(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return (float) ((lutIndex / 16) * 8) / 128.0F;
}
@Override
public float getXAdvance(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return charWidth[lutIndex];
}
@Override
public float getGlyphW(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return charWidth[lutIndex] - 0.01F;
}
@Override
public float getUSize(char chr) {
int lutIndex = lookupMcFontPosition(chr);
return (charWidth[lutIndex] - 1.01F) / 128.0F;
}
@Override
public float getVSize(char chr) {
return 7.99F / 128.0F;
}
@Override
public float getShadowOffset() {
return FontConfig.fontShadowOffset;
}
@Override
public ResourceLocation getTexture(char chr) {
return locationFontTexture;
}
}
@@ -1,93 +0,0 @@
package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.FontConfig;
import net.minecraft.util.ResourceLocation;
public final class FontProviderUnicode implements FontProvider {
private FontProviderUnicode() {}
private static class InstLoader { static final FontProviderUnicode instance = new FontProviderUnicode(); }
public static FontProviderUnicode get() { return FontProviderUnicode.InstLoader.instance; }
private static final ResourceLocation[] unicodePageLocations = new ResourceLocation[256];
public byte[] glyphWidth;
private ResourceLocation getUnicodePageLocation(int page) {
final ResourceLocation lookup = unicodePageLocations[page];
if (lookup == null) {
final ResourceLocation rl = new ResourceLocation(String.format(
"textures/font/unicode_page_%02x.png",
page));
unicodePageLocations[page] = rl;
return rl;
} else {
return lookup;
}
}
@Override
public boolean isGlyphAvailable(char chr) {
return true;
}
@Override
public char getRandomReplacement(char chr) {
return chr;
}
@Override
public float getUStart(char chr) {
final float startColumnF = (float)(this.glyphWidth[chr] >>> 4);
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;
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;
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;
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);
}
}
@@ -1,143 +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");
public static boolean customFontInUse;
static {
// 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);
// create and add the resource pack that provides fonts
HashMap<String, File> packMap = new HashMap<>();
for (int i = 0; i < FontProviderCustom.ATLAS_COUNT; i++) {
packMap.put(FontProviderCustom.getPrimary().getAtlasResourceName(i), new File(FontProviderCustom.getPrimary().getAtlasFullPath(i)));
packMap.put(FontProviderCustom.getFallback().getAtlasResourceName(i), new File(FontProviderCustom.getFallback().getAtlasFullPath(i)));
}
DefaultResourcePack fontResourcePack = new DefaultResourcePack(packMap);
List defaultResourcePacks = ((ResourceAccessor) Minecraft.getMinecraft()).angelica$getDefaultResourcePacks();
defaultResourcePacks.add(fontResourcePack);
Minecraft.getMinecraft().refreshResources();
// determine if a custom font is in use based on config values
boolean primaryFontFound = false;
boolean fallbackFontFound = false;
for (Font availableFont : availableFonts) {
if (Objects.equals(FontConfig.customFontNamePrimary, availableFont.getFontName())) {
primaryFontFound = true;
break;
}
if (Objects.equals(FontConfig.customFontNameFallback, availableFont.getFontName())) {
fallbackFontFound = true;
break;
}
}
customFontInUse = (FontConfig.enableCustomFont && (primaryFontFound || fallbackFontFound));
}
/**
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);
boolean primaryFontFound = false;
boolean fallbackFontFound = false;
for (int i = 0; i < availableFonts.length; i++) {
if (Objects.equals(FontConfig.customFontNamePrimary, availableFonts[i].getFontName())) {
FontProviderCustom.getPrimary().reloadFont(i);
primaryFontFound = true;
}
if (Objects.equals(FontConfig.customFontNameFallback, availableFonts[i].getFontName())) {
FontProviderCustom.getFallback().reloadFont(i);
fallbackFontFound = true;
}
if (primaryFontFound && fallbackFontFound) { break; }
}
customFontInUse = (FontConfig.enableCustomFont && (primaryFontFound || fallbackFontFound));
}
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,537 +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 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);
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()
);
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;
FontStrategist.customFontInUse = false;
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);
fontList.drawScreen(mouseX, mouseY, delta);
searchBox.drawTextBox();
// I bet you thought this was drawn inside the search box, not on top of it.
if (!this.searchBox.isFocused() && this.searchBox.getText().isEmpty()) {
this.drawCenteredString(this.fontRendererObj, this.searchPrompt,
this.searchBox.xPosition + this.fontRendererObj.getStringWidth(this.searchPrompt) / 2 + 4,
this.searchBox.yPosition + this.searchBox.height / 2 - 4, 0xFFFFFF);
}
drawCenteredString(this.fontRendererObj, this.title, (int) (this.width * 0.5), 8, 0xFFFFFF);
drawCenteredString(this.fontRendererObj, I18n.format("options.angelica.fontconfig.currentfonts",
FontConfig.customFontNamePrimary, FontConfig.customFontNameFallback), (int) (this.width * 0.5), this.height - 92, 0xFFFFFF);
if (this.testAreaInfo == null) {
this.testAreaInfo = this.fontRendererObj.listFormattedStringToWidth(
I18n.format("options.angelica.fontconfig.testareainfo"), this.width / 4
);
}
int cumulativeY = 73;
for (String s : this.testAreaInfo) {
drawCenteredString(this.fontRendererObj, s, this.width * 5 / 6, cumulativeY, 0xFFFFFF);
cumulativeY += this.fontRendererObj.FONT_HEIGHT;
}
cumulativeY += 4;
this.testArea.yPosition = cumulativeY;
this.testArea.drawTextBox();
if (!this.testArea.isFocused() && this.testArea.getText().isEmpty()) {
this.drawCenteredString(this.fontRendererObj, this.testAreaPrompt,
this.testArea.xPosition + this.fontRendererObj.getStringWidth(this.testAreaPrompt) / 2 + 4,
this.testArea.yPosition + this.testArea.height / 2 - 4, 0xFFFFFF);
}
List<String> testText = this.fontRendererObj.listFormattedStringToWidth(
this.testArea.getText().replace('&', '§'), this.width / 4
);
cumulativeY += this.testArea.height + 4;
for (String s : testText) {
drawCenteredString(this.fontRendererObj, s, this.width * 5 / 6, cumulativeY, 0xFFFFFF);
cumulativeY += this.fontRendererObj.FONT_HEIGHT;
}
super.drawScreen(mouseX, mouseY, delta);
for (GuiButton guiButton : buttonList) {
if (!(guiButton instanceof SliderClone slider)) { continue; }
final int top = slider.yPosition;
final int bot = slider.yPosition + slider.height;
final int left = slider.xPosition;
final int right = slider.xPosition + slider.width;
if (mouseY < top || mouseY >= bot || mouseX < left || mouseX >= right) { continue; }
if (mouseX == lastMouseX && mouseY == lastMouseY) {
if (lastStillTime == 0) {
lastStillTime = System.currentTimeMillis();
}
if (lastStillTime + 500L < System.currentTimeMillis()) {
displayTooltip(mouseX, mouseY, slider.tooltipKey);
}
} else {
lastStillTime = 0;
}
break;
}
lastMouseX = mouseX;
lastMouseY = mouseY;
}
private void displayTooltip(int x, int y, String langKey) {
List<String> lines = this.fontRendererObj.listFormattedStringToWidth(I18n.format(langKey), this.width / 2);
this.drawHoveringText(lines, x, y, this.fontRendererObj);
}
@Override
protected void actionPerformed(GuiButton guiButton) {
if (guiButton.enabled) {
if (guiButton instanceof IrisButton irisButton) {
irisButton.onPress();
}
}
}
@Override
protected void keyTyped(char typedChar, int keyCode) {
if (this.searchBox.isFocused()) {
if (keyCode == Keyboard.KEY_ESCAPE) {
this.searchBox.setFocused(false);
return;
}
this.searchBox.textboxKeyTyped(typedChar, keyCode);
this.displayedFonts = filterFonts(this.searchBox.getText().toLowerCase());
}
if (this.testArea.isFocused()) {
if (keyCode == Keyboard.KEY_ESCAPE) {
this.testArea.setFocused(false);
return;
}
this.testArea.textboxKeyTyped(typedChar, keyCode);
}
if (keyCode == Keyboard.KEY_ESCAPE) {
this.onClose();
}
}
@Override
protected void mouseClicked(int mouseX, int mouseY, int mouseButton) {
super.mouseClicked(mouseX, mouseY, mouseButton);
this.searchBox.mouseClicked(mouseX, mouseY, mouseButton);
this.testArea.mouseClicked(mouseX, mouseY, mouseButton);
this.fontList.mouseClicked(mouseX, mouseY, mouseButton);
}
@Override
public void updateScreen() {
this.searchBox.updateCursorCounter();
this.testArea.updateCursorCounter();
}
// this could be more efficient, but it's good enough as is
private ArrayList<Font> filterFonts(String search) {
ArrayList<Font> results;
if (search == null || search.isEmpty()) {
results = new ArrayList<>(Arrays.asList(availableFonts));
} else {
results = Arrays.stream(availableFonts).filter((font -> font.getFontName().toLowerCase().contains(search))).collect(Collectors.toCollection(ArrayList::new));
}
selectedPrimaryFontListPos = -1;
selectedFallbackFontListPos = -1;
for (int i = 0; i < results.size(); i++) {
if (Objects.equals(currentPrimaryFontName, results.get(i).getFontName())) {
selectedPrimaryFontListPos = i;
}
if (Objects.equals(currentFallbackFontName, results.get(i).getFontName())) {
selectedFallbackFontListPos = i;
}
}
return results;
}
class FontList extends GuiSlot {
public FontList() {
super(FontConfigScreen.this.mc, FontConfigScreen.this.width * 2 / 3, FontConfigScreen.this.height, 52, FontConfigScreen.this.height - 102, 18);
}
private void mouseClicked(int mouseX, int mouseY, int mouseButton) {
if (mouseY < this.top || mouseY > this.bottom) { return; }
int listLeftBound = this.width / 2 - this.getListWidth() / 2;
int listRightBound = this.width / 2 + this.getListWidth() / 2;
if (mouseX < listLeftBound || mouseX > listRightBound) { return; }
int mousePosInList = mouseY - this.top - this.headerPadding + (int)this.amountScrolled - 4;
int slotIndex = mousePosInList / this.slotHeight;
if (slotIndex < 0 || slotIndex >= this.getSize()) { return; }
if (mouseButton == 0) {
onElemClicked(slotIndex, false);
} else if (mouseButton == 1) {
onElemClicked(slotIndex, true);
}
}
protected void onElemClicked(int index, boolean rightClick) {
if (!rightClick) {
selectedPrimaryFontListPos = index;
currentPrimaryFontName = displayedFonts.get(index).getFontName();
} else {
selectedFallbackFontListPos = index;
currentFallbackFontName = displayedFonts.get(index).getFontName();
}
applyChanges(false);
}
protected boolean isSelected(int index) {
return (index == selectedPrimaryFontListPos || index == selectedFallbackFontListPos);
}
protected int getSize() {
return displayedFonts.size();
}
@Override
protected int getContentHeight() {
return this.getSize() * 18;
}
@Override
protected int getScrollBarX() {
return this.width * 15 / 16 - 5;
}
@Override
public int getListWidth() {
return this.width * 2 / 3;
}
// see onElemClicked for the proper version that supports right-clicking
protected void elementClicked(int index, boolean doubleClicked, int mouseX, int mouseY) {}
protected void drawBackground() {
// do nothing, the font list doesn't occupy the entire screen
}
protected void drawSlot(int index, int x, int y, int p_148126_4_, Tessellator tessellator, int p_148126_6_, int p_148126_7_) {
int color = 0xffffff;
if (index == selectedPrimaryFontListPos) {
color &= 0xffff55;
}
if (index == selectedFallbackFontListPos) {
color &= 0x55ffff;
}
drawCenteredString(fontRendererObj, displayedFonts.get(index).getFontName(), this.width / 2, y + 1, color);
}
}
}
@@ -1,164 +0,0 @@
package com.gtnewhorizons.angelica.client.gui;
import lombok.AllArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.client.resources.I18n;
import org.lwjgl.opengl.GL11;
import java.util.function.Consumer;
import java.util.function.Function;
public class SliderClone extends GuiButton {
private final Function<Float, String> displayStringFormatter;
private final Consumer<Float> valueSetter;
private final Option option;
private float sliderPercentage;
public final String tooltipKey;
public boolean mouseDragging;
@AllArgsConstructor
public static class Option {
float min;
float max;
float step;
float denormalize(float alpha) {
float lerp = min * (1 - alpha) + max * alpha;
return Math.round(lerp / step) * step;
}
float normalize(float value) {
return (value - min) / (max - min);
}
}
public SliderClone(int x, int y, int width, int height, Option option, float initialValue,
Consumer<Float> setter, Function<Float, String> formatter, String tooltipKey) {
super(0, x, y, width, height, "");
this.option = option;
this.sliderPercentage = option.normalize(initialValue);
this.displayString = formatter.apply(initialValue);
this.valueSetter = setter;
this.displayStringFormatter = formatter;
this.tooltipKey = tooltipKey;
}
public static SliderCloneBuilder builder() {
return new SliderCloneBuilder();
}
public void setCenterPosition(int x, int y) {
this.xPosition = x - this.width / 2;
this.yPosition = y - this.height / 2;
}
@Accessors(fluent = true) @Setter
public static class SliderCloneBuilder {
private int x = 0, y = 0; // center position, not a corner
private int width = 144, height = 20;
private Option option;
private float initialValue;
private Consumer<Float> setter;
private String formatString = "%3.2f";
private String langKey;
// overrides: if null, these get set to default values
private String tooltipKey;
private Function<Float, String> formatter;
public SliderClone build() {
if (this.formatter == null) {
this.formatter = value -> I18n.format(this.langKey, String.format(this.formatString, value));
}
if (this.tooltipKey == null) {
this.tooltipKey = this.langKey + ".tooltip";
}
return new SliderClone(
this.x - this.width / 2,
this.y - this.height / 2,
this.width,
this.height,
this.option,
this.initialValue,
this.setter,
this.formatter,
this.tooltipKey
);
}
}
/**
* Returns 0 if the button is disabled, 1 if the mouse is NOT hovering over this button and 2 if it IS hovering over
* this button.
*/
public int getHoverState(boolean mouseOver) {
return 0;
}
/**
* Fired when the mouse button is dragged. Equivalent of MouseListener.mouseDragged(MouseEvent e).
*/
protected void mouseDragged(Minecraft mc, int mouseX, int mouseY) {
if (this.visible) {
if (this.mouseDragging) {
this.sliderPercentage = (float)(mouseX - (this.xPosition + 4)) / (float)(this.width - 8);
if (this.sliderPercentage < 0.0F) {
this.sliderPercentage = 0.0F;
}
if (this.sliderPercentage > 1.0F) {
this.sliderPercentage = 1.0F;
}
float f = this.option.denormalize(this.sliderPercentage);
valueSetter.accept(f);
this.sliderPercentage = this.option.normalize(f);
this.displayString = displayStringFormatter.apply(f);
}
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderPercentage * (float)(this.width - 8)), this.yPosition, 0, 66, 4, 20);
this.drawTexturedModalRect(this.xPosition + (int)(this.sliderPercentage * (float)(this.width - 8)) + 4, this.yPosition, 196, 66, 4, 20);
}
}
/**
* Returns true if the mouse has been pressed on this control. Equivalent of MouseListener.mousePressed(MouseEvent e).
*/
public boolean mousePressed(Minecraft mc, int mouseX, int mouseY) {
if (super.mousePressed(mc, mouseX, mouseY)) {
this.sliderPercentage = (float)(mouseX - (this.xPosition + 4)) / (float)(this.width - 8);
if (this.sliderPercentage < 0.0F) {
this.sliderPercentage = 0.0F;
}
if (this.sliderPercentage > 1.0F) {
this.sliderPercentage = 1.0F;
}
float f = this.option.denormalize(this.sliderPercentage);
valueSetter.accept(f);
this.sliderPercentage = this.option.normalize(f);
this.displayString = displayStringFormatter.apply(f);
this.mouseDragging = true;
return true;
} else {
return false;
}
}
/**
* Fired when the mouse button is released. Equivalent of MouseListener.mouseReleased(MouseEvent e).
*/
public void mouseReleased(int mouseX, int mouseY) {
this.mouseDragging = false;
}
}
@@ -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,6 +1,8 @@
package com.gtnewhorizons.angelica.compat;
import com.falsepattern.chunk.api.DataRegistry;
import com.gtnewhorizons.angelica.mixins.interfaces.ExtendedBlockStorageAccessor;
import com.gtnewhorizons.angelica.mixins.interfaces.ExtendedNibbleArray;
import com.gtnewhorizons.neid.mixins.interfaces.IExtendedBlockStorageMixin;
import net.minecraft.world.chunk.Chunk;
@@ -17,7 +19,7 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
}
public ExtendedBlockStorageExt(Chunk chunk, ExtendedBlockStorage storage) {
super(storage.yBase, storage.getSkylightArray() != null);
super(((ExtendedBlockStorageAccessor) storage).getYBase(), storage.getSkylightArray() != null);
if (ModStatus.isChunkAPILoaded) {
if (storage.getSkylightArray() != null) {
@@ -31,7 +33,7 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
System.arraycopy(((IExtendedBlockStorageMixin)(Object)storage).getBlock16BArray(), 0, block16BArray, 0, block16BArray.length);
if(storage.getBlockMSBArray() != null) {
this.setBlockMSBArray(new NibbleArray(block16BArray.length, 4));
copyNibbleArray(storage.getBlockMSBArray(), this.getBlockMSBArray());
copyNibbleArray((ExtendedNibbleArray) storage.getBlockMSBArray(), (ExtendedNibbleArray) this.getBlockMSBArray());
}
arrayLen = block16BArray.length;
if (ModStatus.isNEIDMetadataExtended) {
@@ -50,31 +52,31 @@ public class ExtendedBlockStorageExt extends ExtendedBlockStorage {
System.arraycopy(storage.getBlockLSBArray(), 0, blockLSBArray, 0, blockLSBArray.length);
if(storage.getBlockMSBArray() != null) {
this.setBlockMSBArray(new NibbleArray(blockLSBArray.length, 4));
copyNibbleArray(storage.getBlockMSBArray(), this.getBlockMSBArray());
copyNibbleArray((ExtendedNibbleArray) storage.getBlockMSBArray(), (ExtendedNibbleArray) this.getBlockMSBArray());
}
arrayLen = blockLSBArray.length;
}
if (!ModStatus.isNEIDMetadataExtended) copyNibbleArray(storage.getMetadataArray(), this.getMetadataArray());
copyNibbleArray(storage.getBlocklightArray(), this.getBlocklightArray());
if (!ModStatus.isNEIDMetadataExtended) copyNibbleArray((ExtendedNibbleArray) storage.getMetadataArray(), (ExtendedNibbleArray)this.getMetadataArray());
copyNibbleArray((ExtendedNibbleArray) storage.getBlocklightArray(), (ExtendedNibbleArray)this.getBlocklightArray());
if(storage.getSkylightArray() != null) {
hasSky = true;
if(this.getSkylightArray() == null) {
this.setSkylightArray(new NibbleArray(arrayLen, 4));
}
copyNibbleArray(storage.getSkylightArray(), this.getSkylightArray());
copyNibbleArray((ExtendedNibbleArray) storage.getSkylightArray(), (ExtendedNibbleArray) this.getSkylightArray());
}
}
this.blockRefCount = storage.blockRefCount;
((ExtendedBlockStorageAccessor) this).setBlockRefCount(((ExtendedBlockStorageAccessor) storage).getBlockRefCount());
}
private static void copyNibbleArray(NibbleArray srcArray, NibbleArray dstArray) {
private static void copyNibbleArray(ExtendedNibbleArray srcArray, ExtendedNibbleArray dstArray) {
if (srcArray == null || dstArray == null) {
throw new RuntimeException("NibbleArray is null src: " + (srcArray == null) + " dst: " + (dstArray == null));
}
final byte[] data = srcArray.data;
System.arraycopy(data, 0, dstArray.data, 0, data.length);
final byte[] data = srcArray.getData();
System.arraycopy(data, 0, dstArray.getData(), 0, data.length);
}
}
@@ -1,17 +1,14 @@
package com.gtnewhorizons.angelica.compat;
import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat;
import com.gtnewhorizons.angelica.helpers.LoadControllerHelper;
import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.versioning.ArtifactVersion;
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
import mods.battlegear2.Battlegear;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.dries007.holoInventory.HoloInventory;
import xonin.backhand.Backhand;
public class ModStatus {
public static final Logger LOGGER = LogManager.getLogger("ModCompat");
public static BackhandReflectionCompat backhandCompat;
/**
* Mixin Version
*/
@@ -20,6 +17,7 @@ public class ModStatus {
* ASM Version
*/
public static boolean isOldNEIDLoaded;
public static boolean isBetterCrashesLoaded;
public static boolean isNEIDMetadataExtended;
public static boolean isLotrLoaded;
@@ -32,14 +30,8 @@ public class ModStatus {
public static boolean isThaumcraftLoaded;
public static boolean isThaumicHorizonsLoaded;
public static boolean isBaublesLoaded;
public static boolean isFluidLoggedLoaded;
public static void preInit(){
isBackhandLoaded = Loader.isModLoaded("backhand");
if(isBackhandLoaded) {
isBackhandLoaded = BackhandReflectionCompat.isBackhandLoaded();
}
isBetterCrashesLoaded = Loader.isModLoaded("bettercrashes");
isNEIDLoaded = Loader.isModLoaded("neid");
isOldNEIDLoaded = Loader.isModLoaded("notenoughIDs");
@@ -49,12 +41,17 @@ public class ModStatus {
isXaerosMinimapLoaded = Loader.isModLoaded("XaeroMinimap");
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
isBattlegearLoaded = Loader.isModLoaded("battlegear2");
isBackhandLoaded = Loader.isModLoaded("backhand");
isThaumcraftLoaded = Loader.isModLoaded("Thaumcraft");
isThaumicHorizonsLoaded = Loader.isModLoaded("ThaumicHorizons");
isBaublesLoaded = Loader.isModLoaded("Baubles");
isFluidLoggedLoaded = Loader.isModLoaded("fluidlogged");
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
if (isHoloInventoryLoaded){
isHoloInventoryLoaded = new DefaultArtifactVersion("2.4.4-GTNH")
.compareTo(
LoadControllerHelper.getOwningMod(HoloInventory.class).getProcessedVersion()
) <= 0;
}
// remove compat with original release of BG2
if (isBattlegearLoaded){
@@ -64,9 +61,17 @@ public class ModStatus {
) <= 0;
}
if (isBackhandLoaded){
isBackhandLoaded = new DefaultArtifactVersion("1.4.1")
.compareTo(
LoadControllerHelper.getOwningMod(Backhand.class).getProcessedVersion()
) <= 0;
}
isNEIDMetadataExtended = false;
if (isNEIDLoaded) {
final int majorVersion = Integer.parseInt(Loader.instance().getIndexedModList().get("neid").getVersion().split("\\.")[0]);
int majorVersion = Integer.parseInt(Loader.instance().getIndexedModList().get("neid").getVersion().split("\\.")[0]);
if (majorVersion >= 2) {
isNEIDMetadataExtended = true;
}
@@ -1,76 +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 (Throwable e) {
LOGGER.error("Failed to invoke Backhand renderOffhandReturn", e);
isLoaded = false;
}
}
}
public static ItemStack getOffhandItem(EntityPlayer player) {
if (isLoaded && getOffhandItem != null) {
try {
return (ItemStack) getOffhandItem.invokeExact(player);
} catch (Throwable e) {
LOGGER.error("Failed to invoke Backhand getOffhandItem", e);
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();
}
}
@@ -1,93 +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 (Throwable e) {
LOGGER.error("Failed to invoke HoloInventory renderEvent", e);
isLoaded = false;
}
}
public static void setAngelicaOverride(boolean value) {
if (!isLoaded) return;
try {
angelicaOverrideSetter.invokeExact(value);
} catch (Throwable e) {
LOGGER.error("Failed to set HoloInventory angelicaOverride", e);
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,102 +0,0 @@
package com.gtnewhorizons.angelica.compat.iris;
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)
*/
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 synchronized void initialize() {
if (initialized) return;
initialized = true;
initializeBOP();
initializeRWG();
}
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 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
for (Map.Entry<Class<?>, BiomeCategories> entry : rwgClassMap.entrySet()) {
if (entry.getKey().isInstance(biome)) {
return entry.getValue();
}
}
return 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,62 +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 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 feet 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();
thirdPerson = Minecraft.getMinecraft().gameSettings.thirdPersonView > 0;
final double camX, camY, camZ;
offset.set(0, 0, 0);
if (thirdPerson) {
// Third person: use inverse modelview to find camera offset
inverseModelView.set(RenderingState.INSTANCE.getModelViewMatrix()).invert();
inverseModelView.transformPosition(offset);
}
camX = entityX + offset.x;
camY = entityY + eyeHeight + offset.y;
camZ = entityZ + offset.z;
pos.set(camX, camY, camZ);
blockPos.set(MathHelper.floor_double(camX), MathHelper.floor_double(camY), MathHelper.floor_double(camZ));
pitch = entity.cameraPitch;
yaw = entity.rotationYaw;
thirdPerson = Minecraft.getMinecraft().gameSettings.thirdPersonView == 1;
}
}
@@ -40,7 +40,6 @@ public class ChunkPos {
return (long)x & 4294967295L | ((long)z & 4294967295L) << 32;
}
@Override
public int hashCode()
{
final int i = 1664525 * this.x + 1013904223;
@@ -48,7 +47,6 @@ public class ChunkPos {
return i ^ j;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
@@ -1,17 +0,0 @@
package com.gtnewhorizons.angelica.compat.mojang;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.PlayerControllerMP;
public final class GameModeUtil {
private GameModeUtil() {
}
public static boolean isSpectator() {
final PlayerControllerMP controller = Minecraft.getMinecraft().playerController;
if (controller == null) {
return false;
}
return controller.currentGameType.getID() == 3;
}
}
@@ -1,23 +1,10 @@
package com.gtnewhorizons.angelica.compat.mojang;
import com.gtnewhorizons.angelica.compat.ModStatus;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
public enum InteractionHand {
MAIN_HAND,
OFF_HAND;
InteractionHand() {
}
public ItemStack getItemInHand(EntityPlayer player){
if (ModStatus.isBackhandLoaded && this == InteractionHand.OFF_HAND){ // off hand (requires backhand)
return ModStatus.backhandCompat.getOffhandItem(player);
}
else { // main hand
return player.getHeldItem();
}
}
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.compat.mojang;
import com.gtnewhorizons.angelica.compat.lwjgl.MemoryStack;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
@@ -7,8 +8,6 @@ import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import javax.imageio.ImageIO;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import lombok.Getter;
import net.coderbot.iris.Iris;
import org.lwjgl.opengl.GL11;
@@ -59,20 +58,15 @@ public class NativeImage extends BufferedImage {
// final int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_WIDTH);
// final int height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_HEIGHT);
final IntBuffer buffer = MemoryUtilities.memAllocInt(size);
try {
try (final MemoryStack stack = MemoryStack.stackPush()) {
final IntBuffer buffer = stack.mallocInt(size);
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, level, format.glFormat, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
int[] data = new int[size];
buffer.get(data);
setRGB(0, 0, width, height, data, 0, width);
} finally {
MemoryUtilities.memFree(buffer);
}
}
public void writeToFile(File file) throws IOException{
try {
ImageIO.write(this, "png", file);
@@ -110,15 +104,18 @@ public class NativeImage extends BufferedImage {
public enum Format {
RGBA(4, GL11.GL_RGBA),
RGB(3, GL11.GL_RGB);
RGBA(4, GL11.GL_RGBA, BufferedImage.TYPE_INT_ARGB),
RGB(3, GL11.GL_RGB, BufferedImage.TYPE_INT_RGB);
private final int components;
private final int glFormat;
private final int imageType;
Format(int components, int glFormat) {
Format(int components, int glFormat, int imageType) {
this.components = components;
this.glFormat = glFormat;
this.imageType = imageType;
}
}
@@ -0,0 +1,63 @@
package com.gtnewhorizons.angelica.compat.nd;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
public class BufferWriter {
// Adapted from Neodymium
private final ByteBuffer buf;
private final FloatBuffer floatBuffer;
private final ShortBuffer shortBuffer;
private final IntBuffer intBuffer;
public BufferWriter(ByteBuffer buf) {
this.buf = buf;
this.floatBuffer = buf.asFloatBuffer();
this.shortBuffer = buf.asShortBuffer();
this.intBuffer = buf.asIntBuffer();
}
private void incrementPosition(int add) {
buf.position(buf.position() + add);
floatBuffer.position(buf.position() / 4);
shortBuffer.position(buf.position() / 2);
intBuffer.position(buf.position() / 4);
}
public void writeFloat(float x) {
try {
floatBuffer.put(x);
incrementPosition(4);
} catch(Exception e){
e.printStackTrace();
}
}
public void writeInt(int x) {
intBuffer.put(x);
incrementPosition(4);
}
public void writeByte(byte x) {
buf.put(x); // this increments the buffer position by 1
incrementPosition(0);
}
public int position() {
return buf.position();
}
public void writeShort(short s) {
shortBuffer.put(s);
incrementPosition(2);
}
}
@@ -0,0 +1,47 @@
package com.gtnewhorizons.angelica.compat.nd.writers;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
import java.nio.ByteBuffer;
public class ItemVBOQuadWriter implements IWriteQuads {
private boolean direct;
public ItemVBOQuadWriter() {
init(false);
}
public void init(boolean direct) {
// This would need to be re-initialized if the direct vs indirect changes
this.direct = direct;
}
@Override
public void writeQuad(QuadView quad, ByteBuffer buf) {
if(direct) {
writeQuadDirect(quad, buf);
} else {
writeQuadIndirect(quad, buf);
}
}
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
throw new UnsupportedOperationException("Direct mode not supported yet");
}
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
for(int idx = 0; idx < 4; ++idx) {
// Position
buf.putFloat(quad.getX(idx));
buf.putFloat(quad.getY(idx));
buf.putFloat(quad.getZ(idx));
// Texture
buf.putFloat(quad.getTexU(idx));
buf.putFloat(quad.getTexV(idx));
// Normals + Padding
buf.putInt(quad.getNormal(idx));
}
}
}
@@ -0,0 +1,47 @@
package com.gtnewhorizons.angelica.compat.nd.writers;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
import java.nio.ByteBuffer;
public class PositionColorTextureQuadWriter implements IWriteQuads {
private boolean direct;
public PositionColorTextureQuadWriter() {
init(false);
}
public void init(boolean direct) {
// This would need to be re-initialized if the direct vs indirect changes
this.direct = direct;
}
@Override
public void writeQuad(QuadView quad, ByteBuffer buf) {
if(direct) {
writeQuadDirect(quad, buf);
} else {
writeQuadIndirect(quad, buf);
}
}
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
throw new UnsupportedOperationException("Direct mode not supported yet");
}
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
for(int idx = 0; idx < 4; ++idx) {
// Position
buf.putFloat(quad.getX(idx));
buf.putFloat(quad.getY(idx));
buf.putFloat(quad.getZ(idx));
// Color
buf.putInt(quad.getColor(idx));
// Texture
buf.putFloat(quad.getTexU(idx));
buf.putFloat(quad.getTexV(idx));
}
}
}
@@ -0,0 +1,40 @@
package com.gtnewhorizons.angelica.compat.nd.writers;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
import java.nio.ByteBuffer;
public class PositionQuadWriter implements IWriteQuads {
private boolean direct;
public PositionQuadWriter() {
init(false);
}
public void init(boolean direct) {
// This would need to be re-initialized if the direct vs indirect changes
this.direct = direct;
}
@Override
public void writeQuad(QuadView quad, ByteBuffer buf) {
if(direct) {
writeQuadDirect(quad, buf);
} else {
writeQuadIndirect(quad, buf);
}
}
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
throw new UnsupportedOperationException("Direct mode not supported yet");
}
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
for(int idx = 0; idx < 4; ++idx) {
// Position
buf.putFloat(quad.getX(idx));
buf.putFloat(quad.getY(idx));
buf.putFloat(quad.getZ(idx));
}
}
}
@@ -0,0 +1,49 @@
package com.gtnewhorizons.angelica.compat.nd.writers;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
import java.nio.ByteBuffer;
public class PositionTextureColorQuadWriter implements IWriteQuads {
private boolean direct;
public PositionTextureColorQuadWriter() {
init(false);
}
public void init(boolean direct) {
// This would need to be re-initialized if the direct vs indirect changes
this.direct = direct;
}
@Override
public void writeQuad(QuadView quad, ByteBuffer buf) {
if(direct) {
writeQuadDirect(quad, buf);
} else {
writeQuadIndirect(quad, buf);
}
}
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
throw new UnsupportedOperationException("Direct mode not supported yet");
}
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
for(int idx = 0; idx < 4; ++idx) {
// Position
buf.putFloat(quad.getX(idx));
buf.putFloat(quad.getY(idx));
buf.putFloat(quad.getZ(idx));
// Texture
buf.putFloat(quad.getTexU(idx));
buf.putFloat(quad.getTexV(idx));
// Color
buf.putInt(quad.getColor(idx));
}
}
}
@@ -0,0 +1,44 @@
package com.gtnewhorizons.angelica.compat.nd.writers;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.QuadView;
import com.gtnewhorizon.gtnhlib.client.renderer.quad.writers.IWriteQuads;
import java.nio.ByteBuffer;
public class PositionTextureQuadWriter implements IWriteQuads {
private boolean direct;
public PositionTextureQuadWriter() {
init(false);
}
public void init(boolean direct) {
// This would need to be re-initialized if the direct vs indirect changes
this.direct = direct;
}
@Override
public void writeQuad(QuadView quad, ByteBuffer buf) {
if(direct) {
writeQuadDirect(quad, buf);
} else {
writeQuadIndirect(quad, buf);
}
}
protected void writeQuadDirect(QuadView quad, ByteBuffer buf) {
throw new UnsupportedOperationException("Direct mode not supported yet");
}
protected void writeQuadIndirect(QuadView quad, ByteBuffer buf) {
for(int idx = 0; idx < 4; ++idx) {
// Position
buf.putFloat(quad.getX(idx));
buf.putFloat(quad.getY(idx));
buf.putFloat(quad.getZ(idx));
// Texture
buf.putFloat(quad.getTexU(idx));
buf.putFloat(quad.getTexV(idx));
}
}
}
@@ -72,6 +72,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
private final MultiPhaseParameters phases;
private final int hash;
private final Optional<RenderLayer> affectedOutline;
private final boolean outline;
private MultiPhase(String name, VertexFormat vertexFormat, int drawMode, int expectedBufferSize, boolean hasCrumbling, boolean translucent, MultiPhaseParameters phases) {
super(name, vertexFormat, drawMode, expectedBufferSize, () -> {
@@ -83,6 +84,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
this.affectedOutline = phases.outlineMode == RenderLayer.OutlineMode.AFFECTS_OUTLINE ? phases.texture.getId().map((arg2) -> {
return getOutline(arg2, phases.cull);
}) : Optional.empty();
this.outline = phases.outlineMode == RenderLayer.OutlineMode.IS_OUTLINE;
this.hash = Objects.hash(new Object[]{super.hashCode(), phases});
}
@@ -95,23 +97,20 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
return this.affectedOutline;
}
@Override
public boolean equals(@Nullable Object object) {
return this == object;
}
@Override
public int hashCode() {
return this.hash;
}
@Override
public String toString() {
return "RenderType[" + this.phases + ']';
}
static {
CACHE = new ObjectOpenCustomHashSet<>(RenderLayer.MultiPhase.HashStrategy.INSTANCE);
CACHE = new ObjectOpenCustomHashSet(RenderLayer.MultiPhase.HashStrategy.INSTANCE);
}
static enum HashStrategy implements Hash.Strategy<MultiPhase> {
@@ -169,7 +168,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
this.phases = ImmutableList.of(this.texture, this.transparency, this.diffuseLighting, this.shadeModel, this.alpha, this.depthTest, this.cull, this.lightmap, this.fog, this.layering, this.target, this.texturing, this.writeMaskState);
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
@@ -181,12 +179,10 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
}
}
@Override
public int hashCode() {
return Objects.hash(new Object[]{this.phases, this.outlineMode});
}
@Override
public String toString() {
return "CompositeState[" + this.phases + ", outlineProperty=" + this.outlineMode + ']';
}
@@ -297,7 +293,6 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris
this.name = name;
}
@Override
public String toString() {
return this.name;
}
@@ -11,6 +11,7 @@ import org.lwjgl.opengl.GL11;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalDouble;
public abstract class RenderPhase {
@@ -97,7 +98,6 @@ public abstract class RenderPhase {
this.endAction.run();
}
@Override
public boolean equals(@Nullable Object object) {
if (this == object) {
return true;
@@ -109,12 +109,10 @@ public abstract class RenderPhase {
}
}
@Override
public int hashCode() {
return this.name.hashCode();
}
@Override
public String toString() {
return this.name;
}
@@ -17,12 +17,12 @@ public class AngelicaConfig {
@Config.DefaultBoolean(true)
public static boolean enableReesesSodiumOptions;
@Config.Comment("Enable Sodium fluid rendering [Experimental]")
@Config.Comment("Enable Sodium fluid rendering")
@Config.DefaultBoolean(false)
@Config.RequiresMcRestart
public static boolean enableSodiumFluidRendering;
@Config.Comment("Inject BakedModel rendering into some vanilla blocks")
@Config.Comment("Inject QuadProvider rendering into some vanilla blocks")
@Config.DefaultBoolean(false)
@Config.RequiresMcRestart
public static boolean injectQPRendering;
@@ -38,14 +38,14 @@ public class AngelicaConfig {
public static boolean enableIris;
@Config.Comment("Enable MCPatcherForge features, still in Alpha. Individual features are toggled in mcpatcher.json")
@Config.DefaultBoolean(true)
@Config.DefaultBoolean(false)
@Config.RequiresMcRestart
public static boolean enableMCPatcherForgeFeatures;
@Config.Comment("Replace cloud renderer with a VBO version.")
@Config.Comment("Replace some vanilla render paths with more optimized versions. Disable if you encounter mixin conflicts.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enableVBOClouds;
public static boolean enableVBO;
@Config.Comment("Enable NotFine features")
@Config.DefaultBoolean(true)
@@ -133,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
@@ -148,60 +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("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;
@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.GTBlockOre",
"shukaro.artifice.block.world.BlockOre",
"bartworks.system.material.BWMetaGeneratedOres",
"gtPlusPlus.core.block.base.BlockBaseOre",
})
public static String[] blockCrackFixRenderPassWhitelist_;
@Config.Comment("Register HardcodedCustomUniforms in Iris Shaders. May help with compatibility in certain shader packs")
@Config.DefaultBoolean(false)
public static boolean enableHardcodedCustomUniforms;
}
@@ -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,68 +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;
}
@@ -28,10 +28,9 @@ import static org.lwjgl.opengl.GL20.glUniform1;
import static org.lwjgl.opengl.GL20.glUniform1f;
import static org.lwjgl.opengl.GL20.glUniform1i;
import static org.lwjgl.opengl.GL20.glVertexAttribPointer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.*;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
import com.gtnewhorizon.gtnhlib.client.renderer.shader.ShaderProgram;
import com.gtnewhorizons.angelica.compat.lwjgl.MemoryStack;
import java.nio.FloatBuffer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
@@ -132,7 +131,7 @@ public abstract class F3Graph {
// Load vertex buffer
vertBuf = glGenBuffers();
glBindBuffer(GL_ARRAY_BUFFER, vertBuf);
try (final MemoryStack stack = stackPush()) {
try (final MemoryStack stack = MemoryStack.stackPush()) {
final FloatBuffer vertices = stack.mallocFloat(VERT_COUNT * VERT_FLOATS);
// Since we use a triangle strip, we only need 4 vertices. The quad extends to the top of the screen so spikes
// don't get truncated. The max height is replaced in the vert shader, no need to be precise.
@@ -6,18 +6,19 @@ import com.gtnewhorizon.gtnhlib.blockpos.IBlockPos;
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
import com.gtnewhorizons.angelica.api.IDynamicLightProducer;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.compat.battlegear2.Battlegear2Compat;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import 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;
@@ -27,6 +28,7 @@ import net.minecraft.item.ItemStack;
import net.minecraft.util.MathHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xonin.backhand.api.core.BackhandUtils;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -37,8 +39,6 @@ public class DynamicLights {
public static DynamicLightsMode Mode = DynamicLightsMode.OFF;
public static boolean ShaderForce = false;
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<>();
@@ -53,7 +53,7 @@ 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
@@ -378,13 +378,13 @@ public class DynamicLights {
player instanceof IBattlePlayer battlePlayer &&
battlePlayer.battlegear2$isBattlemode()
) {
ItemStack offhand = Battlegear2Compat.getBattlegear2Offhand(player);
ItemStack offhand = ((IInventoryPlayerBattle) player.inventory).battlegear2$getCurrentOffhandWeapon();
if (offhand != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand, inWater));
}
}
else if (ModStatus.isBackhandLoaded && living instanceof EntityPlayer player){
ItemStack offhand = ModStatus.backhandCompat.getOffhandItem(player);
ItemStack offhand = BackhandUtils.getOffhandItem(player);
if (offhand != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand, inWater));
}
@@ -1,205 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
import com.gtnewhorizons.angelica.glsm.recording.CommandBuffer;
import com.gtnewhorizons.angelica.glsm.recording.CommandBufferProcessor;
import com.gtnewhorizons.angelica.glsm.recording.CommandBufferProcessor.BufferTransformOptimizer;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* Builds final command buffers from raw command buffers and compiled VBO data.
* 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 buildOptimizedFromRawBuffer(
CommandBuffer rawBuffer,
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
final CompiledLineBuffer compiledLineBuffer,
final Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers,
VertexBuffer[] ownedVbos,
int glListId,
CommandBuffer finalBuffer) {
// Build VBO index map for efficient lookup (no boxing)
final Object2IntMap<VertexBuffer> vboIndexMap = new Object2IntOpenHashMap<>();
vboIndexMap.defaultReturnValue(-1);
for (int i = 0; i < ownedVbos.length; i++) {
vboIndexMap.put(ownedVbos[i], i);
}
// Collect all merged draw ranges sorted by command index
final List<DrawRangeWithBuffer> allRanges = collectDrawRanges(compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers);
// Buffer optimizer state
final BufferTransformOptimizer opt = new BufferTransformOptimizer();
rawBuffer.resetRead();
int cmdIndex = 0;
int rangeIndex = 0;
while (rawBuffer.hasRemaining()) {
// Emit draw ranges at this command position
while (rangeIndex < allRanges.size() && allRanges.get(rangeIndex).range().commandIndex() == cmdIndex) {
emitDrawRangeToBuffer(allRanges.get(rangeIndex++), opt, finalBuffer, vboIndexMap);
}
// Read and process the command
final int opcode = rawBuffer.readInt();
CommandBufferProcessor.processCommandForOptimization(opcode, rawBuffer, opt, finalBuffer);
cmdIndex++;
}
// Emit remaining draw ranges at end of command stream
while (rangeIndex < allRanges.size()) {
emitDrawRangeToBuffer(allRanges.get(rangeIndex++), opt, finalBuffer, vboIndexMap);
}
// Emit residual transform if not identity
if (!opt.isIdentity()) {
opt.emitPendingTransform(finalBuffer);
}
}
/**
* Build commands without transform collapsing.
* Copies state commands directly (no transform optimization), but still uses
* merged draw ranges (draws already collapsed by format + transform in VBO compilation).
*/
public static void buildUnoptimizedFromRawBuffer(
CommandBuffer rawBuffer,
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
CompiledLineBuffer compiledLineBuffer,
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers,
VertexBuffer[] ownedVbos,
CommandBuffer finalBuffer) {
// Build VBO index map for efficient lookup (no boxing)
final Object2IntMap<VertexBuffer> vboIndexMap = new Object2IntOpenHashMap<>();
vboIndexMap.defaultReturnValue(-1);
for (int i = 0; i < ownedVbos.length; i++) {
vboIndexMap.put(ownedVbos[i], i);
}
// Collect all merged draw ranges sorted by command index
final List<DrawRangeWithBuffer> allRanges = collectDrawRanges(compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers);
rawBuffer.resetRead();
int cmdIndex = 0;
int rangeIndex = 0;
while (rawBuffer.hasRemaining()) {
// Emit draw ranges at this command position
while (rangeIndex < allRanges.size() && allRanges.get(rangeIndex).range().commandIndex() == cmdIndex) {
final DrawRangeWithBuffer drb = allRanges.get(rangeIndex++);
final int vboIdx = vboIndexMap.getInt(drb.vbo());
if (vboIdx >= 0) {
finalBuffer.writeDrawRange(vboIdx, drb.range().startVertex(), drb.range().vertexCount(), drb.hasBrightness());
}
}
// Copy the command directly
CommandBufferProcessor.copyCommand(rawBuffer, finalBuffer);
cmdIndex++;
}
// Emit remaining draw ranges at end of command stream
while (rangeIndex < allRanges.size()) {
final DrawRangeWithBuffer drb = allRanges.get(rangeIndex++);
final int vboIdx = vboIndexMap.getInt(drb.vbo());
if (vboIdx >= 0) {
finalBuffer.writeDrawRange(vboIdx, drb.range().startVertex(), drb.range().vertexCount(), drb.hasBrightness());
}
}
}
/**
* Collect all draw ranges from compiled buffers, sorted by command index.
*/
static List<DrawRangeWithBuffer> collectDrawRanges(
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
CompiledLineBuffer compiledLineBuffer,
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers) {
final List<DrawRangeWithBuffer> allRanges = new ArrayList<>();
// Add quad ranges
final CompiledFormatBuffer[] quadBufferArray = compiledQuadBuffers.values().toArray(new CompiledFormatBuffer[0]);
for (int b = 0; b < quadBufferArray.length; b++) {
final CompiledFormatBuffer compiledBuffer = quadBufferArray[b];
final DrawRange[] ranges = compiledBuffer.mergedRanges();
for (int i = 0; i < ranges.length; i++) {
allRanges.add(new DrawRangeWithBuffer(ranges[i], compiledBuffer.vbo(), compiledBuffer.flags().hasBrightness));
}
}
// Add immediate mode line ranges
if (compiledLineBuffer != null) {
final DrawRange[] ranges = compiledLineBuffer.mergedRanges();
for (int i = 0; i < ranges.length; i++) {
allRanges.add(new DrawRangeWithBuffer(ranges[i], compiledLineBuffer.vbo(), false));
}
}
// Add tessellator primitive ranges (lines and triangles) - grouped by format
if (compiledPrimitiveBuffers != null && !compiledPrimitiveBuffers.isEmpty()) {
final CompiledPrimitiveBuffers[] primBufferArray = compiledPrimitiveBuffers.values().toArray(new CompiledPrimitiveBuffers[0]);
for (int p = 0; p < primBufferArray.length; p++) {
final CompiledPrimitiveBuffers primBuffers = primBufferArray[p];
final boolean hasBrightness = primBuffers.flags().hasBrightness;
// Tessellator lines
if (primBuffers.hasLines()) {
final DrawRange[] ranges = primBuffers.lineMergedRanges();
final VertexBuffer vbo = primBuffers.lineVbo();
for (int i = 0; i < ranges.length; i++) {
allRanges.add(new DrawRangeWithBuffer(ranges[i], vbo, hasBrightness));
}
}
// Tessellator triangles
if (primBuffers.hasTriangles()) {
final DrawRange[] ranges = primBuffers.triangleMergedRanges();
final VertexBuffer vbo = primBuffers.triangleVbo();
for (int i = 0; i < ranges.length; i++) {
allRanges.add(new DrawRangeWithBuffer(ranges[i], vbo, hasBrightness));
}
}
}
}
allRanges.sort(Comparator.comparingInt(r -> r.range().commandIndex()));
return allRanges;
}
/**
* Emit a draw range to the output buffer, with transform if needed.
*/
private static void emitDrawRangeToBuffer(
DrawRangeWithBuffer drb,
BufferTransformOptimizer opt,
CommandBuffer out,
Object2IntMap<VertexBuffer> vboIndexMap) {
// Emit transform to reach the draw's expected transform
opt.emitTransformTo(out, drb.range().transform());
// Write the draw range command
final int vboIdx = vboIndexMap.getInt(drb.vbo());
if (vboIdx >= 0) {
out.writeDrawRange(vboIdx, drb.range().startVertex(), drb.range().vertexCount(), drb.hasBrightness());
}
}
}
@@ -1,23 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
/**
* A compiled VBO with its associated draw ranges.
* All ranges reference vertices within this single VBO.
* <p>
* Contains two range arrays:
* <ul>
* <li><b>mergedRanges</b>: For optimized path - consecutive same-transform draws merged</li>
* <li><b>perDrawRanges</b>: For unoptimized path - 1:1 with original draws</li>
* </ul>
*/
@Desugar
public record CompiledFormatBuffer(
VertexBuffer vbo,
CapturingTessellator.Flags flags,
DrawRange[] mergedRanges,
DrawRange[] perDrawRanges
) {}
@@ -1,21 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
/**
* A compiled VBO containing all line geometry with associated draw ranges.
* All ranges reference vertices within this single VBO.
* <p>
* Contains two range arrays:
* <ul>
* <li><b>mergedRanges</b>: For optimized path - consecutive same-transform draws merged</li>
* <li><b>perDrawRanges</b>: For unoptimized path - 1:1 with original draws</li>
* </ul>
*/
@Desugar
record CompiledLineBuffer(
VertexBuffer vbo,
DrawRange[] mergedRanges,
DrawRange[] perDrawRanges
) {}
@@ -1,31 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
/**
* Compiled VBOs containing tessellator primitives (lines and triangles) for a single vertex format.
* Separate VBOs are used for lines (GL_LINES) and triangles (GL_TRIANGLES) since they have different draw modes.
* <p>
* Contains range arrays for both optimized (merged) and unoptimized (per-draw) paths.
* Similar to CompiledFormatBuffer but handles primitives instead of quads.
*/
@Desugar
record CompiledPrimitiveBuffers(
CapturingTessellator.Flags flags,
VertexBuffer lineVbo,
DrawRange[] lineMergedRanges,
DrawRange[] linePerDrawRanges,
VertexBuffer triangleVbo,
DrawRange[] triangleMergedRanges,
DrawRange[] trianglePerDrawRanges
) {
public boolean hasLines() {
return lineVbo != null;
}
public boolean hasTriangles() {
return triangleVbo != null;
}
}
@@ -1,935 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.primitive.ModelPrimitiveView;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadViewMutable;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VBOManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedLineDraw;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedPrimitiveDraw;
import com.gtnewhorizons.angelica.glsm.recording.CommandBuffer;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.OptimizationContext;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.Getter;
import lombok.experimental.UtilityClass;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
import org.joml.Matrix4fStack;
import org.joml.Vector3f;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Manages display list compilation, caching, and playback.
* Handles VBO-based display list emulation with command recording and batching.
*
* <p>This class is responsible for:
* <ul>
* <li>Display list lifecycle (glNewList, glEndList, glCallList, glDeleteLists)</li>
* <li>Command recording and accumulation during compilation</li>
* <li>Building optimized (batched) and unoptimized (nested-safe) command lists</li>
* <li>Transform tracking and collapsing for optimal GL call counts</li>
* <li>Cache management for compiled display lists</li>
* </ul>
*
* <h3>Architecture: Transform Collapsing</h3>
* <p>The optimization strategy collapses consecutive MODELVIEW transforms into single MultMatrix
* commands, emitting them at "barriers" (draws, CallList, exit). This approach:</p>
* <ul>
* <li>Keeps vertices canonical (untransformed) in VBOs</li>
* <li>Properly handles nested display lists (CallList) - GL state is correct before sub-list</li>
* <li>Reduces GL calls (multiple transforms → one MultMatrix)</li>
* <li>Maintains proper Push/Pop stack semantics</li>
* </ul>
*
* <h4>Transform Tracking State</h4>
* <ul>
* <li><b>accumulated:</b> Total relative transform (what GL matrix should be)</li>
* <li><b>lastEmitted:</b> What we've emitted (what GL matrix is)</li>
* <li><b>stack:</b> Saved accumulated values at Push points</li>
* </ul>
*
* <h4>Batching Strategy</h4>
* <p>Format-based batching: All draws with the same vertex format share a single VBO via
* {@link FormatBuffer}. Each draw's position within the VBO is tracked as a {@link DrawRange}.
* Consecutive draws with the same transform are merged into single ranges. Transforms are
* handled via delta-based emission using {@link TransformOptimizer}.</p>
*
* @see com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList
* @see com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw
* @see com.gtnewhorizons.angelica.glsm.recording.commands.DrawRangeCmd
*/
@UtilityClass
public class DisplayListManager {
private static final Logger LOGGER = LogManager.getLogger("DisplayListManager");
/**
* Debug flag to enable building unoptimized display list commands.
* When false (default), unoptimized commands are not built
* When true, unoptimized commands are built for debugging GL behavior.
* Set via: -Dangelica.debugDisplayLists=true
*/
private static final boolean DEBUG_DISPLAY_LISTS = Boolean.getBoolean("angelica.debugDisplayLists");
// Track which display list is currently being rendered
@Getter private static int currentRenderingList = -1;
/**
* Check if we're currently rendering a display list.
* Used by format overrides to avoid interfering with display list VBOs.
*/
public static boolean isRenderingDisplayList() {
return currentRenderingList != -1;
}
public static boolean isIdentity(Matrix4f m) {
return (m.properties() & Matrix4f.PROPERTY_IDENTITY) != 0;
}
/** Transform operation types for relative transform tracking. */
public enum TransformOp { TRANSLATE, SCALE, ROTATE }
// Display list compilation state (current/active context)
private static int glListMode = 0;
private static int glListId = -1;
private static boolean tessellatorCompiling = false; // Track if we started compiling
private static CommandRecorder currentRecorder = null; // Command recorder (null when not recording)
private static volatile Thread recordingThread = null; // Thread that started recording (for thread-safety)
private static List<AccumulatedDraw> accumulatedDraws = null; // Accumulates quad draws for batching
private static List<AccumulatedLineDraw> accumulatedLineDraws = null; // Accumulates immediate mode line draws
private static List<AccumulatedPrimitiveDraw> accumulatedPrimitiveDraws = null; // Accumulates tessellator primitive draws (lines, triangles)
private static Matrix4fStack relativeTransform = null; // Tracks relative transforms during compilation (with push/pop support)
@Getter private static ImmediateModeRecorder immediateModeRecorder = null; // Records glBegin/glEnd/glVertex during compilation
// Nested compilation support - stack of parent contexts
private static final Deque<CompilationContext> compilationStack = new ArrayDeque<>();
// Display list cache
private static final Int2ObjectMap<CompiledDisplayList> displayListCache = new Int2ObjectOpenHashMap<>();
/**
* Compilation context for a single display list being compiled.
* Used to support nested glNewList() calls (spec-violating but needed for mod compatibility).
*/
@Desugar
private record CompilationContext(
int listId,
int listMode,
CommandRecorder recorder,
List<AccumulatedDraw> draws,
List<AccumulatedLineDraw> lineDraws,
List<AccumulatedPrimitiveDraw> primitiveDraws,
Matrix4fStack transform,
boolean wasTessellatorCompiling,
ImmediateModeRecorder immediateRecorder
) {}
/**
* Check if the current thread is recording a display list.
* Only returns true for the thread that started recording.
* This prevents other threads from having their GL calls captured.
*/
public static boolean isRecording() {
return currentRecorder != null && Thread.currentThread() == recordingThread;
}
/**
* Get the ID of the display list currently being recorded.
* @return The list ID, or -1 if not recording
*/
public static int getRecordingListId() {
return glListId;
}
/**
* Get the current display list mode (GL_COMPILE or GL_COMPILE_AND_EXECUTE).
* @return The current list mode, or 0 if not recording
*/
public static int getListMode() {
return glListMode;
}
/**
* Get the current command count (for draw position tracking).
* @return The current command position
*/
public static int getCommandCount() {
return currentRecorder != null ? currentRecorder.getCommandCount() : 0;
}
// ==================== Recording Method Delegates ====================
// These delegate to the current CommandRecorder during compilation.
public static void recordEnable(int cap) { if (currentRecorder != null) currentRecorder.recordEnable(cap); }
public static void recordDisable(int cap) { if (currentRecorder != null) currentRecorder.recordDisable(cap); }
public static void recordClear(int mask) { if (currentRecorder != null) currentRecorder.recordClear(mask); }
public static void recordClearColor(float r, float g, float b, float a) { if (currentRecorder != null) currentRecorder.recordClearColor(r, g, b, a); }
public static void recordClearDepth(double depth) { if (currentRecorder != null) currentRecorder.recordClearDepth(depth); }
public static void recordBlendColor(float r, float g, float b, float a) { if (currentRecorder != null) currentRecorder.recordBlendColor(r, g, b, a); }
public static void recordClearStencil(int s) { if (currentRecorder != null) currentRecorder.recordClearStencil(s); }
public static void recordColor(float r, float g, float b, float a) { if (currentRecorder != null) currentRecorder.recordColor(r, g, b, a); }
public static void recordColorMask(boolean r, boolean g, boolean b, boolean a) { if (currentRecorder != null) currentRecorder.recordColorMask(r, g, b, a); }
public static void recordDepthMask(boolean flag) { if (currentRecorder != null) currentRecorder.recordDepthMask(flag); }
public static void recordFrontFace(int mode) { if (currentRecorder != null) currentRecorder.recordFrontFace(mode); }
public static void recordDepthFunc(int func) { if (currentRecorder != null) currentRecorder.recordDepthFunc(func); }
public static void recordBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) { if (currentRecorder != null) currentRecorder.recordBlendFunc(srcRgb, dstRgb, srcAlpha, dstAlpha); }
public static void recordAlphaFunc(int func, float ref) { if (currentRecorder != null) currentRecorder.recordAlphaFunc(func, ref); }
public static void recordCullFace(int mode) { if (currentRecorder != null) currentRecorder.recordCullFace(mode); }
public static void recordShadeModel(int mode) { if (currentRecorder != null) currentRecorder.recordShadeModel(mode); }
public static void recordBindTexture(int target, int texture) { if (currentRecorder != null) currentRecorder.recordBindTexture(target, texture); }
public static void recordTexParameteri(int target, int pname, int param) { if (currentRecorder != null) currentRecorder.recordTexParameteri(target, pname, param); }
public static void recordTexParameterf(int target, int pname, float param) { if (currentRecorder != null) currentRecorder.recordTexParameterf(target, pname, param); }
public static void recordMatrixMode(int mode) { if (currentRecorder != null) currentRecorder.recordMatrixMode(mode); }
public static void recordLoadIdentity(int matrixMode) { if (currentRecorder != null) currentRecorder.recordLoadIdentity(matrixMode); }
public static void recordPushMatrix(int matrixMode) { if (currentRecorder != null) currentRecorder.recordPushMatrix(matrixMode); }
public static void recordPopMatrix(int matrixMode) { if (currentRecorder != null) currentRecorder.recordPopMatrix(matrixMode); }
public static void recordTranslate(int matrixMode, double x, double y, double z) { if (currentRecorder != null) currentRecorder.recordTranslate(matrixMode, x, y, z); }
public static void recordRotate(int matrixMode, double angle, double x, double y, double z) { if (currentRecorder != null) currentRecorder.recordRotate(matrixMode, angle, x, y, z); }
public static void recordScale(int matrixMode, double x, double y, double z) { if (currentRecorder != null) currentRecorder.recordScale(matrixMode, x, y, z); }
public static void recordMultMatrix(int matrixMode, Matrix4f matrix) { if (currentRecorder != null) currentRecorder.recordMultMatrix(matrixMode, matrix); }
public static void recordLoadMatrix(int matrixMode, Matrix4f matrix) { if (currentRecorder != null) currentRecorder.recordLoadMatrix(matrixMode, matrix); }
public static void recordOrtho(double left, double right, double bottom, double top, double zNear, double zFar) { if (currentRecorder != null) currentRecorder.recordOrtho(left, right, bottom, top, zNear, zFar); }
public static void recordFrustum(double left, double right, double bottom, double top, double zNear, double zFar) { if (currentRecorder != null) currentRecorder.recordFrustum(left, right, bottom, top, zNear, zFar); }
public static void recordViewport(int x, int y, int width, int height) { if (currentRecorder != null) currentRecorder.recordViewport(x, y, width, height); }
public static void recordNormal(float nx, float ny, float nz) { if (currentRecorder != null) currentRecorder.recordNormal(nx, ny, nz); }
public static void recordPointSize(float size) { if (currentRecorder != null) currentRecorder.recordPointSize(size); }
public static void recordLineWidth(float width) { if (currentRecorder != null) currentRecorder.recordLineWidth(width); }
public static void recordLineStipple(int factor, int pattern) { if (currentRecorder != null) currentRecorder.recordLineStipple(factor, pattern); }
public static void recordPolygonOffset(float factor, float units) { if (currentRecorder != null) currentRecorder.recordPolygonOffset(factor, units); }
public static void recordPolygonMode(int face, int mode) { if (currentRecorder != null) currentRecorder.recordPolygonMode(face, mode); }
public static void recordColorMaterial(int face, int mode) { if (currentRecorder != null) currentRecorder.recordColorMaterial(face, mode); }
public static void recordLogicOp(int opcode) { if (currentRecorder != null) currentRecorder.recordLogicOp(opcode); }
public static void recordActiveTexture(int texture) { if (currentRecorder != null) currentRecorder.recordActiveTexture(texture); }
public static void recordUseProgram(int program) { if (currentRecorder != null) currentRecorder.recordUseProgram(program); }
public static void recordPushAttrib(int mask) { if (currentRecorder != null) currentRecorder.recordPushAttrib(mask); }
public static void recordPopAttrib() { if (currentRecorder != null) currentRecorder.recordPopAttrib(); }
public static void recordFogf(int pname, float param) { if (currentRecorder != null) currentRecorder.recordFogf(pname, param); }
public static void recordFogi(int pname, int param) { if (currentRecorder != null) currentRecorder.recordFogi(pname, param); }
public static void recordHint(int target, int mode) { if (currentRecorder != null) currentRecorder.recordHint(target, mode); }
public static void recordFog(int pname, java.nio.FloatBuffer params) { if (currentRecorder != null) currentRecorder.recordFog(pname, params); }
public static void recordLightf(int light, int pname, float param) { if (currentRecorder != null) currentRecorder.recordLightf(light, pname, param); }
public static void recordLighti(int light, int pname, int param) { if (currentRecorder != null) currentRecorder.recordLighti(light, pname, param); }
public static void recordLight(int light, int pname, java.nio.FloatBuffer params) { if (currentRecorder != null) currentRecorder.recordLight(light, pname, params); }
public static void recordLightModelf(int pname, float param) { if (currentRecorder != null) currentRecorder.recordLightModelf(pname, param); }
public static void recordLightModeli(int pname, int param) { if (currentRecorder != null) currentRecorder.recordLightModeli(pname, param); }
public static void recordLightModel(int pname, java.nio.FloatBuffer params) { if (currentRecorder != null) currentRecorder.recordLightModel(pname, params); }
public static void recordMaterialf(int face, int pname, float val) { if (currentRecorder != null) currentRecorder.recordMaterialf(face, pname, val); }
public static void recordMaterial(int face, int pname, java.nio.FloatBuffer params) { if (currentRecorder != null) currentRecorder.recordMaterial(face, pname, params); }
public static void recordStencilFunc(int func, int ref, int mask) { if (currentRecorder != null) currentRecorder.recordStencilFunc(func, ref, mask); }
public static void recordStencilFuncSeparate(int face, int func, int ref, int mask) { if (currentRecorder != null) currentRecorder.recordStencilFuncSeparate(face, func, ref, mask); }
public static void recordStencilOp(int fail, int zfail, int zpass) { if (currentRecorder != null) currentRecorder.recordStencilOp(fail, zfail, zpass); }
public static void recordStencilOpSeparate(int face, int sfail, int dpfail, int dppass) { if (currentRecorder != null) currentRecorder.recordStencilOpSeparate(face, sfail, dpfail, dppass); }
public static void recordStencilMask(int mask) { if (currentRecorder != null) currentRecorder.recordStencilMask(mask); }
public static void recordStencilMaskSeparate(int face, int mask) { if (currentRecorder != null) currentRecorder.recordStencilMaskSeparate(face, mask); }
public static void recordCallList(int listId) { if (currentRecorder != null) currentRecorder.recordCallList(listId); }
public static void recordComplexCommand(DisplayListCommand cmd) { if (currentRecorder != null) currentRecorder.recordComplexCommand(cmd); }
/**
* Add an immediate mode draw to the current display list compilation.
* Called by GLStateManager.glEnd() when immediate mode geometry is ready.
*
* <p>This method captures the current transform and command position, creating
* an AccumulatedDraw that will be interleaved correctly with other commands
* during display list playback.
*
* @param result The immediate mode result containing quads, lines, and flags
*/
public static void addImmediateModeDraw(ImmediateModeRecorder.Result result) {
if (accumulatedDraws == null || result == null) {
return;
}
// Get relative transform (changes since glNewList, not absolute matrix state)
final Matrix4f currentTransform = new Matrix4f(relativeTransform);
// Add quad draw if we have quads
if (!result.quads().isEmpty()) {
final AccumulatedDraw quadDraw = new AccumulatedDraw(result.quads(), currentTransform, result.flags(), getCommandCount());
accumulatedDraws.add(quadDraw);
}
// Add line draw if we have lines
if (!result.lines().isEmpty()) {
final AccumulatedLineDraw lineDraw = new AccumulatedLineDraw(result.lines(), currentTransform, result.flags(), getCommandCount());
accumulatedLineDraws.add(lineDraw);
}
}
/**
* Update the relative transform during display list compilation.
* Called by GLStateManager matrix methods (glTranslate, glRotate, glScale).
*
* @param x Translation x, scale x, or rotation angle (degrees)
* @param y Translation y or scale y (ignored for rotate)
* @param z Translation z or scale z (ignored for rotate)
* @param op The operation type
* @param rotationAxis For rotation only, the axis vector (null otherwise)
*/
public static void updateRelativeTransform(float x, float y, float z, TransformOp op, Vector3f rotationAxis) {
if (relativeTransform == null || !GLStateManager.isModelViewMatrix()) {
return;
}
switch (op) {
case TRANSLATE -> relativeTransform.translate(x, y, z);
case SCALE -> relativeTransform.scale(x, y, z);
case ROTATE -> {
if (rotationAxis != null) {
relativeTransform.rotate((float) Math.toRadians(x), rotationAxis);
}
}
}
}
/**
* Update the relative transform with a matrix multiplication during display list compilation.
* Called by GLStateManager.glMultMatrix().
*
* @param matrix The matrix to multiply
*/
public static void updateRelativeTransform(Matrix4f matrix) {
if (relativeTransform == null) {
return;
}
// Only track MODELVIEW - projection/texture don't affect baked geometry
if (!GLStateManager.isModelViewMatrix()) {
return;
}
relativeTransform.mul(matrix);
}
/**
* Push the current MODELVIEW transform onto the stack.
* Only operates when current matrix mode is MODELVIEW.
* Called by GLStateManager.glPushMatrix() during recording.
*/
public static void pushRelativeTransform() {
if (relativeTransform == null) {
return;
}
// Only track MODELVIEW - projection/texture don't affect baked geometry
if (!GLStateManager.isModelViewMatrix()) {
return;
}
relativeTransform.pushMatrix();
}
/**
* Pop the MODELVIEW transform from stack.
* Only operates when current matrix mode is MODELVIEW.
* Called by GLStateManager.glPopMatrix() during recording.
*/
public static void popRelativeTransform() {
if (relativeTransform == null) {
return;
}
// Only track MODELVIEW - projection/texture don't affect baked geometry
if (!GLStateManager.isModelViewMatrix()) {
return;
}
try {
relativeTransform.popMatrix();
} catch (RuntimeException e) {
LOGGER.warn("[DisplayList] Transform stack underflow in list={}", glListId);
}
}
/**
* Reset the relative transform to identity during display list compilation.
* Called by GLStateManager.glLoadMatrix() - after loading an absolute matrix,
* subsequent transforms are relative to that loaded matrix (i.e., start from identity).
*/
public static void resetRelativeTransform() {
if (relativeTransform == null) {
return;
}
relativeTransform.identity();
}
/**
* Check if a display list exists (has been compiled and stored).
* Checks both DisplayListManager's cache and VBOManager for GTNHLib compatibility.
*
* @param list The display list ID to check
* @return true if the display list exists, false otherwise
*/
public static boolean displayListExists(int list) {
if (displayListCache.containsKey(list)) {
return true;
}
// Check VBOManager for GTNHLib compatibility (negative IDs)
if (list < -1) {
return VBOManager.get(list) != null;
}
return false;
}
/**
* Get a compiled display list from the cache.
* Used by CallListCmd to execute nested display lists with the unoptimized version.
*
* @param list The display list ID
* @return The CompiledDisplayList, or null if not found
*/
public static CompiledDisplayList getDisplayList(int list) {
return displayListCache.get(list);
}
/**
* Start display list compilation.
* Supports nested glNewList() calls (spec-violating but needed for mod compatibility).
*/
public static void glNewList(int list, int mode) {
// Assert main thread for display list compilation
if (!Thread.currentThread().equals(GLStateManager.getMainThread())) {
throw new IllegalStateException("Display list compilation must happen on main thread");
}
// Handle nested compilation - push current context onto stack
final boolean isNested = glListMode > 0;
if (isNested) {
// Nested display list compilation violates OpenGL spec, but some of our optimizations require it
// Save current compilation context and start fresh for nested list
final CompilationContext parentContext = new CompilationContext(
glListId, glListMode, currentRecorder, accumulatedDraws, accumulatedLineDraws,
accumulatedPrimitiveDraws, relativeTransform, tessellatorCompiling, immediateModeRecorder
);
compilationStack.push(parentContext);
}
// Initialize fresh context for this (possibly nested) list
glListId = list;
glListMode = mode;
recordingThread = Thread.currentThread(); // Track which thread is recording
currentRecorder = new CommandRecorder(); // Create command recorder
accumulatedDraws = new ArrayList<>(64); // Fewer draws than commands typically
accumulatedLineDraws = new ArrayList<>(16); // Immediate mode line draws are less common
accumulatedPrimitiveDraws = new ArrayList<>(16); // Tessellator primitive draws are less common
relativeTransform = new Matrix4fStack(GLStateManager.MAX_MODELVIEW_STACK_DEPTH);
relativeTransform.identity(); // Track relative transforms from identity
immediateModeRecorder = new ImmediateModeRecorder(); // For glBegin/glEnd/glVertex
// Start compiling mode with per-draw callback (works for both root and nested lists now)
// Callback receives quads (GL_QUADS) and primitives (GL_TRIANGLES, GL_LINES, etc.)
TessellatorManager.setCompiling((quads, primitives, flags) -> {
final boolean hasQuads = !quads.isEmpty();
final boolean hasPrimitives = !primitives.isEmpty();
// Early exit to avoid allocations when nothing to accumulate
if (!hasQuads && !hasPrimitives) {
return;
}
// Get relative transform (changes since glNewList, not absolute matrix state)
final Matrix4f currentTransform = new Matrix4f(relativeTransform);
final int cmdIndex = getCommandCount();
// Accumulate quads for batching at glEndList()
if (hasQuads) {
accumulatedDraws.add(new AccumulatedDraw(quads, currentTransform, flags, cmdIndex));
}
// Accumulate primitives (lines, triangles) for batching at glEndList()
if (hasPrimitives) {
accumulatedPrimitiveDraws.add(new AccumulatedPrimitiveDraw(primitives, currentTransform, flags, cmdIndex));
}
});
tessellatorCompiling = true;
// We hijack display list compilation completely - no GL11.glNewList() calls
// During compile don't actually apply any changes to GL, but do track them
GLStateManager.getModelViewMatrix().pushMatrix(); // Save current model view matrix state
GLStateManager.pushState(GL11.GL_ALL_ATTRIB_BITS);
}
/**
* End display list compilation and build optimized/unoptimized versions.
*/
public static void glEndList() {
if (glListMode == 0) {
throw new RuntimeException("glEndList called outside of a display list!");
}
final boolean needsRender = glListMode == GL11.GL_COMPILE_AND_EXECUTE;
final boolean isNested = !compilationStack.isEmpty();
// Stop compiling mode (works for both root and nested lists now)
if (tessellatorCompiling) {
TessellatorManager.stopCompiling();
tessellatorCompiling = false;
}
final CompiledDisplayList compiled;
// Create CompiledDisplayList with both unoptimized and optimized versions
final CommandBuffer rawCommandBuffer = currentRecorder != null ? currentRecorder.getBuffer() : null;
final boolean hasCommands = rawCommandBuffer != null && rawCommandBuffer.size() > 0;
final boolean hasDraws = accumulatedDraws != null && !accumulatedDraws.isEmpty();
final boolean hasLineDraws = accumulatedLineDraws != null && !accumulatedLineDraws.isEmpty();
final boolean hasPrimitiveDraws = accumulatedPrimitiveDraws != null && !accumulatedPrimitiveDraws.isEmpty();
if (hasCommands || hasDraws || hasLineDraws || hasPrimitiveDraws) {
// Phase 1: Compile format-based VBOs (shared by both optimized and unoptimized paths)
final Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers = compileFormatBasedVBOs(accumulatedDraws);
// Phase 2: Compile immediate mode line VBO (shared by both paths)
final CompiledLineBuffer compiledLineBuffer = compileLineBuffer(accumulatedLineDraws);
// Phase 3: Compile tessellator primitive VBOs (lines, triangles) - grouped by format
final Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers = compilePrimitiveBuffers(accumulatedPrimitiveDraws);
// Collect all owned VBOs (quads + immediate mode lines + tessellator primitives)
final VertexBuffer[] ownedVbos = extractOwnedVbos(compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers);
// Build to CommandBuffer - optimize raw buffer to final buffer
final CommandBuffer finalBuffer = new CommandBuffer();
if (DEBUG_DISPLAY_LISTS) {
CommandBufferBuilder.buildUnoptimizedFromRawBuffer(rawCommandBuffer, compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers, ownedVbos, finalBuffer);
} else {
CommandBufferBuilder.buildOptimizedFromRawBuffer(rawCommandBuffer, compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers, ownedVbos, glListId, finalBuffer);
}
// Free the recorder (and its buffer) after optimization
currentRecorder.free();
compiled = new CompiledDisplayList(finalBuffer.toBuffer(), finalBuffer.getComplexObjects(), ownedVbos);
displayListCache.put(glListId, compiled);
} else {
// Free the recorder even if empty
if (currentRecorder != null) {
currentRecorder.free();
}
compiled = null;
}
// Handle nested compilation - restore parent context
if (isNested) {
final CompilationContext parentContext = compilationStack.pop();
// Restore parent context
glListId = parentContext.listId;
glListMode = parentContext.listMode;
currentRecorder = parentContext.recorder;
accumulatedDraws = parentContext.draws;
accumulatedLineDraws = parentContext.lineDraws;
accumulatedPrimitiveDraws = parentContext.primitiveDraws;
relativeTransform = parentContext.transform;
tessellatorCompiling = parentContext.wasTessellatorCompiling;
immediateModeRecorder = parentContext.immediateRecorder;
// Note: TessellatorManager callback stack was popped by stopCompiling()
// Parent's COMPILING callback is now active again
} else {
// Not nested - clear all compilation state
currentRecorder = null;
recordingThread = null;
accumulatedDraws = null;
accumulatedLineDraws = null;
accumulatedPrimitiveDraws = null;
relativeTransform = null;
immediateModeRecorder = null;
glListId = -1;
glListMode = 0;
}
GLStateManager.getModelViewMatrix().popMatrix();
GLStateManager.popState();
// Only execute if COMPILE_AND_EXECUTE AND this is the root (non-nested) list
// Nested lists will execute naturally when the parent executes via CallListCmd
if (needsRender && !isNested && compiled != null) {
compiled.render();
}
}
/**
* Execute a compiled display list.
*/
public static void glCallList(int list) {
// If we're currently recording, record a reference to this list Per OpenGL spec: the inner list is NOT executed during compilation.
// It only executes when the parent list is played back.
// For GL_COMPILE_AND_EXECUTE: the entire parent list (including this CallListCmd) executes at the end of glEndList, not during compilation.
if (currentRecorder != null) {
// Record a CallList command that will execute the referenced list during playback
recordCallList(list);
return;
}
// Normal playback mode
final CompiledDisplayList compiled = displayListCache.get(list);
if (compiled != null) {
GLStateManager.trySyncProgram();
final int prevList = currentRenderingList;
currentRenderingList = list;
compiled.render();
currentRenderingList = prevList;
return;
}
// Fall back to VBOManager or legacy display list
if (list < 0) {
// Negative IDs are VBOManager space (entity models, etc.)
final VertexBuffer vbo = VBOManager.get(list);
if (vbo != null) {
vbo.render();
}
// Per OpenGL spec: if list is undefined, glCallList has no effect
} else {
// Positive IDs - fall back to native GL display lists
// This happens for lists allocated but never compiled via glNewList
GLStateManager.trySyncProgram();
GL11.glCallList(list);
}
}
/**
* Delete display lists and free their VBO resources.
*/
public static void glDeleteLists(int list, int range) {
for (int i = list; i < list + range; i++) {
CompiledDisplayList compiled = displayListCache.remove(i);
if (compiled != null) {
compiled.delete();
}
}
// Also call legacy glDeleteLists for any lists we didn't handle
GL11.glDeleteLists(list, range);
}
/**
* Compile format-based VBOs from accumulated draws.
* Groups draws by vertex format and compiles each format's geometry into a single VBO.
* Both optimized and unoptimized paths share these VBOs.
*
* @param accumulatedDraws The accumulated draws to compile
* @return Map from format flags to compiled buffers (VBO + ranges)
*/
private static Map<CapturingTessellator.Flags, CompiledFormatBuffer> compileFormatBasedVBOs(
List<AccumulatedDraw> accumulatedDraws) {
if (accumulatedDraws == null || accumulatedDraws.isEmpty()) {
return Collections.emptyMap();
}
// Group draws by vertex format
final Map<CapturingTessellator.Flags, FormatBuffer> formatBuffers = new HashMap<>();
for (AccumulatedDraw draw : accumulatedDraws) {
formatBuffers.computeIfAbsent(draw.flags, FormatBuffer::new).addDraw(draw);
}
// Compile each format's geometry into a single VBO
final Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiled = new HashMap<>();
for (var entry : formatBuffers.entrySet()) {
compiled.put(entry.getKey(), entry.getValue().finish());
}
return compiled;
}
/**
* Compile line draws into a shared VBO.
*
* @param lineDraws The accumulated line draws
* @return Compiled line buffer, or null if no lines
*/
private static CompiledLineBuffer compileLineBuffer(List<AccumulatedLineDraw> lineDraws) {
if (lineDraws == null || lineDraws.isEmpty()) {
return null;
}
final LineBuffer buffer = new LineBuffer();
for (AccumulatedLineDraw draw : lineDraws) {
buffer.addDraw(draw);
}
return buffer.finish();
}
/**
* Extract owned VBOs from compiled buffers (quads + lines).
* These VBOs are shared by both optimized and unoptimized paths.
*
* @param compiledQuadBuffers The compiled quad format buffers
* @param compiledLineBuffer The compiled line buffer (may be null)
* @return Array of VBOs that need to be deleted when the display list is deleted
*/
private static VertexBuffer[] extractOwnedVbos(
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
CompiledLineBuffer compiledLineBuffer,
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers) {
final List<VertexBuffer> vbos = new ArrayList<>();
// Add quad VBOs
final CompiledFormatBuffer[] quadBufferArray = compiledQuadBuffers.values().toArray(new CompiledFormatBuffer[0]);
for (int i = 0; i < quadBufferArray.length; i++) {
vbos.add(quadBufferArray[i].vbo());
}
// Add immediate mode line VBO if present
if (compiledLineBuffer != null) {
vbos.add(compiledLineBuffer.vbo());
}
// Add tessellator primitive VBOs if present (one per format, separate for lines/triangles)
if (!compiledPrimitiveBuffers.isEmpty()) {
final CompiledPrimitiveBuffers[] primBufferArray = compiledPrimitiveBuffers.values().toArray(new CompiledPrimitiveBuffers[0]);
for (int i = 0; i < primBufferArray.length; i++) {
final CompiledPrimitiveBuffers primBuffers = primBufferArray[i];
if (primBuffers.hasLines()) {
vbos.add(primBuffers.lineVbo());
}
if (primBuffers.hasTriangles()) {
vbos.add(primBuffers.triangleVbo());
}
}
}
return vbos.toArray(new VertexBuffer[0]);
}
/**
* Compile tessellator primitives (lines, triangles) into format-based VBOs.
* Groups draws by vertex format for optimal memory usage.
*
* @param primitiveDraws The accumulated primitive draws from tessellator callback
* @return Map of flags to compiled primitive buffers, empty map if no primitives
*/
private static Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compilePrimitiveBuffers(
List<AccumulatedPrimitiveDraw> primitiveDraws) {
if (primitiveDraws == null || primitiveDraws.isEmpty()) {
return Collections.emptyMap();
}
// Group draws by vertex format
final Map<CapturingTessellator.Flags, TessellatorPrimitiveBuffer> formatBuffers = new HashMap<>();
final int size = primitiveDraws.size();
for (int i = 0; i < size; i++) {
final AccumulatedPrimitiveDraw draw = primitiveDraws.get(i);
formatBuffers.computeIfAbsent(draw.flags, TessellatorPrimitiveBuffer::new).addDraw(draw);
}
// Compile each format's geometry into VBOs
final Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiled = new HashMap<>();
@SuppressWarnings("unchecked")
final Map.Entry<CapturingTessellator.Flags, TessellatorPrimitiveBuffer>[] entries =
formatBuffers.entrySet().toArray(new Map.Entry[0]);
for (int i = 0; i < entries.length; i++) {
final Map.Entry<CapturingTessellator.Flags, TessellatorPrimitiveBuffer> entry = entries[i];
final CompiledPrimitiveBuffers result = entry.getValue().finish();
if (result != null) {
compiled.put(entry.getKey(), result);
}
}
return compiled;
}
// ==================== Buffer-to-Buffer Optimization ====================
/**
* Build optimized command list using pre-compiled VBOs.
* Uses mergedRanges (consecutive same-transform draws combined) and collapses MODELVIEW transforms.
* (Package-private for testing)
*
* @param currentCommands Original matrix/state commands
* @param compiledQuadBuffers Pre-compiled format-based quad VBOs
* @param compiledLineBuffer Pre-compiled line VBO (may be null)
* @param glListId Display list ID for logging
* @return Optimized command array
*/
static DisplayListCommand[] buildOptimizedCommands(
List<DisplayListCommand> currentCommands,
Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers,
CompiledLineBuffer compiledLineBuffer,
Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers,
int glListId) {
final List<DisplayListCommand> optimized = new ArrayList<>();
final TransformOptimizer transformOpt = new TransformOptimizer(glListId);
// Collect all merged draw ranges (quads + lines + primitives) sorted by command index
final List<DrawRangeWithBuffer> allRanges = CommandBufferBuilder.collectDrawRanges(
compiledQuadBuffers, compiledLineBuffer, compiledPrimitiveBuffers);
// Process command stream with interleaved draws
final OptimizationContextImpl ctx = new OptimizationContextImpl(transformOpt, optimized);
int rangeIndex = 0;
for (int i = 0; i < currentCommands.size(); i++) {
// Emit draw ranges (quads and lines) at this command position
while (rangeIndex < allRanges.size() && allRanges.get(rangeIndex).range().commandIndex() == i) {
emitDrawRangeLegacy(allRanges.get(rangeIndex++), transformOpt, optimized);
}
// Process the original command
final DisplayListCommand cmd = currentCommands.get(i);
if (cmd.handleOptimization(ctx)) {
optimized.add(cmd);
}
}
// Emit remaining draw ranges at end of command stream
while (rangeIndex < allRanges.size()) {
emitDrawRangeLegacy(allRanges.get(rangeIndex++), transformOpt, optimized);
}
// Emit residual transform to match expected GL state
if (!transformOpt.isIdentity()) {
transformOpt.emitPendingTransform(optimized);
}
return optimized.toArray(new DisplayListCommand[0]);
}
/**
* Build optimized display list: batches draws with same flags, collapses MODELVIEW transforms.
* Instead of baking transforms into vertices, we emit collapsed MultMatrix commands at barriers.
* This properly handles nested display lists (CallList) which need GL state to be correct.
*
* <p>Transform collapsing strategy:
* <ul>
* <li>Track accumulated MODELVIEW transform during command stream analysis</li>
* <li>At barriers (Draw, CallList), emit a single MultMatrix if transform changed</li>
* <li>Push/Pop maintain proper stack semantics</li>
* <li>Vertices stay canonical (untransformed) in VBOs</li>
* </ul>
*/
// Package-private for testing
static OptimizedListResult buildOptimizedDisplayList(
List<DisplayListCommand> currentCommands,
List<AccumulatedDraw> accumulatedDraws,
List<AccumulatedPrimitiveDraw> accumulatedPrimitiveDraws,
int glListId) {
// Compile quad VBOs
final Map<CapturingTessellator.Flags, CompiledFormatBuffer> compiledQuadBuffers = compileFormatBasedVBOs(accumulatedDraws);
// Compile tessellator primitive VBOs (lines, triangles) - grouped by format
final Map<CapturingTessellator.Flags, CompiledPrimitiveBuffers> compiledPrimitiveBuffers = compilePrimitiveBuffers(accumulatedPrimitiveDraws);
// Extract owned VBOs (quads + primitives, no immediate mode lines in test path)
final VertexBuffer[] ownedVbos = extractOwnedVbos(compiledQuadBuffers, null, compiledPrimitiveBuffers);
// Build optimized commands using the new unified path
final DisplayListCommand[] optimized = buildOptimizedCommands(currentCommands, compiledQuadBuffers, null, compiledPrimitiveBuffers, glListId);
return new OptimizedListResult(optimized, ownedVbos);
}
/**
* Emit a draw range command for the legacy path (testing).
* Uses delta-based transform emission via TransformOptimizer.
*/
private static void emitDrawRangeLegacy(DrawRangeWithBuffer drwb, TransformOptimizer transformOpt,
List<DisplayListCommand> output) {
final DrawRange range = drwb.range();
// Delta-based: only emit MultMatrix if transform differs from last emitted
if (transformOpt.needsTransformForDraw(range.transform())) {
transformOpt.emitTransformTo(output, range.transform());
}
output.add(new com.gtnewhorizons.angelica.glsm.recording.commands.DrawRangeCmd(
drwb.vbo(),
range.startVertex(),
range.vertexCount(),
drwb.hasBrightness()
));
}
/**
* Implementation of OptimizationContext that wraps transform optimizer.
*/
@Desugar
private record OptimizationContextImpl(
TransformOptimizer transformOpt,
List<DisplayListCommand> output
) implements OptimizationContext {
@Override public Matrix4f getAccumulatedTransform() { return transformOpt.getAccumulated(); }
@Override public void loadIdentity() { transformOpt.loadIdentity(); }
@Override public void pushTransform() { transformOpt.pushTransform(); }
@Override public void popTransform() { transformOpt.popTransform(); }
@Override public void emitPendingTransform() { transformOpt.emitPendingTransform(output); }
@Override public void emitTransformTo(Matrix4f target) { transformOpt.emitTransformTo(output, target); }
@Override public void emit(DisplayListCommand cmd) { output.add(cmd); }
@Override public void markAbsoluteMatrix() { transformOpt.markAbsoluteMatrix(); }
@Override public boolean checkAndClearAbsoluteMatrix() { return transformOpt.checkAndClearAbsoluteMatrix(); }
}
/**
* Selects the optimal VertexFormat from GTNHLib defaults based on actual attribute usage.
* This reduces memory usage by excluding unused attributes.
*/
static VertexFormat selectOptimalFormat(CapturingTessellator.Flags flags) {
final boolean hasColor = flags.hasColor;
final boolean hasTexture = flags.hasTexture;
final boolean hasBrightness = flags.hasBrightness;
final boolean hasNormals = flags.hasNormals;
final VertexFormat format;
// Map flags to GTNHLib formats, ordered by guess at frequency/likelihood
if (!hasColor && hasTexture && !hasBrightness && !hasNormals) {
format = DefaultVertexFormat.POSITION_TEXTURE; // Entity models, common
} else if (!hasColor && !hasTexture && !hasBrightness && !hasNormals) {
format = DefaultVertexFormat.POSITION; // Sky rendering, simple geometry
} else if (hasColor && hasTexture && !hasBrightness && !hasNormals) {
format = DefaultVertexFormat.POSITION_COLOR_TEXTURE; // Colored blocks/UI
} else if (!hasColor && hasTexture && hasBrightness && hasNormals) {
format = DefaultVertexFormat.POSITION_TEXTURE_LIGHT_NORMAL; // BuildCraft fluids
} else if (!hasColor && hasTexture && !hasBrightness && hasNormals) {
format = DefaultVertexFormat.POSITION_TEXTURE_NORMAL; // Lit geometry with normals
} else if (hasColor && hasTexture && hasBrightness && !hasNormals) {
format = DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP; // Colored lit blocks
} else if (hasColor && !hasTexture && !hasBrightness && !hasNormals) {
format = DefaultVertexFormat.POSITION_COLOR; // Colored lines/particles
} else {
format = DefaultVertexFormat.POSITION_COLOR_TEXTURE_LIGHT_NORMAL; // Full format fallback
}
return format;
}
/**
* Compile captured Tessellator quads into a VertexBuffer (VAO or VBO).
* Uses VertexBuffer for proper compatibility with fixed-function pipeline.
*
* @param quads The quads to compile
* @param flags The vertex attribute flags
* @return The uploaded VertexBuffer
*/
static VertexBuffer compileQuads(List<ModelQuadViewMutable> quads, CapturingTessellator.Flags flags) {
// Select optimal predefined format from GTNHLib
final VertexFormat format = selectOptimalFormat(flags);
// Use VAOManager to create VAO with optimal format (falls back to VBO if unsupported)
final VertexBuffer vao = VAOManager.createVAO(format, GL11.GL_QUADS);
final ByteBuffer buffer = CapturingTessellator.quadsToBuffer(quads, format);
vao.upload(buffer);
return vao;
}
}
@@ -1,11 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import org.joml.Matrix4f;
/**
* Represents a vertex range within a shared VBO, along with its transform.
* Used during format-based batching to track where each draw's vertices are stored.
*/
@Desugar
record DrawRange(int startVertex, int vertexCount, Matrix4f transform, int commandIndex) {}
@@ -1,12 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
/**
* Helper record pairing a DrawRange with its VBO and brightness flag.
* Used for sorting ranges by command index while retaining VBO reference.
* Works for both quad ranges (from CompiledFormatBuffer) and line ranges (from CompiledLineBuffer).
*/
@Desugar
record DrawRangeWithBuffer(DrawRange range, VertexBuffer vbo, boolean hasBrightness) {}
@@ -2,17 +2,14 @@ package com.gtnewhorizons.angelica.glsm;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -22,66 +19,24 @@ public class Feature {
GL13.GL_MULTISAMPLE_BIT, GL11.GL_PIXEL_MODE_BIT, GL11.GL_POINT_BIT, GL11.GL_POLYGON_BIT, GL11.GL_POLYGON_STIPPLE_BIT, GL11.GL_SCISSOR_BIT,
GL11.GL_STENCIL_BUFFER_BIT, GL11.GL_TEXTURE_BIT, GL11.GL_TRANSFORM_BIT, GL11.GL_VIEWPORT_BIT };
static final Int2ObjectMap<List<IStateStack<?>>> maskToFeaturesMap = new Int2ObjectOpenHashMap<>();
static final Int2ObjectMap<IStateStack<?>[]> maskToNonBooleanStacksMap = new Int2ObjectOpenHashMap<>();
static final Int2ObjectMap<Set<IStateStack<?>>> maskToFeaturesMap = new Int2ObjectOpenHashMap<>();
static List<IStateStack<?>> maskToFeatures(int mask) {
static Set<IStateStack<?>> maskToFeatures(int mask) {
if(maskToFeaturesMap.containsKey(mask)) {
return maskToFeaturesMap.get(mask);
}
final Set<IStateStack<?>> features = new HashSet<>();
for(int attrib : Feature.supportedAttribs) {
if((mask & attrib) == attrib) {
features.addAll(getFeatures(attrib));
}
}
final List<IStateStack<?>> asList = new ArrayList<>(features);
maskToFeaturesMap.put(mask, asList);
return asList;
}
/**
* Returns only non-BooleanStateStack instances for the given mask.
* These use traditional push/pop without global depth tracking.
*/
static IStateStack<?>[] maskToNonBooleanStacks(int mask) {
IStateStack<?>[] cached = maskToNonBooleanStacksMap.get(mask);
if (cached != null) {
return cached;
}
final List<IStateStack<?>> all = maskToFeatures(mask);
final List<IStateStack<?>> nonBooleans = new ArrayList<>();
for (int i = 0; i < all.size(); i++) {
final IStateStack<?> stack = all.get(i);
if (!(stack instanceof BooleanStateStack)) {
nonBooleans.add(stack);
}
}
cached = nonBooleans.toArray(new IStateStack<?>[0]);
maskToNonBooleanStacksMap.put(mask, cached);
return cached;
maskToFeaturesMap.put(mask, features);
return features;
}
private static final Map<Integer, Set<IStateStack<?>>> attribToFeatures = new HashMap<>();
/**
* Helper method to add all texture gen states (S, T, R, Q) for all texture units to a collection.
* Used by both GL_ENABLE_BIT and GL_TEXTURE_BIT.
*/
private static void addTextureGenStates(Set<IStateStack<?>> collection) {
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
collection.add(GLStateManager.textures.getTexGenSStates(i));
collection.add(GLStateManager.textures.getTexGenTStates(i));
collection.add(GLStateManager.textures.getTexGenRStates(i));
collection.add(GLStateManager.textures.getTexGenQStates(i));
}
}
static {
attribToFeatures.put(GL11.GL_COLOR_BUFFER_BIT, ImmutableSet.of(
GLStateManager.alphaTest // GL_ALPHA_TEST enable bit
@@ -90,10 +45,10 @@ public class Feature {
, GLStateManager.blendState // Blending source and destination functions
// Constant blend color
// Blending equation
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP enable bit
, GLStateManager.ditherState // GL_DITHER enable bit
, GLStateManager.drawBuffer // GL_DRAW_BUFFER setting
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP enable bit
// GL_DITHER enable bit
// GL_DRAW_BUFFER setting
// GL_COLOR_LOGIC_OP enable bit
// GL_INDEX_LOGIC_OP enable bit
// Logic op function
, GLStateManager.colorMask // Color-mode and index-mode writemasks
, GLStateManager.clearColor // Color-mode and index-mode clear values
@@ -119,15 +74,14 @@ public class Feature {
final HashSet<IStateStack<?>> enableBits = new HashSet<>(ImmutableSet.of(
GLStateManager.alphaTest // GL_ALPHA_TEST flag
, GLStateManager.autoNormalState // GL_AUTO_NORMAL flag
// GL_AUTO_NORMAL flag
, GLStateManager.blendMode // GL_BLEND flag
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP flag
// Enable bits for the user-definable clipping planes
, GLStateManager.colorMaterial // GL_COLOR_MATERIAL
, GLStateManager.cullState // GL_CULL_FACE flag
, GLStateManager.depthTest // GL_DEPTH_TEST flag
, GLStateManager.ditherState // GL_DITHER flag
// GL_DITHER flag
, GLStateManager.fogMode // GL_FOG flag
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP flag
, GLStateManager.lightStates[0] // GL_LIGHT0
, GLStateManager.lightStates[1] // GL_LIGHT1
, GLStateManager.lightStates[2] // GL_LIGHT2
@@ -137,57 +91,36 @@ public class Feature {
, GLStateManager.lightStates[6] // GL_LIGHT6
, GLStateManager.lightStates[7] // GL_LIGHT7
, GLStateManager.lightingState // GL_LIGHTING flag
, GLStateManager.lineSmoothState // GL_LINE_SMOOTH flag
, GLStateManager.lineStippleState // GL_LINE_STIPPLE flag
, GLStateManager.map1Color4State // GL_MAP1_COLOR_4
, GLStateManager.map1IndexState // GL_MAP1_INDEX
, GLStateManager.map1NormalState // GL_MAP1_NORMAL
, GLStateManager.map1TextureCoord1State // GL_MAP1_TEXTURE_COORD_1
, GLStateManager.map1TextureCoord2State // GL_MAP1_TEXTURE_COORD_2
, GLStateManager.map1TextureCoord3State // GL_MAP1_TEXTURE_COORD_3
, GLStateManager.map1TextureCoord4State // GL_MAP1_TEXTURE_COORD_4
, GLStateManager.map1Vertex3State // GL_MAP1_VERTEX_3
, GLStateManager.map1Vertex4State // GL_MAP1_VERTEX_4
, GLStateManager.map2Color4State // GL_MAP2_COLOR_4
, GLStateManager.map2IndexState // GL_MAP2_INDEX
, GLStateManager.map2NormalState // GL_MAP2_NORMAL
, GLStateManager.map2TextureCoord1State // GL_MAP2_TEXTURE_COORD_1
, GLStateManager.map2TextureCoord2State // GL_MAP2_TEXTURE_COORD_2
, GLStateManager.map2TextureCoord3State // GL_MAP2_TEXTURE_COORD_3
, GLStateManager.map2TextureCoord4State // GL_MAP2_TEXTURE_COORD_4
, GLStateManager.map2Vertex3State // GL_MAP2_VERTEX_3
, GLStateManager.map2Vertex4State // GL_MAP2_VERTEX_4
, GLStateManager.multisampleState // GL_MULTISAMPLE flag
, GLStateManager.normalizeState // GL_NORMALIZE flag
, GLStateManager.pointSmoothState // GL_POINT_SMOOTH flag
, GLStateManager.polygonOffsetPointState // GL_POLYGON_OFFSET_POINT flag
, GLStateManager.polygonOffsetLineState // GL_POLYGON_OFFSET_LINE flag
, GLStateManager.polygonOffsetFillState // GL_POLYGON_OFFSET_FILL flag
, GLStateManager.polygonSmoothState // GL_POLYGON_SMOOTH flag
, GLStateManager.polygonStippleState // GL_POLYGON_STIPPLE flag
, GLStateManager.rescaleNormalState // GL_RESCALE_NORMAL flag
, GLStateManager.sampleAlphaToCoverageState // GL_SAMPLE_ALPHA_TO_COVERAGE flag
, GLStateManager.sampleAlphaToOneState // GL_SAMPLE_ALPHA_TO_ONE flag
, GLStateManager.sampleCoverageState // GL_SAMPLE_COVERAGE flag
// GL_LINE_SMOOTH flag
// GL_LINE_STIPPLE flag
// GL_INDEX_LOGIC_OP flag
// GL_COLOR_LOGIC_OP flag
// GL_MAP1_x where x is a map type
// GL_MAP2_x where x is a map type
// GL_MULTISAMPLE flag
// GL_NORMALIZE flag
// GL_POINT_SMOOTH flag
// GL_POLYGON_OFFSET_LINE flag
// GL_POLYGON_OFFSET_FILL flag
// GL_POLYGON_OFFSET_POINT flag
// GL_POLYGON_SMOOTH flag
// GL_POLYGON_STIPPLE flag
// GL_SAMPLE_ALPHA_TO_COVERAGE flag
// GL_SAMPLE_ALPHA_TO_ONE flag
// GL_SAMPLE_COVERAGE flag
, GLStateManager.scissorTest // GL_SCISSOR_TEST flag
, GLStateManager.stencilTest // GL_STENCIL_TEST flag
// GL_STENCIL_TEST flag
// GL_TEXTURE_1D flag
// GL_TEXTURE_2D flag - Below
// GL_TEXTURE_3D flag
// Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q
));
// Enable bits for the user-definable clipping planes
for(int i = 0; i < GLStateManager.clipPlaneStates.length; i++) {
enableBits.add(GLStateManager.clipPlaneStates[i]);
}
// GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D flags
// GL_TEXTURE_2D flag
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
enableBits.add(GLStateManager.textures.getTexture1DStates(i));
enableBits.add(GLStateManager.textures.getTextureUnitStates(i));
enableBits.add(GLStateManager.textures.getTexture3DStates(i));
}
// Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q
addTextureGenStates(enableBits);
attribToFeatures.put(GL11.GL_ENABLE_BIT, enableBits);
attribToFeatures.put(GL11.GL_EVAL_BIT, ImmutableSet.of(
// GL_MAP1_x enable bits, where x is a map type
@@ -248,9 +181,10 @@ public class Feature {
, GLStateManager.shadeModelState // GL_SHADE_MODEL setting
));
attribToFeatures.put(GL11.GL_LINE_BIT, ImmutableSet.of(
GLStateManager.lineSmoothState // GL_LINE_SMOOTH flag
, GLStateManager.lineStippleState // GL_LINE_STIPPLE enable bit
, GLStateManager.lineState // Line stipple pattern, repeat counter, and width
// GL_LINE_SMOOTH flag
// GL_LINE_STIPPLE enable bit
// Line stipple pattern and repeat counter
// Line width
));
attribToFeatures.put(GL11.GL_LIST_BIT, ImmutableSet.of(
// GL_LIST_BASE setting
@@ -275,17 +209,21 @@ public class Feature {
// GL_READ_BUFFER setting
));
attribToFeatures.put(GL11.GL_POINT_BIT, ImmutableSet.of(
GLStateManager.pointSmoothState // GL_POINT_SMOOTH flag
, GLStateManager.pointState // Point size
// GL_POINT_SMOOTH flag
// Point size
));
attribToFeatures.put(GL11.GL_POLYGON_BIT, ImmutableSet.of(
GLStateManager.cullState // GL_CULL_FACE enable bit
, GLStateManager.polygonSmoothState // GL_POLYGON_SMOOTH flag
, GLStateManager.polygonStippleState // GL_POLYGON_STIPPLE enable bit
, GLStateManager.polygonOffsetFillState // GL_POLYGON_OFFSET_FILL flag
, GLStateManager.polygonOffsetLineState // GL_POLYGON_OFFSET_LINE flag
, GLStateManager.polygonOffsetPointState // GL_POLYGON_OFFSET_POINT flag
, GLStateManager.polygonState // GL_CULL_FACE_MODE, GL_FRONT_FACE, GL_POLYGON_MODE, GL_POLYGON_OFFSET_FACTOR/UNITS
// GL_CULL_FACE_MODE value
// GL_FRONT_FACE indicator
// GL_POLYGON_MODE setting
// GL_POLYGON_SMOOTH flag
// GL_POLYGON_STIPPLE enable bit
// GL_POLYGON_OFFSET_FILL flag
// GL_POLYGON_OFFSET_LINE flag
// GL_POLYGON_OFFSET_POINT flag
// GL_POLYGON_OFFSET_FACTOR
// GL_POLYGON_OFFSET_UNITS
));
attribToFeatures.put(GL11.GL_POLYGON_STIPPLE_BIT, ImmutableSet.of(
// Polygon stipple pattern
@@ -295,8 +233,12 @@ public class Feature {
// Scissor box
));
attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of(
GLStateManager.stencilTest // GL_STENCIL_TEST enable bit
, GLStateManager.stencilState // Stencil function, ref, mask, ops, writemask, clear value
// GL_STENCIL_TEST enable bit
// Stencil function and reference value
// Stencil value mask
// Stencil fail, pass, and depth buffer pass actions
// Stencil buffer clear value
// Stencil buffer writemask
));
final Set<IStateStack<?>> textureAttribs = new HashSet<>(ImmutableSet.of(
GLStateManager.activeTextureUnit // Active texture unit
@@ -320,16 +262,13 @@ public class Feature {
// textureAttribs.add(GLStateManager.textures.getInfo(i))
}
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q
addTextureGenStates(textureAttribs);
attribToFeatures.put(GL11.GL_TEXTURE_BIT, textureAttribs);
attribToFeatures.put(GL11.GL_TRANSFORM_BIT, ImmutableSet.of(
// Coefficients of the six clipping planes
// Enable bits for the user-definable clipping planes
GLStateManager.matrixMode
, GLStateManager.normalizeState // GL_NORMALIZE flag
// GL_NORMALIZE flag
, GLStateManager.rescaleNormalState // GL_RESCALE_NORMAL flag
));
attribToFeatures.put(GL11.GL_VIEWPORT_BIT, ImmutableSet.of(
@@ -1,96 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadViewMutable;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
import org.joml.Matrix4f;
import java.util.ArrayList;
import java.util.List;
/**
* Accumulates geometry for a single vertex format during display list compilation.
* All draws with the same format are collected here, then compiled into a single VBO.
* <p>
* Tracks two types of ranges:
* <ul>
* <li><b>mergedRanges</b>: Consecutive same-transform draws merged for optimized path</li>
* <li><b>perDrawRanges</b>: One range per input draw for unoptimized path (1:1 with addDraw calls)</li>
* </ul>
*/
class FormatBuffer {
final CapturingTessellator.Flags flags;
final List<ModelQuadViewMutable> allQuads = new ArrayList<>();
// Merged ranges for optimized path (consecutive same-transform draws combined)
final List<DrawRange> mergedRanges = new ArrayList<>();
// Per-draw ranges for unoptimized path (1:1 with input draws)
final List<DrawRange> perDrawRanges = new ArrayList<>();
int currentVertexOffset = 0;
// Track the current range being built for merging consecutive same-transform draws
private int pendingStartVertex = -1;
private int pendingVertexCount = 0;
private Matrix4f pendingTransform = null;
private int pendingCommandIndex = -1;
FormatBuffer(CapturingTessellator.Flags flags) {
this.flags = flags;
}
/**
* Add a draw to this format buffer.
* Tracks both merged (for optimized) and per-draw (for unoptimized) ranges.
*/
void addDraw(AccumulatedDraw draw) {
int vertexCount = draw.quads.size() * 4; // 4 vertices per quad
// Always track per-draw range for unoptimized path
perDrawRanges.add(new DrawRange(currentVertexOffset, vertexCount, draw.transform, draw.commandIndex));
// Merge logic for optimized path
if (pendingTransform != null && pendingTransform.equals(draw.transform)) {
// Extend the pending range
pendingVertexCount += vertexCount;
} else {
// Flush pending range if any
flushPendingRange();
// Start new pending range
pendingStartVertex = currentVertexOffset;
pendingVertexCount = vertexCount;
pendingTransform = new Matrix4f(draw.transform);
pendingCommandIndex = draw.commandIndex;
}
allQuads.addAll(draw.quads);
currentVertexOffset += vertexCount;
}
private void flushPendingRange() {
if (pendingTransform != null) {
mergedRanges.add(new DrawRange(pendingStartVertex, pendingVertexCount, pendingTransform, pendingCommandIndex));
pendingTransform = null;
}
}
/**
* Compile all accumulated quads into a single VBO.
* @return The compiled buffer with VBO and both range types
*/
CompiledFormatBuffer finish() {
// Flush any remaining pending range
flushPendingRange();
VertexBuffer vbo = DisplayListManager.compileQuads(allQuads, flags);
return new CompiledFormatBuffer(
vbo,
flags,
mergedRanges.toArray(new DrawRange[0]),
perDrawRanges.toArray(new DrawRange[0])
);
}
}
@@ -9,11 +9,7 @@ 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;
@@ -273,304 +269,6 @@ public final class GLDebug {
};
}
public static String getMatrixModeName(int mode) {
return switch (mode) {
case GL11.GL_MODELVIEW -> "MODELVIEW";
case GL11.GL_PROJECTION -> "PROJECTION";
case GL11.GL_TEXTURE -> "TEXTURE";
case GL11.GL_COLOR -> "COLOR";
default -> String.format("0x%X", mode);
};
}
public static String getCapabilityName(int cap) {
return switch (cap) {
case GL11.GL_ALPHA_TEST -> "ALPHA_TEST";
case GL11.GL_BLEND -> "BLEND";
case GL11.GL_COLOR_MATERIAL -> "COLOR_MATERIAL";
case GL11.GL_CULL_FACE -> "CULL_FACE";
case GL11.GL_DEPTH_TEST -> "DEPTH_TEST";
case GL11.GL_FOG -> "FOG";
case GL11.GL_LIGHTING -> "LIGHTING";
case GL11.GL_LINE_SMOOTH -> "LINE_SMOOTH";
case GL11.GL_NORMALIZE -> "NORMALIZE";
case GL11.GL_POINT_SMOOTH -> "POINT_SMOOTH";
case GL11.GL_POLYGON_OFFSET_FILL -> "POLYGON_OFFSET_FILL";
case GL11.GL_POLYGON_OFFSET_LINE -> "POLYGON_OFFSET_LINE";
case GL11.GL_POLYGON_SMOOTH -> "POLYGON_SMOOTH";
case GL12.GL_RESCALE_NORMAL -> "RESCALE_NORMAL";
case GL11.GL_SCISSOR_TEST -> "SCISSOR_TEST";
case GL11.GL_STENCIL_TEST -> "STENCIL_TEST";
case GL11.GL_TEXTURE_1D -> "TEXTURE_1D";
case GL11.GL_TEXTURE_2D -> "TEXTURE_2D";
case GL11.GL_LIGHT0 -> "LIGHT0";
case GL11.GL_LIGHT1 -> "LIGHT1";
case GL11.GL_LIGHT2 -> "LIGHT2";
case GL11.GL_LIGHT3 -> "LIGHT3";
case GL11.GL_LIGHT4 -> "LIGHT4";
case GL11.GL_LIGHT5 -> "LIGHT5";
case GL11.GL_LIGHT6 -> "LIGHT6";
case GL11.GL_LIGHT7 -> "LIGHT7";
default -> String.format("0x%X", cap);
};
}
public static String getComparisonFuncName(int func) {
return switch (func) {
case GL11.GL_NEVER -> "NEVER";
case GL11.GL_LESS -> "LESS";
case GL11.GL_EQUAL -> "EQUAL";
case GL11.GL_LEQUAL -> "LEQUAL";
case GL11.GL_GREATER -> "GREATER";
case GL11.GL_NOTEQUAL -> "NOTEQUAL";
case GL11.GL_GEQUAL -> "GEQUAL";
case GL11.GL_ALWAYS -> "ALWAYS";
default -> String.format("0x%X", func);
};
}
public static String getBlendFactorName(int factor) {
return switch (factor) {
case GL11.GL_ZERO -> "ZERO";
case GL11.GL_ONE -> "ONE";
case GL11.GL_SRC_COLOR -> "SRC_COLOR";
case GL11.GL_ONE_MINUS_SRC_COLOR -> "ONE_MINUS_SRC_COLOR";
case GL11.GL_DST_COLOR -> "DST_COLOR";
case GL11.GL_ONE_MINUS_DST_COLOR -> "ONE_MINUS_DST_COLOR";
case GL11.GL_SRC_ALPHA -> "SRC_ALPHA";
case GL11.GL_ONE_MINUS_SRC_ALPHA -> "ONE_MINUS_SRC_ALPHA";
case GL11.GL_DST_ALPHA -> "DST_ALPHA";
case GL11.GL_ONE_MINUS_DST_ALPHA -> "ONE_MINUS_DST_ALPHA";
case EXTBlendColor.GL_CONSTANT_COLOR_EXT -> "CONSTANT_COLOR";
case EXTBlendColor.GL_ONE_MINUS_CONSTANT_COLOR_EXT -> "ONE_MINUS_CONSTANT_COLOR";
case EXTBlendColor.GL_CONSTANT_ALPHA_EXT -> "CONSTANT_ALPHA";
case EXTBlendColor.GL_ONE_MINUS_CONSTANT_ALPHA_EXT -> "ONE_MINUS_CONSTANT_ALPHA";
case GL11.GL_SRC_ALPHA_SATURATE -> "SRC_ALPHA_SATURATE";
default -> String.format("0x%X", factor);
};
}
public static String getShadeModelName(int mode) {
return switch (mode) {
case GL11.GL_FLAT -> "FLAT";
case GL11.GL_SMOOTH -> "SMOOTH";
default -> String.format("0x%X", mode);
};
}
public static String getTextureTargetName(int target) {
return switch (target) {
case GL11.GL_TEXTURE_1D -> "TEXTURE_1D";
case GL11.GL_TEXTURE_2D -> "TEXTURE_2D";
case GL12.GL_TEXTURE_3D -> "TEXTURE_3D";
case GL13.GL_TEXTURE_CUBE_MAP -> "TEXTURE_CUBE_MAP";
default -> String.format("0x%X", target);
};
}
public static String getTexturePnameName(int pname) {
return switch (pname) {
case GL11.GL_TEXTURE_MIN_FILTER -> "TEXTURE_MIN_FILTER";
case GL11.GL_TEXTURE_MAG_FILTER -> "TEXTURE_MAG_FILTER";
case GL11.GL_TEXTURE_WRAP_S -> "TEXTURE_WRAP_S";
case GL11.GL_TEXTURE_WRAP_T -> "TEXTURE_WRAP_T";
case GL12.GL_TEXTURE_WRAP_R -> "TEXTURE_WRAP_R";
case GL12.GL_TEXTURE_MIN_LOD -> "TEXTURE_MIN_LOD";
case GL12.GL_TEXTURE_MAX_LOD -> "TEXTURE_MAX_LOD";
case GL12.GL_TEXTURE_BASE_LEVEL -> "TEXTURE_BASE_LEVEL";
case GL12.GL_TEXTURE_MAX_LEVEL -> "TEXTURE_MAX_LEVEL";
case GL11.GL_TEXTURE_BORDER_COLOR -> "TEXTURE_BORDER_COLOR";
case GL11.GL_TEXTURE_PRIORITY -> "TEXTURE_PRIORITY";
case GL14.GL_TEXTURE_LOD_BIAS -> "TEXTURE_LOD_BIAS";
case GL14.GL_GENERATE_MIPMAP -> "GENERATE_MIPMAP";
default -> String.format("0x%X", pname);
};
}
public static String getTextureFormatName(int format) {
return switch (format) {
case GL11.GL_RED -> "RED";
case GL11.GL_GREEN -> "GREEN";
case GL11.GL_BLUE -> "BLUE";
case GL11.GL_ALPHA -> "ALPHA";
case GL11.GL_RGB -> "RGB";
case GL11.GL_RGBA -> "RGBA";
case GL11.GL_LUMINANCE -> "LUMINANCE";
case GL11.GL_LUMINANCE_ALPHA -> "LUMINANCE_ALPHA";
case GL12.GL_BGR -> "BGR";
case GL12.GL_BGRA -> "BGRA";
case GL11.GL_DEPTH_COMPONENT -> "DEPTH_COMPONENT";
case GL11.GL_STENCIL_INDEX -> "STENCIL_INDEX";
case GL30.GL_DEPTH_STENCIL -> "DEPTH_STENCIL";
// Internal formats
case GL11.GL_RGB8 -> "RGB8";
case GL11.GL_RGBA8 -> "RGBA8";
case GL11.GL_ALPHA8 -> "ALPHA8";
case GL11.GL_LUMINANCE8 -> "LUMINANCE8";
case GL11.GL_LUMINANCE8_ALPHA8 -> "LUMINANCE8_ALPHA8";
default -> String.format("0x%X", format);
};
}
public static String getDataTypeName(int type) {
return switch (type) {
case GL11.GL_UNSIGNED_BYTE -> "UNSIGNED_BYTE";
case GL11.GL_BYTE -> "BYTE";
case GL11.GL_UNSIGNED_SHORT -> "UNSIGNED_SHORT";
case GL11.GL_SHORT -> "SHORT";
case GL11.GL_UNSIGNED_INT -> "UNSIGNED_INT";
case GL11.GL_INT -> "INT";
case GL11.GL_FLOAT -> "FLOAT";
case GL11.GL_DOUBLE -> "DOUBLE";
case GL12.GL_UNSIGNED_BYTE_3_3_2 -> "UNSIGNED_BYTE_3_3_2";
case GL12.GL_UNSIGNED_SHORT_4_4_4_4 -> "UNSIGNED_SHORT_4_4_4_4";
case GL12.GL_UNSIGNED_SHORT_5_5_5_1 -> "UNSIGNED_SHORT_5_5_5_1";
case GL12.GL_UNSIGNED_INT_8_8_8_8 -> "UNSIGNED_INT_8_8_8_8";
case GL12.GL_UNSIGNED_INT_10_10_10_2 -> "UNSIGNED_INT_10_10_10_2";
default -> String.format("0x%X", type);
};
}
public static String getLightName(int light) {
return switch (light) {
case GL11.GL_LIGHT0 -> "LIGHT0";
case GL11.GL_LIGHT1 -> "LIGHT1";
case GL11.GL_LIGHT2 -> "LIGHT2";
case GL11.GL_LIGHT3 -> "LIGHT3";
case GL11.GL_LIGHT4 -> "LIGHT4";
case GL11.GL_LIGHT5 -> "LIGHT5";
case GL11.GL_LIGHT6 -> "LIGHT6";
case GL11.GL_LIGHT7 -> "LIGHT7";
default -> String.format("0x%X", light);
};
}
public static String getLightPnameName(int pname) {
return switch (pname) {
case GL11.GL_AMBIENT -> "AMBIENT";
case GL11.GL_DIFFUSE -> "DIFFUSE";
case GL11.GL_SPECULAR -> "SPECULAR";
case GL11.GL_POSITION -> "POSITION";
case GL11.GL_SPOT_DIRECTION -> "SPOT_DIRECTION";
case GL11.GL_SPOT_EXPONENT -> "SPOT_EXPONENT";
case GL11.GL_SPOT_CUTOFF -> "SPOT_CUTOFF";
case GL11.GL_CONSTANT_ATTENUATION -> "CONSTANT_ATTENUATION";
case GL11.GL_LINEAR_ATTENUATION -> "LINEAR_ATTENUATION";
case GL11.GL_QUADRATIC_ATTENUATION -> "QUADRATIC_ATTENUATION";
default -> String.format("0x%X", pname);
};
}
public static String getMaterialPnameName(int pname) {
return switch (pname) {
case GL11.GL_AMBIENT -> "AMBIENT";
case GL11.GL_DIFFUSE -> "DIFFUSE";
case GL11.GL_SPECULAR -> "SPECULAR";
case GL11.GL_EMISSION -> "EMISSION";
case GL11.GL_SHININESS -> "SHININESS";
case GL11.GL_AMBIENT_AND_DIFFUSE -> "AMBIENT_AND_DIFFUSE";
case GL11.GL_COLOR_INDEXES -> "COLOR_INDEXES";
default -> String.format("0x%X", pname);
};
}
public static String getFaceName(int face) {
return switch (face) {
case GL11.GL_FRONT -> "FRONT";
case GL11.GL_BACK -> "BACK";
case GL11.GL_FRONT_AND_BACK -> "FRONT_AND_BACK";
default -> String.format("0x%X", face);
};
}
public static String getColorMaterialModeName(int mode) {
return switch (mode) {
case GL11.GL_AMBIENT -> "AMBIENT";
case GL11.GL_DIFFUSE -> "DIFFUSE";
case GL11.GL_SPECULAR -> "SPECULAR";
case GL11.GL_EMISSION -> "EMISSION";
case GL11.GL_AMBIENT_AND_DIFFUSE -> "AMBIENT_AND_DIFFUSE";
default -> String.format("0x%X", mode);
};
}
public static String getFogPnameName(int pname) {
return switch (pname) {
case GL11.GL_FOG_MODE -> "FOG_MODE";
case GL11.GL_FOG_DENSITY -> "FOG_DENSITY";
case GL11.GL_FOG_START -> "FOG_START";
case GL11.GL_FOG_END -> "FOG_END";
case GL11.GL_FOG_INDEX -> "FOG_INDEX";
case GL11.GL_FOG_COLOR -> "FOG_COLOR";
default -> String.format("0x%X", pname);
};
}
public static String getLightModelPnameName(int pname) {
return switch (pname) {
case GL11.GL_LIGHT_MODEL_AMBIENT -> "LIGHT_MODEL_AMBIENT";
case GL11.GL_LIGHT_MODEL_LOCAL_VIEWER -> "LIGHT_MODEL_LOCAL_VIEWER";
case GL11.GL_LIGHT_MODEL_TWO_SIDE -> "LIGHT_MODEL_TWO_SIDE";
case GL12.GL_LIGHT_MODEL_COLOR_CONTROL -> "LIGHT_MODEL_COLOR_CONTROL";
default -> String.format("0x%X", pname);
};
}
public static String getClearMaskString(int mask) {
StringBuilder sb = new StringBuilder();
boolean first = true;
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
sb.append("COLOR");
first = false;
}
if ((mask & GL11.GL_DEPTH_BUFFER_BIT) != 0) {
if (!first) sb.append("|");
sb.append("DEPTH");
first = false;
}
if ((mask & GL11.GL_STENCIL_BUFFER_BIT) != 0) {
if (!first) sb.append("|");
sb.append("STENCIL");
first = false;
}
if ((mask & GL11.GL_ACCUM_BUFFER_BIT) != 0) {
if (!first) sb.append("|");
sb.append("ACCUM");
}
return sb.toString();
}
public static String getCullFaceName(int mode) {
return switch (mode) {
case GL11.GL_FRONT -> "FRONT";
case GL11.GL_BACK -> "BACK";
case GL11.GL_FRONT_AND_BACK -> "FRONT_AND_BACK";
default -> String.format("0x%X", mode);
};
}
public static String getLogicOpName(int opcode) {
return switch (opcode) {
case GL11.GL_CLEAR -> "CLEAR";
case GL11.GL_AND -> "AND";
case GL11.GL_AND_REVERSE -> "AND_REVERSE";
case GL11.GL_COPY -> "COPY";
case GL11.GL_AND_INVERTED -> "AND_INVERTED";
case GL11.GL_NOOP -> "NOOP";
case GL11.GL_XOR -> "XOR";
case GL11.GL_OR -> "OR";
case GL11.GL_NOR -> "NOR";
case GL11.GL_EQUIV -> "EQUIV";
case GL11.GL_INVERT -> "INVERT";
case GL11.GL_OR_REVERSE -> "OR_REVERSE";
case GL11.GL_COPY_INVERTED -> "COPY_INVERTED";
case GL11.GL_OR_INVERTED -> "OR_INVERTED";
case GL11.GL_NAND -> "NAND";
case GL11.GL_SET -> "SET";
default -> String.format("0x%X", opcode);
};
}
private static DebugState debugState;
private interface DebugState {
File diff suppressed because it is too large. Load diff
@@ -1,125 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedLineDraw;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* Accumulates line geometry during display list compilation.
* All line draws are collected here, then compiled into a single VBO.
* <p>
* Lines all use the same format (POSITION_COLOR), so no format-based grouping needed.
* <p>
* Tracks two types of ranges:
* <ul>
* <li><b>mergedRanges</b>: Consecutive same-transform draws merged for optimized path</li>
* <li><b>perDrawRanges</b>: One range per input draw for unoptimized path (1:1 with addDraw calls)</li>
* </ul>
*/
class LineBuffer {
final List<ImmediateModeRecorder.LineVertex> allVertices = new ArrayList<>();
// Merged ranges for optimized path (consecutive same-transform draws combined)
final List<DrawRange> mergedRanges = new ArrayList<>();
// Per-draw ranges for unoptimized path (1:1 with input draws)
final List<DrawRange> perDrawRanges = new ArrayList<>();
int currentVertexOffset = 0;
// Track the current range being built for merging consecutive same-transform draws
private int pendingStartVertex = -1;
private int pendingVertexCount = 0;
private Matrix4f pendingTransform = null;
private int pendingCommandIndex = -1;
/**
* Add a line draw to this buffer.
* Tracks both merged (for optimized) and per-draw (for unoptimized) ranges.
*/
void addDraw(AccumulatedLineDraw draw) {
int vertexCount = draw.lines.size(); // Each LineVertex is one vertex, lines come in pairs
// Always track per-draw range for unoptimized path
perDrawRanges.add(new DrawRange(currentVertexOffset, vertexCount, draw.transform, draw.commandIndex));
// Merge logic for optimized path
if (pendingTransform != null && pendingTransform.equals(draw.transform)) {
// Extend the pending range
pendingVertexCount += vertexCount;
} else {
// Flush pending range if any
flushPendingRange();
// Start new pending range
pendingStartVertex = currentVertexOffset;
pendingVertexCount = vertexCount;
pendingTransform = new Matrix4f(draw.transform);
pendingCommandIndex = draw.commandIndex;
}
allVertices.addAll(draw.lines);
currentVertexOffset += vertexCount;
}
private void flushPendingRange() {
if (pendingTransform != null) {
mergedRanges.add(new DrawRange(pendingStartVertex, pendingVertexCount, pendingTransform, pendingCommandIndex));
pendingTransform = null;
}
}
/**
* Compile all accumulated line vertices into a single VBO.
* @return The compiled buffer with VBO and both range types, or null if empty
*/
CompiledLineBuffer finish() {
if (allVertices.isEmpty()) {
return null;
}
// Flush any remaining pending range
flushPendingRange();
VertexBuffer vbo = compileLinesToVBO(allVertices);
return new CompiledLineBuffer(
vbo,
mergedRanges.toArray(new DrawRange[0]),
perDrawRanges.toArray(new DrawRange[0])
);
}
/**
* Compile line vertices into a VertexBuffer (VAO or VBO).
* Lines use POSITION_COLOR format (x, y, z, color).
*/
private static VertexBuffer compileLinesToVBO(List<ImmediateModeRecorder.LineVertex> lines) {
final VertexFormat format = DefaultVertexFormat.POSITION_COLOR;
final VertexBuffer vao = VAOManager.createVAO(format, GL11.GL_LINES);
// POSITION_COLOR format: 3 floats (12 bytes) + 4 bytes color = 16 bytes per vertex
final int vertexSize = format.getVertexSize();
final ByteBuffer buffer = org.lwjgl.BufferUtils.createByteBuffer(vertexSize * lines.size());
for (ImmediateModeRecorder.LineVertex v : lines) {
buffer.putFloat(v.x);
buffer.putFloat(v.y);
buffer.putFloat(v.z);
buffer.putInt(v.color);
}
buffer.flip();
vao.upload(buffer);
return vao;
}
}
@@ -1,12 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
/**
* Result of building an optimized display list.
* Contains both the commands and the shared VBOs that need lifecycle management.
*/
@Desugar
record OptimizedListResult(DisplayListCommand[] commands, VertexBuffer[] ownedVbos) {}
@@ -30,6 +30,7 @@ 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 RenderSystem() {}
@@ -40,6 +41,7 @@ public class RenderSystem {
dsaState = new DSACore();
LOGGER.info("OpenGL 4.5 detected, enabling DSA.");
}
hasMultibind = GLStateManager.capabilities.OpenGL45;
} catch (NoSuchFieldError ignored) {}
try {
@@ -53,6 +55,10 @@ 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();
}
@@ -97,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);
}
@@ -131,22 +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 String getProgramInfoLog(int program) {
return GL20.glGetProgramInfoLog(program, GL20.glGetProgrami(program, GL20.GL_INFO_LOG_LENGTH));
}
@@ -1,228 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.line.ModelLine;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.primitive.ModelPrimitiveView;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.tri.ModelTriangle;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedPrimitiveDraw;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* Accumulates tessellator primitives (lines, triangles) for a single vertex format during display list compilation.
* Similar to FormatBuffer but handles ModelPrimitiveView instances instead of quads.
* <p>
* Unlike LineBuffer which handles ImmediateModeRecorder.LineVertex (fixed 16-byte: pos+color),
* this handles ModelLine/ModelTriangle from the tessellator with format-optimized vertex size.
* <p>
* Primitives are separated by draw mode (GL_LINES, GL_TRIANGLES) into separate VBOs.
*/
class TessellatorPrimitiveBuffer {
final CapturingTessellator.Flags flags;
// Accumulated primitives separated by type
final List<ModelLine> allLines = new ArrayList<>();
final List<ModelTriangle> allTriangles = new ArrayList<>();
// Ranges for lines
final List<DrawRange> lineMergedRanges = new ArrayList<>();
final List<DrawRange> linePerDrawRanges = new ArrayList<>();
int currentLineVertexOffset = 0;
private int pendingLineStartVertex = -1;
private int pendingLineVertexCount = 0;
private Matrix4f pendingLineTransform = null;
private int pendingLineCommandIndex = -1;
// Ranges for triangles
final List<DrawRange> triangleMergedRanges = new ArrayList<>();
final List<DrawRange> trianglePerDrawRanges = new ArrayList<>();
int currentTriangleVertexOffset = 0;
private int pendingTriangleStartVertex = -1;
private int pendingTriangleVertexCount = 0;
private Matrix4f pendingTriangleTransform = null;
private int pendingTriangleCommandIndex = -1;
TessellatorPrimitiveBuffer(CapturingTessellator.Flags flags) {
this.flags = flags;
}
/**
* Add a primitive draw to this buffer.
* Separates lines and triangles, tracking ranges for both merged and per-draw paths.
*/
void addDraw(AccumulatedPrimitiveDraw draw) {
final List<ModelPrimitiveView> primitives = draw.primitives;
final int size = primitives.size();
// Single pass: count and collect primitives by type
int lineCount = 0;
int triangleCount = 0;
for (int i = 0; i < size; i++) {
final ModelPrimitiveView prim = primitives.get(i);
if (prim instanceof ModelLine ml) {
allLines.add(ml);
lineCount++;
} else if (prim instanceof ModelTriangle mt) {
allTriangles.add(mt);
triangleCount++;
}
}
// Process lines
if (lineCount > 0) {
final int lineVertexCount = lineCount * 2; // 2 vertices per line
linePerDrawRanges.add(new DrawRange(currentLineVertexOffset, lineVertexCount, draw.transform, draw.commandIndex));
// Merge logic for optimized path
if (pendingLineTransform != null && pendingLineTransform.equals(draw.transform)) {
pendingLineVertexCount += lineVertexCount;
} else {
flushPendingLineRange();
pendingLineStartVertex = currentLineVertexOffset;
pendingLineVertexCount = lineVertexCount;
pendingLineTransform = new Matrix4f(draw.transform);
pendingLineCommandIndex = draw.commandIndex;
}
currentLineVertexOffset += lineVertexCount;
}
// Process triangles
if (triangleCount > 0) {
final int triangleVertexCount = triangleCount * 3; // 3 vertices per triangle
trianglePerDrawRanges.add(new DrawRange(currentTriangleVertexOffset, triangleVertexCount, draw.transform, draw.commandIndex));
// Merge logic for optimized path
if (pendingTriangleTransform != null && pendingTriangleTransform.equals(draw.transform)) {
pendingTriangleVertexCount += triangleVertexCount;
} else {
flushPendingTriangleRange();
pendingTriangleStartVertex = currentTriangleVertexOffset;
pendingTriangleVertexCount = triangleVertexCount;
pendingTriangleTransform = new Matrix4f(draw.transform);
pendingTriangleCommandIndex = draw.commandIndex;
}
currentTriangleVertexOffset += triangleVertexCount;
}
}
private void flushPendingLineRange() {
if (pendingLineTransform != null) {
lineMergedRanges.add(new DrawRange(pendingLineStartVertex, pendingLineVertexCount, pendingLineTransform, pendingLineCommandIndex));
pendingLineTransform = null;
}
}
private void flushPendingTriangleRange() {
if (pendingTriangleTransform != null) {
triangleMergedRanges.add(new DrawRange(pendingTriangleStartVertex, pendingTriangleVertexCount, pendingTriangleTransform, pendingTriangleCommandIndex));
pendingTriangleTransform = null;
}
}
/**
* Compile all accumulated primitives into VBOs using optimal vertex format.
* @return The compiled buffers with separate VBOs for lines and triangles, or null if empty
*/
CompiledPrimitiveBuffers finish() {
flushPendingLineRange();
flushPendingTriangleRange();
if (allLines.isEmpty() && allTriangles.isEmpty()) {
return null;
}
// Use optimal format based on flags (same logic as quads)
final VertexFormat format = DisplayListManager.selectOptimalFormat(flags);
final VertexBuffer lineVbo = allLines.isEmpty() ? null : compileLinesToVBO(format);
final VertexBuffer triangleVbo = allTriangles.isEmpty() ? null : compileTrianglesToVBO(format);
return new CompiledPrimitiveBuffers(
flags,
lineVbo,
lineMergedRanges.toArray(new DrawRange[0]),
linePerDrawRanges.toArray(new DrawRange[0]),
triangleVbo,
triangleMergedRanges.toArray(new DrawRange[0]),
trianglePerDrawRanges.toArray(new DrawRange[0])
);
}
private VertexBuffer compileLinesToVBO(VertexFormat format) {
final int vertexSize = format.getVertexSize();
final int size = allLines.size();
final ByteBuffer buffer = BufferUtils.createByteBuffer(vertexSize * size * 2);
for (int i = 0; i < size; i++) {
writePrimitiveToBuffer(allLines.get(i), buffer);
}
buffer.flip();
final VertexBuffer vbo = VAOManager.createVAO(format, GL11.GL_LINES);
vbo.upload(buffer);
return vbo;
}
private VertexBuffer compileTrianglesToVBO(VertexFormat format) {
final int vertexSize = format.getVertexSize();
final int size = allTriangles.size();
final ByteBuffer buffer = BufferUtils.createByteBuffer(vertexSize * size * 3);
for (int i = 0; i < size; i++) {
writePrimitiveToBuffer(allTriangles.get(i), buffer);
}
buffer.flip();
final VertexBuffer vbo = VAOManager.createVAO(format, GL11.GL_TRIANGLES);
vbo.upload(buffer);
return vbo;
}
/**
* Write a primitive's vertices to the buffer using the format specified by flags.
* Only writes attributes that are present in the format.
*/
private void writePrimitiveToBuffer(ModelPrimitiveView prim, ByteBuffer buffer) {
final int vertexCount = prim.getVertexCount();
final boolean hasTexture = flags.hasTexture;
final boolean hasBrightness = flags.hasBrightness;
final boolean hasColor = flags.hasColor;
final boolean hasNormals = flags.hasNormals;
for (int i = 0; i < vertexCount; i++) {
// Position (always present) - 12 bytes
buffer.putFloat(prim.getX(i));
buffer.putFloat(prim.getY(i));
buffer.putFloat(prim.getZ(i));
// Color (4 bytes, ABGR) - if present
if (hasColor) {
buffer.putInt(prim.getColor(i));
}
// Texture (2 floats = 8 bytes) - if present
if (hasTexture) {
buffer.putFloat(prim.getTexU(i));
buffer.putFloat(prim.getTexV(i));
}
// Light/Brightness (4 bytes, packed) - if present
if (hasBrightness) {
buffer.putInt(prim.getLight(i));
}
// Normal (4 bytes, packed) - if present
if (hasNormals) {
buffer.putInt(prim.getForgeNormal(i));
}
}
}
}
@@ -1,137 +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 org.lwjgl.opengl.GL11;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
/**
* Helper class for tracking and collapsing MODELVIEW 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, GL11.GL_MODELVIEW));
} else {
// Need to emit delta: inv(lastEmitted) * target
final Matrix4f delta = new Matrix4f(lastEmitted).invert().mul(target);
output.add(MultMatrixCmd.create(delta, GL11.GL_MODELVIEW));
}
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 by LoadIdentityCmd to determine if it 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,22 +1,5 @@
package com.gtnewhorizons.angelica.glsm.debug;
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;
@@ -26,6 +9,14 @@ import java.util.ArrayList;
import java.util.List;
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
@@ -39,7 +30,7 @@ public class OpenGLDebugging {
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;
@@ -454,12 +445,11 @@ public class OpenGLDebugging {
}
}
public static String getGLSMErrorLog() {
StringBuilder error = new StringBuilder();
public static void checkGLSM() {
boolean mismatch = false;
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);
// Some precision issues due to RAD vs degrees, and translated (vs f)
@@ -471,87 +461,14 @@ public class OpenGLDebugging {
final String cached = gLProperty.getAsString(true);
final String uncached = gLProperty.getAsString(false);
if (!cached.equals(uncached)) {
error.append("GLSM mismatch: " + gLProperty.name + " cached: " + cached + " uncached: " + uncached);
error.append('\n');
LOGGER.error("GLSM mismatch: " + gLProperty.name + " cached: " + cached + " uncached: " + uncached);
mismatch = true;
}
}
}
final TextureUnitArray textureUnits = GLStateManager.getTextures();
int currentActiveTexture = GLStateManager.getActiveTextureUnit();
for (int i = 0; i < textureUnits.textureMatricies.length; i++) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + i);
final boolean cached = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D);
final boolean uncached = GL11.glIsEnabled(GL11.GL_TEXTURE_2D);
if (cached != uncached) {
error.append("GLSM mismatch: " + "GL_TEXTURE_2D" + " cached: " + cached + " uncached: " + uncached + ", texture unit: GL_TEXTURE" + i);
error.append('\n');
mismatch = true;
}
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + currentActiveTexture);
if (!mismatch) {
return null;
}
return error.toString();
}
public static void checkGLSM() {
String output = getGLSMErrorLog();
if (output == null) {
LOGGER.info("GLSM state matches!");
} else {
LOGGER.error(output);
}
}
public static void copyTexture(ResourceLocation resource, File output) {
Object object = Minecraft.getMinecraft().renderEngine.mapTextureObjects.get(resource);
if (object != null) {
copyTexture(((ITextureObject) object).getGlTextureId(), output);
}
}
public static void copyTexture(int textureID, File output) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, textureID);
int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH);
int height = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_HEIGHT);
BufferedImage bufferedimage = new BufferedImage(width, height, 1);
final int[] pixelValues = ((DataBufferInt) bufferedimage.getRaster().getDataBuffer()).getData();
IntBuffer pixelBuffer = BufferUtils.createIntBuffer(pixelValues.length);
GL11.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer);
pixelBuffer.get(pixelValues);
// Flip texture
final int halfHeight = height / 2;
final int[] rowBuffer = new int[width];
for (int y = 0; y < halfHeight; y++) {
final int top = y * width;
final int bottom = (height - 1 - y) * width;
System.arraycopy(pixelValues, top, rowBuffer, 0, width);
System.arraycopy(pixelValues, bottom, pixelValues, top, width);
System.arraycopy(rowBuffer, 0, pixelValues, bottom, width);
}
File dir = output.getParentFile();
dir.mkdirs();
copyBufferedImageToFile(bufferedimage, output);
bufferedimage.flush();
}
private static void copyBufferedImageToFile(BufferedImage image, File file) {
try {
ImageIO.write(image, "png", file);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -4,7 +4,6 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.ARBDirectStateAccess;
import org.lwjgl.opengl.GL45;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
@@ -15,29 +14,6 @@ public class DSAARB extends DSAUnsupported {
ARBDirectStateAccess.glGenerateTextureMipmap(texture);
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
// Note: DSA glTextureImage2D doesn't exist in ARB_direct_state_access
// We need to use glTextureStorage2D + glTextureSubImage2D, or fall back to bind-based approach
// For simplicity, use the fallback which binds, uploads, and restores
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
super.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
ARBDirectStateAccess.glTextureSubImage2D(texture, level, xoffset, yoffset, width, height, format, type, pixels);
}
@Override
public void texParameteri(int texture, int target, int pname, int param) {
if(!GLStateManager.updateTexParameteriCache(target, texture, pname, param)) return;
@@ -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);
@@ -9,7 +9,6 @@ import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
@@ -21,39 +20,6 @@ public class DSAUnsupported implements DSAAccess {
GL30.glGenerateMipmap(target);
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
// Get what cache thinks is bound - we'll restore to this to keep cache/GL in sync
final int cachedBinding = GLStateManager.getBoundTexture();
GL11.glBindTexture(target, texture);
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
GL11.glBindTexture(target, cachedBinding); // Restore to cache value
}
@Override
public void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
final int cachedBinding = GLStateManager.getBoundTexture();
GL11.glBindTexture(target, texture);
GL11.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
GL11.glBindTexture(target, cachedBinding);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
final int cachedBinding = GLStateManager.getBoundTexture();
GL11.glBindTexture(target, texture);
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
GL11.glBindTexture(target, cachedBinding);
}
@Override
public void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
final int cachedBinding = GLStateManager.getBoundTexture();
GL11.glBindTexture(target, texture);
GL11.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
GL11.glBindTexture(target, cachedBinding);
}
@Override
public void texParameteri(int texture, int target, int pname, int param) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
@@ -1,35 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadViewMutable;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil;
import org.joml.Matrix4f;
import java.util.List;
/**
* Represents an accumulated draw call during display list compilation.
* Matrix-as-Data Architecture: Stores untransformed vertices + captured matrix.
* The matrix will be applied at runtime, not baked into vertices.
*/
public class AccumulatedDraw {
public final List<ModelQuadViewMutable> quads;
public final Matrix4f transform;
public final CapturingTessellator.Flags flags;
public final int commandIndex; // Position in command list for state tracking
public AccumulatedDraw(List<ModelQuadViewMutable> quads, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex) {
// Deep copy quads - pooled quads are reused after callback
this.quads = ModelQuadUtil.deepCopyQuads(quads);
this.transform = new Matrix4f(transform); // Snapshot for runtime application
this.flags = flags;
this.commandIndex = commandIndex;
}
@Override
public String toString() {
return String.format("AccumulatedDraw[quads=%d, cmdIdx=%d, flags=C%dT%dL%dN%d]",
quads.size(), commandIndex, flags.hasColor ? 1 : 0, flags.hasTexture ? 1 : 0, flags.hasBrightness ? 1 : 0, flags.hasNormals ? 1 : 0);
}
}
@@ -1,33 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import org.joml.Matrix4f;
import java.util.ArrayList;
import java.util.List;
/**
* Represents an accumulated line draw call during display list compilation.
* Similar to AccumulatedDraw but for line primitives.
* Stores untransformed line vertices + captured matrix.
*/
public class AccumulatedLineDraw {
public final List<ImmediateModeRecorder.LineVertex> lines;
public final Matrix4f transform;
public final CapturingTessellator.Flags flags;
public final int commandIndex; // Position in command list for state tracking
public AccumulatedLineDraw(List<ImmediateModeRecorder.LineVertex> lines, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex) {
// Copy lines - the source list may be cleared
this.lines = new ArrayList<>(lines);
this.transform = new Matrix4f(transform); // Snapshot for runtime application
this.flags = flags;
this.commandIndex = commandIndex;
}
@Override
public String toString() {
return String.format("AccumulatedLineDraw[lines=%d, cmdIdx=%d, flags=C%dT%dL%dN%d]",
lines.size() / 2, commandIndex, flags.hasColor ? 1 : 0, flags.hasTexture ? 1 : 0, flags.hasBrightness ? 1 : 0, flags.hasNormals ? 1 : 0);
}
}
@@ -1,58 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.line.ModelLine;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.primitive.ModelPrimitiveView;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.tri.ModelTriangle;
import org.joml.Matrix4f;
import java.util.ArrayList;
import java.util.List;
/**
* Represents an accumulated primitive draw call during display list compilation.
* Stores primitives (lines, triangles) from tessellator callback with their transform.
* <p>
* Unlike AccumulatedLineDraw which stores ImmediateModeRecorder.LineVertex (16-byte: pos+color),
* this stores ModelPrimitiveView (32-byte: pos+color+tex+light+normal) from the tessellator.
*/
public class AccumulatedPrimitiveDraw {
public final List<ModelPrimitiveView> primitives;
public final Matrix4f transform;
public final CapturingTessellator.Flags flags;
public final int commandIndex;
public AccumulatedPrimitiveDraw(List<ModelPrimitiveView> primitives, Matrix4f transform, CapturingTessellator.Flags flags, int commandIndex) {
// Deep copy primitives - pooled primitives are reused after callback
this.primitives = deepCopyPrimitives(primitives);
this.transform = new Matrix4f(transform);
this.flags = flags;
this.commandIndex = commandIndex;
}
/**
* Creates deep copies of all primitives in the list.
* Required because CapturingTessellator pools and reuses primitive objects.
*/
private static List<ModelPrimitiveView> deepCopyPrimitives(List<ModelPrimitiveView> source) {
final int size = source.size();
final List<ModelPrimitiveView> copies = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
final ModelPrimitiveView prim = source.get(i);
if (prim instanceof ModelLine ml) {
copies.add(new ModelLine().copyFrom(ml));
} else if (prim instanceof ModelTriangle mt) {
copies.add(new ModelTriangle().copyFrom(mt));
}
}
return copies;
}
@Override
public String toString() {
return String.format("AccumulatedPrimitiveDraw[primitives=%d, cmdIdx=%d, mode=%d, flags=C%dT%dL%dN%d]",
primitives.size(), commandIndex, flags.drawMode,
flags.hasColor ? 1 : 0, flags.hasTexture ? 1 : 0,
flags.hasBrightness ? 1 : 0, flags.hasNormals ? 1 : 0);
}
}
@@ -1,688 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.joml.Matrix4f;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
public final class CommandBuffer {
private static final int DEFAULT_CAPACITY = 4096; // Guess at typical display list size
private ByteBuffer buffer;
private long basePointer;
private long writePointer;
private final List<Object> complexObjects = new ArrayList<>();
public CommandBuffer() {
this(DEFAULT_CAPACITY);
}
public CommandBuffer(int initialCapacity) {
this.buffer = memAlloc(initialCapacity);
this.basePointer = memAddress(buffer);
this.writePointer = basePointer;
}
// === Low-level write operations ===
private void writeInt(int value) {
memPutInt(writePointer, value);
writePointer += 4;
}
private void writeFloat(float value) {
memPutFloat(writePointer, value);
writePointer += 4;
}
private void writeDouble(double value) {
memPutDouble(writePointer, value);
writePointer += 8;
}
private void ensureCapacity(int needed) {
final int size = size();
final int remaining = buffer.capacity() - size;
if (remaining < needed) {
final int newCapacity = Math.max(buffer.capacity() * 2, buffer.capacity() + needed);
buffer = memRealloc(buffer, newCapacity);
basePointer = memAddress(buffer);
writePointer = basePointer + size;
}
}
// === Single int commands ===
public void writeEnable(int cap) {
ensureCapacity(8);
writeInt(GLCommand.ENABLE);
writeInt(cap);
}
public void writeDisable(int cap) {
ensureCapacity(8);
writeInt(GLCommand.DISABLE);
writeInt(cap);
}
public void writeClear(int mask) {
ensureCapacity(8);
writeInt(GLCommand.CLEAR);
writeInt(mask);
}
public void writeClearStencil(int s) {
ensureCapacity(8);
writeInt(GLCommand.CLEAR_STENCIL);
writeInt(s);
}
public void writeCullFace(int mode) {
ensureCapacity(8);
writeInt(GLCommand.CULL_FACE);
writeInt(mode);
}
public void writeDepthFunc(int func) {
ensureCapacity(8);
writeInt(GLCommand.DEPTH_FUNC);
writeInt(func);
}
public void writeShadeModel(int mode) {
ensureCapacity(8);
writeInt(GLCommand.SHADE_MODEL);
writeInt(mode);
}
public void writeLogicOp(int opcode) {
ensureCapacity(8);
writeInt(GLCommand.LOGIC_OP);
writeInt(opcode);
}
public void writeMatrixMode(int mode) {
ensureCapacity(8);
writeInt(GLCommand.MATRIX_MODE);
writeInt(mode);
}
public void writeActiveTexture(int texture) {
ensureCapacity(8);
writeInt(GLCommand.ACTIVE_TEXTURE);
writeInt(texture);
}
public void writeUseProgram(int program) {
ensureCapacity(8);
writeInt(GLCommand.USE_PROGRAM);
writeInt(program);
}
public void writePushAttrib(int mask) {
ensureCapacity(8);
writeInt(GLCommand.PUSH_ATTRIB);
writeInt(mask);
}
public void writePopAttrib() {
ensureCapacity(8);
writeInt(GLCommand.POP_ATTRIB);
writeInt(0); // unused padding
}
public void writeLoadIdentity(int matrixMode) {
ensureCapacity(8);
writeInt(GLCommand.LOAD_IDENTITY);
writeInt(matrixMode);
}
public void writePushMatrix(int matrixMode) {
ensureCapacity(8);
writeInt(GLCommand.PUSH_MATRIX);
writeInt(matrixMode);
}
public void writePopMatrix(int matrixMode) {
ensureCapacity(8);
writeInt(GLCommand.POP_MATRIX);
writeInt(matrixMode);
}
public void writeStencilMask(int mask) {
ensureCapacity(8);
writeInt(GLCommand.STENCIL_MASK);
writeInt(mask);
}
public void writeDepthMask(boolean flag) {
ensureCapacity(8);
writeInt(GLCommand.DEPTH_MASK);
writeInt(flag ? 1 : 0);
}
public void writeFrontFace(int mode) {
ensureCapacity(8);
writeInt(GLCommand.FRONT_FACE);
writeInt(mode);
}
// === Two int commands ===
public void writeBindTexture(int target, int texture) {
ensureCapacity(12);
writeInt(GLCommand.BIND_TEXTURE);
writeInt(target);
writeInt(texture);
}
public void writePolygonMode(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_MODE);
writeInt(face);
writeInt(mode);
}
public void writeColorMaterial(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.COLOR_MATERIAL);
writeInt(face);
writeInt(mode);
}
public void writeLineStipple(int factor, int pattern) {
ensureCapacity(12);
writeInt(GLCommand.LINE_STIPPLE);
writeInt(factor);
writeInt(pattern);
}
public void writeStencilMaskSeparate(int face, int mask) {
ensureCapacity(12);
writeInt(GLCommand.STENCIL_MASK_SEPARATE);
writeInt(face);
writeInt(mask);
}
public void writeFogi(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.FOGI);
writeInt(pname);
writeInt(param);
}
public void writeHint(int target, int mode) {
ensureCapacity(12);
writeInt(GLCommand.HINT);
writeInt(target);
writeInt(mode);
}
// === Three int commands ===
public void writeStencilFunc(int func, int ref, int mask) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_FUNC);
writeInt(func);
writeInt(ref);
writeInt(mask);
}
public void writeStencilOp(int fail, int zfail, int zpass) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_OP);
writeInt(fail);
writeInt(zfail);
writeInt(zpass);
}
public void writeTexParameteri(int target, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.TEX_PARAMETERI);
writeInt(target);
writeInt(pname);
writeInt(param);
}
// === Four int commands ===
public void writeViewport(int x, int y, int width, int height) {
ensureCapacity(20);
writeInt(GLCommand.VIEWPORT);
writeInt(x);
writeInt(y);
writeInt(width);
writeInt(height);
}
public void writeBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
ensureCapacity(20);
writeInt(GLCommand.BLEND_FUNC);
writeInt(srcRgb);
writeInt(dstRgb);
writeInt(srcAlpha);
writeInt(dstAlpha);
}
public void writeColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
ensureCapacity(20);
writeInt(GLCommand.COLOR_MASK);
writeInt(red ? 1 : 0);
writeInt(green ? 1 : 0);
writeInt(blue ? 1 : 0);
writeInt(alpha ? 1 : 0);
}
public void writeStencilFuncSeparate(int face, int func, int ref, int mask) {
ensureCapacity(20);
writeInt(GLCommand.STENCIL_FUNC_SEPARATE);
writeInt(face);
writeInt(func);
writeInt(ref);
writeInt(mask);
}
public void writeStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
ensureCapacity(20);
writeInt(GLCommand.STENCIL_OP_SEPARATE);
writeInt(face);
writeInt(sfail);
writeInt(dpfail);
writeInt(dppass);
}
// === Float commands ===
public void writePointSize(float size) {
ensureCapacity(8);
writeInt(GLCommand.POINT_SIZE);
writeFloat(size);
}
public void writeLineWidth(float width) {
ensureCapacity(8);
writeInt(GLCommand.LINE_WIDTH);
writeFloat(width);
}
public void writePolygonOffset(float factor, float units) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_OFFSET);
writeFloat(factor);
writeFloat(units);
}
public void writeNormal(float x, float y, float z) {
ensureCapacity(16);
writeInt(GLCommand.NORMAL);
writeFloat(x);
writeFloat(y);
writeFloat(z);
}
public void writeColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void writeClearColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.CLEAR_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void writeClearDepth(double depth) {
ensureCapacity(12);
writeInt(GLCommand.CLEAR_DEPTH);
writeDouble(depth);
}
public void writeBlendColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.BLEND_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
// === Mixed int+float commands ===
public void writeAlphaFunc(int func, float ref) {
ensureCapacity(12);
writeInt(GLCommand.ALPHA_FUNC);
writeInt(func);
writeFloat(ref);
}
public void writeFogf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.FOGF);
writeInt(pname);
writeFloat(param);
}
public void writeLightf(int light, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTF);
writeInt(light);
writeInt(pname);
writeFloat(param);
}
public void writeLightModelf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELF);
writeInt(pname);
writeFloat(param);
}
public void writeLighti(int light, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTI);
writeInt(light);
writeInt(pname);
writeInt(param);
}
public void writeLightModeli(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELI);
writeInt(pname);
writeInt(param);
}
public void writeMaterialf(int face, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.MATERIALF);
writeInt(face);
writeInt(pname);
writeFloat(param);
}
public void writeTexParameterf(int target, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.TEX_PARAMETERF);
writeInt(target);
writeInt(pname);
writeFloat(param);
}
// === Double commands ===
public void writeTranslate(int matrixMode, double x, double y, double z) {
ensureCapacity(32);
writeInt(GLCommand.TRANSLATE);
writeInt(matrixMode);
writeDouble(x);
writeDouble(y);
writeDouble(z);
}
public void writeRotate(int matrixMode, double angle, double x, double y, double z) {
ensureCapacity(40);
writeInt(GLCommand.ROTATE);
writeInt(matrixMode);
writeDouble(angle);
writeDouble(x);
writeDouble(y);
writeDouble(z);
}
public void writeScale(int matrixMode, double x, double y, double z) {
ensureCapacity(32);
writeInt(GLCommand.SCALE);
writeInt(matrixMode);
writeDouble(x);
writeDouble(y);
writeDouble(z);
}
public void writeOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
ensureCapacity(52);
writeInt(GLCommand.ORTHO);
writeDouble(left);
writeDouble(right);
writeDouble(bottom);
writeDouble(top);
writeDouble(zNear);
writeDouble(zFar);
}
public void writeFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
ensureCapacity(52);
writeInt(GLCommand.FRUSTUM);
writeDouble(left);
writeDouble(right);
writeDouble(bottom);
writeDouble(top);
writeDouble(zNear);
writeDouble(zFar);
}
// === Matrix commands ===
public void writeMultMatrix(int matrixMode, Matrix4f matrix) {
ensureCapacity(72);
writeInt(GLCommand.MULT_MATRIX);
writeInt(matrixMode);
writeMatrix4f(matrix);
}
public void writeLoadMatrix(int matrixMode, Matrix4f matrix) {
ensureCapacity(72);
writeInt(GLCommand.LOAD_MATRIX);
writeInt(matrixMode);
writeMatrix4f(matrix);
}
/**
* Write a Matrix4f to the buffer in column-major order (OpenGL convention).
* Uses accessor methods instead of getToAddress() to avoid JOML MemUtil issues.
*/
private void writeMatrix4f(Matrix4f m) {
// Column 0
writeFloat(m.m00()); writeFloat(m.m01()); writeFloat(m.m02()); writeFloat(m.m03());
// Column 1
writeFloat(m.m10()); writeFloat(m.m11()); writeFloat(m.m12()); writeFloat(m.m13());
// Column 2
writeFloat(m.m20()); writeFloat(m.m21()); writeFloat(m.m22()); writeFloat(m.m23());
// Column 3
writeFloat(m.m30()); writeFloat(m.m31()); writeFloat(m.m32()); writeFloat(m.m33());
}
// === Buffer commands (inline 4 floats) ===
public void writeFog(int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(12 + count * 4);
writeInt(GLCommand.FOG);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeLight(int light, int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(16 + count * 4);
writeInt(GLCommand.LIGHT);
writeInt(light);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeLightModel(int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(12 + count * 4);
writeInt(GLCommand.LIGHT_MODEL);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeMaterial(int face, int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(16 + count * 4);
writeInt(GLCommand.MATERIAL);
writeInt(face);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
// === Draw commands ===
public void writeDrawRange(int vboIndex, int startVertex, int vertexCount, boolean hasBrightness) {
ensureCapacity(20);
writeInt(GLCommand.DRAW_RANGE);
writeInt(vboIndex);
writeInt(startVertex);
writeInt(vertexCount);
writeInt(hasBrightness ? 1 : 0);
}
public void writeCallList(int listId) {
ensureCapacity(8);
writeInt(GLCommand.CALL_LIST);
writeInt(listId);
}
// === Complex object reference ===
public void writeComplexRef(DisplayListCommand cmd) {
ensureCapacity(8);
final int index = complexObjects.size();
complexObjects.add(cmd);
writeInt(GLCommand.COMPLEX_REF);
writeInt(index);
}
// === Reading methods for optimization pass ===
private long readPointer = 0;
/**
* Reset read position to the beginning of the buffer.
*/
public void resetRead() {
readPointer = basePointer;
}
/**
* Check if there's more data to read.
*/
public boolean hasRemaining() {
return readPointer < writePointer;
}
/**
* Read an int from the current position.
*/
public int readInt() {
final int value = memGetInt(readPointer);
readPointer += 4;
return value;
}
/**
* Read a float from the current position.
*/
public float readFloat() {
final float value = memGetFloat(readPointer);
readPointer += 4;
return value;
}
/**
* Read a double from the current position.
*/
public double readDouble() {
final double value = memGetDouble(readPointer);
readPointer += 8;
return value;
}
/**
* Read a Matrix4f from the current position (16 floats in column-major order).
*/
public Matrix4f readMatrix4f(Matrix4f dest) {
dest.m00(readFloat()); dest.m01(readFloat()); dest.m02(readFloat()); dest.m03(readFloat());
dest.m10(readFloat()); dest.m11(readFloat()); dest.m12(readFloat()); dest.m13(readFloat());
dest.m20(readFloat()); dest.m21(readFloat()); dest.m22(readFloat()); dest.m23(readFloat());
dest.m30(readFloat()); dest.m31(readFloat()); dest.m32(readFloat()); dest.m33(readFloat());
return dest;
}
// === Lifecycle ===
public int size() {
return (int) (writePointer - basePointer);
}
public long address() {
return basePointer;
}
public ByteBuffer toBuffer() {
buffer.limit(size());
buffer.position(0);
return buffer;
}
public Object[] getComplexObjects() {
return complexObjects.toArray();
}
public Object getComplexObject(int index) {
return complexObjects.get(index);
}
public boolean isEmpty() {
return size() == 0 && complexObjects.isEmpty();
}
/**
* Clear the buffer for reuse. Does NOT free memory.
*/
public void clear() {
writePointer = basePointer;
complexObjects.clear();
}
/**
* Free the underlying buffer. Must be called when done.
*/
public void free() {
if (buffer != null) {
memFree(buffer);
buffer = null;
basePointer = 0;
writePointer = 0;
}
}
}
@@ -1,517 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.lwjgl.BufferUtils;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
/**
* Executes commands from a CommandBuffer
*/
public final class CommandBufferExecutor {
private CommandBufferExecutor() {}
// Static reusable buffer for matrix ops (render thread only, LWJGL2/3 compatible)
private static final FloatBuffer MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
// Static reusable buffer for fog/light/material params
private static final FloatBuffer PARAMS_BUFFER = BufferUtils.createFloatBuffer(4);
/**
* Execute all commands in the buffer.
*
* @param buffer The command buffer (position should be at 0, limit at size)
* @param complexObjects Array of complex objects (TexImage2DCmd, etc.)
* @param ownedVbos Array of VBOs owned by the display list (indexed by DrawRange commands)
*/
public static void execute(ByteBuffer buffer, Object[] complexObjects, VertexBuffer[] ownedVbos) {
long ptr = memAddress(buffer);
final long end = ptr + buffer.limit();
while (ptr < end) {
final int cmd = memGetInt(ptr);
ptr += 4;
switch (cmd) {
// === Single int commands ===
case GLCommand.ENABLE -> {
GLStateManager.glEnable(memGetInt(ptr));
ptr += 4;
}
case GLCommand.DISABLE -> {
GLStateManager.glDisable(memGetInt(ptr));
ptr += 4;
}
case GLCommand.CLEAR -> {
GLStateManager.glClear(memGetInt(ptr));
ptr += 4;
}
case GLCommand.CLEAR_STENCIL -> {
GLStateManager.glClearStencil(memGetInt(ptr));
ptr += 4;
}
case GLCommand.CULL_FACE -> {
GLStateManager.glCullFace(memGetInt(ptr));
ptr += 4;
}
case GLCommand.DEPTH_FUNC -> {
GLStateManager.glDepthFunc(memGetInt(ptr));
ptr += 4;
}
case GLCommand.SHADE_MODEL -> {
GLStateManager.glShadeModel(memGetInt(ptr));
ptr += 4;
}
case GLCommand.LOGIC_OP -> {
GLStateManager.glLogicOp(memGetInt(ptr));
ptr += 4;
}
case GLCommand.MATRIX_MODE -> {
GLStateManager.glMatrixMode(memGetInt(ptr));
ptr += 4;
}
case GLCommand.ACTIVE_TEXTURE -> {
GLStateManager.glActiveTexture(memGetInt(ptr));
ptr += 4;
}
case GLCommand.USE_PROGRAM -> {
GLStateManager.glUseProgram(memGetInt(ptr));
ptr += 4;
}
case GLCommand.PUSH_ATTRIB -> {
GLStateManager.glPushAttrib(memGetInt(ptr));
ptr += 4;
}
case GLCommand.POP_ATTRIB -> {
GLStateManager.glPopAttrib();
ptr += 4; // Skip unused padding
}
case GLCommand.LOAD_IDENTITY -> {
final int mode = memGetInt(ptr);
ptr += 4;
final int savedMode = GLStateManager.getMatrixMode().getMode();
GLStateManager.glMatrixMode(mode);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(savedMode);
}
case GLCommand.PUSH_MATRIX -> {
final int mode = memGetInt(ptr);
ptr += 4;
final int savedMode = GLStateManager.getMatrixMode().getMode();
GLStateManager.glMatrixMode(mode);
GLStateManager.glPushMatrix();
GLStateManager.glMatrixMode(savedMode);
}
case GLCommand.POP_MATRIX -> {
final int mode = memGetInt(ptr);
ptr += 4;
final int savedMode = GLStateManager.getMatrixMode().getMode();
GLStateManager.glMatrixMode(mode);
GLStateManager.glPopMatrix();
GLStateManager.glMatrixMode(savedMode);
}
case GLCommand.STENCIL_MASK -> {
GLStateManager.glStencilMask(memGetInt(ptr));
ptr += 4;
}
case GLCommand.DEPTH_MASK -> {
GLStateManager.glDepthMask(memGetInt(ptr) != 0);
ptr += 4;
}
case GLCommand.FRONT_FACE -> {
GLStateManager.glFrontFace(memGetInt(ptr));
ptr += 4;
}
// === Two int commands ===
case GLCommand.BIND_TEXTURE -> {
final int target = memGetInt(ptr);
final int texture = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glBindTexture(target, texture);
}
case GLCommand.POLYGON_MODE -> {
final int face = memGetInt(ptr);
final int mode = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glPolygonMode(face, mode);
}
case GLCommand.COLOR_MATERIAL -> {
final int face = memGetInt(ptr);
final int mode = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glColorMaterial(face, mode);
}
case GLCommand.LINE_STIPPLE -> {
final int factor = memGetInt(ptr);
final int pattern = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glLineStipple(factor, (short) pattern);
}
case GLCommand.STENCIL_MASK_SEPARATE -> {
final int face = memGetInt(ptr);
final int mask = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glStencilMaskSeparate(face, mask);
}
case GLCommand.FOGI -> {
final int pname = memGetInt(ptr);
final int param = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glFogi(pname, param);
}
case GLCommand.HINT -> {
final int target = memGetInt(ptr);
final int mode = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glHint(target, mode);
}
// === Three int commands ===
case GLCommand.STENCIL_FUNC -> {
final int func = memGetInt(ptr);
final int ref = memGetInt(ptr + 4);
final int mask = memGetInt(ptr + 8);
ptr += 12;
GLStateManager.glStencilFunc(func, ref, mask);
}
case GLCommand.STENCIL_OP -> {
final int fail = memGetInt(ptr);
final int zfail = memGetInt(ptr + 4);
final int zpass = memGetInt(ptr + 8);
ptr += 12;
GLStateManager.glStencilOp(fail, zfail, zpass);
}
case GLCommand.TEX_PARAMETERI -> {
final int target = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final int param = memGetInt(ptr + 8);
ptr += 12;
GLStateManager.glTexParameteri(target, pname, param);
}
// === Four int commands ===
case GLCommand.VIEWPORT -> {
final int x = memGetInt(ptr);
final int y = memGetInt(ptr + 4);
final int width = memGetInt(ptr + 8);
final int height = memGetInt(ptr + 12);
ptr += 16;
GLStateManager.glViewport(x, y, width, height);
}
case GLCommand.BLEND_FUNC -> {
final int srcRgb = memGetInt(ptr);
final int dstRgb = memGetInt(ptr + 4);
final int srcAlpha = memGetInt(ptr + 8);
final int dstAlpha = memGetInt(ptr + 12);
ptr += 16;
if (srcRgb == srcAlpha && dstRgb == dstAlpha) {
GLStateManager.glBlendFunc(srcRgb, dstRgb);
} else {
GLStateManager.tryBlendFuncSeparate(srcRgb, dstRgb, srcAlpha, dstAlpha);
}
}
case GLCommand.COLOR_MASK -> {
final boolean r = memGetInt(ptr) != 0;
final boolean g = memGetInt(ptr + 4) != 0;
final boolean b = memGetInt(ptr + 8) != 0;
final boolean a = memGetInt(ptr + 12) != 0;
ptr += 16;
GLStateManager.glColorMask(r, g, b, a);
}
case GLCommand.STENCIL_FUNC_SEPARATE -> {
final int face = memGetInt(ptr);
final int func = memGetInt(ptr + 4);
final int ref = memGetInt(ptr + 8);
final int mask = memGetInt(ptr + 12);
ptr += 16;
GLStateManager.glStencilFuncSeparate(face, func, ref, mask);
}
case GLCommand.STENCIL_OP_SEPARATE -> {
final int face = memGetInt(ptr);
final int sfail = memGetInt(ptr + 4);
final int dpfail = memGetInt(ptr + 8);
final int dppass = memGetInt(ptr + 12);
ptr += 16;
GLStateManager.glStencilOpSeparate(face, sfail, dpfail, dppass);
}
// === Float commands ===
case GLCommand.POINT_SIZE -> {
GLStateManager.glPointSize(memGetFloat(ptr));
ptr += 4;
}
case GLCommand.LINE_WIDTH -> {
GLStateManager.glLineWidth(memGetFloat(ptr));
ptr += 4;
}
case GLCommand.POLYGON_OFFSET -> {
final float factor = memGetFloat(ptr);
final float units = memGetFloat(ptr + 4);
ptr += 8;
GLStateManager.glPolygonOffset(factor, units);
}
case GLCommand.NORMAL -> {
final float x = memGetFloat(ptr);
final float y = memGetFloat(ptr + 4);
final float z = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glNormal3f(x, y, z);
}
case GLCommand.COLOR -> {
final float r = memGetFloat(ptr);
final float g = memGetFloat(ptr + 4);
final float b = memGetFloat(ptr + 8);
final float a = memGetFloat(ptr + 12);
ptr += 16;
GLStateManager.glColor4f(r, g, b, a);
}
case GLCommand.CLEAR_COLOR -> {
final float r = memGetFloat(ptr);
final float g = memGetFloat(ptr + 4);
final float b = memGetFloat(ptr + 8);
final float a = memGetFloat(ptr + 12);
ptr += 16;
GLStateManager.glClearColor(r, g, b, a);
}
case GLCommand.CLEAR_DEPTH -> {
final double depth = memGetDouble(ptr);
ptr += 8;
GLStateManager.glClearDepth(depth);
}
case GLCommand.BLEND_COLOR -> {
final float r = memGetFloat(ptr);
final float g = memGetFloat(ptr + 4);
final float b = memGetFloat(ptr + 8);
final float a = memGetFloat(ptr + 12);
ptr += 16;
GLStateManager.glBlendColor(r, g, b, a);
}
// === Mixed int+float commands ===
case GLCommand.ALPHA_FUNC -> {
final int func = memGetInt(ptr);
final float ref = memGetFloat(ptr + 4);
ptr += 8;
GLStateManager.glAlphaFunc(func, ref);
}
case GLCommand.FOGF -> {
final int pname = memGetInt(ptr);
final float param = memGetFloat(ptr + 4);
ptr += 8;
GLStateManager.glFogf(pname, param);
}
case GLCommand.LIGHTF -> {
final int light = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final float param = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glLightf(light, pname, param);
}
case GLCommand.LIGHT_MODELF -> {
final int pname = memGetInt(ptr);
final float param = memGetFloat(ptr + 4);
ptr += 8;
GLStateManager.glLightModelf(pname, param);
}
case GLCommand.LIGHTI -> {
final int light = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final int param = memGetInt(ptr + 8);
ptr += 12;
GLStateManager.glLighti(light, pname, param);
}
case GLCommand.LIGHT_MODELI -> {
final int pname = memGetInt(ptr);
final int param = memGetInt(ptr + 4);
ptr += 8;
GLStateManager.glLightModeli(pname, param);
}
case GLCommand.MATERIALF -> {
final int face = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final float param = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glMaterialf(face, pname, param);
}
case GLCommand.TEX_PARAMETERF -> {
final int target = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final float param = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glTexParameterf(target, pname, param);
}
// === Double commands ===
case GLCommand.TRANSLATE -> {
final int mode = memGetInt(ptr);
final double x = memGetDouble(ptr + 4);
final double y = memGetDouble(ptr + 12);
final double z = memGetDouble(ptr + 20);
ptr += 28;
final int savedMode = GLStateManager.getMatrixMode().getMode();
GLStateManager.glMatrixMode(mode);
GLStateManager.glTranslated(x, y, z);
GLStateManager.glMatrixMode(savedMode);
}
case GLCommand.ROTATE -> {
final int mode = memGetInt(ptr);
final double angle = memGetDouble(ptr + 4);
final double x = memGetDouble(ptr + 12);
final double y = memGetDouble(ptr + 20);
final double z = memGetDouble(ptr + 28);
ptr += 36;
final int savedMode = GLStateManager.getMatrixMode().getMode();
GLStateManager.glMatrixMode(mode);
GLStateManager.glRotated(angle, x, y, z);
GLStateManager.glMatrixMode(savedMode);
}
case GLCommand.SCALE -> {
final int mode = memGetInt(ptr);
final double x = memGetDouble(ptr + 4);
final double y = memGetDouble(ptr + 12);
final double z = memGetDouble(ptr + 20);
ptr += 28;
final int savedMode = GLStateManager.getMatrixMode().getMode();
GLStateManager.glMatrixMode(mode);
GLStateManager.glScaled(x, y, z);
GLStateManager.glMatrixMode(savedMode);
}
case GLCommand.ORTHO -> {
final double left = memGetDouble(ptr);
final double right = memGetDouble(ptr + 8);
final double bottom = memGetDouble(ptr + 16);
final double top = memGetDouble(ptr + 24);
final double zNear = memGetDouble(ptr + 32);
final double zFar = memGetDouble(ptr + 40);
ptr += 48;
GLStateManager.glOrtho(left, right, bottom, top, zNear, zFar);
}
case GLCommand.FRUSTUM -> {
final double left = memGetDouble(ptr);
final double right = memGetDouble(ptr + 8);
final double bottom = memGetDouble(ptr + 16);
final double top = memGetDouble(ptr + 24);
final double zNear = memGetDouble(ptr + 32);
final double zFar = memGetDouble(ptr + 40);
ptr += 48;
GLStateManager.glFrustum(left, right, bottom, top, zNear, zFar);
}
// === Matrix commands ===
case GLCommand.MULT_MATRIX -> {
final int mode = memGetInt(ptr);
ptr += 4;
MATRIX_BUFFER.clear();
for (int i = 0; i < 16; i++) {
MATRIX_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
MATRIX_BUFFER.flip();
final int savedMode = GLStateManager.getMatrixMode().getMode();
GLStateManager.glMatrixMode(mode);
GLStateManager.glMultMatrix(MATRIX_BUFFER);
GLStateManager.glMatrixMode(savedMode);
}
case GLCommand.LOAD_MATRIX -> {
final int mode = memGetInt(ptr);
ptr += 4;
MATRIX_BUFFER.clear();
for (int i = 0; i < 16; i++) {
MATRIX_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
MATRIX_BUFFER.flip();
final int savedMode = GLStateManager.getMatrixMode().getMode();
GLStateManager.glMatrixMode(mode);
GLStateManager.glLoadMatrix(MATRIX_BUFFER);
GLStateManager.glMatrixMode(savedMode);
}
// === Buffer commands ===
case GLCommand.FOG -> {
final int pname = memGetInt(ptr);
final int count = memGetInt(ptr + 4);
ptr += 8;
PARAMS_BUFFER.clear();
for (int i = 0; i < count; i++) {
PARAMS_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
PARAMS_BUFFER.flip();
GLStateManager.glFog(pname, PARAMS_BUFFER);
}
case GLCommand.LIGHT -> {
final int light = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final int count = memGetInt(ptr + 8);
ptr += 12;
PARAMS_BUFFER.clear();
for (int i = 0; i < count; i++) {
PARAMS_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
PARAMS_BUFFER.flip();
GLStateManager.glLight(light, pname, PARAMS_BUFFER);
}
case GLCommand.LIGHT_MODEL -> {
final int pname = memGetInt(ptr);
final int count = memGetInt(ptr + 4);
ptr += 8;
PARAMS_BUFFER.clear();
for (int i = 0; i < count; i++) {
PARAMS_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
PARAMS_BUFFER.flip();
GLStateManager.glLightModel(pname, PARAMS_BUFFER);
}
case GLCommand.MATERIAL -> {
final int face = memGetInt(ptr);
final int pname = memGetInt(ptr + 4);
final int count = memGetInt(ptr + 8);
ptr += 12;
PARAMS_BUFFER.clear();
for (int i = 0; i < count; i++) {
PARAMS_BUFFER.put(memGetFloat(ptr));
ptr += 4;
}
PARAMS_BUFFER.flip();
GLStateManager.glMaterial(face, pname, PARAMS_BUFFER);
}
// === Draw commands ===
case GLCommand.DRAW_RANGE -> {
final int vboIndex = memGetInt(ptr);
final int start = memGetInt(ptr + 4);
final int count = memGetInt(ptr + 8);
final boolean hasBrightness = memGetInt(ptr + 12) != 0;
ptr += 16;
final VertexBuffer vbo = ownedVbos[vboIndex];
vbo.setupState();
vbo.draw(start, count);
vbo.cleanupState();
}
case GLCommand.CALL_LIST -> {
final int listId = memGetInt(ptr);
ptr += 4;
DisplayListManager.glCallList(listId);
}
// === Complex object reference ===
case GLCommand.COMPLEX_REF -> {
final int index = memGetInt(ptr);
ptr += 4;
((DisplayListCommand) complexObjects[index]).execute();
}
default -> throw new IllegalStateException("Unknown command opcode: " + cmd);
}
}
}
}
@@ -1,350 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import java.util.ArrayDeque;
import java.util.Deque;
public final class CommandBufferProcessor {
private static final Logger LOGGER = LogManager.getLogger("CommandBufferProcessor");
private static final Matrix4f tempMatrix = new Matrix4f();
private CommandBufferProcessor() {}
public static void processCommandForOptimization(int opcode, CommandBuffer raw, BufferTransformOptimizer opt, CommandBuffer out) {
switch (opcode) {
// === MODELVIEW matrix commands - accumulate instead of copying ===
case GLCommand.TRANSLATE -> {
final int mode = raw.readInt();
final double x = raw.readDouble();
final double y = raw.readDouble();
final double z = raw.readDouble();
if (mode == GL11.GL_MODELVIEW) {
opt.translate(x, y, z);
} else {
out.writeTranslate(mode, x, y, z);
}
}
case GLCommand.ROTATE -> {
final int mode = raw.readInt();
final double angle = raw.readDouble();
final double x = raw.readDouble();
final double y = raw.readDouble();
final double z = raw.readDouble();
if (mode == GL11.GL_MODELVIEW) {
opt.rotate(angle, x, y, z);
} else {
out.writeRotate(mode, angle, x, y, z);
}
}
case GLCommand.SCALE -> {
final int mode = raw.readInt();
final double x = raw.readDouble();
final double y = raw.readDouble();
final double z = raw.readDouble();
if (mode == GL11.GL_MODELVIEW) {
opt.scale(x, y, z);
} else {
out.writeScale(mode, x, y, z);
}
}
case GLCommand.MULT_MATRIX -> {
final int mode = raw.readInt();
raw.readMatrix4f(tempMatrix);
if (mode == GL11.GL_MODELVIEW) {
opt.multMatrix(tempMatrix);
} else {
out.writeMultMatrix(mode, tempMatrix);
}
}
case GLCommand.LOAD_IDENTITY -> {
final int mode = raw.readInt();
if (mode == GL11.GL_MODELVIEW) {
if (opt.checkAndClearAbsoluteMatrix()) {
out.writeLoadIdentity(mode);
}
opt.loadIdentity();
} else {
out.writeLoadIdentity(mode);
}
}
case GLCommand.LOAD_MATRIX -> {
final int mode = raw.readInt();
raw.readMatrix4f(tempMatrix);
if (mode == GL11.GL_MODELVIEW) {
opt.emitPendingTransform(out);
out.writeLoadMatrix(mode, tempMatrix);
opt.loadIdentity();
opt.markAbsoluteMatrix();
} else {
out.writeLoadMatrix(mode, tempMatrix);
}
}
// === Stack operations - update optimizer state and copy ===
case GLCommand.PUSH_MATRIX -> {
final int mode = raw.readInt();
if (mode == GL11.GL_MODELVIEW) {
opt.emitPendingTransform(out);
opt.push();
}
out.writePushMatrix(mode);
}
case GLCommand.POP_MATRIX -> {
final int mode = raw.readInt();
if (mode == GL11.GL_MODELVIEW) {
opt.pop();
}
out.writePopMatrix(mode);
}
// === Barriers - emit pending transform before copying ===
case GLCommand.CALL_LIST -> {
final int listId = raw.readInt();
opt.emitPendingTransform(out);
out.writeCallList(listId);
}
case GLCommand.PUSH_ATTRIB -> {
final int mask = raw.readInt();
if ((mask & GL11.GL_TRANSFORM_BIT) != 0) {
opt.emitPendingTransform(out);
}
out.writePushAttrib(mask);
}
// === All other commands - just copy ===
default -> copyCommandData(opcode, raw, out);
}
}
/**
* Copy a complete command (opcode already read) from raw to final buffer.
*/
public static void copyCommand(CommandBuffer raw, CommandBuffer out) {
final int opcode = raw.readInt();
copyCommandData(opcode, raw, out);
}
/**
* Copy command data based on opcode (opcode already consumed from raw).
*/
public static void copyCommandData(int opcode, CommandBuffer raw, CommandBuffer out) {
switch (opcode) {
// Single int commands
case GLCommand.ENABLE -> out.writeEnable(raw.readInt());
case GLCommand.DISABLE -> out.writeDisable(raw.readInt());
case GLCommand.CLEAR -> out.writeClear(raw.readInt());
case GLCommand.CLEAR_STENCIL -> out.writeClearStencil(raw.readInt());
case GLCommand.CULL_FACE -> out.writeCullFace(raw.readInt());
case GLCommand.DEPTH_FUNC -> out.writeDepthFunc(raw.readInt());
case GLCommand.SHADE_MODEL -> out.writeShadeModel(raw.readInt());
case GLCommand.LOGIC_OP -> out.writeLogicOp(raw.readInt());
case GLCommand.MATRIX_MODE -> out.writeMatrixMode(raw.readInt());
case GLCommand.ACTIVE_TEXTURE -> out.writeActiveTexture(raw.readInt());
case GLCommand.USE_PROGRAM -> out.writeUseProgram(raw.readInt());
case GLCommand.PUSH_ATTRIB -> out.writePushAttrib(raw.readInt());
case GLCommand.POP_ATTRIB -> { raw.readInt(); out.writePopAttrib(); }
case GLCommand.LOAD_IDENTITY -> out.writeLoadIdentity(raw.readInt());
case GLCommand.PUSH_MATRIX -> out.writePushMatrix(raw.readInt());
case GLCommand.POP_MATRIX -> out.writePopMatrix(raw.readInt());
case GLCommand.STENCIL_MASK -> out.writeStencilMask(raw.readInt());
case GLCommand.DEPTH_MASK -> out.writeDepthMask(raw.readInt() != 0);
case GLCommand.FRONT_FACE -> out.writeFrontFace(raw.readInt());
// Two int commands
case GLCommand.BIND_TEXTURE -> out.writeBindTexture(raw.readInt(), raw.readInt());
case GLCommand.POLYGON_MODE -> out.writePolygonMode(raw.readInt(), raw.readInt());
case GLCommand.COLOR_MATERIAL -> out.writeColorMaterial(raw.readInt(), raw.readInt());
case GLCommand.LINE_STIPPLE -> out.writeLineStipple(raw.readInt(), raw.readInt());
case GLCommand.STENCIL_MASK_SEPARATE -> out.writeStencilMaskSeparate(raw.readInt(), raw.readInt());
case GLCommand.FOGI -> out.writeFogi(raw.readInt(), raw.readInt());
case GLCommand.HINT -> out.writeHint(raw.readInt(), raw.readInt());
// Three int commands
case GLCommand.STENCIL_FUNC -> out.writeStencilFunc(raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.STENCIL_OP -> out.writeStencilOp(raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.TEX_PARAMETERI -> out.writeTexParameteri(raw.readInt(), raw.readInt(), raw.readInt());
// Four int commands
case GLCommand.VIEWPORT -> out.writeViewport(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.BLEND_FUNC -> out.writeBlendFunc(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.COLOR_MASK -> out.writeColorMask(raw.readInt() != 0, raw.readInt() != 0, raw.readInt() != 0, raw.readInt() != 0);
case GLCommand.STENCIL_FUNC_SEPARATE -> out.writeStencilFuncSeparate(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.STENCIL_OP_SEPARATE -> out.writeStencilOpSeparate(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
// Float commands
case GLCommand.POINT_SIZE -> out.writePointSize(raw.readFloat());
case GLCommand.LINE_WIDTH -> out.writeLineWidth(raw.readFloat());
case GLCommand.POLYGON_OFFSET -> out.writePolygonOffset(raw.readFloat(), raw.readFloat());
case GLCommand.NORMAL -> out.writeNormal(raw.readFloat(), raw.readFloat(), raw.readFloat());
case GLCommand.COLOR -> out.writeColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
case GLCommand.CLEAR_COLOR -> out.writeClearColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
// Single double commands
case GLCommand.CLEAR_DEPTH -> out.writeClearDepth(raw.readDouble());
// Four float commands
case GLCommand.BLEND_COLOR -> out.writeBlendColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
// Mixed int+float commands
case GLCommand.ALPHA_FUNC -> out.writeAlphaFunc(raw.readInt(), raw.readFloat());
case GLCommand.FOGF -> out.writeFogf(raw.readInt(), raw.readFloat());
case GLCommand.LIGHTF -> out.writeLightf(raw.readInt(), raw.readInt(), raw.readFloat());
case GLCommand.LIGHT_MODELF -> out.writeLightModelf(raw.readInt(), raw.readFloat());
case GLCommand.LIGHTI -> out.writeLighti(raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.LIGHT_MODELI -> out.writeLightModeli(raw.readInt(), raw.readInt());
case GLCommand.MATERIALF -> out.writeMaterialf(raw.readInt(), raw.readInt(), raw.readFloat());
case GLCommand.TEX_PARAMETERF -> out.writeTexParameterf(raw.readInt(), raw.readInt(), raw.readFloat());
// Double commands
case GLCommand.TRANSLATE -> out.writeTranslate(raw.readInt(), raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.ROTATE -> out.writeRotate(raw.readInt(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.SCALE -> out.writeScale(raw.readInt(), raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.ORTHO -> out.writeOrtho(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.FRUSTUM -> out.writeFrustum(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
// Matrix commands
case GLCommand.MULT_MATRIX -> { int mode = raw.readInt(); out.writeMultMatrix(mode, raw.readMatrix4f(tempMatrix)); }
case GLCommand.LOAD_MATRIX -> { int mode = raw.readInt(); out.writeLoadMatrix(mode, raw.readMatrix4f(tempMatrix)); }
// FloatBuffer commands (inline 4 floats max)
case GLCommand.FOG -> {
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeFog(pname, fb);
}
case GLCommand.LIGHT -> {
final int light = raw.readInt();
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeLight(light, pname, fb);
}
case GLCommand.LIGHT_MODEL -> {
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeLightModel(pname, fb);
}
case GLCommand.MATERIAL -> {
final int face = raw.readInt();
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeMaterial(face, pname, fb);
}
// Draw/call commands
case GLCommand.DRAW_RANGE -> out.writeDrawRange(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt() != 0);
case GLCommand.CALL_LIST -> out.writeCallList(raw.readInt());
// Complex object reference - transfer the object to output buffer
case GLCommand.COMPLEX_REF -> {
final int index = raw.readInt();
final Object obj = raw.getComplexObject(index);
out.writeComplexRef((DisplayListCommand) obj);
}
default -> LOGGER.warn("[CommandBufferProcessor] Unknown opcode in buffer copy: {}", opcode);
}
}
/**
* Lightweight transform optimizer for buffer-to-buffer processing.
* Tracks accumulated MODELVIEW transform and emits MultMatrix commands when needed.
*/
public static class BufferTransformOptimizer {
private final Matrix4f accumulated = new Matrix4f();
private final Matrix4f lastEmitted = new Matrix4f();
private final Deque<Matrix4f> stack = new ArrayDeque<>();
private boolean absoluteMatrixFlag = false;
public BufferTransformOptimizer() {
accumulated.identity();
lastEmitted.identity();
}
public void translate(double x, double y, double z) {
accumulated.translate((float) x, (float) y, (float) z);
}
public void rotate(double angle, double x, double y, double z) {
accumulated.rotate((float) Math.toRadians(angle), (float) x, (float) y, (float) z);
}
public void scale(double x, double y, double z) {
accumulated.scale((float) x, (float) y, (float) z);
}
public void multMatrix(Matrix4f m) {
accumulated.mul(m);
}
public void loadIdentity() {
accumulated.identity();
}
public void push() {
stack.push(new Matrix4f(accumulated));
}
public void pop() {
if (!stack.isEmpty()) {
accumulated.set(stack.pop());
lastEmitted.set(accumulated);
} else {
accumulated.identity();
lastEmitted.identity();
}
}
public boolean isIdentity() {
return DisplayListManager.isIdentity(accumulated);
}
public void markAbsoluteMatrix() {
absoluteMatrixFlag = true;
}
public boolean checkAndClearAbsoluteMatrix() {
final boolean was = absoluteMatrixFlag;
absoluteMatrixFlag = false;
return was;
}
public void emitPendingTransform(CommandBuffer out) {
if (!accumulated.equals(lastEmitted)) {
emitTransformTo(out, accumulated);
}
}
public void emitTransformTo(CommandBuffer out, Matrix4f target) {
if (target.equals(lastEmitted)) {
return;
}
if (DisplayListManager.isIdentity(lastEmitted)) {
out.writeMultMatrix(GL11.GL_MODELVIEW, target);
} else {
final Matrix4f delta = new Matrix4f(lastEmitted).invert().mul(target);
out.writeMultMatrix(GL11.GL_MODELVIEW, delta);
}
lastEmitted.set(target);
}
}
}
@@ -1,348 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.joml.Matrix4f;
import java.nio.FloatBuffer;
/**
* Records GL commands to a CommandBuffer during display list compilation.
*/
public class CommandRecorder {
private final CommandBuffer buffer;
private int commandCount;
public CommandRecorder() {
this.buffer = new CommandBuffer();
this.commandCount = 0;
}
public CommandBuffer getBuffer() {
return buffer;
}
public int getCommandCount() {
return commandCount;
}
public void free() {
buffer.free();
}
// ==================== Recording Methods ====================
public void recordEnable(int cap) {
buffer.writeEnable(cap);
commandCount++;
}
public void recordDisable(int cap) {
buffer.writeDisable(cap);
commandCount++;
}
public void recordClear(int mask) {
buffer.writeClear(mask);
commandCount++;
}
public void recordClearColor(float r, float g, float b, float a) {
buffer.writeClearColor(r, g, b, a);
commandCount++;
}
public void recordClearDepth(double depth) {
buffer.writeClearDepth(depth);
commandCount++;
}
public void recordBlendColor(float r, float g, float b, float a) {
buffer.writeBlendColor(r, g, b, a);
commandCount++;
}
public void recordClearStencil(int s) {
buffer.writeClearStencil(s);
commandCount++;
}
public void recordColor(float r, float g, float b, float a) {
buffer.writeColor(r, g, b, a);
commandCount++;
}
public void recordColorMask(boolean r, boolean g, boolean b, boolean a) {
buffer.writeColorMask(r, g, b, a);
commandCount++;
}
public void recordDepthMask(boolean flag) {
buffer.writeDepthMask(flag);
commandCount++;
}
public void recordFrontFace(int mode) {
buffer.writeFrontFace(mode);
commandCount++;
}
public void recordDepthFunc(int func) {
buffer.writeDepthFunc(func);
commandCount++;
}
public void recordBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
buffer.writeBlendFunc(srcRgb, dstRgb, srcAlpha, dstAlpha);
commandCount++;
}
public void recordAlphaFunc(int func, float ref) {
buffer.writeAlphaFunc(func, ref);
commandCount++;
}
public void recordCullFace(int mode) {
buffer.writeCullFace(mode);
commandCount++;
}
public void recordShadeModel(int mode) {
buffer.writeShadeModel(mode);
commandCount++;
}
public void recordBindTexture(int target, int texture) {
buffer.writeBindTexture(target, texture);
commandCount++;
}
public void recordTexParameteri(int target, int pname, int param) {
buffer.writeTexParameteri(target, pname, param);
commandCount++;
}
public void recordTexParameterf(int target, int pname, float param) {
buffer.writeTexParameterf(target, pname, param);
commandCount++;
}
public void recordMatrixMode(int mode) {
buffer.writeMatrixMode(mode);
commandCount++;
}
public void recordLoadIdentity(int matrixMode) {
buffer.writeLoadIdentity(matrixMode);
commandCount++;
}
public void recordPushMatrix(int matrixMode) {
buffer.writePushMatrix(matrixMode);
commandCount++;
}
public void recordPopMatrix(int matrixMode) {
buffer.writePopMatrix(matrixMode);
commandCount++;
}
public void recordTranslate(int matrixMode, double x, double y, double z) {
buffer.writeTranslate(matrixMode, x, y, z);
commandCount++;
}
public void recordRotate(int matrixMode, double angle, double x, double y, double z) {
buffer.writeRotate(matrixMode, angle, x, y, z);
commandCount++;
}
public void recordScale(int matrixMode, double x, double y, double z) {
buffer.writeScale(matrixMode, x, y, z);
commandCount++;
}
public void recordMultMatrix(int matrixMode, Matrix4f matrix) {
buffer.writeMultMatrix(matrixMode, matrix);
commandCount++;
}
public void recordLoadMatrix(int matrixMode, Matrix4f matrix) {
buffer.writeLoadMatrix(matrixMode, matrix);
commandCount++;
}
public void recordOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
buffer.writeOrtho(left, right, bottom, top, zNear, zFar);
commandCount++;
}
public void recordFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
buffer.writeFrustum(left, right, bottom, top, zNear, zFar);
commandCount++;
}
public void recordViewport(int x, int y, int width, int height) {
buffer.writeViewport(x, y, width, height);
commandCount++;
}
public void recordNormal(float nx, float ny, float nz) {
buffer.writeNormal(nx, ny, nz);
commandCount++;
}
public void recordPointSize(float size) {
buffer.writePointSize(size);
commandCount++;
}
public void recordLineWidth(float width) {
buffer.writeLineWidth(width);
commandCount++;
}
public void recordLineStipple(int factor, int pattern) {
buffer.writeLineStipple(factor, pattern);
commandCount++;
}
public void recordPolygonOffset(float factor, float units) {
buffer.writePolygonOffset(factor, units);
commandCount++;
}
public void recordPolygonMode(int face, int mode) {
buffer.writePolygonMode(face, mode);
commandCount++;
}
public void recordColorMaterial(int face, int mode) {
buffer.writeColorMaterial(face, mode);
commandCount++;
}
public void recordLogicOp(int opcode) {
buffer.writeLogicOp(opcode);
commandCount++;
}
public void recordActiveTexture(int texture) {
buffer.writeActiveTexture(texture);
commandCount++;
}
public void recordUseProgram(int program) {
buffer.writeUseProgram(program);
commandCount++;
}
public void recordPushAttrib(int mask) {
buffer.writePushAttrib(mask);
commandCount++;
}
public void recordPopAttrib() {
buffer.writePopAttrib();
commandCount++;
}
public void recordFogf(int pname, float param) {
buffer.writeFogf(pname, param);
commandCount++;
}
public void recordFogi(int pname, int param) {
buffer.writeFogi(pname, param);
commandCount++;
}
public void recordHint(int target, int mode) {
buffer.writeHint(target, mode);
commandCount++;
}
public void recordFog(int pname, FloatBuffer params) {
buffer.writeFog(pname, params);
commandCount++;
}
public void recordLightf(int light, int pname, float param) {
buffer.writeLightf(light, pname, param);
commandCount++;
}
public void recordLighti(int light, int pname, int param) {
buffer.writeLighti(light, pname, param);
commandCount++;
}
public void recordLight(int light, int pname, FloatBuffer params) {
buffer.writeLight(light, pname, params);
commandCount++;
}
public void recordLightModelf(int pname, float param) {
buffer.writeLightModelf(pname, param);
commandCount++;
}
public void recordLightModeli(int pname, int param) {
buffer.writeLightModeli(pname, param);
commandCount++;
}
public void recordLightModel(int pname, FloatBuffer params) {
buffer.writeLightModel(pname, params);
commandCount++;
}
public void recordMaterialf(int face, int pname, float val) {
buffer.writeMaterialf(face, pname, val);
commandCount++;
}
public void recordMaterial(int face, int pname, FloatBuffer params) {
buffer.writeMaterial(face, pname, params);
commandCount++;
}
public void recordStencilFunc(int func, int ref, int mask) {
buffer.writeStencilFunc(func, ref, mask);
commandCount++;
}
public void recordStencilFuncSeparate(int face, int func, int ref, int mask) {
buffer.writeStencilFuncSeparate(face, func, ref, mask);
commandCount++;
}
public void recordStencilOp(int fail, int zfail, int zpass) {
buffer.writeStencilOp(fail, zfail, zpass);
commandCount++;
}
public void recordStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
buffer.writeStencilOpSeparate(face, sfail, dpfail, dppass);
commandCount++;
}
public void recordStencilMask(int mask) {
buffer.writeStencilMask(mask);
commandCount++;
}
public void recordStencilMaskSeparate(int face, int mask) {
buffer.writeStencilMaskSeparate(face, mask);
commandCount++;
}
public void recordCallList(int listId) {
buffer.writeCallList(listId);
commandCount++;
}
public void recordComplexCommand(DisplayListCommand cmd) {
buffer.writeComplexRef(cmd);
commandCount++;
}
}
@@ -1,186 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
/**
* Represents a compiled display list.
*
* <p>Contains commands serialized to a direct ByteBuffer for off-heap storage,
* with complex objects (TexImage2DCmd, TexSubImage2DCmd) stored separately.
*
* <p>The ownedVbos array contains VBOs that are referenced by DrawRange commands.
*/
public final class CompiledDisplayList {
private final ByteBuffer commandBuffer; // Off-heap command storage, must be freed
private final Object[] complexObjects; // Complex commands (TexImage2D, etc.)
private final VertexBuffer[] ownedVbos; // GPU resources referenced by index
public CompiledDisplayList(ByteBuffer commandBuffer, Object[] complexObjects, VertexBuffer[] ownedVbos) {
this.commandBuffer = commandBuffer;
this.complexObjects = complexObjects;
this.ownedVbos = ownedVbos;
}
/**
* Render this display list by executing all commands.
*/
public void render() {
if (commandBuffer != null && commandBuffer.limit() > 0) {
CommandBufferExecutor.execute(commandBuffer, complexObjects, ownedVbos);
}
}
/**
* Delete all resources held by this display list.
*/
public void delete() {
// Delete complex objects that hold resources
if (complexObjects != null) {
for (Object obj : complexObjects) {
if (obj instanceof DisplayListCommand cmd) {
cmd.delete();
}
}
}
// Close VBOs
if (ownedVbos != null) {
for (VertexBuffer vbo : ownedVbos) {
if (vbo != null) {
vbo.close();
}
}
}
// Free the command buffer (off-heap memory)
if (commandBuffer != null) {
memFree(commandBuffer);
}
}
/**
* Get the command buffer for debugging/inspection.
*/
public ByteBuffer getCommandBuffer() {
return commandBuffer;
}
/**
* Get complex objects for inlining into another display list (nested lists).
*/
public Object[] getComplexObjects() {
return complexObjects;
}
/**
* Get owned VBOs for inlining into another display list.
*/
public VertexBuffer[] getOwnedVbos() {
return ownedVbos;
}
// === Test inspection methods ===
/**
* Get counts of each command type in the buffer.
* Used for testing to verify optimization results.
*/
public Int2IntMap getCommandCounts() {
Int2IntMap counts = new Int2IntOpenHashMap();
if (commandBuffer == null || commandBuffer.limit() == 0) {
return counts;
}
long ptr = memAddress(commandBuffer);
long end = ptr + commandBuffer.limit();
while (ptr < end) {
int cmd = memGetInt(ptr);
counts.mergeInt(cmd, 1, Integer::sum);
ptr += getCommandSize(cmd, ptr);
}
return counts;
}
/**
* Get the sequence of command opcodes in order.
* Used for testing to verify command ordering.
*/
public IntList getCommandOpcodes() {
IntList opcodes = new IntArrayList();
if (commandBuffer == null || commandBuffer.limit() == 0) {
return opcodes;
}
long ptr = memAddress(commandBuffer);
long end = ptr + commandBuffer.limit();
while (ptr < end) {
int cmd = memGetInt(ptr);
opcodes.add(cmd);
ptr += getCommandSize(cmd, ptr);
}
return opcodes;
}
/**
* Get size of a command in bytes (including the opcode).
*/
private static int getCommandSize(int cmd, long ptr) {
return switch (cmd) {
// Single int commands (8 bytes)
case GLCommand.ENABLE, GLCommand.DISABLE, GLCommand.CLEAR, GLCommand.CLEAR_STENCIL,
GLCommand.CULL_FACE, GLCommand.DEPTH_FUNC, GLCommand.SHADE_MODEL, GLCommand.LOGIC_OP,
GLCommand.MATRIX_MODE, GLCommand.ACTIVE_TEXTURE, GLCommand.USE_PROGRAM,
GLCommand.PUSH_ATTRIB, GLCommand.POP_ATTRIB, GLCommand.LOAD_IDENTITY,
GLCommand.PUSH_MATRIX, GLCommand.POP_MATRIX, GLCommand.STENCIL_MASK,
GLCommand.DEPTH_MASK, GLCommand.FRONT_FACE, GLCommand.POINT_SIZE, GLCommand.LINE_WIDTH,
GLCommand.CALL_LIST, GLCommand.COMPLEX_REF -> 8;
// Two int commands (12 bytes)
case GLCommand.BIND_TEXTURE, GLCommand.POLYGON_MODE, GLCommand.COLOR_MATERIAL,
GLCommand.LINE_STIPPLE, GLCommand.STENCIL_MASK_SEPARATE, GLCommand.FOGI,
GLCommand.HINT, GLCommand.POLYGON_OFFSET, GLCommand.ALPHA_FUNC, GLCommand.FOGF,
GLCommand.LIGHT_MODELF, GLCommand.LIGHT_MODELI -> 12;
// Three int commands (16 bytes)
case GLCommand.STENCIL_FUNC, GLCommand.STENCIL_OP, GLCommand.TEX_PARAMETERI,
GLCommand.NORMAL, GLCommand.LIGHTF, GLCommand.LIGHTI,
GLCommand.MATERIALF, GLCommand.TEX_PARAMETERF -> 16;
// Four int commands (20 bytes)
case GLCommand.VIEWPORT, GLCommand.BLEND_FUNC, GLCommand.COLOR_MASK,
GLCommand.STENCIL_FUNC_SEPARATE, GLCommand.STENCIL_OP_SEPARATE,
GLCommand.COLOR, GLCommand.CLEAR_COLOR, GLCommand.BLEND_COLOR,
GLCommand.DRAW_RANGE -> 20;
// Double commands
case GLCommand.TRANSLATE, GLCommand.SCALE -> 32; // cmd + mode + 3 doubles
case GLCommand.CLEAR_DEPTH -> 12; // cmd + 1 double
case GLCommand.ROTATE -> 40; // cmd + mode + 4 doubles
case GLCommand.ORTHO, GLCommand.FRUSTUM -> 52; // cmd + 6 doubles
// Matrix commands (72 bytes)
case GLCommand.MULT_MATRIX, GLCommand.LOAD_MATRIX -> 72;
// Buffer commands (variable, read count)
case GLCommand.FOG, GLCommand.LIGHT_MODEL -> {
int count = memGetInt(ptr + 8); // pname at +4, count at +8
yield 12 + count * 4;
}
case GLCommand.LIGHT, GLCommand.MATERIAL -> {
int count = memGetInt(ptr + 12); // light/face at +4, pname at +8, count at +12
yield 16 + count * 4;
}
default -> throw new IllegalStateException("Unknown command: " + cmd);
};
}
}
@@ -1,169 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
/**
* Command opcodes for ByteBuffer-based display list serialization.
* Each command has a fixed layout described in comments.
* All values are 4-byte aligned.
*/
public final class GLCommand {
private GLCommand() {}
// === Single int commands (8 bytes: opcode + param) ===
public static final int ENABLE = 1; // [cmd:4][cap:4]
public static final int DISABLE = 2; // [cmd:4][cap:4]
public static final int CLEAR = 3; // [cmd:4][mask:4]
public static final int CLEAR_STENCIL = 4; // [cmd:4][s:4]
public static final int CULL_FACE = 5; // [cmd:4][mode:4]
public static final int DEPTH_FUNC = 6; // [cmd:4][func:4]
public static final int SHADE_MODEL = 7; // [cmd:4][mode:4]
public static final int LOGIC_OP = 8; // [cmd:4][opcode:4]
public static final int MATRIX_MODE = 9; // [cmd:4][mode:4]
public static final int ACTIVE_TEXTURE = 10; // [cmd:4][texture:4]
public static final int USE_PROGRAM = 11; // [cmd:4][program:4]
public static final int PUSH_ATTRIB = 12; // [cmd:4][mask:4]
public static final int POP_ATTRIB = 13; // [cmd:4][unused:4]
public static final int LOAD_IDENTITY = 14; // [cmd:4][matrixMode:4]
public static final int PUSH_MATRIX = 15; // [cmd:4][matrixMode:4]
public static final int POP_MATRIX = 16; // [cmd:4][matrixMode:4]
public static final int STENCIL_MASK = 17; // [cmd:4][mask:4]
public static final int DEPTH_MASK = 18; // [cmd:4][flag:4] (0 or 1)
public static final int FRONT_FACE = 19; // [cmd:4][mode:4]
// === Two int commands (12 bytes) ===
public static final int BIND_TEXTURE = 20; // [cmd:4][target:4][texture:4]
public static final int POLYGON_MODE = 21; // [cmd:4][face:4][mode:4]
public static final int COLOR_MATERIAL = 22; // [cmd:4][face:4][mode:4]
public static final int LINE_STIPPLE = 23; // [cmd:4][factor:4][pattern:4]
public static final int STENCIL_MASK_SEPARATE = 24; // [cmd:4][face:4][mask:4]
public static final int FOGI = 25; // [cmd:4][pname:4][param:4]
public static final int HINT = 26; // [cmd:4][target:4][mode:4]
// === Three int commands (16 bytes) ===
public static final int STENCIL_FUNC = 30; // [cmd:4][func:4][ref:4][mask:4]
public static final int STENCIL_OP = 31; // [cmd:4][fail:4][zfail:4][zpass:4]
public static final int TEX_PARAMETERI = 32; // [cmd:4][target:4][pname:4][param:4]
// === Four int commands (20 bytes) ===
public static final int VIEWPORT = 40; // [cmd:4][x:4][y:4][width:4][height:4]
public static final int BLEND_FUNC = 41; // [cmd:4][srcRgb:4][dstRgb:4][srcAlpha:4][dstAlpha:4]
public static final int COLOR_MASK = 42; // [cmd:4][r:4][g:4][b:4][a:4] (0 or 1 each)
public static final int STENCIL_FUNC_SEPARATE = 43; // [cmd:4][face:4][func:4][ref:4][mask:4]
public static final int STENCIL_OP_SEPARATE = 44; // [cmd:4][face:4][sfail:4][dpfail:4][dppass:4]
// === Float commands ===
public static final int POINT_SIZE = 50; // [cmd:4][size:4f]
public static final int LINE_WIDTH = 51; // [cmd:4][width:4f]
public static final int POLYGON_OFFSET = 52; // [cmd:4][factor:4f][units:4f]
public static final int NORMAL = 53; // [cmd:4][x:4f][y:4f][z:4f]
public static final int COLOR = 54; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int CLEAR_COLOR = 55; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int BLEND_COLOR = 56; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
// === Mixed int+float commands ===
public static final int ALPHA_FUNC = 60; // [cmd:4][func:4][ref:4f]
public static final int FOGF = 61; // [cmd:4][pname:4][param:4f]
public static final int LIGHTF = 62; // [cmd:4][light:4][pname:4][param:4f]
public static final int LIGHT_MODELF = 63; // [cmd:4][pname:4][param:4f]
public static final int LIGHTI = 64; // [cmd:4][light:4][pname:4][param:4]
public static final int LIGHT_MODELI = 65; // [cmd:4][pname:4][param:4]
public static final int MATERIALF = 66; // [cmd:4][face:4][pname:4][param:4f]
public static final int TEX_PARAMETERF = 67; // [cmd:4][target:4][pname:4][param:4f]
// === Double commands ===
public static final int TRANSLATE = 70; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int ROTATE = 71; // [cmd:4][mode:4][angle:8d][x:8d][y:8d][z:8d] = 40 bytes
public static final int SCALE = 72; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int ORTHO = 73; // [cmd:4][left:8d][right:8d][bottom:8d][top:8d][zNear:8d][zFar:8d] = 52 bytes
public static final int FRUSTUM = 74; // [cmd:4][left:8d][right:8d][bottom:8d][top:8d][zNear:8d][zFar:8d] = 52 bytes
public static final int CLEAR_DEPTH = 75; // [cmd:4][depth:8d] = 12 bytes
// === Matrix commands (72 bytes) ===
public static final int MULT_MATRIX = 80; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
public static final int LOAD_MATRIX = 81; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
// === Buffer commands (inline 4 floats max) ===
public static final int FOG = 90; // [cmd:4][pname:4][count:4][params:16f] = 28 bytes
public static final int LIGHT = 91; // [cmd:4][light:4][pname:4][count:4][params:16f] = 32 bytes
public static final int LIGHT_MODEL = 92; // [cmd:4][pname:4][count:4][params:16f] = 28 bytes
public static final int MATERIAL = 93; // [cmd:4][face:4][pname:4][count:4][params:16f] = 32 bytes
// === Draw commands ===
public static final int DRAW_RANGE = 100; // [cmd:4][vboIndex:4][start:4][count:4][hasBrightness:4] = 20 bytes
public static final int CALL_LIST = 101; // [cmd:4][listId:4] = 8 bytes
// === Complex object reference ===
public static final int COMPLEX_REF = 255; // [cmd:4][index:4] = 8 bytes
/**
* Get a human-readable name for an opcode (for debugging).
*/
public static String getName(int opcode) {
return switch (opcode) {
case ENABLE -> "ENABLE";
case DISABLE -> "DISABLE";
case CLEAR -> "CLEAR";
case CLEAR_STENCIL -> "CLEAR_STENCIL";
case CULL_FACE -> "CULL_FACE";
case DEPTH_FUNC -> "DEPTH_FUNC";
case SHADE_MODEL -> "SHADE_MODEL";
case LOGIC_OP -> "LOGIC_OP";
case MATRIX_MODE -> "MATRIX_MODE";
case ACTIVE_TEXTURE -> "ACTIVE_TEXTURE";
case USE_PROGRAM -> "USE_PROGRAM";
case PUSH_ATTRIB -> "PUSH_ATTRIB";
case POP_ATTRIB -> "POP_ATTRIB";
case LOAD_IDENTITY -> "LOAD_IDENTITY";
case PUSH_MATRIX -> "PUSH_MATRIX";
case POP_MATRIX -> "POP_MATRIX";
case STENCIL_MASK -> "STENCIL_MASK";
case DEPTH_MASK -> "DEPTH_MASK";
case FRONT_FACE -> "FRONT_FACE";
case BIND_TEXTURE -> "BIND_TEXTURE";
case POLYGON_MODE -> "POLYGON_MODE";
case COLOR_MATERIAL -> "COLOR_MATERIAL";
case LINE_STIPPLE -> "LINE_STIPPLE";
case STENCIL_MASK_SEPARATE -> "STENCIL_MASK_SEPARATE";
case FOGI -> "FOGI";
case HINT -> "HINT";
case STENCIL_FUNC -> "STENCIL_FUNC";
case STENCIL_OP -> "STENCIL_OP";
case TEX_PARAMETERI -> "TEX_PARAMETERI";
case VIEWPORT -> "VIEWPORT";
case BLEND_FUNC -> "BLEND_FUNC";
case COLOR_MASK -> "COLOR_MASK";
case STENCIL_FUNC_SEPARATE -> "STENCIL_FUNC_SEPARATE";
case STENCIL_OP_SEPARATE -> "STENCIL_OP_SEPARATE";
case POINT_SIZE -> "POINT_SIZE";
case LINE_WIDTH -> "LINE_WIDTH";
case POLYGON_OFFSET -> "POLYGON_OFFSET";
case NORMAL -> "NORMAL";
case COLOR -> "COLOR";
case CLEAR_COLOR -> "CLEAR_COLOR";
case BLEND_COLOR -> "BLEND_COLOR";
case ALPHA_FUNC -> "ALPHA_FUNC";
case FOGF -> "FOGF";
case LIGHTF -> "LIGHTF";
case LIGHT_MODELF -> "LIGHT_MODELF";
case LIGHTI -> "LIGHTI";
case LIGHT_MODELI -> "LIGHT_MODELI";
case MATERIALF -> "MATERIALF";
case TEX_PARAMETERF -> "TEX_PARAMETERF";
case TRANSLATE -> "TRANSLATE";
case ROTATE -> "ROTATE";
case SCALE -> "SCALE";
case ORTHO -> "ORTHO";
case FRUSTUM -> "FRUSTUM";
case CLEAR_DEPTH -> "CLEAR_DEPTH";
case MULT_MATRIX -> "MULT_MATRIX";
case LOAD_MATRIX -> "LOAD_MATRIX";
case FOG -> "FOG";
case LIGHT -> "LIGHT";
case LIGHT_MODEL -> "LIGHT_MODEL";
case MATERIAL -> "MATERIAL";
case DRAW_RANGE -> "DRAW_RANGE";
case CALL_LIST -> "CALL_LIST";
case COMPLEX_REF -> "COMPLEX_REF";
default -> "UNKNOWN(" + opcode + ")";
};
}
}
@@ -1,708 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuad;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.model.quad.ModelQuadViewMutable;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* Records immediate mode GL calls (glBegin/glEnd/glVertex) during display list compilation.
* Converts immediate mode geometry to ModelQuad format for VBO compilation.
*
* <p>Tracks current immediate mode state:
* <ul>
* <li>TexCoord: Set by glTexCoord* calls</li>
* <li>Normal: Set by glNormal* calls</li>
* <li>Color: Read from GLStateManager.getColor() at vertex emission time</li>
* </ul>
*
* <p>Supported primitive types:
* <ul>
* <li>GL_QUADS: 4 vertices per quad (native support)</li>
* <li>GL_TRIANGLES: 3 vertices per triangle (converted to degenerate quad)</li>
* <li>GL_TRIANGLE_FAN: Fan of triangles (converted to individual quads)</li>
* <li>GL_TRIANGLE_STRIP: Strip of triangles (converted to individual quads)</li>
* <li>GL_QUAD_STRIP: Strip of quads (converted to individual quads)</li>
* <li>GL_POLYGON: Polygon (treated as triangle fan)</li>
* </ul>
*
* <p>Unsupported primitive types (logged warning, geometry discarded):
* <ul>
* <li>GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP: Line primitives</li>
* <li>GL_POINTS: Point primitives</li>
* </ul>
*/
public class ImmediateModeRecorder {
private static final Logger LOGGER = LogManager.getLogger("ImmediateModeRecorder");
// Accumulated quads from completed primitives
private final List<ModelQuadViewMutable> quads = new ArrayList<>();
// Accumulated line vertices (pairs of vertices for GL_LINES)
private final List<LineVertex> lineVertices = new ArrayList<>();
// Current primitive state
private int currentPrimitiveType = -1;
private boolean inPrimitive = false;
// Vertices for current primitive (cleared on glEnd)
private final List<ImmediateVertex> currentVertices = new ArrayList<>();
// Current immediate mode state (set by glTexCoord*, glNormal*)
private float texCoordS = 0.0f;
private float texCoordT = 0.0f;
private float normalX = 0.0f;
private float normalY = 0.0f;
private float normalZ = 1.0f; // Default normal points +Z
// Track which attributes have been set (for flags)
private boolean hasTexCoord = false;
private boolean hasNormal = false;
private boolean hasColor = false;
/**
* Represents a single vertex captured during immediate mode.
*/
private static class ImmediateVertex {
float x, y, z; // Position
int color; // Packed ABGR color
float texU, texV; // Texture coordinates
int normal; // Packed normal
ImmediateVertex(float x, float y, float z, int color, float texU, float texV, int normal) {
this.x = x;
this.y = y;
this.z = z;
this.color = color;
this.texU = texU;
this.texV = texV;
this.normal = normal;
}
}
/**
* Represents a vertex for line primitives.
* Simpler than ImmediateVertex - just position and color.
*/
public static class LineVertex {
public final float x, y, z;
public final int color; // Packed ABGR color
public LineVertex(float x, float y, float z, int color) {
this.x = x;
this.y = y;
this.z = z;
this.color = color;
}
}
public ImmediateModeRecorder() {
// Recorder is ready to capture geometry
}
/**
* Set the current texture coordinate.
* Called by GLStateManager.glTexCoord*.
*/
public void setTexCoord(float s, float t) {
this.texCoordS = s;
this.texCoordT = t;
this.hasTexCoord = true;
}
/**
* Set the current normal vector.
* Called by GLStateManager.glNormal*.
*/
public void setNormal(float x, float y, float z) {
this.normalX = x;
this.normalY = y;
this.normalZ = z;
this.hasNormal = true;
}
/**
* Check if currently in a primitive (between glBegin and glEnd).
*/
public boolean isInPrimitive() {
return inPrimitive;
}
/**
* Start recording a new primitive.
*/
public void begin(int primitiveType) {
if (inPrimitive) {
throw new IllegalStateException("glBegin called while already in primitive");
}
this.currentPrimitiveType = primitiveType;
this.currentVertices.clear();
this.inPrimitive = true;
}
/**
* End the current primitive, convert to quads/lines, and return them immediately.
* This allows the caller to create AccumulatedDraw at the correct command position.
*
* @return Result containing quads, lines, and flags, or null if no geometry was produced
*/
public Result end() {
if (!inPrimitive) {
throw new IllegalStateException("glEnd called without glBegin");
}
// Convert this primitive's vertices to quads or lines
convertPrimitiveToQuads();
this.currentVertices.clear();
this.inPrimitive = false;
// Return geometry from this primitive immediately (don't accumulate)
if (quads.isEmpty() && lineVertices.isEmpty()) {
return null;
}
// Copy geometry and create flags
List<ModelQuadViewMutable> resultQuads = new ArrayList<>(quads);
List<LineVertex> resultLines = new ArrayList<>(lineVertices);
CapturingTessellator.Flags flags = new CapturingTessellator.Flags(
hasTexCoord, // hasTexture
false, // hasBrightness - immediate mode doesn't typically use lightmap
hasColor, // hasColor
hasNormal // hasNormals
);
// Clear for next primitive (but keep attribute tracking state)
quads.clear();
lineVertices.clear();
return new Result(resultQuads, resultLines, flags);
}
/**
* Record a vertex with current attributes.
* Color is read from GLStateManager at call time.
*/
public void vertex(float x, float y, float z) {
if (!inPrimitive) {
throw new IllegalStateException("glVertex called outside glBegin/glEnd");
}
// Get current color from GLStateManager
int packedColor = packColor(
GLStateManager.getColor().getRed(),
GLStateManager.getColor().getGreen(),
GLStateManager.getColor().getBlue(),
GLStateManager.getColor().getAlpha()
);
if (packedColor != 0xFFFFFFFF) {
hasColor = true;
}
// Pack normal
int packedNormal = NormI8.pack(normalX, normalY, normalZ);
currentVertices.add(new ImmediateVertex(
x, y, z,
packedColor,
texCoordS, texCoordT,
packedNormal
));
}
/**
* Pack RGBA floats (0-1) into ABGR int format used by ModelQuad.
*/
private static int packColor(float r, float g, float b, float a) {
int ri = (int) (r * 255.0f) & 0xFF;
int gi = (int) (g * 255.0f) & 0xFF;
int bi = (int) (b * 255.0f) & 0xFF;
int ai = (int) (a * 255.0f) & 0xFF;
// ABGR format (little-endian: stored as RGBA bytes)
return (ai << 24) | (bi << 16) | (gi << 8) | ri;
}
/**
* Convert the current primitive vertices to quads.
* Handles different primitive types.
*/
private void convertPrimitiveToQuads() {
int vertexCount = currentVertices.size();
if (vertexCount == 0) {
return;
}
switch (currentPrimitiveType) {
case GL11.GL_QUADS -> convertQuads();
case GL11.GL_TRIANGLES -> convertTriangles();
case GL11.GL_TRIANGLE_FAN, GL11.GL_POLYGON -> convertTriangleFan();
case GL11.GL_TRIANGLE_STRIP -> convertTriangleStrip();
case GL11.GL_QUAD_STRIP -> convertQuadStrip();
case GL11.GL_LINES -> convertLines();
case GL11.GL_LINE_STRIP -> convertLineStripToLines();
case GL11.GL_LINE_LOOP -> convertLineLoopToLines();
case GL11.GL_POINTS -> {
LOGGER.warn("[ImmediateModeRecorder] Point primitives not supported, discarding {} vertices", vertexCount);
}
default -> {
LOGGER.warn("[ImmediateModeRecorder] Unknown primitive type {}, discarding {} vertices",
currentPrimitiveType, vertexCount);
}
}
}
/**
* Convert GL_QUADS vertices to ModelQuads (4 vertices per quad).
*/
private void convertQuads() {
int vertexCount = currentVertices.size();
int fullQuads = vertexCount / 4;
for (int i = 0; i < fullQuads; i++) {
int base = i * 4;
addQuad(
currentVertices.get(base),
currentVertices.get(base + 1),
currentVertices.get(base + 2),
currentVertices.get(base + 3)
);
}
int remaining = vertexCount % 4;
if (remaining > 0) {
LOGGER.warn("[ImmediateModeRecorder] GL_QUADS: Incomplete quad, discarding {} vertices", remaining);
}
}
/**
* Convert GL_TRIANGLES vertices to degenerate quads (3 vertices per triangle, duplicate last).
*/
private void convertTriangles() {
int vertexCount = currentVertices.size();
int fullTriangles = vertexCount / 3;
for (int i = 0; i < fullTriangles; i++) {
int base = i * 3;
ImmediateVertex v0 = currentVertices.get(base);
ImmediateVertex v1 = currentVertices.get(base + 1);
ImmediateVertex v2 = currentVertices.get(base + 2);
// Degenerate quad: duplicate third vertex
addQuad(v0, v1, v2, v2);
}
int remaining = vertexCount % 3;
if (remaining > 0) {
LOGGER.warn("[ImmediateModeRecorder] GL_TRIANGLES: Incomplete triangle, discarding {} vertices", remaining);
}
}
/**
* Convert GL_TRIANGLE_FAN vertices to triangles then to degenerate quads.
* First vertex is center, subsequent vertices form fan.
*/
private void convertTriangleFan() {
int vertexCount = currentVertices.size();
if (vertexCount < 3) {
LOGGER.warn("[ImmediateModeRecorder] GL_TRIANGLE_FAN: Need at least 3 vertices, got {}", vertexCount);
return;
}
ImmediateVertex center = currentVertices.get(0);
for (int i = 1; i < vertexCount - 1; i++) {
ImmediateVertex v1 = currentVertices.get(i);
ImmediateVertex v2 = currentVertices.get(i + 1);
// Triangle: center, v1, v2 -> degenerate quad
addQuad(center, v1, v2, v2);
}
}
/**
* Convert GL_TRIANGLE_STRIP vertices to triangles then to degenerate quads.
*/
private void convertTriangleStrip() {
int vertexCount = currentVertices.size();
if (vertexCount < 3) {
LOGGER.warn("[ImmediateModeRecorder] GL_TRIANGLE_STRIP: Need at least 3 vertices, got {}", vertexCount);
return;
}
for (int i = 0; i < vertexCount - 2; i++) {
ImmediateVertex v0, v1, v2;
if ((i & 1) == 0) {
// Even triangle: v[i], v[i+1], v[i+2]
v0 = currentVertices.get(i);
v1 = currentVertices.get(i + 1);
v2 = currentVertices.get(i + 2);
} else {
// Odd triangle: v[i+1], v[i], v[i+2] (reverse winding)
v0 = currentVertices.get(i + 1);
v1 = currentVertices.get(i);
v2 = currentVertices.get(i + 2);
}
addQuad(v0, v1, v2, v2);
}
}
/**
* Convert GL_QUAD_STRIP vertices to quads.
* Vertices come in pairs: (v0,v1), (v2,v3), (v4,v5)...
* Each quad uses 4 consecutive vertices in pattern: v[2i], v[2i+1], v[2i+3], v[2i+2]
*/
private void convertQuadStrip() {
int vertexCount = currentVertices.size();
if (vertexCount < 4) {
LOGGER.warn("[ImmediateModeRecorder] GL_QUAD_STRIP: Need at least 4 vertices, got {}", vertexCount);
return;
}
int numQuads = (vertexCount - 2) / 2;
for (int i = 0; i < numQuads; i++) {
int base = i * 2;
// Quad strip winding: v[0], v[1], v[3], v[2]
addQuad(
currentVertices.get(base),
currentVertices.get(base + 1),
currentVertices.get(base + 3),
currentVertices.get(base + 2)
);
}
}
/**
* Convert GL_LINES vertices to line segments (pairs of vertices).
*/
private void convertLines() {
int vertexCount = currentVertices.size();
int fullLines = vertexCount / 2;
for (int i = 0; i < fullLines; i++) {
int base = i * 2;
ImmediateVertex v0 = currentVertices.get(base);
ImmediateVertex v1 = currentVertices.get(base + 1);
lineVertices.add(new LineVertex(v0.x, v0.y, v0.z, v0.color));
lineVertices.add(new LineVertex(v1.x, v1.y, v1.z, v1.color));
}
int remaining = vertexCount % 2;
if (remaining > 0) {
LOGGER.warn("[ImmediateModeRecorder] GL_LINES: Incomplete line, discarding {} vertices", remaining);
}
}
/**
* Convert GL_LINE_STRIP to individual line segments.
* Vertices 0-1-2-3 become lines: 0-1, 1-2, 2-3
*/
private void convertLineStripToLines() {
int vertexCount = currentVertices.size();
if (vertexCount < 2) {
LOGGER.warn("[ImmediateModeRecorder] GL_LINE_STRIP: Need at least 2 vertices, got {}", vertexCount);
return;
}
for (int i = 0; i < vertexCount - 1; i++) {
ImmediateVertex v0 = currentVertices.get(i);
ImmediateVertex v1 = currentVertices.get(i + 1);
lineVertices.add(new LineVertex(v0.x, v0.y, v0.z, v0.color));
lineVertices.add(new LineVertex(v1.x, v1.y, v1.z, v1.color));
}
}
/**
* Convert GL_LINE_LOOP to individual line segments.
* Like LINE_STRIP but also closes the loop from last to first vertex.
*/
private void convertLineLoopToLines() {
int vertexCount = currentVertices.size();
if (vertexCount < 2) {
LOGGER.warn("[ImmediateModeRecorder] GL_LINE_LOOP: Need at least 2 vertices, got {}", vertexCount);
return;
}
// Convert as strip first
convertLineStripToLines();
// Close the loop: add last-to-first segment
ImmediateVertex vLast = currentVertices.get(vertexCount - 1);
ImmediateVertex vFirst = currentVertices.get(0);
lineVertices.add(new LineVertex(vLast.x, vLast.y, vLast.z, vLast.color));
lineVertices.add(new LineVertex(vFirst.x, vFirst.y, vFirst.z, vFirst.color));
}
/**
* Create a ModelQuad from 4 ImmediateVertex objects.
*/
private void addQuad(ImmediateVertex v0, ImmediateVertex v1, ImmediateVertex v2, ImmediateVertex v3) {
ModelQuad quad = new ModelQuad();
setVertex(quad, 0, v0);
setVertex(quad, 1, v1);
setVertex(quad, 2, v2);
setVertex(quad, 3, v3);
// Set light face based on computed normal
quad.setLightFace(ModelQuadUtil.findLightFace(quad.getComputedFaceNormal()));
quads.add(quad);
}
/**
* Set a single vertex in a ModelQuad.
*/
private void setVertex(ModelQuad quad, int idx, ImmediateVertex v) {
quad.setX(idx, v.x);
quad.setY(idx, v.y);
quad.setZ(idx, v.z);
quad.setColor(idx, v.color);
quad.setTexU(idx, v.texU);
quad.setTexV(idx, v.texV);
quad.setForgeNormal(idx, v.normal);
// Light defaults to full brightness (no lightmap for immediate mode typically)
quad.setLight(idx, ModelQuadUtil.DEFAULT_LIGHTMAP);
}
/**
* Check if any immediate mode geometry has been captured.
*
* <p>Note: With immediate flush mode, this will typically return false as geometry
* is returned from end() immediately. This method is kept for edge cases
* where primitives might fail to convert (e.g., incomplete quads).
*/
public boolean hasGeometry() {
return !quads.isEmpty() || !lineVertices.isEmpty();
}
/**
* Get any remaining accumulated geometry and clear the recorder.
*
* <p>Note: With immediate flush mode, this will typically return null as geometry
* is returned from end() immediately. This method is kept for cleanup and
* edge cases where geometry might remain (e.g., if end() was never called).
*
* @return Result containing remaining quads, lines, and flags, or null if no geometry
*/
public Result getQuadsAndClear() {
if (inPrimitive) {
throw new IllegalStateException("Cannot get geometry while in primitive (missing glEnd)");
}
if (quads.isEmpty() && lineVertices.isEmpty()) {
return null;
}
// Copy geometry and create flags
List<ModelQuadViewMutable> resultQuads = new ArrayList<>(quads);
List<LineVertex> resultLines = new ArrayList<>(lineVertices);
CapturingTessellator.Flags flags = new CapturingTessellator.Flags(
hasTexCoord, // hasTexture
false, // hasBrightness - immediate mode doesn't typically use lightmap
hasColor, // hasColor
hasNormal // hasNormals
);
// Clear state for reuse
quads.clear();
lineVertices.clear();
hasColor = false;
hasTexCoord = false;
hasNormal = false;
// Note: current texcoord/normal values are NOT reset - they persist per OpenGL spec
return new Result(resultQuads, resultLines, flags);
}
/**
* Result of immediate mode recording: quads, lines, and their attribute flags.
*/
@com.github.bsideup.jabel.Desugar
public record Result(List<ModelQuadViewMutable> quads, List<LineVertex> lines, CapturingTessellator.Flags flags) {}
/**
* Reset all state including current texcoord/normal.
* Called when starting a new display list.
*/
public void reset() {
quads.clear();
lineVertices.clear();
currentVertices.clear();
inPrimitive = false;
currentPrimitiveType = -1;
texCoordS = 0.0f;
texCoordT = 0.0f;
normalX = 0.0f;
normalY = 0.0f;
normalZ = 1.0f;
hasColor = false;
hasTexCoord = false;
hasNormal = false;
}
/**
* Process a glDrawArrays call during display list recording.
* Reads vertex data from client arrays (tracked globally in GLStateManager) and converts to immediate mode geometry.
*
* @param mode Primitive type (GL_QUADS, GL_LINES, etc.)
* @param first Starting index in arrays
* @param count Number of vertices
* @return Result containing converted geometry, or null if no geometry produced
*/
public Result processDrawArrays(int mode, int first, int count) {
final var cas = GLStateManager.getClientArrayState();
final var vertexPointer = cas.isVertexArrayEnabled() ? cas.getVertexPointer() : null;
final var colorPointer = cas.isColorArrayEnabled() ? cas.getColorPointer() : null;
return convertClientArrays(
mode, first, count,
vertexPointer,
cas.getVertexPointerType(), cas.getVertexPointerSize(), cas.getVertexPointerStride(),
colorPointer,
cas.getColorPointerType(), cas.getColorPointerSize(), cas.getColorPointerStride()
);
}
/**
* Convert client array data to immediate mode.
* Called by glDrawArrays during display list recording.
* Like Mesa's save_DrawArrays, reads vertex data from arrays and emits as immediate mode.
*
* @param mode Primitive type (GL_QUADS, GL_LINES, etc.)
* @param first Starting index in arrays
* @param count Number of vertices
* @param vertexPointer Vertex position buffer (or null if not enabled)
* @param vertexType GL type (GL_FLOAT, GL_DOUBLE, etc.)
* @param vertexSize Size of vertex (2, 3, or 4)
* @param vertexStride Stride in bytes (0 for tightly packed)
* @param colorPointer Color buffer (or null if not enabled)
* @param colorType GL type (GL_FLOAT, GL_UNSIGNED_BYTE, etc.)
* @param colorSize Size of color (3 or 4)
* @param colorStride Stride in bytes (0 for tightly packed)
* @return Result containing converted geometry, or null if no geometry produced
*/
public Result convertClientArrays(
int mode, int first, int count,
java.nio.Buffer vertexPointer, int vertexType, int vertexSize, int vertexStride,
java.nio.Buffer colorPointer, int colorType, int colorSize, int colorStride
) {
begin(mode);
// Hoist type checks and stride calculations outside the loop for performance.
// Mesa's algorithm: offset_bytes = index * effective_stride
// effective_stride = (stride == 0) ? (numComponents * sizeof(element)) : stride
// Determine color reader (hoisted out of loop)
final ColorReader colorReader;
final int colorEffectiveStride;
if (colorPointer == null) {
colorReader = null;
colorEffectiveStride = 0;
} else if (colorType == GL11.GL_FLOAT && colorPointer instanceof FloatBuffer fb) {
colorEffectiveStride = (colorStride == 0) ? colorSize * 4 : colorStride;
final int strideInFloats = colorEffectiveStride / 4;
colorReader = (idx) -> {
final int offset = idx * strideInFloats;
final float r = fb.get(offset);
final float g = fb.get(offset + 1);
final float b = fb.get(offset + 2);
final float a = (colorSize == 4) ? fb.get(offset + 3) : 1.0f;
GLStateManager.glColor4f(r, g, b, a);
};
} else if ((colorType == GL11.GL_UNSIGNED_BYTE || colorType == GL11.GL_BYTE) && colorPointer instanceof ByteBuffer bb) {
colorEffectiveStride = (colorStride == 0) ? colorSize : colorStride;
colorReader = (idx) -> {
final int offset = idx * colorEffectiveStride;
final float r = (bb.get(offset) & 0xFF) / 255.0f;
final float g = (bb.get(offset + 1) & 0xFF) / 255.0f;
final float b = (bb.get(offset + 2) & 0xFF) / 255.0f;
final float a = (colorSize == 4) ? (bb.get(offset + 3) & 0xFF) / 255.0f : 1.0f;
GLStateManager.glColor4f(r, g, b, a);
};
} else {
// Unsupported color type - use white
colorEffectiveStride = 0;
colorReader = (idx) -> GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}
// Determine vertex reader (hoisted out of loop)
final VertexReader vertexReader;
if (vertexPointer == null) {
vertexReader = null;
} else if (vertexType == GL11.GL_FLOAT && vertexPointer instanceof FloatBuffer fb) {
final int effectiveStride = (vertexStride == 0) ? vertexSize * 4 : vertexStride;
final int strideInFloats = effectiveStride / 4;
vertexReader = (idx) -> {
final int offset = idx * strideInFloats;
final float x = fb.get(offset);
final float y = fb.get(offset + 1);
final float z = (vertexSize >= 3) ? fb.get(offset + 2) : 0.0f;
vertex(x, y, z);
};
} else if (vertexType == GL11.GL_DOUBLE && vertexPointer instanceof DoubleBuffer db) {
final int effectiveStride = (vertexStride == 0) ? vertexSize * 8 : vertexStride;
final int strideInDoubles = effectiveStride / 8;
vertexReader = (idx) -> {
final int offset = idx * strideInDoubles;
final float x = (float) db.get(offset);
final float y = (float) db.get(offset + 1);
final float z = (vertexSize >= 3) ? (float) db.get(offset + 2) : 0.0f;
vertex(x, y, z);
};
} else if (vertexType == GL11.GL_INT && vertexPointer instanceof IntBuffer ib) {
final int effectiveStride = (vertexStride == 0) ? vertexSize * 4 : vertexStride;
final int strideInInts = effectiveStride / 4;
vertexReader = (idx) -> {
final int offset = idx * strideInInts;
final float x = ib.get(offset);
final float y = ib.get(offset + 1);
final float z = (vertexSize >= 3) ? ib.get(offset + 2) : 0.0f;
vertex(x, y, z);
};
} else if (vertexType == GL11.GL_SHORT && vertexPointer instanceof ShortBuffer sb) {
final int effectiveStride = (vertexStride == 0) ? vertexSize * 2 : vertexStride;
final int strideInShorts = effectiveStride / 2;
vertexReader = (idx) -> {
final int offset = idx * strideInShorts;
final float x = sb.get(offset);
final float y = sb.get(offset + 1);
final float z = (vertexSize >= 3) ? sb.get(offset + 2) : 0.0f;
vertex(x, y, z);
};
} else {
// Unsupported vertex type
vertexReader = null;
}
// Main loop - no type checks, just function calls
for (int i = first; i < first + count; i++) {
if (colorReader != null) {
colorReader.read(i);
}
if (vertexReader != null) {
vertexReader.read(i);
}
}
return end();
}
@FunctionalInterface
private interface ColorReader {
void read(int index);
}
@FunctionalInterface
private interface VertexReader {
void read(int index);
}
}
@@ -1,19 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizons.angelica.glsm.recording.commands.DrawCommand;
import me.jellysquid.mods.sodium.client.gl.attribute.BufferVertexFormat;
import java.nio.ByteBuffer;
import java.util.List;
/**
* Represents recorded geometry from a display list compilation.
* Contains the vertex data buffer and metadata about draw commands.
*/
@Desugar
public record RecordedGeometry(ByteBuffer buffer, BufferVertexFormat format, List<DrawCommand> drawCommands, int totalVertexCount) {
public int getByteSize() {
return buffer.remaining();
}
}
@@ -1,21 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import org.joml.Matrix4f;
/**
* Interface for matrix commands that can be accumulated during transform optimization.
* Implemented by TranslateCmd, RotateCmd, ScaleCmd, and MultMatrixCmd.
*/
public interface AccumulableMatrixCommand extends DisplayListCommand {
boolean isModelView();
void applyTo(Matrix4f matrix);
@Override
default boolean handleOptimization(OptimizationContext ctx) {
if (isModelView()) {
applyTo(ctx.getAccumulatedTransform());
return false; // Accumulated, don't emit
}
return true; // Non-MODELVIEW, emit as-is
}
}
@@ -1,61 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
/**
* Command: Call another display list (glCallList)
* Records a reference to another display list to be executed during playback.
* Per OpenGL spec: the referenced list is NOT executed during compilation,
* only the reference is recorded.
*
*/
@Desugar
public record CallListCmd(int listId) implements DisplayListCommand {
private static final Logger LOGGER = LogManager.getLogger("CallListCmd");
@Override
public void execute() {
final CompiledDisplayList compiled = GLStateManager.getDisplayList(listId);
if (compiled != null) {
GLDebug.pushGroup("CallList(" + listId + ")");
try {
compiled.render();
} finally {
GLDebug.popGroup();
}
} else {
// Fallback if list not found (shouldn't happen in normal operation)
LOGGER.warn("[CallListCmd] List {} not found in cache, using fallback glCallList", listId);
GLStateManager.glCallList(listId);
}
}
@Override
public void delete() {
// No resources to free - we just hold a reference
}
@Override
public boolean needsTransformSync() {
return true; // Nested list uses current GL matrix state
}
@Override
public boolean handleOptimization(OptimizationContext ctx) {
// Nested list needs current GL matrix state
ctx.emitPendingTransform();
return true; // Emit this command
}
@Override
public @NotNull String toString() {
return "CallList(" + listId + ")";
}
}
@@ -1,55 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
/**
* A single command recorded during display list compilation.
* Commands are executed in sequence during glCallList() to replay the display list.
*/
public interface DisplayListCommand {
/**
* Execute this command by calling the appropriate GL function.
*/
void execute();
/**
* Release any resources held by this command (e.g., VBOs).
* Called when the display list is deleted.
*/
default void delete() {
// Most commands don't hold resources
}
/**
* Returns true if this barrier command requires the MODELVIEW transform to be
* synchronized with the accumulated state before execution.
*
* <p>This should return true for commands where the GL matrix state matters,
* such as CallListCmd (nested lists use current GL state) or PushAttribCmd
* with GL_TRANSFORM_BIT (saves the current matrix).</p>
*
* <p>Commands like PushAttribCmd with only GL_COLOR_BUFFER_BIT don't need
* transform sync because they don't interact with the matrix stack.</p>
*
* @return true if transform must be synced to accumulated before this barrier
*/
default boolean needsTransformSync() {
return false;
}
/**
* Handle this command during display list optimization.
*
* <p>Commands override this to implement their optimization behavior:
* <ul>
* <li>Matrix transforms: accumulate into the optimizer, return false</li>
* <li>Push/Pop: update stack, return true to emit</li>
* <li>Barriers: emit pending transforms, return true to emit</li>
* <li>State changes: return true to emit</li>
* </ul>
*
* @param ctx The optimization context providing access to transform state
* @return true if this command should be added to output, false if handled internally
*/
default boolean handleOptimization(OptimizationContext ctx) {
return true;
}
}
@@ -1,14 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.github.bsideup.jabel.Desugar;
/**
* Represents a single draw call within a compiled display list.
* Each DrawCommand corresponds to one glBegin/glEnd block.
*/
@Desugar
public record DrawCommand(
int firstVertex,
int vertexCount,
int primitiveType
) {}
@@ -1,42 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizon.gtnhlib.client.renderer.vbo.VertexBuffer;
import org.jetbrains.annotations.NotNull;
/**
* Command: Draw a range of vertices from a shared VBO.
* <p>
* DrawRangeCmd references a shared VBO owned by CompiledDisplayList.ownedVbos.
* Multiple DrawRangeCmds can reference different ranges within the same VBO,
* enabling format-based batching where all draws with the same vertex format
* share a single VBO.
* <p>
* Transform is NOT stored here - handled by TransformOptimizer emitting
* MultMatrixCmd before this command during optimization.
*/
@Desugar
public record DrawRangeCmd(
VertexBuffer sharedVbo,
int startVertex,
int vertexCount,
boolean hasBrightness
) implements DisplayListCommand {
@Override
public void execute() {
sharedVbo.setupState();
sharedVbo.draw(startVertex, vertexCount);
sharedVbo.cleanupState();
}
@Override
public void delete() {
// No-op: VBO lifetime is managed by CompiledDisplayList.ownedVbos
}
@Override
public @NotNull String toString() {
return "DrawRange(start=" + startVertex + ", count=" + vertexCount + ", hasBrightness=" + hasBrightness + ")";
}
}
@@ -1,44 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.opengl.GL11;
/**
* Command: glLoadIdentity()
* Replaces the current matrix with the identity matrix for the specified matrix mode.
*/
@Desugar
public record LoadIdentityCmd(int matrixMode) implements DisplayListCommand {
@Override
public void execute() {
GLStateManager.glLoadIdentity();
}
public boolean isModelView() {
return matrixMode == GL11.GL_MODELVIEW;
}
@Override
public boolean handleOptimization(OptimizationContext ctx) {
if (isModelView()) {
// Check if we're at an absolute matrix (after LoadMatrix)
// If so, we must emit LoadIdentity to actually reset GL state
if (ctx.checkAndClearAbsoluteMatrix()) {
ctx.emit(this);
ctx.loadIdentity();
return false; // Already emitted
}
ctx.loadIdentity();
return false; // Accumulated, don't emit
}
return true; // Non-MODELVIEW, emit as-is
}
@Override
public @NotNull String toString() {
return String.format("LoadIdentity(%s)", GLDebug.getMatrixModeName(matrixMode));
}
}
@@ -1,75 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.jetbrains.annotations.NotNull;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
/**
* Command: glLoadMatrix(matrix)
* Replaces the current matrix with the specified matrix for the given matrix mode.
*
* <p>Unlike MultMatrixCmd which can be absorbed into the accumulated transform,
* LoadMatrixCmd must always be emitted because it sets an absolute value, not a relative one.
* After a LoadMatrix command, the accumulated transform is reset to identity since
* subsequent transforms are relative to the newly loaded matrix.</p>
*/
@Desugar
public record LoadMatrixCmd(Matrix4f matrix, int matrixMode, FloatBuffer buffer) implements DisplayListCommand {
/**
* Create a LoadMatrixCmd with pre-allocated buffer.
*/
public static LoadMatrixCmd create(Matrix4f matrix, int matrixMode) {
final FloatBuffer buffer = BufferUtils.createFloatBuffer(16);
matrix.get(buffer);
buffer.rewind();
return new LoadMatrixCmd(new Matrix4f(matrix), matrixMode, buffer);
}
/**
* Create a LoadMatrixCmd from a FloatBuffer (copies the data).
*/
public static LoadMatrixCmd create(FloatBuffer src, int matrixMode) {
final Matrix4f matrix = new Matrix4f().set(src);
src.rewind();
return create(matrix, matrixMode);
}
public boolean isModelView() {
return matrixMode == GL11.GL_MODELVIEW;
}
@Override
public void execute() {
// Buffer already populated at construction, just rewind and use
buffer.rewind();
GLStateManager.glLoadMatrix(buffer);
}
@Override
public boolean handleOptimization(OptimizationContext ctx) {
if (isModelView()) {
// LoadMatrix sets an absolute value - emit any pending relative transform first
ctx.emitPendingTransform();
// Emit this command
ctx.emit(this);
// Reset accumulated to identity - subsequent transforms are relative to the loaded matrix
ctx.loadIdentity();
// Mark that GL is at an absolute value - LoadIdentity must emit to reset
ctx.markAbsoluteMatrix();
return false; // Already emitted
}
return true; // Non-MODELVIEW, emit as-is
}
@Override
public @NotNull String toString() {
return String.format("LoadMatrix(%s, %s)", GLDebug.getMatrixModeName(matrixMode), matrix);
}
}
@@ -1,50 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.jetbrains.annotations.NotNull;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
/**
* Command: glMultMatrix(matrix)
* Multiplies the specified matrix mode by the specified matrix.
*/
@Desugar
public record MultMatrixCmd(Matrix4f matrix, int matrixMode, FloatBuffer buffer) implements AccumulableMatrixCommand {
/**
* Create a MultMatrixCmd with pre-allocated buffer.
*/
public static MultMatrixCmd create(Matrix4f matrix, int matrixMode) {
final FloatBuffer buffer = BufferUtils.createFloatBuffer(16);
matrix.get(buffer);
buffer.rewind();
return new MultMatrixCmd(new Matrix4f(matrix), matrixMode, buffer);
}
@Override
public boolean isModelView() {
return matrixMode == GL11.GL_MODELVIEW;
}
@Override
public void applyTo(Matrix4f target) {
target.mul(matrix);
}
@Override
public void execute() {
// Buffer already populated at construction, just rewind and use
buffer.rewind();
GLStateManager.glMultMatrix(buffer);
}
@Override
public @NotNull String toString() {
return String.format("MultMatrix(%s)", matrix);
}
}
@@ -1,42 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import org.joml.Matrix4f;
/**
* Context provided to commands during display list optimization.
* Allows commands to interact with the transform optimizer and draw batcher.
*/
public interface OptimizationContext {
/** Get the accumulated MODELVIEW transform matrix. */
Matrix4f getAccumulatedTransform();
/** Reset accumulated transform to identity. */
void loadIdentity();
/** Push current accumulated transform onto stack. */
void pushTransform();
/** Pop transform from stack, restoring previous accumulated value. */
void popTransform();
/** Emit a MultMatrix command if accumulated differs from last emitted. */
void emitPendingTransform();
/** Emit a MultMatrix command to reach the target transform. */
void emitTransformTo(Matrix4f target);
/** Add a command to the output list. */
void emit(DisplayListCommand cmd);
/**
* Mark that the GL matrix is at an absolute value (after LoadMatrix).
* Subsequent LoadIdentity commands must be emitted to reset to identity.
*/
void markAbsoluteMatrix();
/**
* Check if the GL matrix is at an absolute value and clear the flag.
* @return true if LoadMatrix was called and not yet reset by LoadIdentity
*/
boolean checkAndClearAbsoluteMatrix();
}
@@ -1,33 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.jetbrains.annotations.NotNull;
/**
* Command: glPopAttrib()
* Pops the server attribute stack.
*/
@Desugar
public record PopAttribCmd() implements DisplayListCommand {
@Override
public void execute() {
GLStateManager.glPopAttrib();
}
@Override
public boolean needsTransformSync() {
return true;
}
@Override
public boolean handleOptimization(OptimizationContext ctx) {
ctx.emitPendingTransform();
return true;
}
@Override
public @NotNull String toString() {
return "PopAttrib()";
}
}
@@ -1,36 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.github.bsideup.jabel.Desugar;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.opengl.GL11;
/**
* Command: glPopMatrix()
* Pops the current matrix from the matrix stack for the specified matrix mode.
*/
@Desugar
public record PopMatrixCmd(int matrixMode) implements DisplayListCommand {
@Override
public void execute() {
GLStateManager.glPopMatrix();
}
public boolean isModelView() {
return matrixMode == GL11.GL_MODELVIEW;
}
@Override
public boolean handleOptimization(OptimizationContext ctx) {
if (isModelView()) {
ctx.popTransform();
}
return true; // Always emit Pop
}
@Override
public @NotNull String toString() {
return String.format("PopMatrix(%s)", GLDebug.getMatrixModeName(matrixMode));
}
}
Loaded 100 of 569 files, more files were not shown because too many files have changed in this diff. Show more