Thread safe biome cache & VAO Redirection (#1367)

* Thread safe biome cache

* VAO Redirection
* Delegate GLSM VAO calls to UniversalVAO
* Redirect usages of UniversalVAO & LWJGLService VAO functions to GLSM
* Avoid infinite recursion
This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-02-05 13:56:44 -08:00
1 parent bd56ccc4b8
commit aaab8fd0bc
8 files changed
+82 -23

No files matched your search

+1 -1
View File
@@ -71,7 +71,7 @@ dependencies {
// Iris Shaders
compileOnly('org.jetbrains:annotations:26.0.2')
api("com.github.GTNewHorizons:GTNHLib:0.9.9:dev")
api("com.github.GTNewHorizons:GTNHLib:0.9.10:dev")
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-26.g5ee7d96-GTNH") {
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
import com.mitchej123.glsm.GLStateManagerService;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
@@ -149,17 +150,17 @@ public class AngelicaGLStateManagerService implements GLStateManagerService {
@Override
public int glGenVertexArrays() {
return GL30.glGenVertexArrays();
return UniversalVAO.genVertexArrays();
}
@Override
public void glBindVertexArray(int array) {
GL30.glBindVertexArray(array);
GLStateManager.glBindVertexArray(array);
}
@Override
public void glDeleteVertexArrays(int array) {
GL30.glDeleteVertexArrays(array);
UniversalVAO.deleteVertexArrays(array);
}
// ===================== BUFFER OPERATIONS =====================
@@ -58,7 +58,7 @@ import org.joml.Vector3d;
import org.joml.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.APPLEVertexArrayObject;
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
import org.lwjgl.opengl.ARBMultitexture;
import org.lwjgl.opengl.ContextCapabilities;
import org.lwjgl.opengl.Display;
@@ -3778,20 +3778,12 @@ public class GLStateManager {
}
if (boundVAO != array) {
boundVAO = array;
GL30.glBindVertexArray(array);
UniversalVAO.bindVertexArray(array);
}
}
public static void glBindVertexArrayAPPLE(int array) {
if (DisplayListManager.isRecording()) {
DisplayListManager.recordBindVAO(array);
// Since the vao needs to be bound to do stuff like state setup & data upload, it'll still execute the bind call.
// This is technically wrong, but I'm not sure if there's a better solution here.
}
if (boundVAO != array) {
boundVAO = array;
APPLEVertexArrayObject.glBindVertexArrayAPPLE(array);
}
glBindVertexArray(array);
}
public static boolean isVBOBound() {
@@ -1,6 +1,6 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
@@ -519,7 +519,7 @@ public final class CommandBufferExecutor {
ptr += 4;
}
case GLCommand.BIND_VAO -> {
VAOManager.VAO.glBindVertexArray(memGetInt(ptr));
UniversalVAO.bindVertexArray(memGetInt(ptr));
ptr += 4;
}
@@ -54,6 +54,18 @@ public final class AngelicaRedirector {
private static final String OpenGlHelper = "net/minecraft/client/renderer/OpenGlHelper";
private static final String EXTBlendFunc = "org/lwjgl/opengl/EXTBlendFuncSeparate";
private static final String ARBMultiTexture = "org/lwjgl/opengl/ARBMultitexture";
// Redirect VAO related calls from NHLib and LWJGLService
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
private static final String UniversalVAODot = "com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO";
private static final String VaoFunctions = "com/gtnewhorizon/gtnhlib/client/renderer/vao/VaoFunctions";
private static final String LWJGLService = "com/mitchej123/lwjgl/LWJGLService";
// Don't redirect these items inside of UniversalVAO, since we might be delegating to them
private static final Set<String> UniversalVAOSkippedMethods = ImmutableSet.of(
"glBindVertexArray", "glGenVertexArrays", "glDeleteVertexArrays", "glIsVertexArray"
);
private static final String MinecraftClient = "net.minecraft.client";
private static final String SplashProgress = "cpw.mods.fml.client.SplashProgress";
private static final Set<String> ExcludedMinecraftMainThreadChecks = ImmutableSet.of(
@@ -63,6 +75,7 @@ public final class AngelicaRedirector {
private static final ClassConstantPoolParser cstPoolParser = new ClassConstantPoolParser(GL11, GL12, GL13, GL14, GL20, OpenGlHelper, EXTBlendFunc, ARBMultiTexture, Project);
private static final Map<String, Map<String, String>> methodRedirects = new HashMap<>();
private static final Map<String, Map<String, String>> interfaceRedirects = new HashMap<>();
private static final Map<Integer, String> glCapRedirects = new HashMap<>();
static {
@@ -270,12 +283,29 @@ public final class AngelicaRedirector {
// APPLE
methodRedirects.put(APPLEVertexArrayObject, RedirectMap.newMap()
.add("glBindVertexArrayAPPLE")
.add("glBindVertexArrayAPPLE", "glBindVertexArray")
);
// GTNHLib VAO
methodRedirects.put(UniversalVAO, RedirectMap.newMap()
.add("bindVertexArray", "glBindVertexArray")
);
// OTHER
methodRedirects.put(Project, RedirectMap.newMap().add("gluPerspective"));
// Interface/virtual redirects — callers invoke these on a receiver object
interfaceRedirects.put(VaoFunctions, RedirectMap.newMap()
.add("glBindVertexArray")
);
interfaceRedirects.put(LWJGLService, RedirectMap.newMap()
.add("glBindVertexArray")
);
cstPoolParser.addString(UniversalVAO);
cstPoolParser.addString(VaoFunctions);
cstPoolParser.addString(LWJGLService);
final String glPrefix = "org/lwjgl/opengl/GL";
for (var entry : new HashMap<>(methodRedirects).entrySet()) {
if (entry.getKey().startsWith(glPrefix)) {
@@ -356,7 +386,8 @@ public final class AngelicaRedirector {
final Map<String, String> redirects = methodRedirects.get(mNode.owner);
if (redirects != null) {
final String glsmName = redirects.get(mNode.name);
if (glsmName != null) {
// Skip VAO method redirects inside UniversalVAO
if (glsmName != null && !(transformedName.startsWith(UniversalVAODot) && UniversalVAOSkippedMethods.contains(glsmName))) {
if (LOG_SPAM) {
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1);
LOGGER.info("Redirecting call in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, mNode.name, mNode.desc, glsmName, mNode.desc);
@@ -367,6 +398,30 @@ public final class AngelicaRedirector {
redirectInMethod = true;
}
}
// Interface/virtual redirects: rewrite receiver-based calls to static GLStateManager calls
if ((mNode.getOpcode() == Opcodes.INVOKEINTERFACE || mNode.getOpcode() == Opcodes.INVOKEVIRTUAL) && mNode.desc.equals("(I)V")) {
final Map<String, String> ifaceRedirects = interfaceRedirects.get(mNode.owner);
if (ifaceRedirects != null) {
final String glsmName = ifaceRedirects.get(mNode.name);
if (glsmName != null && !(transformedName.startsWith(UniversalVAODot) && UniversalVAOSkippedMethods.contains(glsmName))) {
if (LOG_SPAM) {
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1);
LOGGER.info("Redirecting interface call in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, mNode.name, mNode.desc, glsmName, "(I)V");
}
// Stack is: [receiver, int_arg]. We need just [int_arg] for static call.
// Insert SWAP + POP before the call to remove the receiver.
mn.instructions.insertBefore(mNode, new InsnNode(Opcodes.SWAP));
mn.instructions.insertBefore(mNode, new InsnNode(Opcodes.POP));
mNode.setOpcode(Opcodes.INVOKESTATIC);
mNode.owner = GLStateManager;
mNode.name = glsmName;
mNode.desc = "(I)V";
mNode.itf = false;
changed = true;
redirectInMethod = true;
}
}
}
}
}
}
@@ -28,9 +28,9 @@ public class AngelicaMainThreadMeshingTask extends AngelicaChunkBuilderMeshingTa
final int minZ = this.render.getOriginZ();
final var world = Minecraft.getMinecraft().theWorld;
this.blockAccess = new ChunkCache(world, minX - 1, minY - 1, minZ - 1, minX + 17, minY + 17, minZ + 17, 1);
this.biomeColorCache = ((WorldClientExtension) world).celeritas$getSmoothBiomeColorCache();
this.biomeColorCache.update(new SectionPos(this.render.getChunkX(), this.render.getChunkY(), this.render.getChunkZ()));
this.blockAccess = new ChunkCache(world, minX - 1, minY - 1, minZ - 1, minX + 17, minY + 17, minZ + 17, 1);
}
@Override
@@ -54,15 +54,14 @@ public class ThreadedAngelicaChunkBuilderMeshingTask extends AngelicaChunkBuilde
final WorldSlice worldSlice = buildContext.getWorldSlice();
worldSlice.copyData(this.renderContext);
this.blockAccess = worldSlice;
this.biomeColorCache = new SmoothBiomeColorCache(worldSlice);
this.biomeColorCache.update(new SectionPos(this.render.getChunkX(), this.render.getChunkY(), this.render.getChunkZ()));
this.biomeColorCache = worldSlice.getBiomeColorCache();
buildContext.setupLightPipeline(minX, minY, minZ);
} else {
// Fallback: direct world access (main thread only)
final var world = Minecraft.getMinecraft().theWorld;
this.blockAccess = new ChunkCache(world, minX - 1, minY - 1, minZ - 1, minX + 17, minY + 17, minZ + 17, 1);
this.biomeColorCache = ((WorldClientExtension) world).celeritas$getSmoothBiomeColorCache();
this.biomeColorCache.update(new SectionPos(this.render.getChunkX(), this.render.getChunkY(), this.render.getChunkZ()));
this.blockAccess = new ChunkCache(world, minX - 1, minY - 1, minZ - 1, minX + 17, minY + 17, minZ + 17, 1);
buildContext.setupLightPipeline(this.blockAccess, minX, minY, minZ);
}
}
@@ -6,11 +6,13 @@ import com.gtnewhorizons.angelica.compat.mojang.ChunkSectionPos;
import com.gtnewhorizons.angelica.compat.mojang.CompatMathHelper;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.IDynamicLightSource;
import com.gtnewhorizons.angelica.rendering.celeritas.SmoothBiomeColorCache;
import java.util.List;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ChunkRenderContext;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSection;
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSectionCache;
import org.embeddedt.embeddium.impl.util.position.SectionPos;
import cpw.mods.fml.common.Optional;
import mega.fluidlogged.api.FLBlockAccess;
import net.minecraft.block.Block;
@@ -98,6 +100,8 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
private ChunkSectionPos origin;
private StructureBoundingBox volume;
private final SmoothBiomeColorCache biomeColorCache;
private List<IDynamicLightSource> chunkLightSources;
private DynamicLights dynamicLightsInstance;
@@ -175,6 +179,8 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
}
}
}
this.biomeColorCache = new SmoothBiomeColorCache(this);
}
public void copyData(ChunkRenderContext context) {
@@ -182,6 +188,8 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
this.sections = context.getSections();
this.volume = context.getVolume();
this.biomeColorCache.update(new SectionPos(origin.x, origin.y, origin.z));
this.baseX = (this.origin.x - NEIGHBOR_CHUNK_RADIUS) << 4;
this.baseY = (this.origin.y - NEIGHBOR_CHUNK_RADIUS) << 4;
this.baseZ = (this.origin.z - NEIGHBOR_CHUNK_RADIUS) << 4;
@@ -221,6 +229,10 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess {
return this.origin;
}
public SmoothBiomeColorCache getBiomeColorCache() {
return this.biomeColorCache;
}
@Override
public int getLightBrightnessForSkyBlocks(int x, int y, int z, int min) {
if (y < 0 || y >= 256 || x < -30_000_000 || z < -30_000_000 || x >= 30_000_000 || z >= 30_000_000) {