Compare commits

...
23 Commits
Author SHA1 Message Date
Jason Mitchell 54c99386d4 Do a similar fix as MixinDroppedItemGlintEdges to held items (#1632) 2026-03-31 18:31:21 -07:00
DarkShadow44 465abf889a Pass mc_Entity like upstream does (#1627) 2026-03-31 16:18:35 -07:00
DarkShadow44 4acb83be6a Port shadow translucent (aka shadow_water) from upstream (#1628) 2026-03-31 16:09:25 -07:00
DarkShadow44 80f9724ee6 Handle GL_QUAD_STRIP in legacy conversion (#1629) 2026-03-31 15:57:57 -07:00
Jason Mitchell 8cf55bb216 Safely handle off client thread crash reports/glGetString (#1630) 2026-03-31 15:49:47 -07:00
Eclipse-Sol 3ebc8868c7 Fix item reset hopefully for the last time (#1621) 2026-03-30 19:32:47 -07:00
JohnButlerThird 0938e6d975 Only run the logic on bop grass (fix my mod bugging out) (#1620) 2026-03-30 18:17:42 -07:00
Eclipse-Sol d3022a40d2 Create new deferred overlay system to fix z-fighting issues (#1605) 2026-03-30 15:50:18 -07:00
Eclipse-Sol 2645c803f9 Better horse support (#1599) 2026-03-30 15:41:53 -07:00
Jason Mitchell d91c356a99 Shader fixes (#1619) 2026-03-30 15:34:54 -07:00
Jason Mitchell 189ff4a80f Defer client attributes. They're uploaded/handled elsewhere. Should fix a crash on j8/lwjgl2 (#1618) 2026-03-30 09:58:40 -07:00
Jason Mitchell ffb4ca9472 Get rid of some allocations, and use immutable variants for the suppliers (#1612) 2026-03-30 09:58:27 -07:00
Jason Mitchell 7d8c8d306d More Double overloads (#1613) 2026-03-29 15:09:13 -07:00
Kotmatross 117ff0cacf Fix other dynamic uniforms not updating properly (#1610) 2026-03-29 12:53:09 -07:00
Jason Mitchell 83fdd57534 Narrow another overly broad transformer exclusion 2026-03-29 09:24:50 -07:00
Kaiz 951be04e9a fix: correctly handle cross-chunk TileEntity render bounds 2026-03-29 09:20:13 -07:00
Jason Mitchell 0ad69f3433 FINE, we'll work with java8/forge. 2026-03-28 20:09:42 -07:00
Fabian Maurer 0dabecc784 Handle GL_POLYGON in legacy conversion 2026-03-28 14:41:23 -07:00
Jason Mitchell 9b3fad4c6d Ensure sufficient coverage + optimize the check 2026-03-28 13:16:54 -07:00
Kaiz e1e8256861 fix: crash when client-side vertex attrib pointers are used in core profile 2026-03-28 13:16:54 -07:00
Jason Mitchell e65c01c2a3 Handle method handles in invokedynamic 2026-03-28 12:46:19 -07:00
Jason Mitchell b9de4df18e Update alpha test to hopefully work with RTM and AE2 (#1597) 2026-03-28 09:40:10 -07:00
Kotmatross a5d31cd1b6 Return phaseChangeNotifier (#1596) 2026-03-28 09:15:46 -07:00
69 changed files with 1448 additions and 298 deletions

No files matched your search

+1 -1
View File
@@ -83,7 +83,7 @@ dependencies {
shadowImplementation("net.minecraftforge:eventbus:7.0-beta.15")
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-26.g5ee7d96-GTNH") {
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-32.g7dd88a4-GTNH") {
exclude module: "antlr4" // we only want to shadow the runtime module
}
shadowImplementation("org.antlr:antlr4-runtime:4.13.2")
+2 -1
View File
@@ -76,7 +76,7 @@ dependencies {
compileOnly("org.lwjgl.lwjgl:lwjgl:${property("lwjglVersion")}")
compileOnly("org.lwjgl.lwjgl:lwjgl_util:${property("lwjglVersion")}")
compileOnly("org.ow2.asm:asm-tree:9.7")
compileOnly("org.ow2.asm:asm-tree:5.0.3")
// Our Deps
api("com.github.GTNewHorizons:GTNHLib:${property("gtnhlibVersion")}:dev")
@@ -100,6 +100,7 @@ dependencies {
testImplementation(sourceSets["stubs"].output)
testImplementation(platform("org.junit:junit-bom:${property("junitBomVersion")}"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.ow2.asm:asm-tree:9.7")
testImplementation("org.lwjgl.lwjgl:lwjgl:${property("lwjglVersion")}")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testRuntimeOnly("org.apache.logging.log4j:log4j-core:${property("log4jVersion")}")
@@ -345,6 +345,27 @@ public class FeedbackManager {
}
break;
case GL11.GL_QUAD_STRIP:
for (int i = 0; i + 3 < count; i += 2) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(4);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i + 3), stride);
emitVertex(reader.read(i + 2), stride);
}
break;
case GL11.GL_POLYGON:
if (count >= 3) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(count);
for (int i = 0; i < count; i++) {
emitVertex(reader.read(i), stride);
}
}
break;
default:
throw new UnsupportedOperationException("Feedback mode: unsupported primitive mode 0x" + Integer.toHexString(mode));
}
@@ -419,6 +419,9 @@ public class GLStateManager {
@Getter protected static int boundVBO;
@Getter protected static int boundEBO;
@Getter protected static int boundVAO;
private static int clientArraysVBO = 0;
private static int clientArraysVBOCapacity = 0;
private static final int[] clientArraysVBOOffsets = new int[VertexAttribState.MAX_ATTRIBS];
private static int boundPixelUnpackBuffer;
private static int boundPixelPackBuffer;
private static final Int2IntOpenHashMap vaoEboMap = new Int2IntOpenHashMap();
@@ -2145,6 +2148,7 @@ public class GLStateManager {
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
RENDER_BACKEND.drawElements(mode, indices);
}
@@ -2158,6 +2162,7 @@ public class GLStateManager {
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices);
} else {
@@ -2175,6 +2180,7 @@ public class GLStateManager {
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices);
} else {
@@ -2192,6 +2198,7 @@ public class GLStateManager {
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(count, type, indices);
} else {
@@ -2223,6 +2230,7 @@ public class GLStateManager {
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices_count, type, indices_buffer_offset);
} else {
@@ -2245,20 +2253,72 @@ public class GLStateManager {
public static void glMultiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
ShaderManager.getInstance().preDraw();
prepareClientArrays();
RENDER_BACKEND.multiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
}
public static void glMultiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex) {
ShaderManager.getInstance().preDraw();
prepareClientArrays();
RENDER_BACKEND.multiDrawElementsBaseVertex(mode, pCount, type, pIndices, drawcount, pBaseVertex);
}
public static void glDrawElementsBaseVertex(int mode, int count, int type, long indices, int basevertex) {
ShaderManager.getInstance().preDraw();
prepareClientArrays();
RENDER_BACKEND.drawElementsBaseVertex(mode, count, type, indices, basevertex);
}
public static void glDrawElementsInstanced(int mode, int count, int type, long indices, int primcount) { RENDER_BACKEND.drawElementsInstanced(mode, count, type, indices, primcount); }
public static void glDrawElementsInstanced(int mode, int count, int type, long indices, int primcount) {
ShaderManager.getInstance().preDraw();
prepareClientArrays();
RENDER_BACKEND.drawElementsInstanced(mode, count, type, indices, primcount);
}
private static void prepareClientArrays() {
if (ShaderManager.getInstance().isEnabled() && VertexAttribState.hasAnyClientSideEnabledAttrib()) {
uploadClientArraysToVBO();
}
}
/**
* If any enabled vertex attribute uses a client-side pointer (no VBO), upload all such
* attribs into a shared stream VBO so the draw succeeds under core profile.
*/
private static void uploadClientArraysToVBO() {
int totalBytes = 0;
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
clientArraysVBOOffsets[i] = -1;
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!a.enabled || a.clientPointer == null) continue;
clientArraysVBOOffsets[i] = totalBytes;
totalBytes += a.clientPointer.remaining();
}
if (totalBytes == 0) return;
if (clientArraysVBO == 0) {
clientArraysVBO = RENDER_BACKEND.genBuffers();
}
final int savedVBO = boundVBO;
glBindBuffer(GL15.GL_ARRAY_BUFFER, clientArraysVBO);
if (totalBytes > clientArraysVBOCapacity) {
int newCap = Math.max(4096, clientArraysVBOCapacity);
while (newCap < totalBytes) newCap *= 2;
clientArraysVBOCapacity = newCap;
}
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, clientArraysVBOCapacity, GL15.GL_STREAM_DRAW);
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
if (clientArraysVBOOffsets[i] < 0) continue;
final VertexAttribState.Attrib a = VertexAttribState.get(i);
RENDER_BACKEND.bufferSubData(GL15.GL_ARRAY_BUFFER, clientArraysVBOOffsets[i], a.clientPointer.duplicate());
RENDER_BACKEND.vertexAttribPointer(i, a.size, a.type, a.normalized, a.stride, (long) clientArraysVBOOffsets[i]);
}
glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
}
public static void glDrawArrays(int mode, int first, int count) {
CommandRecorder savedRecorder = null;
@@ -2278,8 +2338,13 @@ public class GLStateManager {
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(first, count);
} else if (mode == GL11.GL_QUAD_STRIP) {
RENDER_BACKEND.drawArrays(GL11.GL_TRIANGLE_STRIP, first, count & ~1);
} else if (mode == GL11.GL_POLYGON) {
RENDER_BACKEND.drawArrays(GL11.GL_TRIANGLE_FAN, first, count);
} else {
RENDER_BACKEND.drawArrays(mode, first, count);
}
@@ -2298,6 +2363,10 @@ public class GLStateManager {
glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, GL11.GL_FLOAT, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glVertexPointer(int size, int stride, DoubleBuffer pointer) {
glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, GL11.GL_DOUBLE, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glVertexPointer(int size, int type, int stride, ByteBuffer pointer) {
glVertexAttribPointer(Usage.POSITION.getAttributeLocation(), size, type, false, stride, pointer);
}
@@ -2339,6 +2408,10 @@ public class GLStateManager {
glNormalPointer(GL11.GL_INT, stride, pointer);
}
public static void glNormalPointer(int stride, DoubleBuffer pointer) {
glNormalPointer(GL11.GL_DOUBLE, stride, pointer);
}
public static void glNormalPointer(int type, int stride, ByteBuffer pointer) {
glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, type, Usage.NORMAL.isNormalized(), stride, pointer);
}
@@ -2355,6 +2428,10 @@ public class GLStateManager {
glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_INT, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glNormalPointer(int type, int stride, DoubleBuffer pointer) {
glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, GL11.GL_DOUBLE, Usage.NORMAL.isNormalized(), stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glNormalPointer(int type, int stride, long pointer_buffer_offset) {
glVertexAttribPointer(Usage.NORMAL.getAttributeLocation(), 3, type, Usage.NORMAL.isNormalized(), stride, pointer_buffer_offset);
}
@@ -2377,6 +2454,10 @@ public class GLStateManager {
glTexCoordPointer(size, GL11.GL_SHORT, stride, pointer);
}
public static void glTexCoordPointer(int size, int stride, DoubleBuffer pointer) {
glTexCoordPointer(size, GL11.GL_DOUBLE, stride, pointer);
}
public static void glTexCoordPointer(int size, int type, int stride, FloatBuffer pointer) {
final int loc = texCoordAttributeLocation();
if (loc < 0) return;
@@ -2395,6 +2476,12 @@ public class GLStateManager {
glVertexAttribPointer(loc, size, GL11.GL_INT, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glTexCoordPointer(int size, int type, int stride, DoubleBuffer pointer) {
final int loc = texCoordAttributeLocation();
if (loc < 0) return;
glVertexAttribPointer(loc, size, GL11.GL_DOUBLE, false, stride, MemoryUtilities.memByteBuffer(pointer));
}
public static void glTexCoordPointer(int size, int type, int stride, long pointer_buffer_offset) {
final int loc = texCoordAttributeLocation();
if (loc < 0) return;
@@ -2441,32 +2528,23 @@ public class GLStateManager {
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, FloatBuffer pointer) {
final ByteBuffer bb = MemoryUtilities.memByteBuffer(pointer);
VertexAttribState.set(index, size, type, normalized, stride, bb, boundVBO);
RENDER_BACKEND.vertexAttribPointer(index, size, type, normalized, stride, bb);
VertexAttribState.set(index, size, type, normalized, stride, MemoryUtilities.memByteBuffer(pointer), 0);
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, DoubleBuffer pointer) {
final ByteBuffer bb = MemoryUtilities.memByteBuffer(pointer);
VertexAttribState.set(index, size, type, normalized, stride, bb, boundVBO);
RENDER_BACKEND.vertexAttribPointer(index, size, type, normalized, stride, bb);
VertexAttribState.set(index, size, type, normalized, stride, MemoryUtilities.memByteBuffer(pointer), 0);
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, IntBuffer pointer) {
final ByteBuffer bb = MemoryUtilities.memByteBuffer(pointer);
VertexAttribState.set(index, size, type, normalized, stride, bb, boundVBO);
RENDER_BACKEND.vertexAttribPointer(index, size, type, normalized, stride, bb);
VertexAttribState.set(index, size, type, normalized, stride, MemoryUtilities.memByteBuffer(pointer), 0);
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, ShortBuffer pointer) {
final ByteBuffer bb = MemoryUtilities.memByteBuffer(pointer);
VertexAttribState.set(index, size, type, normalized, stride, bb, boundVBO);
RENDER_BACKEND.vertexAttribPointer(index, size, type, normalized, stride, bb);
VertexAttribState.set(index, size, type, normalized, stride, MemoryUtilities.memByteBuffer(pointer), 0);
}
public static void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
VertexAttribState.set(index, size, type, normalized, stride, pointer, boundVBO);
RENDER_BACKEND.vertexAttribPointer(index, size, type, normalized, stride, pointer);
VertexAttribState.set(index, size, type, normalized, stride, pointer, 0);
}
public static void glVertexAttribIPointer(int index, int size, int type, int stride, long offset) {
@@ -2475,20 +2553,15 @@ public class GLStateManager {
}
public static void glVertexAttribIPointer(int index, int size, int type, int stride, ByteBuffer pointer) {
VertexAttribState.set(index, size, type, false, stride, pointer, boundVBO);
RENDER_BACKEND.vertexAttribIPointer(index, size, type, stride, pointer);
VertexAttribState.set(index, size, type, false, stride, pointer, 0);
}
public static void glVertexAttribIPointer(int index, int size, int type, int stride, IntBuffer pointer) {
final ByteBuffer bb = MemoryUtilities.memByteBuffer(pointer);
VertexAttribState.set(index, size, type, false, stride, bb, boundVBO);
RENDER_BACKEND.vertexAttribIPointer(index, size, type, stride, bb);
VertexAttribState.set(index, size, type, false, stride, MemoryUtilities.memByteBuffer(pointer), 0);
}
public static void glVertexAttribIPointer(int index, int size, int type, int stride, ShortBuffer pointer) {
final ByteBuffer bb = MemoryUtilities.memByteBuffer(pointer);
VertexAttribState.set(index, size, type, false, stride, bb, boundVBO);
RENDER_BACKEND.vertexAttribIPointer(index, size, type, stride, bb);
VertexAttribState.set(index, size, type, false, stride, MemoryUtilities.memByteBuffer(pointer), 0);
}
public static void glVertexAttribDivisor(int index, int divisor) { RENDER_BACKEND.vertexAttribDivisor(index, divisor); }
@@ -4297,9 +4370,20 @@ public class GLStateManager {
RENDER_BACKEND.finish();
}
public static int glGetError() { return RENDER_BACKEND.getError(); }
public static String glGetString(int pname) { return RENDER_BACKEND.getString(pname); }
public static String glGetStringi(int name, int index) { return RENDER_BACKEND.getStringi(name, index); }
public static int glGetError() {
if (!RENDER_BACKEND.hasContext()) return 0;
return RENDER_BACKEND.getError();
}
public static String glGetString(int pname) {
if (!RENDER_BACKEND.hasContext()) return "no valid GL/render context";
return RENDER_BACKEND.getString(pname);
}
public static String glGetStringi(int name, int index) {
if (!RENDER_BACKEND.hasContext()) return "no valid GL/render context";
return RENDER_BACKEND.getStringi(name, index);
}
public static void glShaderSource(int shader, CharSequence source) {
String src = source.toString();
@@ -9,6 +9,7 @@ import org.lwjgl.opengl.ARBDebugOutputCallback;
import org.lwjgl.opengl.ARBDirectStateAccess;
import org.lwjgl.opengl.ContextCapabilities;
import org.lwjgl.opengl.EXTDirectStateAccess;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
@@ -67,7 +68,7 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
@Override
public void init() {
caps = org.lwjgl.opengl.GLContext.getCapabilities();
caps = GLContext.getCapabilities();
activeDebugExtension = 0;
}
@@ -86,6 +87,15 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
return "OpenGL (LWJGL2)";
}
@Override
public boolean hasContext() {
try {
return GLContext.getCapabilities() != null;
} catch (Exception e) {
return false;
}
}
@Override
public int getMinGLSLVersion() {return 330;}
@@ -19,6 +19,9 @@ public abstract class RenderBackend {
public abstract boolean isAvailable();
public abstract String getName();
/** Returns true if the current thread has a valid render context. Safe to call from any thread. */
public abstract boolean hasContext();
/** Higher priority backends are preferred. */
public int getPriority() { return 0; }
@@ -273,23 +273,27 @@ public final class FragmentShaderGenerator {
sb.append(" // Alpha test\n");
final int func = FragmentKey.decodeAlphaFunc(key.alphaTestFunc());
sb.append(" float qa = round(color.a * 255.0);\n");
sb.append(" float qr = round(u_AlphaRef * 255.0);\n");
switch (func) {
case GL11.GL_NEVER ->
sb.append(" discard;\n");
case GL11.GL_LESS ->
sb.append(" if (qa >= qr) discard;\n");
case GL11.GL_EQUAL ->
sb.append(" if (qa >= u_AlphaRef * 255.0) discard;\n");
case GL11.GL_EQUAL -> {
sb.append(" float qr = round(u_AlphaRef * 255.0);\n");
sb.append(" if (qa != qr) discard;\n");
}
case GL11.GL_LEQUAL ->
sb.append(" if (qa > qr) discard;\n");
sb.append(" if (qa > u_AlphaRef * 255.0) discard;\n");
case GL11.GL_GREATER ->
sb.append(" if (qa <= qr) discard;\n");
case GL11.GL_NOTEQUAL ->
sb.append(" if (qa <= u_AlphaRef * 255.0) discard;\n");
case GL11.GL_NOTEQUAL -> {
sb.append(" float qr = round(u_AlphaRef * 255.0);\n");
sb.append(" if (qa == qr) discard;\n");
}
case GL11.GL_GEQUAL ->
sb.append(" if (qa < qr) discard;\n");
sb.append(" if (qa < u_AlphaRef * 255.0) discard;\n");
case GL11.GL_ALWAYS -> {}
}
}
@@ -75,6 +75,12 @@ public final class DisplayListVBOBuilder {
if (data.drawMode == GL11.GL_QUADS) {
final IVertexArrayObject indexedVAO = new IndexedVAO(vbo, IndexBuffer.convertQuadsToTrigs(start, start + vertexCount));
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(indexedVAO, GL11.GL_TRIANGLES, 0, vertexCount / 4 * 6);
} else if (data.drawMode == GL11.GL_QUAD_STRIP) {
// GL_QUAD_STRIP is removed in core profile; an even-length GL_TRIANGLE_STRIP produces the same quads
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, GL11.GL_TRIANGLE_STRIP, start, vertexCount & ~1);
} else if (data.drawMode == GL11.GL_POLYGON) {
// GL_POLYGON is removed in core profile; convert to GL_TRIANGLE_FAN which is equivalent for convex polygons
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, GL11.GL_TRIANGLE_FAN, start, vertexCount);
} else {
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, data.drawMode, start, vertexCount);
}
@@ -4,10 +4,12 @@ import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
@@ -524,7 +526,7 @@ public class GLSMRedirector {
boolean changed = false;
final boolean isOpenGlHelper = transformedName.equals("net.minecraft.client.renderer.OpenGlHelper");
for (MethodNode mn : cn.methods) {
for (MethodNode mn : (List<MethodNode>) cn.methods) {
if (isOpenGlHelper && (mn.name.equals("glBlendFunc") || mn.name.equals("func_148821_a"))) {
continue;
}
@@ -610,6 +612,22 @@ public class GLSMRedirector {
changed = true;
}
}
} else if (node instanceof InvokeDynamicInsnNode dynNode) {
// Redirect method handles in invokedynamic bootstrap arguments
for (int i = 0; i < dynNode.bsmArgs.length; i++) {
if (!(dynNode.bsmArgs[i] instanceof Handle handle)) continue;
final Map<String, String> redirects = handle.getOwner().startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(handle.getOwner());
if (redirects == null) continue;
final String glsmName = redirects.get(handle.getName());
if (glsmName == null) continue;
if (LOG_SPAM) {
final String shortOwner = handle.getOwner().substring(handle.getOwner().lastIndexOf("/") + 1);
LOGGER.info("Redirecting invokedynamic handle in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, handle.getName(), handle.getDesc(), glsmName, handle.getDesc());
}
dynNode.bsmArgs[i] = new Handle(handle.getTag(), GLStateManager, glsmName, handle.getDesc());
changed = true;
redirectInMethod = true;
}
}
}
if (ASSERT_MAIN_THREAD && redirectInMethod && !transformedName.startsWith(SplashProgress) && !(transformedName.startsWith(MinecraftClient) && ExcludedMinecraftMainThreadChecks.contains(mn.name))) {
@@ -16,16 +16,19 @@ public class VertexAttribState {
private static final Int2ObjectOpenHashMap<Attrib[]> vaoAttribs = new Int2ObjectOpenHashMap<>();
private static Attrib[] current;
private static final ArrayDeque<Attrib[]> pool = new ArrayDeque<>();
private static int clientSideEnabledCount;
public static void init(int defaultVAO) {
vaoAttribs.clear();
pool.clear();
current = allocAttribArray();
vaoAttribs.put(defaultVAO, current);
clientSideEnabledCount = 0;
}
public static void onBindVertexArray(int newVAO) {
current = vaoAttribs.computeIfAbsent(newVAO, k -> allocAttribArray());
recomputeClientSideCount();
}
public static void onDeleteVertexArray(int vaoId) {
@@ -39,6 +42,7 @@ public class VertexAttribState {
vaoAttribs.clear();
pool.clear();
current = null;
clientSideEnabledCount = 0;
}
private static Attrib[] allocAttribArray() {
@@ -55,6 +59,7 @@ public class VertexAttribState {
public static void set(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
final boolean was = a.isClientSide();
a.size = size;
a.type = type;
a.normalized = normalized;
@@ -62,11 +67,13 @@ public class VertexAttribState {
a.offset = offset;
a.vboId = vboId;
a.clientPointer = null;
if (was) clientSideEnabledCount--;
}
public static void set(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
final boolean was = a.isClientSide();
a.size = size;
a.type = type;
a.normalized = normalized;
@@ -74,17 +81,33 @@ public class VertexAttribState {
a.offset = 0;
a.vboId = vboId;
a.clientPointer = (vboId == 0) ? pointer : null;
final boolean now = a.isClientSide();
if (was != now) clientSideEnabledCount += now ? 1 : -1;
}
public static void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
current[index].enabled = enabled;
final Attrib a = current[index];
if (a.clientPointer != null && a.enabled != enabled) {
clientSideEnabledCount += enabled ? 1 : -1;
}
a.enabled = enabled;
}
public static Attrib get(int index) {
return current[index];
}
private static void recomputeClientSideCount() {
int count = 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
if (current[i].isClientSide()) count++;
}
clientSideEnabledCount = count;
}
public static boolean hasVBOBoundAttrib() {
for (int i = 0; i < MAX_ATTRIBS; i++) {
if (current[i].enabled && current[i].vboId != 0) return true;
@@ -92,6 +115,14 @@ public class VertexAttribState {
return false;
}
/**
* Returns true if any currently enabled vertex attribute was registered without a VBO
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
* offsets into a non-existent buffer, causing a native crash at draw time.
*/
public static boolean hasAnyClientSideEnabledAttrib() {
return clientSideEnabledCount > 0;
}
public static class Attrib {
public boolean enabled;
public int size;
@@ -102,6 +133,10 @@ public class VertexAttribState {
public int vboId;
public ByteBuffer clientPointer;
public boolean isClientSide() {
return enabled && clientPointer != null;
}
public void reset() {
enabled = false;
size = 0;
@@ -156,6 +156,108 @@ class FeedbackManagerTest {
GLStateManager.glDeleteVertexArrays(vao);
}
@Test
void testPolygonFeedback_GL2D() {
final FloatBuffer buf = BufferUtils.createFloatBuffer(128);
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
GLStateManager.getModelViewMatrix().identity();
GLStateManager.getProjectionMatrix().identity();
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
final int vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
final float[] verts = {
0f, 0f, 0f,
1f, 0f, 0f,
1f, 1f, 0f,
0f, 1f, 0f
};
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(verts.length);
vertBuf.put(verts).flip();
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
FeedbackManager.processDrawArrays(GL11.GL_POLYGON, 0, 4);
final int count = FeedbackManager.glRenderMode(GL11.GL_RENDER);
assertEquals(10, count, "Polygon in GL_2D should produce 10 floats");
assertEquals(GL11.GL_POLYGON_TOKEN, buf.get(0), 0.001f);
assertEquals(4.0f, buf.get(1), 0.001f);
assertEquals(400.0f, buf.get(2), 0.01f);
assertEquals(300.0f, buf.get(3), 0.01f);
assertEquals(800.0f, buf.get(4), 0.01f);
assertEquals(300.0f, buf.get(5), 0.01f);
assertEquals(800.0f, buf.get(6), 0.01f);
assertEquals(600.0f, buf.get(7), 0.01f);
assertEquals(400.0f, buf.get(8), 0.01f);
assertEquals(600.0f, buf.get(9), 0.01f);
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteVertexArrays(vao);
}
@Test
void testQuadStripFeedback_GL2D() {
final FloatBuffer buf = BufferUtils.createFloatBuffer(128);
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
GLStateManager.getModelViewMatrix().identity();
GLStateManager.getProjectionMatrix().identity();
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
final int vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
final float[] verts = {
0f, 0f, 0f,
0f, 1f, 0f,
1f, 0f, 0f,
1f, 1f, 0f
};
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(verts.length);
vertBuf.put(verts).flip();
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
FeedbackManager.processDrawArrays(GL11.GL_QUAD_STRIP, 0, 4);
final int count = FeedbackManager.glRenderMode(GL11.GL_RENDER);
assertEquals(10, count, "Quad strip in GL_2D should produce 10 floats");
assertEquals(GL11.GL_POLYGON_TOKEN, buf.get(0), 0.001f);
assertEquals(4.0f, buf.get(1), 0.001f);
assertEquals(400.0f, buf.get(2), 0.01f);
assertEquals(300.0f, buf.get(3), 0.01f);
assertEquals(400.0f, buf.get(4), 0.01f);
assertEquals(600.0f, buf.get(5), 0.01f);
assertEquals(800.0f, buf.get(6), 0.01f);
assertEquals(600.0f, buf.get(7), 0.01f);
assertEquals(800.0f, buf.get(8), 0.01f);
assertEquals(300.0f, buf.get(9), 0.01f);
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteVertexArrays(vao);
}
@Test
void testIdentityTransform_viewportMapping() {
final FloatBuffer buf = BufferUtils.createFloatBuffer(64);
@@ -0,0 +1,142 @@
package com.gtnewhorizons.angelica.glsm.redirect;
import org.junit.jupiter.api.Test;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.MethodNode;
import java.util.ArrayList;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class GLSMRedirectorTest {
private static final String GLSM = GLSMRedirector.getTargetClassName();
private static final Handle LAMBDA_METAFACTORY = new Handle(
Opcodes.H_INVOKESTATIC,
"java/lang/invoke/LambdaMetafactory",
"metafactory",
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;"
+ "Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)"
+ "Ljava/lang/invoke/CallSite;",
false);
private static ClassNode makeClassWithInvokeDynamic(Handle implHandle) {
final ClassNode cn = new ClassNode();
cn.version = Opcodes.V1_8;
cn.access = Opcodes.ACC_PUBLIC;
cn.name = "com/example/TestClass";
cn.superName = "java/lang/Object";
cn.methods = new ArrayList<>();
final MethodNode mn = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "test", "()V", null, null);
mn.instructions.add(new InvokeDynamicInsnNode(
"run",
"()Ljava/lang/Runnable;",
LAMBDA_METAFACTORY,
Type.getType("()V"),
implHandle,
Type.getType("()V")));
mn.instructions.add(new InsnNode(Opcodes.RETURN));
cn.methods.add(mn);
return cn;
}
private static Handle getResultHandle(ClassNode cn) {
final InvokeDynamicInsnNode dyn = (InvokeDynamicInsnNode) cn.methods.get(0).instructions.get(0);
return (Handle) dyn.bsmArgs[1];
}
@Test
void invokeDynamicGL11HandleIsRedirected() {
final Handle impl = new Handle(Opcodes.H_INVOKESTATIC, "org/lwjgl/opengl/GL11", "glPopMatrix", "()V", false);
final ClassNode cn = makeClassWithInvokeDynamic(impl);
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertTrue(changed);
final Handle result = getResultHandle(cn);
assertEquals(GLSM, result.getOwner());
assertEquals("glPopMatrix", result.getName());
assertEquals("()V", result.getDesc());
assertEquals(Opcodes.H_INVOKESTATIC, result.getTag());
}
@Test
void invokeDynamicNamedClassHandleIsRedirected() {
final Handle impl = new Handle(Opcodes.H_INVOKESTATIC, "org/lwjgl/opengl/EXTBlendFuncSeparate", "glBlendFuncSeparateEXT", "(IIII)V", false);
final ClassNode cn = makeClassWithInvokeDynamic(impl);
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertTrue(changed);
final Handle result = getResultHandle(cn);
assertEquals(GLSM, result.getOwner());
assertEquals("tryBlendFuncSeparate", result.getName());
assertEquals("(IIII)V", result.getDesc());
}
@Test
void invokeDynamicNonGLHandleIsNotTouched() {
final Handle impl = new Handle(Opcodes.H_INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
final ClassNode cn = makeClassWithInvokeDynamic(impl);
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertFalse(changed);
final Handle result = getResultHandle(cn);
assertEquals("java/lang/System", result.getOwner());
assertEquals("currentTimeMillis", result.getName());
}
@Test
void invokeDynamicUnknownGLMethodIsNotTouched() {
final Handle impl = new Handle(Opcodes.H_INVOKESTATIC, "org/lwjgl/opengl/GL11", "glNonexistent", "()V", false);
final ClassNode cn = makeClassWithInvokeDynamic(impl);
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertFalse(changed);
final Handle result = getResultHandle(cn);
assertEquals("org/lwjgl/opengl/GL11", result.getOwner());
}
@Test
void invokeDynamicGLCVariantIsRedirected() {
final Handle impl = new Handle(Opcodes.H_INVOKESTATIC, "org/lwjgl/opengl/GL11C", "glDrawArrays", "(III)V", false);
final ClassNode cn = makeClassWithInvokeDynamic(impl);
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertTrue(changed);
final Handle result = getResultHandle(cn);
assertEquals(GLSM, result.getOwner());
assertEquals("glDrawArrays", result.getName());
assertEquals("(III)V", result.getDesc());
}
@Test
void transformedClassProducesValidBytecode() {
final Handle impl = new Handle(Opcodes.H_INVOKESTATIC, "org/lwjgl/opengl/GL11", "glPopMatrix", "()V", false);
final ClassNode cn = makeClassWithInvokeDynamic(impl);
new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
assertDoesNotThrow(() -> {
cn.accept(cw);
final byte[] bytes = cw.toByteArray();
assertTrue(bytes.length > 0);
});
}
}
@@ -68,6 +68,16 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
return "OpenGL (LWJGL3)";
}
@Override
public boolean hasContext() {
try {
GL.getCapabilities();
return true;
} catch (IllegalStateException e) {
return false;
}
}
@Override
public int getPriority() { return 50; }
@@ -1,10 +1,8 @@
package com.gtnewhorizons.angelica.client.rendering;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.coderbot.iris.gl.state.StateUpdateNotifiers;
import net.minecraft.client.renderer.texture.AbstractTexture;
import org.jetbrains.annotations.Nullable;
@@ -15,11 +13,15 @@ public class TextureTracker {
*/
public static final TextureTracker INSTANCE = new TextureTracker();
private static Runnable bindTextureListener;
static {
StateUpdateNotifiers.bindTextureNotifier = listener -> bindTextureListener = listener;
}
private final Int2ObjectMap<AbstractTexture> textures = new Int2ObjectOpenHashMap<>();
private boolean lockBindCallback;
private TextureTracker() {
}
@@ -31,23 +33,13 @@ public class TextureTracker {
public AbstractTexture getTexture(int id) {
return textures.get(id);
}
public void onBindTexture(int id) {
if (lockBindCallback) {
return;
}
if (GLStateManager.getActiveTextureUnit() == 0) {
lockBindCallback = true;
if (GLSMHooks.TEXTURE_BIND.hasListeners()) {
GLSMHooks.textureBindEvent.textureId = id;
GLSMHooks.TEXTURE_BIND.post(GLSMHooks.textureBindEvent);
}
// Reset texture state
RenderSystem.bindTextureToUnit(0, id);
lockBindCallback = false;
public void onBindTexture() {
if (bindTextureListener != null) {
bindTextureListener.run();
}
}
public void onDeleteTexture(int id) {
textures.remove(id);
}
@@ -25,6 +25,7 @@ public class ModStatus {
public static boolean isThaumicHorizonsLoaded;
public static boolean isBaublesLoaded;
public static boolean isFluidLoggedLoaded;
public static boolean isBOPLoaded;
public static void preInit() {
if (Loader.isModLoaded("backhand")) {
@@ -45,6 +46,7 @@ public class ModStatus {
isFluidLoggedLoaded = Loader.isModLoaded("fluidlogged");
isHoloInventoryLoaded = Loader.isModLoaded("holoinventory");
isBOPLoaded = Loader.isModLoaded("BiomesOPlenty");
// remove compat with original release of BG2
if (isBattlegearLoaded){
@@ -262,6 +262,11 @@ public class AngelicaConfig {
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public boolean narrowAlfheim;
@Config.Comment("Narrow Ears transformer exclusions to allow GL redirection")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public boolean narrowEars;
}
@Config.Comment("Renders chunks before neighbors are ready. Improves loading at render distance edges, useful for low render distance servers.")
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.iris;
import com.gtnewhorizons.angelica.client.rendering.TextureTracker;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredAlphaHandler;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredDepthColorHandler;
@@ -10,6 +11,7 @@ import net.coderbot.iris.gl.blending.AlphaTestStorage;
import net.coderbot.iris.gl.blending.BlendModeStorage;
import net.coderbot.iris.gl.blending.DepthColorStorage;
import net.coderbot.iris.gl.program.ProgramUniforms;
import net.coderbot.iris.gl.state.StateUpdateNotifiers;
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.coderbot.iris.samplers.IrisSamplers;
@@ -17,7 +19,21 @@ import net.coderbot.iris.texture.pbr.PBRTextureManager;
import net.coderbot.iris.vertices.ImmediateState;
public class IrisGLSMBridge {
private static Runnable blendFuncListener = null;
private static Runnable fogModeListener = null;
private static Runnable fogStartListener = null;
private static Runnable fogEndListener = null;
private static Runnable fogDensityListener = null;
static {
StateUpdateNotifiers.blendFuncNotifier = listener -> blendFuncListener = listener;
StateUpdateNotifiers.fogModeNotifier = listener -> fogModeListener = listener;
StateUpdateNotifiers.fogStartNotifier = listener -> fogStartListener = listener;
StateUpdateNotifiers.fogEndNotifier = listener -> fogEndListener = listener;
StateUpdateNotifiers.fogDensityNotifier = listener -> fogDensityListener = listener;
}
public static void register() {
IrisSamplers.initRenderer();
GLSMHooks.blendHandler = new DeferredBlendHandler() {
@@ -75,9 +91,25 @@ public class IrisGLSMBridge {
DepthColorStorage.deferColorMask(r, g, b, a);
}
};
GLSMHooks.BLEND_FUNC_CHANGE.addListener(event -> {
if (Iris.enabled) {
if (blendFuncListener != null) blendFuncListener.run();
}
});
GLSMHooks.FOG_STATE_CHANGE.addListener(event -> {
if (Iris.enabled) {
if (fogModeListener != null) fogModeListener.run();
if (fogStartListener != null) fogStartListener.run();
if (fogEndListener != null) fogEndListener.run();
if (fogDensityListener != null) fogDensityListener.run();
}
});
GLSMHooks.TEXTURE_BIND.addListener(event -> {
if (Iris.enabled) {
TextureTracker.INSTANCE.onBindTexture();
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
if (pipeline != null) {
pipeline.onBindTexture(event.textureId);
@@ -152,6 +152,7 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
narrowTransformerConfig("Xaeros", AngelicaConfig.transformerCompat.narrowXaeros);
narrowTransformerConfig("AdvancedLightsabers", AngelicaConfig.transformerCompat.narrowAdvancedLightsabers);
narrowTransformerConfig("Alfheim", AngelicaConfig.transformerCompat.narrowAlfheim);
narrowTransformerConfig("Ears", AngelicaConfig.transformerCompat.narrowEars);
tweaks.add("com.gtnewhorizons.angelica.loading.fml.tweakers.IncompatibleModsDisablerTweaker");
if (AngelicaConfig.enableHudCaching) {
@@ -71,6 +71,12 @@ public class AngelicaLateTweaker implements ITweaker {
FMLRelaunchLog.info("[Angelica] Narrowed com.fiskmods.lightsabers.asm transformer exclusion to allow GL redirection");
}
if (narrowEnabled("Ears") && exceptions.remove("com.unascribed.ears")) {
exceptions.add("com.unascribed.ears.asm");
exceptions.add("com.unascribed.ears.common.agent");
FMLRelaunchLog.info("[Angelica] Narrowed com.unascribed.ears transformer exclusion to allow GL redirection");
}
if (narrowEnabled("Alfheim") && exceptions.remove("alfheim.common.core.asm.hook")) {
exceptions.add("alfheim.common.core.asm.hook.AlfheimFieldHookHandler");
exceptions.add("alfheim.common.core.asm.hook.AlfheimHPHooks");
@@ -37,9 +37,13 @@ public enum Mixins implements IMixins {
, "angelica.MixinMinecraft_FrameHook"
, "angelica.MixinMinecraftServer"
, "angelica.bugfixes.MixinItemRenderer_EdgeDepth"
, "angelica.bugfixes.MixinModelCreeper_AuraBodyInflate"
, "angelica.bugfixes.MixinModelWither_ArmorCentering"
, "angelica.bugfixes.MixinRenderBlocks_CrossedSquaresNormal"
, "angelica.bugfixes.MixinRenderCreeper_AuraDepth"
, "angelica.bugfixes.MixinRenderGlobal_DeferredEntityOverlay"
, "angelica.bugfixes.MixinRenderGlobal_DestroyBlock"
, "angelica.bugfixes.MixinRendererLivingEntity_DeferredEntityOverlay"
, "angelica.bugfixes.MixinRenderWither_ArmorCentering"
, "angelica.bugfixes.MixinRendererLivingEntity_EyeDepth"
, "angelica.debug.MixinMinecraft_FPSCap"
@@ -240,6 +244,7 @@ public enum Mixins implements IMixins {
.setApplyIf(() -> AngelicaConfig.enableIris)
.addClientMixins(
"shaders.MixinDroppedItemGlintEdges"
, "shaders.MixinHeldItemGlintEdges"
, "shaders.MixinEntityPickupFX"
, "shaders.MixinEntityRenderer"
, "shaders.MixinGuiIngameForge"
@@ -252,6 +257,7 @@ public enum Mixins implements IMixins {
, "shaders.MixinRenderEntityFlame"
, "shaders.MixinRendererLivingEntity"
, "shaders.MixinRenderGlobal"
, "shaders.AccessorEntityHorse"
, "shaders.MixinRenderHorse"
, "shaders.MixinRenderItem"
, "shaders.MixinRenderNameTag"
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.proxy;
import biomesoplenty.api.content.BOPCBlocks;
import com.google.common.base.Objects;
import com.gtnewhorizon.gtnhlib.client.model.loading.ModelRegistry;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.VAOManager;
@@ -85,6 +86,7 @@ public final class ClientProxy extends CommonProxy {
private final Minecraft mc = Minecraft.getMinecraft();
private final FrametimeGraph frametimeGraph = new FrametimeGraph();
private final TPSGraph tpsGraph = new TPSGraph();
public static Block bopGrass;
public static SodiumGameOptions options() {
if (CONFIG == null) {
@@ -182,6 +184,9 @@ public final class ClientProxy extends CommonProxy {
LOGGER.error("Could not replace LOTR handle render code with thread safe version");
}
}
if(ModStatus.isBOPLoaded) {
bopGrass = BOPCBlocks.newBopGrass;
}
}
float lastIntegratedTickTime;
@@ -0,0 +1,215 @@
package com.gtnewhorizons.angelica.rendering;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import lombok.Getter;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.coderbot.iris.uniforms.EntityIdHelper;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.entity.RendererLivingEntity;
import net.minecraft.entity.EntityLivingBase;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* Defers entity overlay rendering (auras, armor effects, etc.) to after all entities have been drawn.
* This lets us fix z-fighting between overlay's own coplanar faces by disabling the depth mask,
* and ensures the overlay composites correctly on top of all opaque geometry.
*
* Used by both the charged creeper aura and the Wither armor overlay.
*
* The deferred render replays shouldRenderPass on the original renderer instance so that any
* mod mixins targeting that method still fire during the deferred pass.
*/
public class DeferredEntityOverlay {
@FunctionalInterface
public interface ShouldRenderPassFn {
int invoke(EntityLivingBase entity, int pass, float partialTick);
}
private static final int INITIAL_CAPACITY = 4;
private static final List<DeferredEntry> deferred = new ArrayList<>(INITIAL_CAPACITY);
private static final List<DeferredEntry> pool = new ArrayList<>(INITIAL_CAPACITY);
private static final FloatBuffer MATRIX_BUF = BufferUtils.createFloatBuffer(16);
// Set by the shouldRenderPass inject, consumed by the doRender WrapOperation.
@Getter
private static boolean overlayPassActive = false;
// Set during deferred flush to prevent re-deferring when shouldRenderPass is replayed.
@Getter
private static boolean replaying = false;
// Pending context from the shouldRenderPass inject, consumed by deferRender.
private static ShouldRenderPassFn pendingShouldRenderPass;
private static RendererLivingEntity pendingRenderer;
private static EntityLivingBase pendingEntity;
private static float pendingPartialTick;
/**
* Called from shouldRenderPass HEAD injects. Marks that the upcoming renderPassModel.render()
* should be deferred. If a previous mark was not consumed (shouldRenderPass returned <= 0,
* so render() was never called).
*/
public static void markOverlayPass(ShouldRenderPassFn shouldRenderPass, RendererLivingEntity renderer,
EntityLivingBase entity, float partialTick) {
overlayPassActive = true;
pendingShouldRenderPass = shouldRenderPass;
pendingRenderer = renderer;
pendingEntity = entity;
pendingPartialTick = partialTick;
}
public static void clearStaleOverlayFlag() {
overlayPassActive = false;
}
/**
* Called from the WrapOperation on renderPassModel.render() in doRender.
* Captures the modelview matrix and animation params, skipping the actual render.
*/
public static void deferRender(float limbSwing, float limbSwingAmount, float ageInTicks,
float headYaw, float headPitch, float scale) {
overlayPassActive = false;
DeferredEntry entry = acquireEntry();
entry.set(pendingShouldRenderPass, pendingRenderer, pendingEntity, pendingPartialTick,
limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, scale);
MATRIX_BUF.clear();
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, MATRIX_BUF);
MATRIX_BUF.get(entry.matrix);
deferred.add(entry);
pendingShouldRenderPass = null;
pendingRenderer = null;
pendingEntity = null;
}
private static DeferredEntry acquireEntry() {
int size = pool.size();
if (size > 0) {
return pool.remove(size - 1);
}
return new DeferredEntry();
}
private static void recycle() {
for (DeferredEntry entry : deferred) {
entry.shouldRenderPass = null;
entry.renderer = null;
entry.entity = null;
pool.add(entry);
}
deferred.clear();
}
public static void clear() {
recycle();
overlayPassActive = false;
replaying = false;
}
public static void renderAll() {
if (deferred.isEmpty()) return;
GbufferPrograms.beginEntities();
try {
for (DeferredEntry entry : deferred) {
CapturedRenderingState.INSTANCE.setCurrentEntity(EntityIdHelper.getEntityId(entry.entity));
renderOverlay(entry);
}
} finally {
CapturedRenderingState.INSTANCE.setCurrentEntity(-1);
GbufferPrograms.endEntities();
}
recycle();
}
private static void renderOverlay(DeferredEntry entry) {
EntityLivingBase entity = entry.entity;
// Restore the entity's lightmap coords
int brightness = entity.getBrightnessForRender(entry.partialTick);
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit,
brightness % 65536, (float) brightness / 65536);
// Restore the saved modelview matrix
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
boolean depthMaskDisabled = false;
try {
MATRIX_BUF.clear();
MATRIX_BUF.put(entry.matrix);
MATRIX_BUF.flip();
GLStateManager.glLoadMatrix(MATRIX_BUF);
// Replay shouldRenderPass pass 1
replaying = true;
int result = entry.shouldRenderPass.invoke(entity, 1, entry.partialTick);
if (result > 0) {
// Disable depth writes so coplanar faces don't z-fight each other
GLStateManager.glDepthMask(false);
depthMaskDisabled = true;
// Disable backface culling so both sides of the overlay shell are visible
GLStateManager.glDisable(GL11.GL_CULL_FACE);
// Render using the model set by shouldRenderPass
entry.renderer.renderPassModel.setLivingAnimations(entity,
entry.limbSwing, entry.limbSwingAmount, entry.partialTick);
entry.renderer.renderPassModel.render(entity,
entry.limbSwing, entry.limbSwingAmount, entry.ageInTicks,
entry.headYaw, entry.headPitch, entry.scale);
}
// Replay shouldRenderPass pass 2
entry.shouldRenderPass.invoke(entity, 2, entry.partialTick);
} finally {
replaying = false;
if (depthMaskDisabled) {
GLStateManager.glDepthMask(true);
GLStateManager.glEnable(GL11.GL_CULL_FACE);
}
GLStateManager.glPopMatrix();
}
}
private static class DeferredEntry {
final float[] matrix = new float[16];
ShouldRenderPassFn shouldRenderPass;
RendererLivingEntity renderer;
EntityLivingBase entity;
float partialTick;
float limbSwing;
float limbSwingAmount;
float ageInTicks;
float headYaw;
float headPitch;
float scale;
void set(ShouldRenderPassFn shouldRenderPass, RendererLivingEntity renderer,
EntityLivingBase entity, float partialTick,
float limbSwing, float limbSwingAmount, float ageInTicks,
float headYaw, float headPitch, float scale) {
this.shouldRenderPass = shouldRenderPass;
this.renderer = renderer;
this.entity = entity;
this.partialTick = partialTick;
this.limbSwing = limbSwing;
this.limbSwingAmount = limbSwingAmount;
this.ageInTicks = ageInTicks;
this.headYaw = headYaw;
this.headPitch = headPitch;
this.scale = scale;
}
}
}
@@ -0,0 +1,31 @@
package com.gtnewhorizons.angelica.rendering;
import net.minecraft.client.model.ModelBox;
import net.minecraft.client.model.ModelHorse;
import net.minecraft.client.model.ModelRenderer;
import java.util.ArrayList;
import java.util.List;
/**
* Horse model with all boxes inflated outward, used as a separate armor layer.
* Extends ModelHorse to inherit all animation and render logic. The constructor
* builds the default geometry, then we rebuild every box with inflate applied.
* This mirrors how modern MC uses a separate ModelLayers.HORSE_ARMOR.
*/
public class ModelHorseArmor extends ModelHorse {
public ModelHorseArmor(float inflate) {
super();
for (ModelRenderer renderer : this.boxList) {
List<ModelBox> originalBoxes = new ArrayList<>(renderer.cubeList);
renderer.cubeList.clear();
for (ModelBox box : originalBoxes) {
int width = Math.round(box.posX2 - box.posX1);
int height = Math.round(box.posY2 - box.posY1);
int depth = Math.round(box.posZ2 - box.posZ1);
renderer.addBox(box.posX1, box.posY1, box.posZ1, width, height, depth, inflate);
}
}
}
}
@@ -20,6 +20,7 @@ import net.minecraft.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory;
import net.minecraft.init.Blocks;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.ReportedException;
import net.minecraft.world.IBlockAccess;
import org.embeddedt.embeddium.impl.render.chunk.RenderSection;
@@ -139,7 +140,20 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
if (block.hasTileEntity(meta)) {
final TileEntity tileEntity = region.getTileEntity(x, y, z);
if (tileEntity != null && TileEntityRendererDispatcher.instance.hasSpecialRenderer(tileEntity)) {
(TileEntityRenderBoundsRegistry.isAlwaysInfiniteExtent(tileEntity) ? renderData.globalBlockEntities : renderData.culledBlockEntities).add(tileEntity);
final boolean isGlobal;
if (TileEntityRenderBoundsRegistry.isAlwaysInfiniteExtent(tileEntity)) {
isGlobal = true;
} else {
final AxisAlignedBB aabb = tileEntity.getRenderBoundingBox();
if (aabb != null) {
final int secMinX = x & ~15, secMinY = y & ~15, secMinZ = z & ~15;
isGlobal = aabb.minX < secMinX || aabb.minY < secMinY || aabb.minZ < secMinZ
|| aabb.maxX > secMinX + 16 || aabb.maxY > secMinY + 16 || aabb.maxZ > secMinZ + 16;
} else {
isGlobal = false;
}
}
(isGlobal ? renderData.globalBlockEntities : renderData.culledBlockEntities).add(tileEntity);
}
}
@@ -79,13 +79,13 @@ public class IrisExtendedChunkVertexEncoder implements ContextAwareChunkVertexEn
baseEncoder.write(ptr, material, vertex, sectionIndex);
// Per-vertex: renderType, midBlock, lightValue
memPutShort(ptr + MC_ENTITY_OFFSET + 2, ctx.renderType);
// Per-vertex: mc_Entity (packed blockId + renderType), midBlock, lightValue
memPutInt(ptr + MC_ENTITY_OFFSET, ((ctx.blockId + 1) << 1) | (ctx.renderType & 1));
final int midBlock = ExtendedDataHelper.computeMidBlock(vertex.x, vertex.y, vertex.z, ctx.localPosX, ctx.localPosY, ctx.localPosZ);
memPutInt(ptr + MID_BLOCK_OFFSET, midBlock);
memPutByte(ptr + MID_BLOCK_OFFSET + 3, ctx.lightValue);
// Per-quad: midTexCoord, blockId, normal, tangent
// Per-quad: midTexCoord, normal, tangent
if (vertexCount == 4) {
vertexCount = 0;
@@ -98,11 +98,6 @@ public class IrisExtendedChunkVertexEncoder implements ContextAwareChunkVertexEn
memPutInt(ptr + MID_TEX_OFFSET - STRIDE * 2, midUV);
memPutInt(ptr + MID_TEX_OFFSET - STRIDE * 3, midUV);
memPutShort(ptr + MC_ENTITY_OFFSET, ctx.blockId);
memPutShort(ptr + MC_ENTITY_OFFSET - STRIDE, ctx.blockId);
memPutShort(ptr + MC_ENTITY_OFFSET - STRIDE * 2, ctx.blockId);
memPutShort(ptr + MC_ENTITY_OFFSET - STRIDE * 3, ctx.blockId);
quad.setup(ptr, STRIDE);
NormalHelper.computeFaceNormal(normal, quad);
final int packedNormal = NormI8.pack(normal);
@@ -13,7 +13,7 @@ import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkVertexType;
* mc_midTexCoord ushort[2] - quad center UV (average of 4 vertices)
* at_tangent byte[4] - tangent vector, normalized
* iris_Normal byte[3]+1 - face normal, normalized
* mc_Entity short[2] - (blockId, renderType)
* mc_Entity uint - packed as ((blockId + 1) << 1) | (renderType & 1)
* at_midBlock byte[4] - (xyz offset from block center, lightValue)
*/
public class IrisExtendedChunkVertexType implements ChunkVertexType {
@@ -25,7 +25,7 @@ public class IrisExtendedChunkVertexType implements ChunkVertexType {
.addElement("mc_midTexCoord", GlVertexFormat.NEXT_ALIGNED_POINTER, GlVertexAttributeFormat.UNSIGNED_SHORT, 2, false, false)
.addElement("at_tangent", GlVertexFormat.NEXT_ALIGNED_POINTER, GlVertexAttributeFormat.BYTE, 4, true, false)
.addElement("iris_Normal", GlVertexFormat.NEXT_ALIGNED_POINTER, GlVertexAttributeFormat.BYTE, 3, true, false)
.addElement("mc_Entity", GlVertexFormat.NEXT_ALIGNED_POINTER, GlVertexAttributeFormat.SHORT, 2, false, false)
.addElement("mc_Entity", GlVertexFormat.NEXT_ALIGNED_POINTER, GlVertexAttributeFormat.UNSIGNED_INT, 1, false, true)
.addElement("at_midBlock", GlVertexFormat.NEXT_ALIGNED_POINTER, GlVertexAttributeFormat.BYTE, 4, false, false)
.build();
@@ -72,9 +72,11 @@ public class CeleritasTerrainPipeline {
Optional<ProgramSource> terrainSource,
Optional<ProgramSource> translucentSource,
Optional<ProgramSource> shadowSource,
Optional<ProgramSource> shadowTranslucentSource,
CompletableFuture<Map<PatchShaderType, String>> terrainFuture,
CompletableFuture<Map<PatchShaderType, String>> translucentFuture,
CompletableFuture<Map<PatchShaderType, String>> shadowFuture,
CompletableFuture<Map<PatchShaderType, String>> shadowTranslucentFuture,
RenderTargets renderTargets,
ImmutableSet<Integer> flippedAfterPrepare,
ImmutableSet<Integer> flippedAfterTranslucent,
@@ -92,6 +94,7 @@ public class CeleritasTerrainPipeline {
gbufferProgramSource.put(IrisTerrainPass.GBUFFER_CUTOUT, terrainSource);
gbufferProgramSource.put(IrisTerrainPass.GBUFFER_TRANSLUCENT, translucentSource.isPresent() ? translucentSource : terrainSource);
gbufferProgramSource.put(IrisTerrainPass.SHADOW, shadowSource);
gbufferProgramSource.put(IrisTerrainPass.SHADOW_TRANSLUCENT, shadowTranslucentSource.isPresent() ? shadowTranslucentSource : shadowSource);
// Initialize PassInfo, framebuffers, blend modes, and alpha in single pass
for (IrisTerrainPass pass : IrisTerrainPass.VALUES) {
@@ -101,11 +104,12 @@ public class CeleritasTerrainPipeline {
// Set up framebuffer, blend mode, and buffer blend overrides
final ProgramId programId = switch (pass) {
case GBUFFER_TRANSLUCENT -> ProgramId.Water;
case SHADOW_TRANSLUCENT -> ProgramId.ShadowWater;
case SHADOW, SHADOW_CUTOUT -> ProgramId.Shadow;
default -> ProgramId.Terrain;
};
if (pass == IrisTerrainPass.SHADOW || pass == IrisTerrainPass.SHADOW_CUTOUT) {
if (pass.isShadow()) {
passInfo.framebuffer = shadowFramebuffer;
passInfo.blendModeOverride = programId.getBlendModeOverride();
passInfo.bufferBlendOverrides = Collections.emptyList();
@@ -145,6 +149,7 @@ public class CeleritasTerrainPipeline {
processShaderFuture(terrainFuture, terrainSource, passInfoMap.get(IrisTerrainPass.GBUFFER_SOLID), passInfoMap.get(IrisTerrainPass.GBUFFER_CUTOUT));
processShaderFuture(translucentFuture, translucentSource, passInfoMap.get(IrisTerrainPass.GBUFFER_TRANSLUCENT));
processShaderFuture(shadowFuture, shadowSource, passInfoMap.get(IrisTerrainPass.SHADOW), passInfoMap.get(IrisTerrainPass.SHADOW_CUTOUT));
processShaderFuture(shadowTranslucentFuture, shadowTranslucentSource.isPresent() ? shadowTranslucentSource : shadowSource, passInfoMap.get(IrisTerrainPass.SHADOW_TRANSLUCENT));
}
private void processShaderFuture(@Nullable CompletableFuture<Map<PatchShaderType, String>> future, Optional<ProgramSource> source, PassInfo... targets) {
@@ -136,8 +136,10 @@ public class IrisCeleritasChunkProgramOverrides {
if (celeritasPipeline != null && !celeritasPipeline.hasShadowPass()) {
throw new IllegalStateException("Shadow program requested, but shader pack has no shadow pass");
}
return programs.get(pass.supportsFragmentDiscard() ?
IrisTerrainPass.SHADOW_CUTOUT : IrisTerrainPass.SHADOW);
if (pass.isReverseOrder()) {
return programs.get(IrisTerrainPass.SHADOW_TRANSLUCENT);
}
return programs.get(pass.supportsFragmentDiscard() ? IrisTerrainPass.SHADOW_CUTOUT : IrisTerrainPass.SHADOW);
} else {
if (pass.supportsFragmentDiscard()) {
return programs.get(IrisTerrainPass.GBUFFER_CUTOUT);
@@ -187,7 +187,11 @@ public class IrisCeleritasChunkShaderInterface implements ChunkShaderInterface {
final boolean isShadow = ShadowRenderingState.areShadowsCurrentlyBeingRendered();
final IrisTerrainPass irisPass;
if (isShadow) {
irisPass = pass.supportsFragmentDiscard() ? IrisTerrainPass.SHADOW_CUTOUT : IrisTerrainPass.SHADOW;
if (pass.isReverseOrder()) {
irisPass = IrisTerrainPass.SHADOW_TRANSLUCENT;
} else {
irisPass = pass.supportsFragmentDiscard() ? IrisTerrainPass.SHADOW_CUTOUT : IrisTerrainPass.SHADOW;
}
} else if (pass.isReverseOrder()) {
irisPass = IrisTerrainPass.GBUFFER_TRANSLUCENT;
} else if (pass.supportsFragmentDiscard()) {
@@ -6,6 +6,7 @@ import org.embeddedt.embeddium.impl.render.chunk.terrain.TerrainRenderPass;
public enum IrisTerrainPass {
SHADOW("shadow"),
SHADOW_CUTOUT("shadow"),
SHADOW_TRANSLUCENT("shadow_water"),
GBUFFER_SOLID("gbuffers_terrain"),
GBUFFER_CUTOUT("gbuffers_terrain_cutout"),
GBUFFER_TRANSLUCENT("gbuffers_water");
@@ -23,19 +24,22 @@ public enum IrisTerrainPass {
}
public boolean isShadow() {
return this == SHADOW || this == SHADOW_CUTOUT;
return this == SHADOW || this == SHADOW_CUTOUT || this == SHADOW_TRANSLUCENT;
}
public TerrainRenderPass toTerrainPass(RenderPassConfiguration<?> config) {
return switch (this) {
case SHADOW, GBUFFER_SOLID -> config.defaultSolidMaterial().pass;
case SHADOW_CUTOUT, GBUFFER_CUTOUT -> config.defaultCutoutMippedMaterial().pass;
case GBUFFER_TRANSLUCENT -> config.defaultTranslucentMaterial().pass;
case SHADOW_TRANSLUCENT, GBUFFER_TRANSLUCENT -> config.defaultTranslucentMaterial().pass;
};
}
public static IrisTerrainPass fromTerrainPass(TerrainRenderPass pass, boolean isShadow) {
if (isShadow) {
if (pass.isReverseOrder()) {
return SHADOW_TRANSLUCENT;
}
return pass.supportsFragmentDiscard() ? SHADOW_CUTOUT : SHADOW;
} else if (pass.supportsFragmentDiscard()) {
return GBUFFER_CUTOUT;
@@ -18,6 +18,7 @@ public enum RenderCondition {
HAND_TRANSLUCENT,
RAIN_SNOW,
WORLD_BORDER,
SHADOW_TRANSLUCENT,
// NB: Must be last due to implementation details of DeferredWorldRenderingPipeline
SHADOW
}
@@ -1,9 +1,16 @@
package net.coderbot.iris.gl.state;
/**
* Holds update notifiers for PBR texture changes.
* Holds some standard update notifiers for various elements of GL state.
*/
public class StateUpdateNotifiers {
public static ValueUpdateNotifier fogModeNotifier;
public static ValueUpdateNotifier fogStartNotifier;
public static ValueUpdateNotifier fogEndNotifier;
public static ValueUpdateNotifier fogDensityNotifier;
public static ValueUpdateNotifier blendFuncNotifier;
public static ValueUpdateNotifier bindTextureNotifier;
public static ValueUpdateNotifier normalTextureChangeNotifier;
public static ValueUpdateNotifier specularTextureChangeNotifier;
public static ValueUpdateNotifier phaseChangeNotifier;
}
@@ -1,11 +1,10 @@
package net.coderbot.iris.gl.uniform;
import net.coderbot.iris.gl.state.ValueUpdateNotifier;
import org.joml.Vector2i;
import org.joml.Vector3i;
import org.joml.Vector2ic;
import org.joml.Vector3ic;
import org.joml.Vector4f;
import org.joml.Vector4i;
import org.joml.Vector4fc;
import org.joml.Vector4ic;
import java.util.function.DoubleSupplier;
import java.util.function.IntSupplier;
@@ -38,7 +37,7 @@ public interface DynamicLocationalUniformHolder extends LocationalUniformHolder,
return this;
}
default DynamicLocationalUniformHolder uniform2i(String name, Supplier<Vector2i> value, ValueUpdateNotifier notifier) {
default DynamicLocationalUniformHolder uniform2i(String name, Supplier<Vector2ic> value, ValueUpdateNotifier notifier) {
location(name, UniformType.VEC2I).ifPresent(id -> addDynamicUniform(new Vector2IntegerJomlUniform(id, value, notifier), notifier));
return this;
@@ -50,7 +49,7 @@ public interface DynamicLocationalUniformHolder extends LocationalUniformHolder,
return this;
}
default DynamicUniformHolder uniform4f(String name, Supplier<Vector4f> value, ValueUpdateNotifier notifier) {
default DynamicUniformHolder uniform4f(String name, Supplier<Vector4fc> value, ValueUpdateNotifier notifier) {
location(name, UniformType.VEC4).ifPresent(id -> addDynamicUniform(new Vector4Uniform(id, value, notifier), notifier));
return this;
@@ -61,7 +60,7 @@ public interface DynamicLocationalUniformHolder extends LocationalUniformHolder,
return this;
}
default DynamicUniformHolder uniform4i(String name, Supplier<Vector4i> value, ValueUpdateNotifier notifier) {
default DynamicUniformHolder uniform4i(String name, Supplier<Vector4ic> value, ValueUpdateNotifier notifier) {
location(name, UniformType.VEC4I).ifPresent(id -> addDynamicUniform(new Vector4IntegerJomlUniform(id, value, notifier), notifier));
return this;
@@ -1,11 +1,10 @@
package net.coderbot.iris.gl.uniform;
import net.coderbot.iris.gl.state.ValueUpdateNotifier;
import org.joml.Vector2i;
import org.joml.Vector3i;
import org.joml.Vector2ic;
import org.joml.Vector3ic;
import org.joml.Vector4f;
import org.joml.Vector4i;
import org.joml.Vector4fc;
import org.joml.Vector4ic;
import java.util.function.DoubleSupplier;
import java.util.function.IntSupplier;
@@ -16,9 +15,9 @@ public interface DynamicUniformHolder extends UniformHolder {
DynamicUniformHolder uniform1f(String name, IntSupplier value, ValueUpdateNotifier notifier);
DynamicUniformHolder uniform1f(String name, DoubleSupplier value, ValueUpdateNotifier notifier);
DynamicUniformHolder uniform1i(String name, IntSupplier value, ValueUpdateNotifier notifier);
DynamicUniformHolder uniform2i(String name, Supplier<Vector2i> value, ValueUpdateNotifier notifier);
DynamicUniformHolder uniform2i(String name, Supplier<Vector2ic> value, ValueUpdateNotifier notifier);
DynamicUniformHolder uniform3i(String name, Supplier<Vector3ic> value, ValueUpdateNotifier notifier);
DynamicUniformHolder uniform4f(String name, Supplier<Vector4f> value, ValueUpdateNotifier notifier);
DynamicUniformHolder uniform4f(String name, Supplier<Vector4fc> value, ValueUpdateNotifier notifier);
DynamicUniformHolder uniform4fArray(String name, Supplier<float[]> value, ValueUpdateNotifier notifier);
DynamicUniformHolder uniform4i(String name, Supplier<Vector4i> value, ValueUpdateNotifier notifier);
DynamicUniformHolder uniform4i(String name, Supplier<Vector4ic> value, ValueUpdateNotifier notifier);
}
@@ -4,13 +4,13 @@ import net.minecraft.util.Vec3;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.joml.Vector2f;
import org.joml.Vector2i;
import org.joml.Vector2ic;
import org.joml.Vector3d;
import org.joml.Vector3f;
import org.joml.Vector3fc;
import org.joml.Vector3i;
import org.joml.Vector3ic;
import org.joml.Vector4f;
import org.joml.Vector4fc;
import java.util.OptionalInt;
import java.util.function.BooleanSupplier;
@@ -66,7 +66,7 @@ public interface LocationalUniformHolder extends UniformHolder {
}
@Override
default LocationalUniformHolder uniform2i(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector2i> value) {
default LocationalUniformHolder uniform2i(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector2ic> value) {
location(name, UniformType.VEC2I).ifPresent(id -> addUniform(updateFrequency, new Vector2IntegerJomlUniform(id, value)));
return this;
@@ -94,7 +94,7 @@ public interface LocationalUniformHolder extends UniformHolder {
}
@Override
default LocationalUniformHolder uniformTruncated3f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector4f> value) {
default LocationalUniformHolder uniformTruncated3f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector4fc> value) {
location(name, UniformType.VEC3).ifPresent(id -> addUniform(updateFrequency, Vector3Uniform.truncated(id, value)));
return this;
@@ -108,7 +108,7 @@ public interface LocationalUniformHolder extends UniformHolder {
}
@Override
default LocationalUniformHolder uniform4f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector4f> value) {
default LocationalUniformHolder uniform4f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector4fc> value) {
location(name, UniformType.VEC4).ifPresent(id -> addUniform(updateFrequency, new Vector4Uniform(id, value)));
return this;
@@ -3,12 +3,12 @@ package net.coderbot.iris.gl.uniform;
import net.minecraft.util.Vec3;
import org.joml.Matrix4fc;
import org.joml.Vector2f;
import org.joml.Vector2i;
import org.joml.Vector2ic;
import org.joml.Vector3d;
import org.joml.Vector3fc;
import org.joml.Vector3i;
import org.joml.Vector3ic;
import org.joml.Vector4f;
import org.joml.Vector4fc;
import java.util.function.BooleanSupplier;
import java.util.function.DoubleSupplier;
@@ -28,7 +28,7 @@ public interface UniformHolder {
UniformHolder uniform2f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector2f> value);
UniformHolder uniform2i(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector2i> value);
UniformHolder uniform2i(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector2ic> value);
UniformHolder uniform3f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector3fc> value);
@@ -36,11 +36,11 @@ public interface UniformHolder {
UniformHolder uniformVanilla3f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vec3> value);
UniformHolder uniformTruncated3f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector4f> value);
UniformHolder uniformTruncated3f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector4fc> value);
UniformHolder uniform3d(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector3d> value);
UniformHolder uniform4f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector4f> value);
UniformHolder uniform4f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector4fc> value);
UniformHolder uniform4fArray(UniformUpdateFrequency updateFrequency, String name, Supplier<float[]> value);
@@ -3,21 +3,22 @@ package net.coderbot.iris.gl.uniform;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import net.coderbot.iris.gl.state.ValueUpdateNotifier;
import org.joml.Vector2i;
import org.joml.Vector2ic;
import java.util.function.Supplier;
public class Vector2IntegerJomlUniform extends Uniform {
private Vector2i cachedValue;
private final Supplier<Vector2i> value;
private final Vector2i cachedValue;
private final Supplier<Vector2ic> value;
Vector2IntegerJomlUniform(int location, Supplier<Vector2i> value) {
Vector2IntegerJomlUniform(int location, Supplier<Vector2ic> value) {
this(location, value, null);
}
Vector2IntegerJomlUniform(int location, Supplier<Vector2i> value, ValueUpdateNotifier notifier) {
Vector2IntegerJomlUniform(int location, Supplier<Vector2ic> value, ValueUpdateNotifier notifier) {
super(location, notifier);
this.cachedValue = null;
this.cachedValue = new Vector2i();
this.value = value;
}
@@ -31,11 +32,11 @@ public class Vector2IntegerJomlUniform extends Uniform {
}
private void updateValue() {
Vector2i newValue = value.get();
Vector2ic newValue = value.get();
if (cachedValue == null || !newValue.equals(cachedValue)) {
cachedValue = newValue;
RenderSystem.uniform2i(this.location, newValue.x, newValue.y);
if (!newValue.equals(cachedValue)) {
cachedValue.set(newValue.x(), newValue.y());
RenderSystem.uniform2i(this.location, cachedValue.x, cachedValue.y);
}
}
}
@@ -4,7 +4,7 @@ import com.gtnewhorizons.angelica.glsm.RenderSystem;
import org.joml.Vector3d;
import org.joml.Vector3f;
import org.joml.Vector3fc;
import org.joml.Vector4f;
import org.joml.Vector4fc;
import java.util.function.Supplier;
@@ -31,11 +31,11 @@ public class Vector3Uniform extends Uniform {
});
}
static Vector3Uniform truncated(int location, Supplier<Vector4f> value) {
static Vector3Uniform truncated(int location, Supplier<Vector4fc> value) {
Vector3f held = new Vector3f();
return new Vector3Uniform(location, () -> {
Vector4f updated = value.get();
Vector4fc updated = value.get();
held.set(updated.x(), updated.y(), updated.z());
@@ -3,21 +3,22 @@ package net.coderbot.iris.gl.uniform;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import net.coderbot.iris.gl.state.ValueUpdateNotifier;
import org.joml.Vector4i;
import org.joml.Vector4ic;
import java.util.function.Supplier;
public class Vector4IntegerJomlUniform extends Uniform {
private Vector4i cachedValue;
private final Supplier<Vector4i> value;
private final Vector4i cachedValue;
private final Supplier<Vector4ic> value;
Vector4IntegerJomlUniform(int location, Supplier<Vector4i> value) {
Vector4IntegerJomlUniform(int location, Supplier<Vector4ic> value) {
this(location, value, null);
}
Vector4IntegerJomlUniform(int location, Supplier<Vector4i> value, ValueUpdateNotifier notifier) {
Vector4IntegerJomlUniform(int location, Supplier<Vector4ic> value, ValueUpdateNotifier notifier) {
super(location, notifier);
this.cachedValue = null;
this.cachedValue = new Vector4i();
this.value = value;
}
@@ -31,11 +32,11 @@ public class Vector4IntegerJomlUniform extends Uniform {
}
private void updateValue() {
Vector4i newValue = value.get();
Vector4ic newValue = value.get();
if (cachedValue == null || !newValue.equals(cachedValue)) {
cachedValue = newValue;
RenderSystem.uniform4i(this.location, newValue.x, newValue.y, newValue.z, newValue.w);
if (!newValue.equals(cachedValue)) {
cachedValue.set(newValue.x(), newValue.y(), newValue.z(), newValue.w());
RenderSystem.uniform4i(this.location, cachedValue.x, cachedValue.y, cachedValue.z, cachedValue.w);
}
}
}
@@ -3,18 +3,19 @@ package net.coderbot.iris.gl.uniform;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import net.coderbot.iris.gl.state.ValueUpdateNotifier;
import org.joml.Vector4f;
import org.joml.Vector4fc;
import java.util.function.Supplier;
public class Vector4Uniform extends Uniform {
private final Vector4f cachedValue;
private final Supplier<Vector4f> value;
private final Supplier<Vector4fc> value;
Vector4Uniform(int location, Supplier<Vector4f> value) {
Vector4Uniform(int location, Supplier<Vector4fc> value) {
this(location, value, null);
}
Vector4Uniform(int location, Supplier<Vector4f> value, ValueUpdateNotifier notifier) {
Vector4Uniform(int location, Supplier<Vector4fc> value, ValueUpdateNotifier notifier) {
super(location, notifier);
this.cachedValue = new Vector4f();
@@ -31,7 +32,7 @@ public class Vector4Uniform extends Uniform {
}
private void updateValue() {
Vector4f newValue = value.get();
Vector4fc newValue = value.get();
if (!newValue.equals(cachedValue)) {
cachedValue.set(newValue.x(), newValue.y(), newValue.z(), newValue.w());
@@ -4,6 +4,7 @@ import net.coderbot.iris.Iris;
import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.coderbot.iris.gl.shader.ProgramCreator;
import net.coderbot.iris.gl.state.StateUpdateNotifiers;
import net.coderbot.iris.pipeline.WorldRenderingPhase;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
@@ -11,7 +12,12 @@ public class GbufferPrograms {
private static boolean entities;
private static boolean blockEntities;
private static boolean outline;
private static Runnable phaseChangeListener;
static {
StateUpdateNotifiers.phaseChangeNotifier = listener -> phaseChangeListener = listener;
}
private static void checkReentrancy() {
if (entities || blockEntities || outline) {
throw new IllegalStateException("GbufferPrograms in weird state, tried to call begin function when entities = "
@@ -106,7 +112,13 @@ public class GbufferPrograms {
public static void teardownSpecialRenderCondition() {
Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setSpecialCondition(null));
}
public static void runPhaseChangeNotifier() {
if (phaseChangeListener != null) {
phaseChangeListener.run();
}
}
public static void init() {
// Empty initializer to run static
}
@@ -41,6 +41,7 @@ import net.coderbot.iris.gl.state.FogMode;
import net.coderbot.iris.gl.texture.DepthBufferFormat;
import net.coderbot.iris.gl.texture.TextureType;
import net.coderbot.iris.helpers.Tri;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.pipeline.transform.PatchShaderType;
import net.coderbot.iris.pipeline.transform.TransformPatcher;
import net.coderbot.iris.postprocess.BufferFlipper;
@@ -219,11 +220,13 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
final Optional<ProgramSource> terrainSource = first(programs.getGbuffersTerrain(), programs.getGbuffersTexturedLit(), programs.getGbuffersTextured(), programs.getGbuffersBasic());
final Optional<ProgramSource> translucentSource = first(programs.getGbuffersWater(), terrainSource);
final Optional<ProgramSource> shadowSource = programs.getShadow();
final Optional<ProgramSource> shadowTranslucentSource = first(programs.getShadowWater(), shadowSource);
// Celeritas terrain transform futures
final CompletableFuture<Map<PatchShaderType, String>> celeritasTerrainFuture = terrainSource.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(null);
final CompletableFuture<Map<PatchShaderType, String>> celeritasTranslucentFuture = translucentSource.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(null);
final CompletableFuture<Map<PatchShaderType, String>> celeritasShadowFuture = shadowSource.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(null);
final CompletableFuture<Map<PatchShaderType, String>> celeritasShadowTranslucentFuture = shadowTranslucentSource.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(null);
this.cloudSetting = programs.getPackDirectives().getCloudSetting();
this.shouldRenderUnderwaterOverlay = programs.getPackDirectives().underwaterOverlay();
@@ -387,6 +390,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
null, null, ProgramId.Weather,
// world border uses textured_lit even though it has no lightmap :/
null, ProgramId.TexturedLit, ProgramId.TexturedLit,
ProgramId.ShadowWater, ProgramId.ShadowWater, ProgramId.ShadowWater,
ProgramId.Shadow, ProgramId.Shadow, ProgramId.Shadow
};
@@ -430,7 +434,7 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
return cachedPasses.computeIfAbsent(Pair.of(id, availability), p -> {
final ProgramSource source = resolver.resolveNullable(p.getLeft());
if (condition == RenderCondition.SHADOW) {
if (condition == RenderCondition.SHADOW || condition == RenderCondition.SHADOW_TRANSLUCENT) {
if (!shadowDirectives.isShadowEnabled().orElse(shadowRenderTargets != null)) {
// shadow is not used
return null;
@@ -447,7 +451,8 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
}
try {
return createPass(source, availability, condition == RenderCondition.SHADOW, finalId);
return createPass(source, availability,
condition == RenderCondition.SHADOW || condition == RenderCondition.SHADOW_TRANSLUCENT, finalId);
} catch (Exception e) {
throw new RuntimeException("Failed to create pass for " + source.getName() + " for rendering condition "
+ condition + " specialized to input availability " + availability, e);
@@ -474,7 +479,9 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
this.shadowRenderer = new ShadowRenderer(programs.getShadow().orElse(null),
programs.getPackDirectives(), shadowRenderTargets, shadowCompositeRenderer);
Program shadowProgram = table.match(RenderCondition.SHADOW, new InputAvailability(true, true)).getProgram();
shadowRenderer.setUsesImages(shadowProgram != null && shadowProgram.getActiveImages() > 0);
Program shadowWaterProgram = table.match(RenderCondition.SHADOW_TRANSLUCENT, new InputAvailability(true, true)).getProgram();
shadowRenderer.setUsesImages((shadowProgram != null && shadowProgram.getActiveImages() > 0)
|| (shadowWaterProgram != null && shadowWaterProgram.getActiveImages() > 0));
} else {
shadowRenderer = null;
}
@@ -591,7 +598,8 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
terrainSource,
translucentSource,
shadowSource,
celeritasTerrainFuture, celeritasTranslucentFuture, celeritasShadowFuture,
shadowTranslucentSource,
celeritasTerrainFuture, celeritasTranslucentFuture, celeritasShadowFuture, celeritasShadowTranslucentFuture,
renderTargets, flippedAfterPrepare, flippedAfterTranslucent,
celeritasShadowFb);
@@ -698,7 +706,10 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
private RenderCondition getCondition(WorldRenderingPhase phase) {
if (isRenderingShadow) {
return RenderCondition.SHADOW;
return switch (phase) {
case TERRAIN_TRANSLUCENT, TRIPWIRE -> RenderCondition.SHADOW_TRANSLUCENT;
default -> RenderCondition.SHADOW;
};
}
if (special != null) {
@@ -774,10 +785,10 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
if (!isRenderingWorld || isRenderingFullScreenPass || isPostChain || !isMainBound) {
return;
}
final RenderCondition condition = getCondition(getPhase());
final Pass matched = table.match(condition, inputs);
beginPass(matched);
}
@@ -1656,12 +1667,14 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
public void setOverridePhase(WorldRenderingPhase phase) {
this.overridePhase = phase;
matchPass();
GbufferPrograms.runPhaseChangeNotifier();
}
@Override
public void setPhase(WorldRenderingPhase phase) {
this.phase = phase;
matchPass();
GbufferPrograms.runPhaseChangeNotifier();
}
@Override
@@ -39,56 +39,54 @@ public class PatchedShaderPrinter {
}
public static void debugPatchedShaders(String name, String vertex, String geometry, String tessControl, String tessEval, String fragment, String compute) {
if (!Iris.getIrisConfig().areDebugOptionsEnabled()) {
if (!prettyPrintShaders && !Iris.getIrisConfig().areDebugOptionsEnabled()) {
return;
}
if (prettyPrintShaders) {
final Path debugOutDir = Minecraft.getMinecraft().mcDataDir.toPath().resolve("patched_shaders");
if (!outputLocationCleared) {
try {
if (Files.exists(debugOutDir)) {
try (Stream<Path> stream = Files.list(debugOutDir)) {
stream.forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
Files.createDirectories(debugOutDir);
} catch (IOException e) {
Iris.logger.warn("Failed to initialize debug patched shader source location", e);
}
outputLocationCleared = true;
}
final Path debugOutDir = Minecraft.getMinecraft().mcDataDir.toPath().resolve("patched_shaders");
if (!outputLocationCleared) {
try {
programCounter++;
String prefix = String.format("%03d_", programCounter);
if (vertex != null) {
Files.write(debugOutDir.resolve(prefix + name + ".vsh"), vertex.getBytes(StandardCharsets.UTF_8));
}
if (geometry != null) {
Files.write(debugOutDir.resolve(prefix + name + ".gsh"), geometry.getBytes(StandardCharsets.UTF_8));
}
if (tessControl != null) {
Files.write(debugOutDir.resolve(prefix + name + ".tcs"), tessControl.getBytes(StandardCharsets.UTF_8));
}
if (tessEval != null) {
Files.write(debugOutDir.resolve(prefix + name + ".tes"), tessEval.getBytes(StandardCharsets.UTF_8));
}
if (fragment != null) {
Files.write(debugOutDir.resolve(prefix + name + ".fsh"), fragment.getBytes(StandardCharsets.UTF_8));
}
if (compute != null) {
Files.write(debugOutDir.resolve(prefix + name + ".csh"), compute.getBytes(StandardCharsets.UTF_8));
if (Files.exists(debugOutDir)) {
try (Stream<Path> stream = Files.list(debugOutDir)) {
stream.forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
Files.createDirectories(debugOutDir);
} catch (IOException e) {
Iris.logger.warn("Failed to write debug patched shader source", e);
Iris.logger.warn("Failed to initialize debug patched shader source location", e);
}
outputLocationCleared = true;
}
try {
programCounter++;
String prefix = String.format("%03d_", programCounter);
if (vertex != null) {
Files.write(debugOutDir.resolve(prefix + name + ".vsh"), vertex.getBytes(StandardCharsets.UTF_8));
}
if (geometry != null) {
Files.write(debugOutDir.resolve(prefix + name + ".gsh"), geometry.getBytes(StandardCharsets.UTF_8));
}
if (tessControl != null) {
Files.write(debugOutDir.resolve(prefix + name + ".tcs"), tessControl.getBytes(StandardCharsets.UTF_8));
}
if (tessEval != null) {
Files.write(debugOutDir.resolve(prefix + name + ".tes"), tessEval.getBytes(StandardCharsets.UTF_8));
}
if (fragment != null) {
Files.write(debugOutDir.resolve(prefix + name + ".fsh"), fragment.getBytes(StandardCharsets.UTF_8));
}
if (compute != null) {
Files.write(debugOutDir.resolve(prefix + name + ".csh"), compute.getBytes(StandardCharsets.UTF_8));
}
} catch (IOException e) {
Iris.logger.warn("Failed to write debug patched shader source", e);
}
}
}
@@ -428,6 +428,7 @@ public class ShaderTransformer {
// Handle mc_midTexCoord for Celeritas
patchMultiTexCoord3(transformer, parameters);
replaceMidTexCoord(transformer, IrisExtendedChunkVertexType.MID_TEX_SCALE);
replaceMCEntity(transformer, parameters);
applyIntelHd4000Workaround(transformer);
break;
case COMPOSITE:
@@ -494,6 +495,55 @@ public class ShaderTransformer {
}
/**
* Replaces shader-declared mc_Entity (vec2/ivec2/float/int/etc.) with upstream-compatible unpacking
* from a single uint attribute. The uint is packed as ((blockId + 1) << 1) | (renderType & 1).
*/
public static void replaceMCEntity(Transformer transformer, Parameters parameters) {
if (parameters.type != ShaderType.VERTEX) return;
final int type = transformer.findType("mc_Entity");
if (type != 0) {
transformer.removeVariable("mc_Entity");
}
transformer.replaceExpression("mc_Entity", "iris_Entity");
switch (type) {
case 0:
case GLSLLexer.BOOL:
return;
case GLSLLexer.FLOAT:
transformer.injectFunction("float iris_Entity = float(int(mc_Entity >> 1u) - 1);");
break;
case GLSLLexer.VEC2:
transformer.injectFunction("vec2 iris_Entity = vec2(int(mc_Entity >> 1u) - 1, mc_Entity & 1u);");
break;
case GLSLLexer.VEC3:
transformer.injectFunction("vec3 iris_Entity = vec3(int(mc_Entity >> 1u) - 1, mc_Entity & 1u, 0.0);");
break;
case GLSLLexer.VEC4:
transformer.injectFunction("vec4 iris_Entity = vec4(int(mc_Entity >> 1u) - 1, mc_Entity & 1u, 0.0, 1.0);");
break;
case GLSLLexer.UINT:
transformer.injectFunction("uint iris_Entity = uint(int(mc_Entity >> 1u) - 1);");
break;
case GLSLLexer.INT:
transformer.injectFunction("int iris_Entity = int(mc_Entity >> 1u) - 1;");
break;
case GLSLLexer.IVEC2:
transformer.injectFunction("ivec2 iris_Entity = ivec2(int(mc_Entity >> 1u) - 1, mc_Entity & 1u);");
break;
case GLSLLexer.IVEC3:
transformer.injectFunction("ivec3 iris_Entity = ivec3(int(mc_Entity >> 1u) - 1, mc_Entity & 1u, 0);");
break;
case GLSLLexer.IVEC4:
transformer.injectFunction("ivec4 iris_Entity = ivec4(int(mc_Entity >> 1u) - 1, mc_Entity & 1u, 0, 1);");
break;
default:
throw new IllegalStateException("Got an invalid format mc_Entity (type token " + type + ").");
}
transformer.injectVariable("in uint mc_Entity;");
}
public static void addIfNotExists(Transformer transformer, String name, String code) {
if (!transformer.hasVariable(name)) {
transformer.injectVariable(code);
@@ -16,6 +16,7 @@ public class ProgramSet {
private final PackDirectives packDirectives;
private final ProgramSource shadow;
private final ProgramSource shadowWater;
private final ComputeSource[] shadowCompute;
private final ProgramSource[] shadowcomp;
@@ -80,6 +81,8 @@ public class ProgramSet {
// - https://github.com/IrisShaders/Iris/issues/987
this.shadow = readProgramSource(directory, sourceProvider, "shadow", this, shaderProperties,
BlendModeOverride.OFF);
this.shadowWater = readProgramSource(directory, sourceProvider, "shadow_water", this, shaderProperties,
BlendModeOverride.OFF);
this.shadowCompute = readComputeArray(directory, sourceProvider, "shadow");
this.shadowcomp = readProgramArray(directory, sourceProvider, "shadowcomp", shaderProperties);
@@ -218,6 +221,7 @@ public class ProgramSet {
List<ComputeSource> computes = new ArrayList<>();
programs.add(shadow);
programs.add(shadowWater);
programs.addAll(Arrays.asList(shadowcomp));
programs.addAll(Arrays.asList(begin));
programs.addAll(Arrays.asList(prepare));
@@ -302,6 +306,10 @@ public class ProgramSet {
return shadow.requireValid();
}
public Optional<ProgramSource> getShadowWater() {
return shadowWater.requireValid();
}
public ProgramSource[] getShadowComposite() {
return shadowcomp;
}
@@ -413,6 +421,7 @@ public class ProgramSet {
public Optional<ProgramSource> get(ProgramId programId) {
return switch (programId) {
case Shadow -> getShadow();
case ShadowWater -> first(getShadowWater(), getShadow());
case Basic -> getGbuffersBasic();
case Line -> gbuffersLine.requireValid();
case Textured -> getGbuffersTextured();
@@ -14,6 +14,7 @@ public enum ProgramId {
Shadow(ProgramGroup.Shadow, ""),
ShadowSolid(ProgramGroup.Shadow, "solid", Shadow),
ShadowCutout(ProgramGroup.Shadow, "cutout", Shadow),
ShadowWater(ProgramGroup.Shadow, "water", Shadow, BlendModeOverride.OFF),
Basic(ProgramGroup.Gbuffers, "basic"),
Line(ProgramGroup.Gbuffers, "line", Basic),
@@ -1,6 +1,7 @@
package net.coderbot.iris.uniforms;
import lombok.Getter;
import lombok.Setter;
import net.coderbot.iris.gl.state.ValueUpdateNotifier;
import org.joml.Vector4f;
@@ -8,7 +9,8 @@ import org.joml.Vector4f;
public class CapturedRenderingState {
public static final CapturedRenderingState INSTANCE = new CapturedRenderingState();
@Getter
@Setter
@Getter
private float tickDelta;
@Getter
private int currentRenderedBlockEntity;
@@ -29,10 +31,6 @@ public class CapturedRenderingState {
private CapturedRenderingState() {
}
public void setTickDelta(float tickDelta) {
this.tickDelta = tickDelta;
}
public void setCurrentBlockEntity(int entity) {
this.currentRenderedBlockEntity = entity;
@@ -47,6 +45,7 @@ public class CapturedRenderingState {
if (this.entityIdListener != null) {
this.entityIdListener.run();
}
setCurrentRenderedItem(0);
}
public void setCurrentEntityColor(float r, float g, float b, float a) {
@@ -9,6 +9,7 @@ import com.gtnewhorizons.angelica.client.rendering.TextureTracker;
import com.gtnewhorizons.angelica.mixins.interfaces.EntityRendererAccessor;
import net.coderbot.iris.compat.dh.DHCompat;
import net.coderbot.iris.gl.state.FogMode;
import net.coderbot.iris.gl.state.StateUpdateNotifiers;
import net.coderbot.iris.gl.state.ValueUpdateNotifier;
import net.coderbot.iris.gl.uniform.DynamicUniformHolder;
import net.coderbot.iris.gl.uniform.UniformHolder;
@@ -40,9 +41,12 @@ import static net.coderbot.iris.gl.uniform.UniformUpdateFrequency.ONCE;
public final class CommonUniforms {
private static final Minecraft client = Minecraft.getMinecraft();
private static final Vector2i ZERO_VECTOR_2i = new Vector2i();
private static final Vector4i ZERO_VECTOR_4i = new Vector4i(0, 0, 0, 0);
private static final Vector3d ZERO_VECTOR_3d = new Vector3d();
// Scratch vectors for push-notified suppliers -- GL thread only, never escapes
private static final Vector2i scratch2i = new Vector2i();
private static final Vector4i scratch4i = new Vector4i();
private CommonUniforms() {
// no construction allowed
}
@@ -78,29 +82,29 @@ public final class CommonUniforms {
final AbstractTexture texture = TextureTracker.INSTANCE.getTexture(glId);
if (texture instanceof TextureMap) {
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(glId);
return new Vector2i(info.getWidth(), info.getHeight());
return scratch2i.set(info.getWidth(), info.getHeight());
}
return ZERO_VECTOR_2i;
}, ValueUpdateNotifier.NONE);
return scratch2i.set(0, 0);
}, StateUpdateNotifiers.bindTextureNotifier);
uniforms.uniform2i("gtextureSize", () -> {
final int glId = GLStateManager.getBoundTextureForServerState(0);
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(glId);
return new Vector2i(info.getWidth(), info.getHeight());
return scratch2i.set(info.getWidth(), info.getHeight());
}, ValueUpdateNotifier.NONE);
}, StateUpdateNotifiers.bindTextureNotifier);
uniforms.uniform4i("blendFunc", () -> {
if(GLStateManager.getBlendMode().isEnabled()) {
final BlendState blend = GLStateManager.getBlendState();
return new Vector4i(blend.getSrcRgb(), blend.getDstRgb(), blend.getSrcAlpha(), blend.getDstAlpha());
return scratch4i.set(blend.getSrcRgb(), blend.getDstRgb(), blend.getSrcAlpha(), blend.getDstAlpha());
}
return ZERO_VECTOR_4i;
}, ValueUpdateNotifier.NONE);
return scratch4i.set(0, 0, 0, 0);
}, StateUpdateNotifiers.blendFuncNotifier);
uniforms.uniform1i("renderStage", () -> GbufferPrograms.getCurrentPhase().ordinal(), ValueUpdateNotifier.NONE);
uniforms.uniform1i("renderStage", () -> GbufferPrograms.getCurrentPhase().ordinal(), StateUpdateNotifiers.phaseChangeNotifier);
uniforms.uniform4f("entityColor", CapturedRenderingState.INSTANCE::getCurrentEntityColor, CapturedRenderingState.INSTANCE.getEntityColorNotifier());
@@ -2,6 +2,7 @@ package net.coderbot.iris.uniforms;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import net.coderbot.iris.gl.state.FogMode;
import net.coderbot.iris.gl.state.StateUpdateNotifiers;
import net.coderbot.iris.gl.state.ValueUpdateNotifier;
import net.coderbot.iris.gl.uniform.DynamicUniformHolder;
import net.coderbot.iris.gl.uniform.UniformUpdateFrequency;
@@ -19,12 +20,12 @@ public class FogUniforms {
uniforms.uniform1i("fogMode", () -> {
if (!GLStateManager.getFogMode().isEnabled()) return 0;
return GLStateManager.getFogState().getFogMode();
}, ValueUpdateNotifier.NONE);
}, StateUpdateNotifiers.fogModeNotifier);
uniforms.uniform1i("fogShape", () -> 0, ValueUpdateNotifier.NONE);
}
uniforms.uniform1f("fogDensity", () -> GLStateManager.getFogState().getDensity(), ValueUpdateNotifier.NONE);
uniforms.uniform1f("fogStart", () -> GLStateManager.getFogState().getStart(), ValueUpdateNotifier.NONE);
uniforms.uniform1f("fogEnd", () -> GLStateManager.getFogState().getEnd(), ValueUpdateNotifier.NONE);
uniforms.uniform1f("fogDensity", () -> GLStateManager.getFogState().getDensity(), StateUpdateNotifiers.fogDensityNotifier);
uniforms.uniform1f("fogStart", () -> GLStateManager.getFogState().getStart(), StateUpdateNotifiers.fogStartNotifier);
uniforms.uniform1f("fogEnd", () -> GLStateManager.getFogState().getEnd(), StateUpdateNotifiers.fogEndNotifier);
}
}
@@ -2,8 +2,6 @@ package net.coderbot.iris.uniforms;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.Object2IntFunction;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
@@ -22,8 +20,6 @@ import net.minecraft.util.ResourceLocation;
public class ItemMaterialHelper {
private static final Reference2ObjectMap<Item, Int2IntMap> MATERIAL_CACHE = new Reference2ObjectOpenHashMap<>();
private static final int CACHE_MISS_SENTINEL = Integer.MIN_VALUE;
public static final IntSet WARNED_UNKNOWN_ARMOR_INDICES = new IntOpenHashSet();
/**
* Get the material ID for an ItemStack.
* For ItemBlock: checks block.properties first, then item.properties.
@@ -114,6 +110,5 @@ public class ItemMaterialHelper {
*/
public static void clearCache() {
MATERIAL_CACHE.clear();
WARNED_UNKNOWN_ARMOR_INDICES.clear();
}
}
@@ -22,12 +22,13 @@ import net.minecraft.util.Vec3;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.joml.Vector2f;
import org.joml.Vector2i;
import org.joml.Vector2ic;
import org.joml.Vector3d;
import org.joml.Vector3f;
import org.joml.Vector3fc;
import org.joml.Vector3ic;
import org.joml.Vector4f;
import org.joml.Vector4fc;
import java.util.Collection;
import java.util.Map;
@@ -112,7 +113,7 @@ public class CustomUniformFixedInputUniformsHolder {
}
@Override
public Builder uniform2i(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector2i> value) {
public Builder uniform2i(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector2ic> value) {
return this.put(name, new Int2VectorCachedUniform(name, updateFrequency, value));
}
@@ -132,10 +133,10 @@ public class CustomUniformFixedInputUniformsHolder {
}
@Override
public Builder uniformTruncated3f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector4f> value) {
public Builder uniformTruncated3f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector4fc> value) {
Vector3f held = new Vector3f();
return this.put(name, new Float3VectorCachedUniform(name, updateFrequency, () -> {
Vector4f vec = value.get();
final Vector4fc vec = value.get();
held.set(vec.x(), vec.y(), vec.z());
return held;
}));
@@ -152,10 +153,10 @@ public class CustomUniformFixedInputUniformsHolder {
}
@Override
public UniformHolder uniform4f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector4f> value) {
public UniformHolder uniform4f(UniformUpdateFrequency updateFrequency, String name, Supplier<Vector4fc> value) {
Vector4f held = new Vector4f();
return this.put(name, new Float4VectorCachedUniform(name, updateFrequency, () -> {
Vector4f vec = value.get();
final Vector4fc vec = value.get();
held.set(vec.x(), vec.y(), vec.z(), vec.w());
return held;
}));
@@ -3,14 +3,23 @@ package net.coderbot.iris.uniforms.custom.cached;
import net.coderbot.iris.gl.uniform.UniformUpdateFrequency;
import net.coderbot.iris.parsing.VectorType;
import org.joml.Vector4f;
import org.joml.Vector4fc;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import java.util.function.Supplier;
public class Float4VectorCachedUniform extends VectorCachedUniform<Vector4f> {
public Float4VectorCachedUniform(String name, UniformUpdateFrequency updateFrequency, Supplier<Vector4f> supplier) {
super(name, updateFrequency, new Vector4f(), supplier);
public Float4VectorCachedUniform(String name, UniformUpdateFrequency updateFrequency, Supplier<Vector4fc> supplier) {
super(name, updateFrequency, new Vector4f(), adapt(supplier));
}
private static Supplier<Vector4f> adapt(Supplier<Vector4fc> supplier) {
final Vector4f scratch = new Vector4f();
return () -> {
final Vector4fc v = supplier.get();
return scratch.set(v.x(), v.y(), v.z(), v.w());
};
}
@Override
@@ -4,14 +4,23 @@ import kroppeb.stareval.function.FunctionReturn;
import net.coderbot.iris.gl.uniform.UniformUpdateFrequency;
import net.coderbot.iris.parsing.VectorType;
import org.joml.Vector2i;
import org.joml.Vector2ic;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import java.util.function.Supplier;
public class Int2VectorCachedUniform extends VectorCachedUniform<Vector2i> {
public Int2VectorCachedUniform(String name, UniformUpdateFrequency updateFrequency, Supplier<Vector2i> supplier) {
super(name, updateFrequency, new Vector2i(), supplier);
public Int2VectorCachedUniform(String name, UniformUpdateFrequency updateFrequency, Supplier<Vector2ic> supplier) {
super(name, updateFrequency, new Vector2i(), adapt(supplier));
}
private static Supplier<Vector2i> adapt(Supplier<Vector2ic> supplier) {
final Vector2i scratch = new Vector2i();
return () -> {
final Vector2ic v = supplier.get();
return scratch.set(v.x(), v.y());
};
}
@Override
@@ -2,7 +2,7 @@ package net.coderbot.iris.uniforms.transforms;
import net.coderbot.iris.uniforms.FrameUpdateNotifier;
import org.joml.Vector2f;
import org.joml.Vector2i;
import org.joml.Vector2ic;
import java.util.function.Supplier;
@@ -10,9 +10,9 @@ public class SmoothedVec2f implements Supplier<Vector2f> {
private final SmoothedFloat x;
private final SmoothedFloat y;
public SmoothedVec2f(float halfLifeUp, float halfLifeDown, Supplier<Vector2i> unsmoothed, FrameUpdateNotifier updateNotifier) {
x = new SmoothedFloat(halfLifeUp, halfLifeDown, () -> unsmoothed.get().x, updateNotifier);
y = new SmoothedFloat(halfLifeUp, halfLifeDown, () -> unsmoothed.get().y, updateNotifier);
public SmoothedVec2f(float halfLifeUp, float halfLifeDown, Supplier<Vector2ic> unsmoothed, FrameUpdateNotifier updateNotifier) {
x = new SmoothedFloat(halfLifeUp, halfLifeDown, () -> unsmoothed.get().x(), updateNotifier);
y = new SmoothedFloat(halfLifeUp, halfLifeDown, () -> unsmoothed.get().y(), updateNotifier);
}
@Override
@@ -1,8 +1,8 @@
package net.coderbot.iris.vertices;
public final class ExtendedDataHelper {
// TODO: Resolve render types for normal blocks?
public static final short BLOCK_RENDER_TYPE = -1;
// Packed mc_Entity only preserves one terrain/fluid bit, so normal blocks must encode as 0.
public static final short BLOCK_RENDER_TYPE = 0;
/** All fluids have a ShadersMod render type of 1, to match behavior of Minecraft 1.7 and earlier. */
public static final short FLUID_RENDER_TYPE = 1;
@@ -0,0 +1,26 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes;
import net.minecraft.client.model.ModelCreeper;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
@Mixin(ModelCreeper.class)
public class MixinModelCreeper_AuraBodyInflate {
/**
* Reduce the body inflate by 2^20 ULPs so the aura body doesn't intersect the base model's head.
* Only applies when inflate > 0 (the aura model), leaving the base model untouched.
* The body addBox is the 3rd call (ordinal 2) in the constructor, and inflate is parameter index 6.
*/
@ModifyArg(
method = "<init>(F)V", at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/model/ModelRenderer;addBox(FFFIIIF)V", ordinal = 2), index = 6
)
private float angelica$shrinkAuraBody(float inflate) {
if (inflate > 0.0F) {
return Float.intBitsToFloat(Float.floatToIntBits(inflate) - 1048576);
}
return inflate;
}
}
@@ -5,6 +5,7 @@ import net.minecraft.client.model.ModelRenderer;
import net.minecraft.client.model.ModelWither;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@@ -26,8 +27,17 @@ public class MixinModelWither_ArmorCentering {
// Heads: [0] center, [1] left, [2] right
@Shadow private ModelRenderer[] field_82904_b;
@Unique
private static final float ARMOR_INFLATE = 0.5F;
// Ribs armor size is reduced by 2^22 ULPs to sit just inside the base spine, preventing z-fighting.
@Unique
private static final float NO_RIBS_Z_INFLATE = Float.intBitsToFloat(Float.floatToIntBits(ARMOR_INFLATE) - 4194304);
// Side head armor is inflated by 2^22 ULPs to make the armor sit above the base shoulder, preventing z-fighting.
@Unique
private static final float NO_HEADS_Z_INFLATE = Float.intBitsToFloat(Float.floatToIntBits(ARMOR_INFLATE) + 4194304);
// If the armor inflate flag is set, rebuild all boxes with uniform inflate.
@Inject(method = "<init>", at = @At("TAIL"))
private void angelica$applyInflate(CallbackInfo ci) {
@@ -44,9 +54,9 @@ public class MixinModelWither_ArmorCentering {
field_82905_a[1] = (new ModelRenderer(self)).setTextureSize(self.textureWidth, self.textureHeight);
field_82905_a[1].setRotationPoint(-2.0F, 6.9F, -0.5F);
field_82905_a[1].setTextureOffset(0, 22).addBox(0.0F, 0.0F, 0.0F, 3, 10, 3, ARMOR_INFLATE);
field_82905_a[1].setTextureOffset(24, 22).addBox(-4.0F, 1.5F, 0.5F, 11, 2, 2, ARMOR_INFLATE);
field_82905_a[1].setTextureOffset(24, 22).addBox(-4.0F, 4.0F, 0.5F, 11, 2, 2, ARMOR_INFLATE);
field_82905_a[1].setTextureOffset(24, 22).addBox(-4.0F, 6.5F, 0.5F, 11, 2, 2, ARMOR_INFLATE);
field_82905_a[1].setTextureOffset(24, 22).addBox(-4.0F, 1.5F, 0.5F, 11, 2, 2, NO_RIBS_Z_INFLATE);
field_82905_a[1].setTextureOffset(24, 22).addBox(-4.0F, 4.0F, 0.5F, 11, 2, 2, NO_RIBS_Z_INFLATE);
field_82905_a[1].setTextureOffset(24, 22).addBox(-4.0F, 6.5F, 0.5F, 11, 2, 2, NO_RIBS_Z_INFLATE);
// [2] Tail
field_82905_a[2] = new ModelRenderer(self, 12, 22);
@@ -58,13 +68,13 @@ public class MixinModelWither_ArmorCentering {
// [1] Left head
field_82904_b[1] = new ModelRenderer(self, 32, 0);
field_82904_b[1].addBox(-4.0F, -4.0F, -4.0F, 6, 6, 6, ARMOR_INFLATE);
field_82904_b[1].addBox(-4.0F, -4.0F, -4.0F, 6, 6, 6, NO_HEADS_Z_INFLATE);
field_82904_b[1].rotationPointX = -8.0F;
field_82904_b[1].rotationPointY = 4.0F;
// [2] Right head
field_82904_b[2] = new ModelRenderer(self, 32, 0);
field_82904_b[2].addBox(-4.0F, -4.0F, -4.0F, 6, 6, 6, ARMOR_INFLATE);
field_82904_b[2].addBox(-4.0F, -4.0F, -4.0F, 6, 6, 6, NO_HEADS_Z_INFLATE);
field_82904_b[2].rotationPointX = 10.0F;
field_82904_b[2].rotationPointY = 4.0F;
}
@@ -0,0 +1,43 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes;
import com.gtnewhorizons.angelica.rendering.DeferredEntityOverlay;
import net.minecraft.client.renderer.entity.RenderCreeper;
import net.minecraft.client.renderer.entity.RendererLivingEntity;
import net.minecraft.entity.monster.EntityCreeper;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(RenderCreeper.class)
public abstract class MixinRenderCreeper_AuraDepth {
@Shadow
protected abstract int shouldRenderPass(EntityCreeper entity, int pass, float partialTick);
@Unique
private DeferredEntityOverlay.ShouldRenderPassFn angelica$cachedPassFn;
/**
* Mark that a charged creeper aura pass is about to execute. The method runs normally
* so any mixins added by mods within shouldRenderPass still fire. The flag is consumed by the
* WrapOperation on renderPassModel.render() in doRender, which defers the actual render.
* See MixinRendererLivingEntity_DeferredEntityOverlay
*/
@Inject(method = "shouldRenderPass(Lnet/minecraft/entity/monster/EntityCreeper;IF)I",
at = @At("HEAD"))
private void angelica$markOverlayPass(EntityCreeper creeper, int pass, float partialTick, CallbackInfoReturnable<Integer> cir) {
if (pass == 1 && creeper.getPowered() && !DeferredEntityOverlay.isReplaying()) {
if (angelica$cachedPassFn == null) {
angelica$cachedPassFn = (entity, pass2, tick) -> this.shouldRenderPass((EntityCreeper) entity, pass2, tick);
}
DeferredEntityOverlay.markOverlayPass(
angelica$cachedPassFn,
(RendererLivingEntity) (Object) this,
creeper, partialTick
);
}
}
}
@@ -0,0 +1,30 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes;
import com.gtnewhorizons.angelica.rendering.DeferredEntityOverlay;
import net.minecraft.client.renderer.RenderGlobal;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(RenderGlobal.class)
public class MixinRenderGlobal_DeferredEntityOverlay {
/**
* Clear stale deferred entries before collecting new ones.
*/
@Inject(method = "renderEntities", at = @At("HEAD"))
private void angelica$clearStaleDeferredOverlays(CallbackInfo ci) {
DeferredEntityOverlay.clear();
}
/**
* Render deferred entity overlays after all entities have been drawn,
* so additive-blended overlays composite correctly without depth issues.
*/
@Inject(method = "renderEntities", at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/renderer/RenderHelper;enableStandardItemLighting()V"))
private void angelica$renderDeferredOverlays(CallbackInfo ci) {
DeferredEntityOverlay.renderAll();
}
}
@@ -1,15 +1,20 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes;
import com.gtnewhorizons.angelica.rendering.DeferredEntityOverlay;
import com.gtnewhorizons.angelica.rendering.WitherArmorState;
import net.minecraft.client.model.ModelBase;
import net.minecraft.client.model.ModelWither;
import net.minecraft.client.renderer.entity.RenderWither;
import net.minecraft.client.renderer.entity.RendererLivingEntity;
import net.minecraft.entity.boss.EntityWither;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/**
* Replaces the Wither's glScalef(1.1) armor approach with a per-box inflated model.
@@ -17,11 +22,17 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
* Not exactly mod friendly but we can figure that out later.
*/
@Mixin(RenderWither.class)
public class MixinRenderWither_ArmorCentering {
public abstract class MixinRenderWither_ArmorCentering {
@Shadow
protected abstract int shouldRenderPass(EntityWither entity, int pass, float partialTick);
@Unique
private ModelWither angelica$armorModel;
@Unique
private DeferredEntityOverlay.ShouldRenderPassFn angelica$cachedPassFn;
@Inject(method = "<init>", at = @At("TAIL"))
private void angelica$createArmorModel(CallbackInfo ci) {
WitherArmorState.pendingInflate = true;
@@ -38,7 +49,7 @@ public class MixinRenderWither_ArmorCentering {
((RenderWither) (Object) this).setRenderPassModel(angelica$armorModel);
}
// No-op glTranslatef
// No-op the model offset translate (ordinal 1)
@Redirect(
method = "shouldRenderPass(Lnet/minecraft/entity/boss/EntityWither;IF)I",
at = @At(value = "INVOKE",
@@ -55,4 +66,20 @@ public class MixinRenderWither_ArmorCentering {
)
private void angelica$skipScale(float x, float y, float z) {
}
// Mark the Wither's armor pass for deferred rendering.
@Inject(method = "shouldRenderPass(Lnet/minecraft/entity/boss/EntityWither;IF)I",
at = @At("HEAD"))
private void angelica$markArmorPass(EntityWither wither, int pass, float partialTick, CallbackInfoReturnable<Integer> cir) {
if (pass == 1 && wither.isArmored() && !DeferredEntityOverlay.isReplaying()) {
if (angelica$cachedPassFn == null) {
angelica$cachedPassFn = (entity, pass2, tick) -> this.shouldRenderPass((EntityWither) entity, pass2, tick);
}
DeferredEntityOverlay.markOverlayPass(
angelica$cachedPassFn,
(RendererLivingEntity) (Object) this,
wither, partialTick
);
}
}
}
@@ -0,0 +1,50 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes;
import com.gtnewhorizons.angelica.rendering.DeferredEntityOverlay;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.client.model.ModelBase;
import net.minecraft.client.renderer.entity.RendererLivingEntity;
import net.minecraft.entity.Entity;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(value = RendererLivingEntity.class, priority = 1200)
public class MixinRendererLivingEntity_DeferredEntityOverlay {
/**
* Intercepts the first renderPassModel.render() call in the shouldRenderPass loop.
* When an entity overlay pass is active, captures the animation params and modelview
* matrix for deferred rendering, skipping the immediate render.
*/
@WrapOperation(
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V", at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V", ordinal = 0)
)
private void angelica$maybeDeferOverlay(ModelBase model, Entity entity,
float limbSwing, float limbSwingAmount, float ageInTicks,
float headYaw, float headPitch, float scale,
Operation<Void> original) {
if (DeferredEntityOverlay.isOverlayPassActive()) {
DeferredEntityOverlay.deferRender(limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, scale);
} else {
original.call(model, entity, limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, scale);
}
}
/**
* Clear a stale overlayPassActive flag after the shouldRenderPass loop.
* Handles the case where markOverlayPass fired but shouldRenderPass returned <= 0,
* so renderPassModel.render() was never called and deferRender never consumed the flag.
*/
@Inject(
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V")
)
private void angelica$clearStaleOverlayFlag(CallbackInfo ci) {
DeferredEntityOverlay.clearStaleOverlayFlag();
}
}
@@ -123,7 +123,7 @@ public abstract class MixinRenderBlocks {
@ModifyExpressionValue(method = "renderStandardBlockWithColorMultiplier",
at = @At(value = "FIELD", target = "Lnet/minecraft/init/Blocks;grass:Lnet/minecraft/block/BlockGrass;", opcode = Opcodes.GETSTATIC))
private BlockGrass angelica$widenGrassCheck(BlockGrass grassBlock, @Local(argsOnly = true, ordinal = 0) Block block) {
return (block instanceof BlockGrass bg) ? bg : grassBlock;
return (block instanceof BlockGrass bg && block == ClientProxy.bopGrass) ? bg : grassBlock;
}
/* Disable diffuse when celeritas AO is in use */
@@ -0,0 +1,13 @@
package com.gtnewhorizons.angelica.mixins.early.shaders;
import net.minecraft.entity.passive.EntityHorse;
import net.minecraft.item.ItemStack;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
@Mixin(EntityHorse.class)
public interface AccessorEntityHorse {
@Invoker("getHorseArmorIndex")
int invokeGetHorseArmorIndex(ItemStack stack);
}
@@ -0,0 +1,50 @@
package com.gtnewhorizons.angelica.mixins.early.shaders;
import net.minecraft.client.renderer.ItemRenderer;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IIcon;
import net.minecraftforge.client.IItemRenderer.ItemRenderType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArgs;
import org.spongepowered.asm.mixin.injection.invoke.arg.Args;
/**
* Fix enchantment glint not rendering on item edges by using the actual icon dimensions
* instead of hardcoded 256x256. This ensures the edge geometry calculation matches the
* main item render, allowing GL_EQUAL depth test to pass on all edges.
*/
@Mixin(ItemRenderer.class)
public class MixinHeldItemGlintEdges {
/**
* First glint pass (ordinal 1)
*/
@ModifyArgs(
method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V", ordinal = 1)
)
private void iris$fixFirstGlintDimensions(Args args, EntityLivingBase entity, ItemStack stack, int pass, ItemRenderType type) {
IIcon icon = entity.getItemIcon(stack, pass);
if (icon != null) {
args.set(5, icon.getIconWidth());
args.set(6, icon.getIconHeight());
}
}
/**
* Second glint pass (ordinal 2)
*/
@ModifyArgs(
method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V", ordinal = 2)
)
private void iris$fixSecondGlintDimensions(Args args, EntityLivingBase entity, ItemStack stack, int pass, ItemRenderType type) {
IIcon icon = entity.getItemIcon(stack, pass);
if (icon != null) {
args.set(5, icon.getIconWidth());
args.set(6, icon.getIconHeight());
}
}
}
@@ -1,84 +1,136 @@
package com.gtnewhorizons.angelica.mixins.early.shaders;
import com.gtnewhorizons.angelica.rendering.ModelHorseArmor;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.coderbot.iris.uniforms.ItemIdManager;
import net.coderbot.iris.uniforms.ItemMaterialHelper;
import net.minecraft.client.Minecraft;
import net.minecraft.client.model.ModelBase;
import net.minecraft.client.model.ModelRenderer;
import net.minecraft.client.renderer.entity.RenderHorse;
import net.minecraft.entity.passive.EntityHorse;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.ResourceLocation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
/**
* Mixin to set the currentRenderedItem ID when rendering horse armor.
* This allows shaders to identify and apply different materials to horse armor.
* Mixin to set the currentRenderedItem ID on the armor portion of horse rendering.
*/
@Mixin(RenderHorse.class)
public abstract class MixinRenderHorse {
@Unique
private static final Logger LOGGER = LogManager.getLogger("Angelica");
@Unique
private static final float ARMOR_INFLATE = 0.125F;
@Unique
private static ModelHorseArmor angelica$armorModel;
/**
* Set the item ID when rendering the horse model if it has armor.
* Wraps the mainModel.render() call to set the material ID only around the actual render.
* Reverse mapping from armor index to Item, built lazily by scanning the item registry
* for items recognized by EntityHorse.func_146085_a (isHorseArmor) and invoking
* EntityHorse.getHorseArmorIndex to determine their index.
* Automatically picks up modded armor that hooks into the vanilla's system. (I hope)
*/
@Unique
private static Int2ObjectOpenHashMap<Item> angelica$armorIndexToItem;
@Unique
private static final Map<String, ResourceLocation> angelica$armorTextureCache = new HashMap<>();
/**
* Two-pass rendering with a separate inflated armor model:
* Pass 1: Render the base horse with the composite texture, no item ID.
* Pass 2: Render the inflated armor model with the armor-only texture and item ID set.
* The inflate makes armor geometry sit above the base.
* Alpha test discards transparent (non-armor) pixels.
*/
@WrapOperation(
method = "renderModel(Lnet/minecraft/entity/passive/EntityHorse;FFFFFF)V",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V")
)
private void iris$setHorseArmorItemId(ModelBase model, net.minecraft.entity.Entity entity, float p1, float p2, float p3, float p4, float p5, float p6, Operation<Void> original, EntityHorse horse) {
// Only set ID if horse has armor equipped
boolean hasArmor = false;
// Check if horse type == 0 (regular horse)
if (horse.func_110259_cr()) {
// Get armor index from DataWatcher (synced from server)
int armorIndex = horse.func_110241_cb();
if (armorIndex > 0) {
// Map armor index to vanilla items
Item armorItem = null;
switch (armorIndex) {
case 1:
armorItem = Items.iron_horse_armor;
break;
case 2:
armorItem = Items.golden_horse_armor;
break;
case 3:
armorItem = Items.diamond_horse_armor;
break;
default:
// Unknown armor index - likely from a mod
// Warn once per unique index to avoid spam
if (!ItemMaterialHelper.WARNED_UNKNOWN_ARMOR_INDICES.contains(armorIndex)) {
ItemMaterialHelper.WARNED_UNKNOWN_ARMOR_INDICES.add(armorIndex);
LOGGER.warn("Unknown horse armor index detected: {}. " +
"This is likely from a mod. Vanilla horse armor uses indices 1-3 (iron/gold/diamond). " +
"Modded horse armor material IDs are not currently supported.", armorIndex);
}
break;
}
if (armorItem != null) {
int id = ItemMaterialHelper.getMaterialId(armorItem, 0);
CapturedRenderingState.INSTANCE.setCurrentRenderedItem(id);
hasArmor = true;
}
}
}
// Call original render
// Pass 1: Render the base horse normally
original.call(model, entity, p1, p2, p3, p4, p5, p6);
// Reset only if we set it
if (hasArmor) {
ItemIdManager.resetItemId();
if (!horse.func_110259_cr()) return;
int armorIndex = horse.func_110241_cb();
if (armorIndex <= 0) return;
String armorTexturePath = horse.getVariantTexturePaths()[2];
if (armorTexturePath == null) return;
// Pass 2: Render inflated armor model with item ID
if (angelica$armorModel == null) {
angelica$armorModel = new ModelHorseArmor(ARMOR_INFLATE);
}
Item armorItem = angelica$getArmorItem(horse, armorIndex);
if (armorItem != null) {
int id = ItemMaterialHelper.getMaterialId(armorItem, 0);
CapturedRenderingState.INSTANCE.setCurrentRenderedItem(id);
}
// Copy animation state from the base model to the armor model
angelica$copyRotations(model, angelica$armorModel);
ResourceLocation armorTexture = angelica$armorTextureCache.computeIfAbsent(armorTexturePath, ResourceLocation::new);
Minecraft.getMinecraft().getTextureManager().bindTexture(armorTexture);
angelica$armorModel.render(horse, p1, p2, p3, p4, p5, p6);
ItemIdManager.resetItemId();
}
// Copy rotation angles and positions from one model's renderers to another's
@Unique
private static void angelica$copyRotations(ModelBase from, ModelBase to) {
for (int i = 0; i < from.boxList.size() && i < to.boxList.size(); i++) {
ModelRenderer src = from.boxList.get(i);
ModelRenderer dst = to.boxList.get(i);
dst.rotateAngleX = src.rotateAngleX;
dst.rotateAngleY = src.rotateAngleY;
dst.rotateAngleZ = src.rotateAngleZ;
dst.rotationPointX = src.rotationPointX;
dst.rotationPointY = src.rotationPointY;
dst.rotationPointZ = src.rotationPointZ;
}
}
/**
* Look up the item for a given armor index. On first call, builds a reverse mapping
* by scanning the item registry for items that pass EntityHorse.func_146085_a (isHorseArmor)
* and invoking the getHorseArmorIndex to determine their index.
*/
@Unique
private static Item angelica$getArmorItem(EntityHorse horse, int armorIndex) {
if (angelica$armorIndexToItem == null) {
angelica$armorIndexToItem = new Int2ObjectOpenHashMap<>();
AccessorEntityHorse accessor = (AccessorEntityHorse) horse;
for (Object obj : Item.itemRegistry) {
Item item = (Item) obj;
if (!EntityHorse.func_146085_a(item)) continue;
int idx = accessor.invokeGetHorseArmorIndex(new ItemStack(item));
if (idx > 0) {
angelica$armorIndexToItem.put(idx, item);
}
}
LOGGER.debug("Built horse armor index map: {} entries", angelica$armorIndexToItem.size());
}
return angelica$armorIndexToItem.get(armorIndex);
}
}
@@ -30,17 +30,6 @@ public class MixinRenderItem {
ItemIdManager.setItemId(itemStack);
}
/**
* Reset the item ID after rendering dropped items.
*/
@Inject(
method = "doRender(Lnet/minecraft/entity/item/EntityItem;DDDFF)V",
at = @At("RETURN")
)
private void iris$resetItemIdAfterRender(EntityItem entity, double x, double y, double z, float entityYaw, float partialTicks, CallbackInfo ci) {
ItemIdManager.resetItemId();
}
/**
* Activate GLINT shader before rendering enchantment glint on dropped items.
*/
@@ -4,7 +4,6 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.coderbot.iris.uniforms.EntityIdHelper;
import net.coderbot.iris.uniforms.ItemIdManager;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.Entity;
@@ -26,7 +25,6 @@ public class MixinRenderManager {
private void iris$wrapDoRender(Render render, Entity entity, double x, double y, double z, float entityYaw, float partialTicks, Operation<Void> original) {
int entityId = EntityIdHelper.getEntityId(entity);
CapturedRenderingState.INSTANCE.setCurrentEntity(entityId);
ItemIdManager.resetItemId();
original.call(render, entity, x, y, z, entityYaw, partialTicks);
CapturedRenderingState.INSTANCE.setCurrentEntity(-1);
}
@@ -4,7 +4,6 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.coderbot.iris.uniforms.EntityIdHelper;
import net.coderbot.iris.uniforms.ItemIdManager;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.Entity;
@@ -24,7 +23,6 @@ public class MixinRenderManagerDAPI {
private void iris$wrapDoRenderDragonAPI(Render render, Entity entity, double x, double y, double z, float entityYaw, float partialTicks, Operation<Void> original) {
int entityId = EntityIdHelper.getEntityId(entity);
CapturedRenderingState.INSTANCE.setCurrentEntity(entityId);
ItemIdManager.resetItemId();
original.call(render, entity, x, y, z, entityYaw, partialTicks);
CapturedRenderingState.INSTANCE.setCurrentEntity(-1);
}
@@ -22,13 +22,13 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(RendererLivingEntity.class)
public class MixinRendererLivingEntity {
/**
* Reset currentRenderedItemId at the start of rendering each living entity.
* Reset ID before damage overlay renders, otherwise you get random shiny zombies
*/
@Inject(
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
at = @At("HEAD")
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V", shift = At.Shift.AFTER)
)
private void iris$resetItemIdAtStart(EntityLivingBase entity, double x, double y, double z, float entityYaw, float partialTicks, CallbackInfo ci) {
private void iris$resetItemIdAfterEquipped(EntityLivingBase entity, double x, double y, double z, float entityYaw, float partialTicks, CallbackInfo ci) {
ItemIdManager.resetItemId();
}
@@ -79,7 +79,6 @@ public class MixinRendererLivingEntity {
remap = false
)
private void iris$glintStart(CallbackInfo ci) {
ItemIdManager.resetItemId();
GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.GLINT);
}