Compare commits

...
9 Commits
34 changed files with 853 additions and 106 deletions

No files matched your search

@@ -345,6 +345,17 @@ 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);
@@ -949,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)) {
@@ -1063,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 -> {
}
@@ -1078,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 -> {
}
}
@@ -1101,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 -> {
@@ -2341,6 +2360,8 @@ public class GLStateManager {
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 {
@@ -4368,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; }
@@ -75,6 +75,9 @@ 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);
@@ -207,6 +207,57 @@ class FeedbackManagerTest {
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);
@@ -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; }
@@ -244,6 +244,7 @@ public enum Mixins implements IMixins {
.setApplyIf(() -> AngelicaConfig.enableIris)
.addClientMixins(
"shaders.MixinDroppedItemGlintEdges"
, "shaders.MixinHeldItemGlintEdges"
, "shaders.MixinEntityPickupFX"
, "shaders.MixinEntityRenderer"
, "shaders.MixinGuiIngameForge"
@@ -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);
}
@@ -270,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
}
@@ -220,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();
@@ -388,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
};
@@ -431,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;
@@ -448,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);
@@ -475,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;
}
@@ -592,7 +598,8 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
terrainSource,
translucentSource,
shadowSource,
celeritasTerrainFuture, celeritasTranslucentFuture, celeritasShadowFuture,
shadowTranslucentSource,
celeritasTerrainFuture, celeritasTranslucentFuture, celeritasShadowFuture, celeritasShadowTranslucentFuture,
renderTargets, flippedAfterPrepare, flippedAfterTranslucent,
celeritasShadowFb);
@@ -699,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) {
@@ -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,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;
@@ -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;
@@ -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,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());
}
}
}