Compare commits

..
27 Commits
Author SHA1 Message Date
Jason Mitchell 8d53e25e0a Compact CTM Fix (#1640) 2026-04-03 16:48:08 -07:00
VortexSo4 bbf83db379 Implement compact ctm (#1636) 2026-04-02 22:32:28 +00:00
Jason Mitchell ec82280b9b Match OpenGL Buffer Behavior for glGet (#1637) 2026-04-02 14:20:06 -07:00
DarkShadow44andJason Mitchell d7588e4c14 Adapt shader block id override for Angelica 2.x (#1601)
Co-authored-by: Jason Mitchell <[email protected]>
2026-04-02 14:18:55 -07:00
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
80 changed files with 2041 additions and 377 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();
@@ -946,10 +949,11 @@ public class GLStateManager {
switch (pname) {
case GL11.GL_COLOR_WRITEMASK -> {
params.put((byte) (colorMask.red ? GL11.GL_TRUE : GL11.GL_FALSE));
params.put((byte) (colorMask.green ? GL11.GL_TRUE : GL11.GL_FALSE));
params.put((byte) (colorMask.blue ? GL11.GL_TRUE : GL11.GL_FALSE));
params.put((byte) (colorMask.alpha ? GL11.GL_TRUE : GL11.GL_FALSE));
final int pos = params.position();
params.put(pos, (byte) (colorMask.red ? GL11.GL_TRUE : GL11.GL_FALSE));
params.put(pos + 1, (byte) (colorMask.green ? GL11.GL_TRUE : GL11.GL_FALSE));
params.put(pos + 2, (byte) (colorMask.blue ? GL11.GL_TRUE : GL11.GL_FALSE));
params.put(pos + 3, (byte) (colorMask.alpha ? GL11.GL_TRUE : GL11.GL_FALSE));
}
default -> {
if (!HAS_MULTIPLE_SET.contains(pname)) {
@@ -1060,7 +1064,7 @@ public class GLStateManager {
case GL11.GL_DIFFUSE -> state.diffuse.get(0, params);
case GL11.GL_SPECULAR -> state.specular.get(0, params);
case GL11.GL_EMISSION -> state.emission.get(0, params);
case GL11.GL_SHININESS -> params.put(state.shininess);
case GL11.GL_SHININESS -> params.put(params.position(), state.shininess);
case GL11.GL_COLOR_INDEXES -> state.colorIndexes.get(0, params);
default -> {
}
@@ -1075,11 +1079,11 @@ public class GLStateManager {
case GL11.GL_SPECULAR -> state.specular.get(0, params);
case GL11.GL_POSITION -> state.position.get(0, params);
case GL11.GL_SPOT_DIRECTION -> state.spotDirection.get(0, params);
case GL11.GL_SPOT_EXPONENT -> params.put(state.spotExponent);
case GL11.GL_SPOT_CUTOFF -> params.put(state.spotCutoff);
case GL11.GL_CONSTANT_ATTENUATION -> params.put(state.constantAttenuation);
case GL11.GL_LINEAR_ATTENUATION -> params.put(state.linearAttenuation);
case GL11.GL_QUADRATIC_ATTENUATION -> params.put(state.quadraticAttenuation);
case GL11.GL_SPOT_EXPONENT -> params.put(params.position(), state.spotExponent);
case GL11.GL_SPOT_CUTOFF -> params.put(params.position(), state.spotCutoff);
case GL11.GL_CONSTANT_ATTENUATION -> params.put(params.position(), state.constantAttenuation);
case GL11.GL_LINEAR_ATTENUATION -> params.put(params.position(), state.linearAttenuation);
case GL11.GL_QUADRATIC_ATTENUATION -> params.put(params.position(), state.quadraticAttenuation);
default -> {
}
}
@@ -1098,21 +1102,39 @@ public class GLStateManager {
case GL11.GL_COLOR_CLEAR_VALUE -> clearColor.get(params);
case GL11.GL_CURRENT_COLOR -> color.get(params);
case GL11.GL_DEPTH_RANGE -> {
params.put((float) viewportState.depthRangeNear);
params.put((float) viewportState.depthRangeFar);
final int pos = params.position();
params.put(pos, (float) viewportState.depthRangeNear);
params.put(pos + 1, (float) viewportState.depthRangeFar);
}
case GL14.GL_BLEND_COLOR -> {
final int pos = params.position();
params.put(pos, blendState.getBlendColorR());
params.put(pos + 1, blendState.getBlendColorG());
params.put(pos + 2, blendState.getBlendColorB());
params.put(pos + 3, blendState.getBlendColorA());
}
case GL14.GL_BLEND_COLOR -> params.put(blendState.getBlendColorR()).put(blendState.getBlendColorG()).put(blendState.getBlendColorB()).put(blendState.getBlendColorA());
case GL11.GL_CURRENT_NORMAL -> {
final Vector3f normal = ShaderManager.getCurrentNormal();
params.put(normal.x).put(normal.y).put(normal.z);
final int pos = params.position();
params.put(pos, normal.x);
params.put(pos + 1, normal.y);
params.put(pos + 2, normal.z);
}
case GL11.GL_CURRENT_TEXTURE_COORDS -> {
final Vector4f tc = ShaderManager.getCurrentTexCoord();
params.put(tc.x).put(tc.y).put(tc.z).put(tc.w);
final int pos = params.position();
params.put(pos, tc.x);
params.put(pos + 1, tc.y);
params.put(pos + 2, tc.z);
params.put(pos + 3, tc.w);
}
case GL11.GL_FOG_COLOR -> {
final FloatBuffer fogBuf = fogState.getFogColorBuffer();
params.put(fogBuf.get(0)).put(fogBuf.get(1)).put(fogBuf.get(2)).put(fogBuf.get(3));
final int pos = params.position();
params.put(pos, fogBuf.get(0));
params.put(pos + 1, fogBuf.get(1));
params.put(pos + 2, fogBuf.get(2));
params.put(pos + 3, fogBuf.get(3));
}
case GL11.GL_LIGHT_MODEL_AMBIENT -> lightModel.ambient.get(0, params);
default -> {
@@ -2145,6 +2167,7 @@ public class GLStateManager {
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
RENDER_BACKEND.drawElements(mode, indices);
}
@@ -2158,6 +2181,7 @@ public class GLStateManager {
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices);
} else {
@@ -2175,6 +2199,7 @@ public class GLStateManager {
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(indices);
} else {
@@ -2192,6 +2217,7 @@ public class GLStateManager {
}
prepareWideLineEmulation(mode);
ShaderManager.getInstance().preDraw();
prepareClientArrays();
if (mode == GL11.GL_QUADS) {
QuadConverter.drawQuadElementsAsTriangles(count, type, indices);
} else {
@@ -2223,6 +2249,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 +2272,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 +2357,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 +2382,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 +2427,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 +2447,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 +2473,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 +2495,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 +2547,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 +2572,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 +4389,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);
}
}
}
}
@@ -16,4 +16,8 @@ public interface StateAwareTessellator {
void angelica$setCeleritasMeshing(boolean active);
int[] angelica$getVertexStates();
int[] angelica$getShaderOverrideBlockIds();
void angelica$setShaderOverrideBlockId(short blockId);
}
@@ -115,6 +115,7 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
@SuppressWarnings("unchecked")
public void copyRawBuffer(int[] rawBuffer, int vertexCount, int[] vertexStates,
int[] shaderOverrideBlockIds,
ChunkBuildBuffers buffers, Material material, boolean isShaderPackOverride,
boolean blockAllowsSmoothLighting) {
if (vertexCount == 0) {
@@ -145,6 +146,8 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
final int worldY = originY + blockY;
final int worldZ = originZ + blockZ;
final short originalBlockId = blockRenderContext.blockId;
int stateIdx = 0;
int facesAtBaseMaterial = 0;
@@ -239,6 +242,9 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
}
final var builder = buffers.get(correctMaterial);
final int shaderOverrideBlockId = shaderOverrideBlockIds[quadIdx * 4];
blockRenderContext.blockId = shaderOverrideBlockId != -1 ? (short) shaderOverrideBlockId : originalBlockId;
if (correctMaterial != material && builder.getEncoder() instanceof IrisExtendedChunkVertexEncoder iris) {
iris.setContext(blockRenderContext);
}
@@ -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);
}
}
@@ -256,6 +270,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
&& block.getLightValue() == 0; // does not emit real block light
buildContext.copyRawBuffer(tessellator.rawBuffer, tessellator.vertexCount,
((StateAwareTessellator)tessellator).angelica$getVertexStates(),
((StateAwareTessellator)tessellator).angelica$getShaderOverrideBlockIds(),
buffers, passMaterial, isShaderPackOverride, blockAllowsSmoothLighting);
tessellator.reset();
tessellator.isDrawing = false;
@@ -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();
@@ -43,6 +43,20 @@ public class CTMUtils {
private static Overrides newOverrides = null;
private static TileLoader tileLoader;
private static final ThreadLocal<TileOverrideImpl.CTMCompact> CURRENT_COMPACT = new ThreadLocal<>();
public static void setCurrentCompact(TileOverrideImpl.CTMCompact compact) {
CURRENT_COMPACT.set(compact);
}
public static TileOverrideImpl.CTMCompact getCurrentCompact() {
return CURRENT_COMPACT.get();
}
public static void clearCurrentCompact() {
CURRENT_COMPACT.remove();
}
private static final StampedLock lock = new StampedLock();
private static class StateAndIterator {
@@ -0,0 +1,39 @@
package com.prupe.mcpatcher.ctm;
import com.prupe.mcpatcher.mal.resource.PropertiesFile;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
public class CompactConnectingCtmProperties {
private final boolean innerSeams;
private final String orientation;
private final Int2IntMap tileReplacementMap = new Int2IntOpenHashMap();
public CompactConnectingCtmProperties(PropertiesFile properties) {
this.innerSeams = properties.getBoolean("innerSeams", false);
this.orientation = properties.getString("orientation", "none").toLowerCase();
for (int i = 0; i < 47; i++) {
String val = properties.getString("ctm." + i, "");
if (!val.isEmpty()) {
try {
int tileIdx = Integer.parseInt(val.trim());
tileReplacementMap.put(i, tileIdx);
} catch (NumberFormatException ignored) {}
}
}
}
public boolean getInnerSeams() {
return innerSeams;
}
public String getOrientation() {
return orientation;
}
public Int2IntMap getTileReplacementMap() {
return tileReplacementMap;
}
}
@@ -0,0 +1,322 @@
package com.prupe.mcpatcher.ctm;
import com.github.bsideup.jabel.Desugar;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import net.minecraft.block.Block;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.util.IIcon;
import net.minecraft.world.IBlockAccess;
public final class CompactCtmQuadProcessor {
private static final int LEFT = 0;
private static final int UP_LEFT = 1;
private static final int UP = 2;
private static final int UP_RIGHT = 3;
private static final int RIGHT = 4;
private static final int DOWN_RIGHT = 5;
private static final int DOWN = 6;
private static final int DOWN_LEFT = 7;
private static final int SPRITE_DEFAULT = 0;
private static final int SPRITE_ALL = 1;
private static final int SPRITE_HORIZONTAL = 2;
private static final int SPRITE_VERTICAL = 3;
private static final int SPRITE_MIXED = 4;
private static final int[] CTM_47_BY_CONNECTIONS = {
0,3,0,3,12,5,12,15,0,3,0,3,12,5,12,15,
1,2,1,2,4,7,4,29,1,2,1,2,13,31,13,14,
0,3,0,3,12,5,12,15,0,3,0,3,12,5,12,15,
1,2,1,2,4,7,4,29,1,2,1,2,13,31,13,14,
36,17,36,17,24,19,24,43,36,17,36,17,24,19,24,43,
16,18,16,18,6,46,6,21,16,18,16,18,28,9,28,22,
36,17,36,17,24,19,24,43,36,17,36,17,24,19,24,43,
37,40,37,40,30,8,30,34,37,40,37,40,25,23,25,45,
0,3,0,3,12,5,12,15,0,3,0,3,12,5,12,15,
1,2,1,2,4,7,4,29,1,2,1,2,13,31,13,14,
0,3,0,3,12,5,12,15,0,3,0,3,12,5,12,15,
1,2,1,2,4,7,4,29,1,2,1,2,13,31,13,14,
36,39,36,39,24,41,24,27,36,39,36,39,24,41,24,27,
16,42,16,42,6,20,6,10,16,42,16,42,28,35,28,44,
36,39,36,39,24,41,24,27,36,39,36,39,24,41,24,27,
37,38,37,38,30,11,30,32,37,38,37,38,25,33,25,26
};
/**
* Face-local basis:
* right = increasing U on the face
* down = increasing V on the face
*/
private static final FaceBasis[] FACE_BASIS = {
new FaceBasis( 1, 0, 0, 0, 0, 1), // 0: Y-
new FaceBasis( 1, 0, 0, 0, 0, 1), // 1: Y+
new FaceBasis(-1, 0, 0, 0, -1, 0), // 2: Z-
new FaceBasis( 1, 0, 0, 0, -1, 0), // 3: Z+
new FaceBasis( 0, 0, 1, 0, -1, 0), // 4: X-
new FaceBasis( 0, 0, -1, 0, -1, 0) // 5: X+
};
/**
* Neighbor offsets in face-local coordinates:
* 0 = left, 1 = up-left, 2 = up, 3 = up-right,
* 4 = right, 5 = down-right, 6 = down, 7 = down-left
*/
private static final int[] RIGHT_FACTOR = {-1, -1, 0, 1, 1, 1, 0, -1};
private static final int[] DOWN_FACTOR = { 0, -1, -1, -1, 0, 1, 1, 1};
/**
* Quadrant side references:
* 0 = top-left
* 1 = top-right
* 2 = bottom-right
* 3 = bottom-left
*/
private static final int[] QUAD_A = {LEFT, UP, RIGHT, DOWN};
private static final int[] QUAD_B = {UP, RIGHT, DOWN, LEFT};
private static final int[] QUAD_DIAG = {UP_LEFT, UP_RIGHT, DOWN_RIGHT, DOWN_LEFT};
private final IIcon[] sprites;
private final CompactConnectingCtmProperties props;
CompactCtmQuadProcessor(IIcon[] sprites, CompactConnectingCtmProperties props) {
this.sprites = new IIcon[5];
if (sprites != null) {
System.arraycopy(sprites, 0, this.sprites, 0, Math.min(sprites.length, this.sprites.length));
}
this.props = props;
}
public boolean processFace(RenderBlocks rb, IBlockAccess world, int x, int y, int z, int face, IIcon origIcon) {
int connections = CtmConnectionHelper.getConnections(world, x, y, z, face, props.getInnerSeams());
int ctmIndex = CTM_47_BY_CONNECTIONS[connections & 0xFF];
IIcon replacement = getReplacementIcon(ctmIndex);
if (replacement != null) {
renderWholeFace(rb, world, x, y, z, face, replacement);
return true;
}
if (connections == 0 || connections == 0xFF) {
IIcon sprite = sprites[getCompactSpriteIndex(connections)];
if (sprite != null) {
renderWholeFace(rb, world, x, y, z, face, sprite);
return true;
}
return false;
}
renderSplitFace(rb, world, x, y, z, face, connections);
return true;
}
private IIcon getReplacementIcon(int ctmIndex) {
Int2IntMap map = props.getTileReplacementMap();
if (map == null || !map.containsKey(ctmIndex)) {
return null;
}
int spriteIndex = map.get(ctmIndex);
if (spriteIndex < 0 || spriteIndex >= sprites.length) {
return null;
}
return sprites[spriteIndex];
}
/**
* Compact 5-sprite variant:
* 0 = default / isolated / mixed
* 1 = all neighbors
* 2 = horizontal line
* 3 = vertical line
*/
private int getCompactSpriteIndex(int connections) {
if (connections == 0xFF) {
return SPRITE_ALL;
}
if ((connections & ((1 << LEFT) | (1 << RIGHT))) == ((1 << LEFT) | (1 << RIGHT))) {
return SPRITE_HORIZONTAL;
}
if ((connections & ((1 << UP) | (1 << DOWN))) == ((1 << UP) | (1 << DOWN))) {
return SPRITE_VERTICAL;
}
return SPRITE_DEFAULT;
}
private void renderSplitFace(RenderBlocks rb, IBlockAccess world, int x, int y, int z, int face, int connections) {
Block block = world.getBlock(x, y, z);
double minX = rb.renderMinX;
double minY = rb.renderMinY;
double minZ = rb.renderMinZ;
double maxX = rb.renderMaxX;
double maxY = rb.renderMaxY;
double maxZ = rb.renderMaxZ;
for (int q = 0; q < 4; q++) {
IIcon icon = sprites[getQuadrantSprite(q, connections)];
if (icon == null) {
continue;
}
setQuadrantBounds(rb, face, q, minX, minY, minZ, maxX, maxY, maxZ);
rb.overrideBlockTexture = icon;
renderFace(rb, block, x, y, z, face, icon);
}
rb.overrideBlockTexture = null;
rb.setRenderBounds(minX, minY, minZ, maxX, maxY, maxZ);
}
private void renderWholeFace(RenderBlocks rb, IBlockAccess world, int x, int y, int z, int face, IIcon icon) {
Block block = world.getBlock(x, y, z);
IIcon saved = rb.overrideBlockTexture;
rb.overrideBlockTexture = icon;
try {
renderFace(rb, block, x, y, z, face, icon);
} finally {
rb.overrideBlockTexture = saved;
}
}
private void renderFace(RenderBlocks rb, Block block, int x, int y, int z, int face, IIcon icon) {
boolean originalFlip = rb.field_152631_f;
rb.field_152631_f = (face == 2 || face == 5);
try {
switch (face) {
case 0 -> rb.renderFaceYNeg(block, x, y, z, icon);
case 1 -> rb.renderFaceYPos(block, x, y, z, icon);
case 2 -> rb.renderFaceZNeg(block, x, y, z, icon);
case 3 -> rb.renderFaceZPos(block, x, y, z, icon);
case 4 -> rb.renderFaceXNeg(block, x, y, z, icon);
case 5 -> rb.renderFaceXPos(block, x, y, z, icon);
}
} finally {
rb.field_152631_f = originalFlip;
}
}
/**
* Quadrants are always interpreted as:
* 0 = top-left, 1 = top-right, 2 = bottom-right, 3 = bottom-left
* in the face-local view.
*/
private void setQuadrantBounds(RenderBlocks rb, int face, int quadrant,
double minX, double minY, double minZ,
double maxX, double maxY, double maxZ) {
double midX = (minX + maxX) * 0.5;
double midY = (minY + maxY) * 0.5;
double midZ = (minZ + maxZ) * 0.5;
switch (face) {
case 0:
case 1:
if (quadrant == 0) rb.setRenderBounds(minX, minY, minZ, midX, maxY, midZ);
if (quadrant == 1) rb.setRenderBounds(midX, minY, minZ, maxX, maxY, midZ);
if (quadrant == 2) rb.setRenderBounds(midX, minY, midZ, maxX, maxY, maxZ);
if (quadrant == 3) rb.setRenderBounds(minX, minY, midZ, midX, maxY, maxZ);
break;
case 2:
if (quadrant == 0) rb.setRenderBounds(midX, midY, minZ, maxX, maxY, maxZ);
if (quadrant == 1) rb.setRenderBounds(minX, midY, minZ, midX, maxY, maxZ);
if (quadrant == 2) rb.setRenderBounds(minX, minY, minZ, midX, midY, maxZ);
if (quadrant == 3) rb.setRenderBounds(midX, minY, minZ, maxX, midY, maxZ);
break;
case 3:
if (quadrant == 0) rb.setRenderBounds(minX, midY, minZ, midX, maxY, maxZ);
if (quadrant == 1) rb.setRenderBounds(midX, midY, minZ, maxX, maxY, maxZ);
if (quadrant == 2) rb.setRenderBounds(midX, minY, minZ, maxX, midY, maxZ);
if (quadrant == 3) rb.setRenderBounds(minX, minY, minZ, midX, midY, maxZ);
break;
case 4:
if (quadrant == 0) rb.setRenderBounds(minX, midY, minZ, maxX, maxY, midZ);
if (quadrant == 1) rb.setRenderBounds(minX, midY, midZ, maxX, maxY, maxZ);
if (quadrant == 2) rb.setRenderBounds(minX, minY, midZ, maxX, midY, maxZ);
if (quadrant == 3) rb.setRenderBounds(minX, minY, minZ, maxX, midY, midZ);
break;
case 5:
if (quadrant == 0) rb.setRenderBounds(minX, midY, midZ, maxX, maxY, maxZ);
if (quadrant == 1) rb.setRenderBounds(minX, midY, minZ, maxX, maxY, midZ);
if (quadrant == 2) rb.setRenderBounds(minX, minY, minZ, maxX, midY, midZ);
if (quadrant == 3) rb.setRenderBounds(minX, minY, midZ, maxX, midY, maxZ);
break;
default:
break;
}
}
/**
* A quadrant is built from two cardinal sides and the diagonal between them.
*/
private int getQuadrantSprite(int quadrant, int connections) {
int sideA = QUAD_A[quadrant];
int sideB = QUAD_B[quadrant];
int diag = QUAD_DIAG[quadrant];
boolean connA = (connections & (1 << sideA)) != 0;
boolean connB = (connections & (1 << sideB)) != 0;
if (connA && connB) {
return (connections & (1 << diag)) != 0 ? SPRITE_ALL : SPRITE_MIXED;
}
if (connA ^ connB) {
int connectedBit = connA ? sideA : sideB;
return (connectedBit == LEFT || connectedBit == RIGHT) ? SPRITE_VERTICAL : SPRITE_HORIZONTAL;
}
return SPRITE_DEFAULT;
}
@Desugar
private record FaceBasis(int rightX, int rightY, int rightZ, int downX, int downY, int downZ) { }
private static final class CtmConnectionHelper {
public static int getConnections(IBlockAccess world, int x, int y, int z, int face, boolean innerSeams) {
Block self = world.getBlock(x, y, z);
FaceBasis basis = FACE_BASIS[face];
int bits = 0;
for (int i = 0; i < 8; i++) {
int dx = RIGHT_FACTOR[i] * basis.rightX + DOWN_FACTOR[i] * basis.downX;
int dy = RIGHT_FACTOR[i] * basis.rightY + DOWN_FACTOR[i] * basis.downY;
int dz = RIGHT_FACTOR[i] * basis.rightZ + DOWN_FACTOR[i] * basis.downZ;
int nx = x + dx;
int ny = y + dy;
int nz = z + dz;
if (world.getBlock(nx, ny, nz) != self) {
continue;
}
if (innerSeams && !shouldRenderInnerSeam(world, nx, ny, nz, face)) {
continue;
}
bits |= (1 << i);
}
return bits;
}
private static boolean shouldRenderInnerSeam(IBlockAccess world, int x, int y, int z, int face) {
int[] normal = RenderBlockState.NORMALS[face];
int sx = x + normal[0];
int sy = y + normal[1];
int sz = z + normal[2];
return world.getBlock(sx, sy, sz).shouldSideBeRendered(world, sx, sy, sz, face);
}
}
}
@@ -28,7 +28,7 @@ import com.prupe.mcpatcher.mal.resource.PropertiesFile;
import com.prupe.mcpatcher.mal.resource.TexturePackAPI;
import com.prupe.mcpatcher.mal.tile.TileLoader;
abstract class TileOverride implements Comparable<TileOverride> {
public abstract class TileOverride implements Comparable<TileOverride> {
private static final MCLogger logger = MCLogger.getLogger(MCLogger.Category.CONNECTED_TEXTURES, "CTM");
@@ -80,6 +80,7 @@ abstract class TileOverride implements Comparable<TileOverride> {
tileLoader);
case "sandstone", "top" -> override = new TileOverrideImpl.Top(properties, tileLoader);
case "repeat", "pattern" -> override = new TileOverrideImpl.Repeat(properties, tileLoader);
case "ctm_compact", "compact" -> override = new TileOverrideImpl.CTMCompact(properties, tileLoader);
default -> properties.error("unknown method \"%s\"", method);
}
@@ -18,7 +18,7 @@ import com.prupe.mcpatcher.mal.resource.PropertiesFile;
import com.prupe.mcpatcher.mal.tile.TileLoader;
import com.prupe.mcpatcher.mal.util.WeightedIndex;
class TileOverrideImpl {
public class TileOverrideImpl {
final static class CTM extends TileOverride {
@@ -481,4 +481,49 @@ class TileOverrideImpl {
return icons[0];
}
}
final public static class CTMCompact extends TileOverride {
private final CompactConnectingCtmProperties properties;
private volatile CompactCtmQuadProcessor processor;
CTMCompact(PropertiesFile propertiesFile, TileLoader tileLoader) {
super(propertiesFile, tileLoader);
this.properties = new CompactConnectingCtmProperties(propertiesFile);
}
@Override
String getMethod() {
return "compact";
}
@Override
String checkTileMap() {
return getNumberOfTiles() == 5 ? null : "requires exactly 5 tiles";
}
@Override
boolean requiresFace() {
return true;
}
@Override
IIcon getTileWorld_Impl(RenderBlockState renderBlockState, IIcon origIcon) {
return origIcon;
}
@Override
IIcon getTileHeld_Impl(RenderBlockState renderBlockState, IIcon origIcon) {
return icons.length > 0 ? icons[0] : origIcon;
}
public CompactCtmQuadProcessor getProcessor() {
CompactCtmQuadProcessor p = this.processor;
if (p == null) {
p = new CompactCtmQuadProcessor(icons, properties);
this.processor = p;
}
return p;
}
}
}
@@ -104,6 +104,8 @@ abstract public class TileOverrideIterator implements Iterator<TileOverride> {
}
public TileOverride go(RenderBlockState renderBlockState, IIcon origIcon) {
CTMUtils.clearCurrentCompact();
this.renderBlockState = renderBlockState;
renderBlockState.setFilter(null);
currentIcon = origIcon;
@@ -121,6 +123,9 @@ abstract public class TileOverrideIterator implements Iterator<TileOverride> {
TileOverride override = next();
IIcon newIcon = getTile(override, renderBlockState, origIcon);
if (newIcon != null) {
if (override instanceof TileOverrideImpl.CTMCompact compact) {
CTMUtils.setCurrentCompact(compact);
}
lastMatchedOverride = override;
skipOverrides.add(override);
currentIcon = newIcon;
+11 -22
View File
@@ -1,11 +1,11 @@
package net.coderbot.iris;
import com.google.common.base.Throwables;
import com.gtnewhorizon.gtnhlib.client.renderer.CapturingTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.Tags;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.StateAwareTessellator;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
import cpw.mods.fml.client.registry.ClientRegistry;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
@@ -13,7 +13,6 @@ import cpw.mods.fml.common.gameevent.InputEvent;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import lombok.Getter;
import net.coderbot.iris.block_context.BlockContextHolder;
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
import net.coderbot.iris.celeritas.IrisCeleritasShaderProvider;
import net.coderbot.iris.compat.dh.DHCompat;
@@ -915,35 +914,25 @@ public class Iris {
ClientRegistry.registerKeyBinding(shaderpackScreenKeybind);
}
static BlockContextHolder contextHolder;
private static int getShaderMaterialOverrideId(Block block, int meta) {
if (contextHolder == null) {
final Reference2ObjectMap<Block, Int2IntMap> blockMetaMatches = BlockRenderingSettings.INSTANCE.getBlockMetaMatches();
if (blockMetaMatches == null) {
return -1;
}
contextHolder = new BlockContextHolder(blockMetaMatches);
}
contextHolder.set(block, meta, (short) block.getRenderType());
return contextHolder.blockId;
}
public static void setShaderMaterialOverride(Block block, int meta) {
if (!enabled)
return;
int blockId = getShaderMaterialOverrideId(block, meta);
final Reference2ObjectMap<Block, Int2IntMap> blockMetaMatches = BlockRenderingSettings.INSTANCE.getBlockMetaMatches();
if (blockMetaMatches == null)
return;
if (TessellatorManager.get() instanceof CapturingTessellator tess)
tess.setShaderBlockId(blockId);
final Int2IntMap metaMap = blockMetaMatches.get(block);
final int blockId = metaMap != null ? metaMap.get(meta) : -1;
if (TessellatorManager.get() instanceof StateAwareTessellator tess)
tess.angelica$setShaderOverrideBlockId((short) blockId);
}
public static void resetShaderMaterialOverride() {
if (!enabled)
return;
if (TessellatorManager.get() instanceof CapturingTessellator tess)
tess.setShaderBlockId(-1);
if (TessellatorManager.get() instanceof StateAwareTessellator tess)
tess.angelica$setShaderOverrideBlockId((short) -1);
}
}
@@ -1,39 +0,0 @@
package net.coderbot.iris.block_context;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import net.minecraft.block.Block;
/**
* Holds block context for shader material ID lookups.
*/
public class BlockContextHolder {
private final Reference2ObjectMap<Block, Int2IntMap> blockMetaMatches;
public int blockId;
public short renderType;
public BlockContextHolder(Reference2ObjectMap<Block, Int2IntMap> blockMetaMatches) {
this.blockMetaMatches = blockMetaMatches;
this.blockId = -1;
this.renderType = -1;
}
/**
* Set the material ID for a block.
* @param block The block
* @param meta The block metadata
* @param renderType The render type
*/
public void set(Block block, int meta, short renderType) {
Int2IntMap metaMap = this.blockMetaMatches != null ? this.blockMetaMatches.get(block) : null;
int id = metaMap != null ? metaMap.get(meta) : -1;
this.blockId = id;
this.renderType = renderType;
}
public void reset() {
this.blockId = -1;
this.renderType = -1;
}
}
@@ -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();
}
}
@@ -6,6 +6,9 @@ import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.StateAwareTessellator;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
import com.llamalad7.mixinextras.sugar.Local;
import com.prupe.mcpatcher.ctm.CTMUtils;
import com.prupe.mcpatcher.ctm.CompactCtmQuadProcessor;
import com.prupe.mcpatcher.ctm.TileOverrideImpl;
import cpw.mods.fml.client.registry.RenderingRegistry;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
@@ -17,6 +20,7 @@ import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.IIcon;
import net.minecraft.util.MathHelper;
import net.minecraft.world.IBlockAccess;
import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Mixin;
@@ -123,7 +127,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 */
@@ -177,4 +181,89 @@ public abstract class MixinRenderBlocks {
@Local(ordinal = 0) Tessellator tessellator) {
((StateAwareTessellator)tessellator).angelica$setAppliedAo(false);
}
@Shadow
public IBlockAccess blockAccess;
@Unique
private boolean compactCtmRendering = false;
@Unique
private CompactCtmQuadProcessor getCompactProcessor() {
TileOverrideImpl.CTMCompact compact = CTMUtils.getCurrentCompact();
if (compact == null) {
return null;
}
return compact.getProcessor();
}
private void handleCompactCtmFace(int face, Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
// recursion protection
if (compactCtmRendering) {
return;
}
if (this.blockAccess == null) {
return;
}
CompactCtmQuadProcessor processor = getCompactProcessor();
if (processor == null) {
return;
}
compactCtmRendering = true;
try {
boolean handled = processor.processFace(
(RenderBlocks)(Object)this,
this.blockAccess,
MathHelper.floor_double(x),
MathHelper.floor_double(y),
MathHelper.floor_double(z),
face,
icon
);
if (handled) {
ci.cancel();
}
} finally {
compactCtmRendering = false;
}
}
@Inject(method = "renderFaceYNeg", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceYNeg(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(0, block, x, y, z, icon, ci);
}
@Inject(method = "renderFaceYPos", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceYPos(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(1, block, x, y, z, icon, ci);
}
@Inject(method = "renderFaceZNeg", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceZNeg(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(2, block, x, y, z, icon, ci);
}
@Inject(method = "renderFaceZPos", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceZPos(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(3, block, x, y, z, icon, ci);
}
@Inject(method = "renderFaceXNeg", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceXNeg(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(4, block, x, y, z, icon, ci);
}
@Inject(method = "renderFaceXPos", at = @At("HEAD"), cancellable = true)
private void compactCtm_onRenderFaceXPos(Block block, double x, double y, double z, IIcon icon, CallbackInfo ci) {
handleCompactCtmFace(5, block, x, y, z, icon, ci);
}
@Inject(method = "renderStandardBlock(Lnet/minecraft/block/Block;III)Z", at = @At("RETURN"))
private void compactCtm_resetAfterBlock(Block block, int x, int y, int z, CallbackInfoReturnable<Boolean> cir) {
CTMUtils.clearCurrentCompact();
}
}
@@ -14,6 +14,12 @@ public class MixinTessellator implements StateAwareTessellator {
@Unique
private final IntArrayList vertexStates = new IntArrayList();
@Unique
private final IntArrayList shaderOverrideBlockIds = new IntArrayList();
@Unique
private short currentShaderOverrideBlockId = -1;
@Unique
private boolean appliedAo;
@@ -33,11 +39,14 @@ public class MixinTessellator implements StateAwareTessellator {
state |= StateAwareTessellator.RENDERED_WITH_VANILLA_AO;
}
this.vertexStates.add(state);
this.shaderOverrideBlockIds.add(currentShaderOverrideBlockId);
}
@Inject(method = "reset", at = @At("RETURN"))
private void resetVertexStates(CallbackInfo ci) {
this.vertexStates.clear();
this.shaderOverrideBlockIds.clear();
this.currentShaderOverrideBlockId = -1;
}
@Override
@@ -49,4 +58,14 @@ public class MixinTessellator implements StateAwareTessellator {
public int[] angelica$getVertexStates() {
return this.vertexStates.elements();
}
@Override
public int[] angelica$getShaderOverrideBlockIds() {
return this.shaderOverrideBlockIds.elements();
}
@Override
public void angelica$setShaderOverrideBlockId(short blockId) {
this.currentShaderOverrideBlockId = blockId;
}
}
@@ -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);
}