mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 01:34:48 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9c45549e35 | ||
|
|
2a20198292 | ||
|
|
f0b68f93af | ||
|
|
dd1d3aa518 | ||
|
|
c9633fd745 | ||
|
|
e2e94b2958 | ||
|
|
3046b043a0 | ||
|
|
8a6c8035db | ||
|
|
2b15370c00 |
No files matched your search
@@ -148,10 +148,21 @@ public class GLStateManager {
|
||||
public static int projGeneration; // projection matrix changes
|
||||
public static int texMatrixGeneration; // texture matrix changes
|
||||
public static int lightingGeneration;
|
||||
public static int fragmentGeneration; // fog + alpha ref
|
||||
public static int fragmentGeneration; // fog + alpha ref + overlay color
|
||||
public static int colorGeneration; // current vertex color
|
||||
public static int clipPlaneGeneration; // clip plane equation changes
|
||||
|
||||
@Getter private static float overlayR = 0.0f;
|
||||
@Getter private static float overlayG = 0.0f;
|
||||
@Getter private static float overlayB = 0.0f;
|
||||
@Getter private static float overlayA = 0.0f;
|
||||
|
||||
public static void setOverlayColor(float r, float g, float b, float a) {
|
||||
if (r == overlayR && g == overlayG && b == overlayB && a == overlayA) return;
|
||||
overlayR = r; overlayG = g; overlayB = b; overlayA = a;
|
||||
fragmentGeneration++;
|
||||
}
|
||||
|
||||
// Deferred vertex attribute upload flags — set when state changes, flushed before draw
|
||||
private static boolean dirtyColorAttrib;
|
||||
private static boolean dirtyNormalAttrib;
|
||||
@@ -3865,7 +3876,12 @@ public class GLStateManager {
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> info.getWidth();
|
||||
case GL11.GL_TEXTURE_HEIGHT -> info.getHeight();
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> info.getInternalFormat();
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
|
||||
if (info.needsInternalFormatResolve() && isRecordingDisplayList()) {
|
||||
throw new IllegalStateException(String.format("glGetTexLevelParameteri(GL_TEXTURE_INTERNAL_FORMAT) needs to resolve a generic compressed format for texture %d during display list recording", getBoundTextureForServerState()));
|
||||
}
|
||||
yield info.getResolvedInternalFormat();
|
||||
}
|
||||
default -> {
|
||||
if (isRecordingDisplayList()) {
|
||||
throw new IllegalStateException(String.format(
|
||||
@@ -5147,8 +5163,12 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
setLightmapTextureCoords(target, s, t);
|
||||
} else {
|
||||
final int unit = target - GL13.GL_TEXTURE0;
|
||||
if (unit >= 2 && unit < 4) {
|
||||
ShaderManager.setCurrentTexCoord(unit, s, t, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
// Units 2+ silently ignored — shader uses v_TexCoord0 for all non-lightmap units
|
||||
}
|
||||
|
||||
public static void glMultiTexCoord2d(int target, double s, double t) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.lwjgl.opengl.GL13;
|
||||
* Packed-long fragment shader permutation key for FFP emulation.
|
||||
*
|
||||
* Layout:
|
||||
* long[0]: global bits (10) + unit 0 (47 bits at offset 10)
|
||||
* long[0]: global bits (11) + unit 0 (47 bits at offset 11)
|
||||
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
|
||||
* Only max(1, nrEnabledUnits) longs are significant.
|
||||
*
|
||||
@@ -63,12 +63,13 @@ public final class FragmentKey {
|
||||
public static final int FOG_EXP = 2;
|
||||
public static final int FOG_EXP2 = 3;
|
||||
|
||||
private static final int GLOBAL_BITS = 10;
|
||||
private static final int GLOBAL_BITS = 11;
|
||||
private static final int BIT_FOG_MODE = 0; // 2 bits
|
||||
private static final int BIT_ALPHA_TEST = 2; // 1 bit
|
||||
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
|
||||
private static final int BIT_SEPARATE_SPECULAR = 6; // 1 bit
|
||||
private static final int BIT_NR_ENABLED_UNITS = 7; // 3 bits
|
||||
private static final int BIT_OVERLAY_ENABLED = 10; // 1 bit
|
||||
|
||||
private static final int U_ENABLED = 0;
|
||||
private static final int U_MODE = 1; // 3 bits
|
||||
@@ -119,6 +120,11 @@ public final class FragmentKey {
|
||||
global |= ((long) (encodeAlphaFunc(GLStateManager.getAlphaState().getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
|
||||
}
|
||||
|
||||
// Damage overlay
|
||||
if (GLStateManager.getOverlayA() != 0.0f) {
|
||||
global |= (1L << BIT_OVERLAY_ENABLED);
|
||||
}
|
||||
|
||||
// Separate specular
|
||||
if (GLStateManager.getLightingState().isEnabled()
|
||||
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
|
||||
@@ -227,6 +233,7 @@ public final class FragmentKey {
|
||||
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
|
||||
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
|
||||
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
|
||||
public boolean overlayEnabled() { return ((packed[0] >> BIT_OVERLAY_ENABLED) & 1) != 0; }
|
||||
|
||||
private long unitBits(int i) {
|
||||
return (i == 0) ? (packed[0] >>> GLOBAL_BITS) : packed[i];
|
||||
@@ -252,6 +259,28 @@ public final class FragmentKey {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Per-unit enable bits as a 4-bit mask (bit i = unit i enabled). */
|
||||
public int enabledUnitMask() {
|
||||
int mask = 0;
|
||||
final int n = Math.min(nrEnabledUnits(), MAX_UNITS);
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (unitEnabled(i)) mask |= (1 << i);
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
/** Per-unit enable bits from a packed scratch as a 4-bit mask (bit i = unit i enabled). */
|
||||
public static int unitMaskFromPacked(long[] packed, int len) {
|
||||
int mask = 0;
|
||||
if (len < 1) return 0;
|
||||
if (((packed[0] >>> GLOBAL_BITS) & 1L) != 0) mask |= 1;
|
||||
final int n = Math.min(len, MAX_UNITS);
|
||||
for (int i = 1; i < n; i++) {
|
||||
if ((packed[i] & 1L) != 0) mask |= (1 << i);
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
public boolean lightmapEnabled() {
|
||||
return nrEnabledUnits() > 1 && unitEnabled(1);
|
||||
}
|
||||
@@ -338,10 +367,10 @@ public final class FragmentKey {
|
||||
default -> "?";
|
||||
};
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b units=%d",
|
||||
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b overlay=%b units=%d",
|
||||
fogName, alphaTestEnabled(),
|
||||
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-",
|
||||
separateSpecular(), nrEnabledUnits()));
|
||||
separateSpecular(), overlayEnabled(), nrEnabledUnits()));
|
||||
for (int i = 0; i < nrEnabledUnits(); i++) {
|
||||
if (!unitEnabled(i)) {
|
||||
sb.append(String.format(" u%d=OFF", i));
|
||||
|
||||
+27
-11
@@ -37,11 +37,11 @@ public final class FragmentShaderGenerator {
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("in vec3 v_SpecularColor;\n");
|
||||
}
|
||||
if (key.textureEnabled()) {
|
||||
sb.append("in vec4 v_TexCoord0;\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("in vec4 v_TexCoord1;\n");
|
||||
// Per-unit texcoord inputs. Each enabled unit sources from its own varying
|
||||
for (int i = 0; i < key.nrEnabledUnits(); i++) {
|
||||
if (key.unitEnabled(i)) {
|
||||
sb.append("in vec4 v_TexCoord").append(i).append(";\n");
|
||||
}
|
||||
}
|
||||
if (key.fogMode() != FragmentKey.FOG_NONE) {
|
||||
sb.append("in float v_FogCoord;\n");
|
||||
@@ -65,6 +65,9 @@ public final class FragmentShaderGenerator {
|
||||
sb.append("uniform vec4 u_TexEnvColor").append(i).append(";\n");
|
||||
}
|
||||
}
|
||||
if (key.overlayEnabled()) {
|
||||
sb.append("uniform vec4 u_OverlayColor;\n");
|
||||
}
|
||||
if (key.fogMode() != FragmentKey.FOG_NONE) {
|
||||
sb.append("uniform vec4 u_FogParams;\n");
|
||||
sb.append("uniform vec4 u_FogColor;\n");
|
||||
@@ -75,11 +78,7 @@ public final class FragmentShaderGenerator {
|
||||
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
|
||||
for (int i = 0; i < key.nrEnabledUnits(); i++) {
|
||||
if (!key.unitEnabled(i)) continue;
|
||||
// Unit 1 is the lightmap — uses v_TexCoord1.
|
||||
// Units 2-3 intentionally share unit 0's texture coordinates since
|
||||
// the vertex shader only provides 2 tex coord varyings (unit 0 and unit 1/lightmap).
|
||||
final String texCoord = (i == 1) ? "v_TexCoord1.st" : "v_TexCoord0.st";
|
||||
sb.append(" vec4 tex").append(i).append("Color = texture(u_Sampler").append(i).append(", ").append(texCoord).append(");\n");
|
||||
sb.append(" vec4 tex").append(i).append("Color = texture(u_Sampler").append(i).append(", v_TexCoord").append(i).append(".st);\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,14 +86,21 @@ public final class FragmentShaderGenerator {
|
||||
if (!key.textureEnabled()) {
|
||||
sb.append(" // No texture -- vertex color only\n");
|
||||
sb.append(" vec4 color = v_Color;\n");
|
||||
emitOverlay(sb, key);
|
||||
return;
|
||||
}
|
||||
|
||||
// Process units in order; "previous" starts as vertex color
|
||||
// Process units in order; "previous" starts as vertex color.
|
||||
boolean firstUnit = true;
|
||||
boolean overlayInjected = false;
|
||||
for (int i = 0; i < key.nrEnabledUnits(); i++) {
|
||||
if (!key.unitEnabled(i)) continue;
|
||||
|
||||
if (!firstUnit && !overlayInjected) {
|
||||
emitOverlay(sb, key);
|
||||
overlayInjected = true;
|
||||
}
|
||||
|
||||
final String texVar = "tex" + i + "Color";
|
||||
final String envColorVar = "u_TexEnvColor" + i;
|
||||
final String prevVar = firstUnit ? "v_Color" : "color";
|
||||
@@ -107,6 +113,16 @@ public final class FragmentShaderGenerator {
|
||||
}
|
||||
firstUnit = false;
|
||||
}
|
||||
|
||||
if (!overlayInjected) emitOverlay(sb, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit the modern-style damage overlay mix.
|
||||
*/
|
||||
private static void emitOverlay(StringBuilder sb, FragmentKey key) {
|
||||
if (!key.overlayEnabled()) return;
|
||||
sb.append(" color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);\n");
|
||||
}
|
||||
|
||||
private static void emitSimpleUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
|
||||
|
||||
+12
-18
@@ -22,12 +22,10 @@ public final class GeometryShaderGenerator {
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("in vec3 v_SpecularColor_gs[];\n");
|
||||
}
|
||||
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
|
||||
sb.append("in vec4 v_TexCoord0_gs[];\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("in vec4 v_TexCoord1_gs[];\n");
|
||||
}
|
||||
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append("in vec4 v_TexCoord0_gs[];\n");
|
||||
if (key.lightmapEnabled()) sb.append("in vec4 v_TexCoord1_gs[];\n");
|
||||
if (key.unitTexCoordEnabled(2)) sb.append("in vec4 v_TexCoord2_gs[];\n");
|
||||
if (key.unitTexCoordEnabled(3)) sb.append("in vec4 v_TexCoord3_gs[];\n");
|
||||
if (key.fogEnabled()) {
|
||||
sb.append("in float v_FogCoord_gs[];\n");
|
||||
}
|
||||
@@ -37,12 +35,10 @@ public final class GeometryShaderGenerator {
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("out vec3 v_SpecularColor;\n");
|
||||
}
|
||||
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
|
||||
sb.append("out vec4 v_TexCoord0;\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("out vec4 v_TexCoord1;\n");
|
||||
}
|
||||
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append("out vec4 v_TexCoord0;\n");
|
||||
if (key.lightmapEnabled()) sb.append("out vec4 v_TexCoord1;\n");
|
||||
if (key.unitTexCoordEnabled(2)) sb.append("out vec4 v_TexCoord2;\n");
|
||||
if (key.unitTexCoordEnabled(3)) sb.append("out vec4 v_TexCoord3;\n");
|
||||
if (key.fogEnabled()) {
|
||||
sb.append("out float v_FogCoord;\n");
|
||||
}
|
||||
@@ -82,12 +78,10 @@ public final class GeometryShaderGenerator {
|
||||
if (key.separateSpecular()) {
|
||||
sb.append(" v_SpecularColor = v_SpecularColor_gs").append(idx).append(";\n");
|
||||
}
|
||||
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
|
||||
sb.append(" v_TexCoord0 = v_TexCoord0_gs").append(idx).append(";\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append(" v_TexCoord1 = v_TexCoord1_gs").append(idx).append(";\n");
|
||||
}
|
||||
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append(" v_TexCoord0 = v_TexCoord0_gs").append(idx).append(";\n");
|
||||
if (key.lightmapEnabled()) sb.append(" v_TexCoord1 = v_TexCoord1_gs").append(idx).append(";\n");
|
||||
if (key.unitTexCoordEnabled(2)) sb.append(" v_TexCoord2 = v_TexCoord2_gs").append(idx).append(";\n");
|
||||
if (key.unitTexCoordEnabled(3)) sb.append(" v_TexCoord3 = v_TexCoord3_gs").append(idx).append(";\n");
|
||||
if (key.fogEnabled()) {
|
||||
sb.append(" v_FogCoord = v_FogCoord_gs").append(idx).append(";\n");
|
||||
}
|
||||
|
||||
@@ -32,12 +32,12 @@ public class Program {
|
||||
public int locProjectionMatrix = -1;
|
||||
public int locMVPMatrix = -1;
|
||||
public int locNormalMatrix = -1;
|
||||
public int locTextureMatrix0 = -1;
|
||||
public final int[] locTextureMatrix = { -1, -1, -1, -1 };
|
||||
|
||||
// Current state defaults
|
||||
public int locCurrentNormal = -1;
|
||||
public int locCurrentColor = -1;
|
||||
public int locCurrentTexCoord = -1;
|
||||
public final int[] locCurrentTexCoord = { -1, -1, -1, -1 };
|
||||
public int locCurrentLightmapCoord = -1;
|
||||
public int locLightmapTextureMatrix = -1;
|
||||
|
||||
@@ -92,6 +92,7 @@ public class Program {
|
||||
public final int[] locSampler = { -1, -1, -1, -1 };
|
||||
public int locAlphaRef = -1;
|
||||
public final int[] locTexEnvColor = { -1, -1, -1, -1 };
|
||||
public int locOverlayColor = -1;
|
||||
public int locFogParams = -1;
|
||||
public int locFogColor = -1;
|
||||
|
||||
@@ -107,11 +108,13 @@ public class Program {
|
||||
locProjectionMatrix = loc("u_ProjectionMatrix");
|
||||
locMVPMatrix = loc("u_MVPMatrix");
|
||||
locNormalMatrix = loc("u_NormalMatrix");
|
||||
locTextureMatrix0 = loc("u_TextureMatrix0");
|
||||
for (int i = 0; i < 4; i++) {
|
||||
locTextureMatrix[i] = loc("u_TextureMatrix" + i);
|
||||
locCurrentTexCoord[i] = loc("u_CurrentTexCoord" + i);
|
||||
}
|
||||
|
||||
locCurrentNormal = loc("u_CurrentNormal");
|
||||
locCurrentColor = loc("u_CurrentColor");
|
||||
locCurrentTexCoord = loc("u_CurrentTexCoord");
|
||||
locCurrentLightmapCoord = loc("u_CurrentLightmapCoord");
|
||||
locLightmapTextureMatrix = loc("u_LightmapTextureMatrix");
|
||||
|
||||
@@ -169,6 +172,7 @@ public class Program {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
locTexEnvColor[i] = loc("u_TexEnvColor" + i);
|
||||
}
|
||||
locOverlayColor = loc("u_OverlayColor");
|
||||
locFogParams = loc("u_FogParams");
|
||||
locFogColor = loc("u_FogColor");
|
||||
}
|
||||
|
||||
@@ -42,11 +42,19 @@ public class ShaderManager {
|
||||
private int currentFKLen = 0;
|
||||
|
||||
@Getter private static final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
|
||||
@Getter private static final Vector4f currentTexCoord = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
private static final Vector4f[] currentTexCoords = {
|
||||
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
|
||||
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
|
||||
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
|
||||
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
|
||||
};
|
||||
@Getter private static final Vec3fStack normalStack = new Vec3fStack(currentNormal);
|
||||
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoord);
|
||||
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoords[0]);
|
||||
@Getter private static int normalGeneration;
|
||||
@Getter private static int texCoordGeneration;
|
||||
|
||||
public static Vector4f getCurrentTexCoord() { return currentTexCoords[0]; }
|
||||
public static Vector4f getCurrentTexCoord(int unit) { return currentTexCoords[unit]; }
|
||||
@Getter private boolean enabled = false;
|
||||
private int currentVertexFlags = VertexFlags.TEXTURE_BIT | VertexFlags.COLOR_BIT | VertexFlags.NORMAL_BIT;
|
||||
|
||||
@@ -108,8 +116,9 @@ public class ShaderManager {
|
||||
}
|
||||
}
|
||||
|
||||
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
|
||||
final int fkLen = FragmentKey.packFromState(currentFKScratch);
|
||||
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
|
||||
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask);
|
||||
|
||||
if (vkPacked != currentVertexKeyPacked || !Arrays.equals(currentFKScratch, 0, fkLen, currentFKPacked, 0, currentFKLen)) {
|
||||
commitVariant(vkPacked, fkLen);
|
||||
@@ -133,8 +142,9 @@ public class ShaderManager {
|
||||
}
|
||||
|
||||
private void updateVariant(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
|
||||
final int fkLen = FragmentKey.packFromState(currentFKScratch);
|
||||
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
|
||||
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask);
|
||||
commitVariant(vkPacked, fkLen);
|
||||
}
|
||||
|
||||
@@ -158,7 +168,12 @@ public class ShaderManager {
|
||||
}
|
||||
|
||||
public static void setCurrentTexCoord(float s, float t, float r, float q) {
|
||||
currentTexCoord.set(s, t, r, q);
|
||||
currentTexCoords[0].set(s, t, r, q);
|
||||
texCoordGeneration++;
|
||||
}
|
||||
|
||||
public static void setCurrentTexCoord(int unit, float s, float t, float r, float q) {
|
||||
currentTexCoords[unit].set(s, t, r, q);
|
||||
texCoordGeneration++;
|
||||
}
|
||||
|
||||
|
||||
@@ -111,10 +111,10 @@ public class Uniforms {
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.getVertexKey().hasVertexTexCoord() && program.getVertexKey().textureEnabled()) {
|
||||
if (program.getVertexKey().anyUnitTexCoordEnabled()) {
|
||||
final int texGen = ShaderManager.getTexCoordGeneration();
|
||||
if (programChanged || texGen != lastTexCoordGen) {
|
||||
uploadCurrentTexCoord(program);
|
||||
uploadCurrentTexCoords(program);
|
||||
lastTexCoordGen = texGen;
|
||||
}
|
||||
}
|
||||
@@ -198,16 +198,15 @@ public class Uniforms {
|
||||
RENDER_BACKEND.uniformMatrix4(program.locMVPMatrix, false, mat4Buf);
|
||||
}
|
||||
|
||||
// Texture matrices
|
||||
// Texture matrices (per-unit).
|
||||
if (texMatChanged) {
|
||||
// Texture matrix unit 0
|
||||
if (program.locTextureMatrix0 != -1) {
|
||||
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
|
||||
texMat.get(mat4Buf);
|
||||
RENDER_BACKEND.uniformMatrix4(program.locTextureMatrix0, false, mat4Buf);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (program.locTextureMatrix[i] != -1) {
|
||||
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(i);
|
||||
texMat.get(mat4Buf);
|
||||
RENDER_BACKEND.uniformMatrix4(program.locTextureMatrix[i], false, mat4Buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Texture matrix unit 1 (lightmap)
|
||||
if (program.locLightmapTextureMatrix != -1) {
|
||||
final Matrix4f lmTexMat = GLStateManager.getTextures().getTextureUnitMatrix(1);
|
||||
lmTexMat.get(mat4Buf);
|
||||
@@ -324,10 +323,12 @@ public class Uniforms {
|
||||
RENDER_BACKEND.uniform4(program.locCurrentColor, vec4Buf);
|
||||
}
|
||||
|
||||
private void uploadCurrentTexCoord(Program program) {
|
||||
if (program.locCurrentTexCoord == -1) return;
|
||||
final var tc = ShaderManager.getCurrentTexCoord();
|
||||
uploadVec4(program.locCurrentTexCoord, tc);
|
||||
private void uploadCurrentTexCoords(Program program) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (program.locCurrentTexCoord[i] != -1) {
|
||||
uploadVec4(program.locCurrentTexCoord[i], ShaderManager.getCurrentTexCoord(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void uploadCurrentLightmapCoord(Program program) {
|
||||
@@ -421,6 +422,16 @@ public class Uniforms {
|
||||
}
|
||||
}
|
||||
|
||||
if (fk.overlayEnabled() && program.locOverlayColor != -1) {
|
||||
vec4Buf.clear();
|
||||
vec4Buf.put(GLStateManager.getOverlayR());
|
||||
vec4Buf.put(GLStateManager.getOverlayG());
|
||||
vec4Buf.put(GLStateManager.getOverlayB());
|
||||
vec4Buf.put(GLStateManager.getOverlayA());
|
||||
vec4Buf.flip();
|
||||
RENDER_BACKEND.uniform4(program.locOverlayColor, vec4Buf);
|
||||
}
|
||||
|
||||
// Fog
|
||||
if (fk.fogMode() != FragmentKey.FOG_NONE) {
|
||||
uploadFog(program);
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.MatrixHelper;
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
@@ -11,9 +12,14 @@ import org.lwjgl.opengl.GL12;
|
||||
/**
|
||||
* Compact key capturing the vertex shader permutation axes for FFP emulation.
|
||||
* Packed into a single long for fast hash/compare.
|
||||
*
|
||||
* Per-unit fields are 4-bit base-indexed: bit (BASE + i) for unit i in [0..3].
|
||||
* Unit 1 is the lightmap; texcoord enable for unit 1 also gates the lightmap-specific code paths.
|
||||
*/
|
||||
public final class VertexKey {
|
||||
|
||||
public static final int MAX_UNITS = 4;
|
||||
|
||||
private final long packed;
|
||||
|
||||
private static final int BIT_LIGHTING = 0;
|
||||
@@ -24,26 +30,25 @@ public final class VertexKey {
|
||||
private static final int BIT_COLOR_MATERIAL = 5;
|
||||
private static final int BIT_SEPARATE_SPECULAR = 6;
|
||||
private static final int BIT_FOG = 7;
|
||||
private static final int BIT_FOG_DIST_MODE = 8;
|
||||
private static final int BIT_FOG_DIST_MODE = 8; // 2 bits: 8-9
|
||||
private static final int BIT_NORMALIZE = 10;
|
||||
private static final int BIT_RESCALE_NORMAL = 11;
|
||||
private static final int BIT_TEXTURE = 12;
|
||||
private static final int BIT_LIGHTMAP = 13;
|
||||
private static final int BIT_TEX_MATRIX = 14;
|
||||
private static final int BIT_HAS_VERTEX_COLOR = 15;
|
||||
private static final int BIT_HAS_VERTEX_NORMAL = 16;
|
||||
private static final int BIT_HAS_VERTEX_TEX = 17;
|
||||
private static final int BIT_HAS_VERTEX_LIGHTMAP = 18;
|
||||
private static final int BIT_COLOR_MAT_MODE = 19;
|
||||
// bits 19-21: colorMaterialMode (3 bits)
|
||||
// TexGen modes: 3 bits each (0=NONE, 1=OBJ_LINEAR, 2=EYE_LINEAR)
|
||||
private static final int BIT_TEXGEN_S = 22;
|
||||
private static final int BIT_TEXGEN_T = 25;
|
||||
private static final int BIT_TEXGEN_R = 28;
|
||||
private static final int BIT_TEXGEN_Q = 31;
|
||||
// bits 31-33: texGenModeQ (3 bits)
|
||||
private static final int BIT_CLIP_PLANES = 34;
|
||||
private static final int BIT_WIDE_LINE = 35;
|
||||
// Per-unit texcoord enable: bits 12-15 (unit 0..3). Unit 1 doubles as the lightmap flag.
|
||||
private static final int BIT_UNIT_TEX_BASE = 12;
|
||||
private static final int BIT_HAS_VERTEX_COLOR = 16;
|
||||
private static final int BIT_HAS_VERTEX_NORMAL = 17;
|
||||
private static final int BIT_HAS_VERTEX_TEX = 18;
|
||||
private static final int BIT_HAS_VERTEX_LIGHTMAP = 19;
|
||||
private static final int BIT_COLOR_MAT_MODE = 20; // 3 bits: 20-22
|
||||
// TexGen modes (unit 0 only, for now): 3 bits each (0=NONE, 1=OBJ_LINEAR, 2=EYE_LINEAR).
|
||||
private static final int BIT_TEXGEN_S = 23; // 3 bits: 23-25
|
||||
private static final int BIT_TEXGEN_T = 26; // 3 bits: 26-28
|
||||
private static final int BIT_TEXGEN_R = 29; // 3 bits: 29-31
|
||||
private static final int BIT_TEXGEN_Q = 32; // 3 bits: 32-34
|
||||
private static final int BIT_CLIP_PLANES = 35;
|
||||
private static final int BIT_WIDE_LINE = 36;
|
||||
// Per-unit texmat enable: bits 37-40 (unit 0..3). Unit 1 is the lightmap matrix.
|
||||
private static final int BIT_UNIT_TEXMAT_BASE = 37;
|
||||
|
||||
public static final int TG_NONE = 0;
|
||||
public static final int TG_OBJ_LINEAR = 1;
|
||||
@@ -72,9 +77,10 @@ public final class VertexKey {
|
||||
public int fogDistanceMode() { return (int)((packed >> BIT_FOG_DIST_MODE) & 0x3); }
|
||||
public boolean normalizeEnabled() { return bit(BIT_NORMALIZE); }
|
||||
public boolean rescaleNormalsEnabled() { return bit(BIT_RESCALE_NORMAL); }
|
||||
public boolean textureEnabled() { return bit(BIT_TEXTURE); }
|
||||
public boolean lightmapEnabled() { return bit(BIT_LIGHTMAP); }
|
||||
public boolean textureMatrixEnabled() { return bit(BIT_TEX_MATRIX); }
|
||||
public boolean unitTexCoordEnabled(int unit) { return bit(BIT_UNIT_TEX_BASE + unit); }
|
||||
public boolean unitTexMatEnabled(int unit) { return bit(BIT_UNIT_TEXMAT_BASE + unit); }
|
||||
public boolean anyUnitTexCoordEnabled() { return ((packed >> BIT_UNIT_TEX_BASE) & 0xF) != 0; }
|
||||
public boolean lightmapEnabled() { return unitTexCoordEnabled(1); }
|
||||
public boolean hasVertexColor() { return bit(BIT_HAS_VERTEX_COLOR); }
|
||||
public boolean hasVertexNormal() { return bit(BIT_HAS_VERTEX_NORMAL); }
|
||||
public boolean hasVertexTexCoord() { return bit(BIT_HAS_VERTEX_TEX); }
|
||||
@@ -82,28 +88,18 @@ public final class VertexKey {
|
||||
/** Color material mode (CM_AMBIENT, CM_DIFFUSE, CM_SPECULAR, CM_EMISSION, CM_AMBIENT_AND_DIFFUSE). Only meaningful when colorMaterialEnabled(). */
|
||||
public int colorMaterialMode() { return (int)((packed >> BIT_COLOR_MAT_MODE) & 0x7); }
|
||||
|
||||
/** TexGen mode for S coordinate (TG_NONE, TG_OBJ_LINEAR, TG_EYE_LINEAR). */
|
||||
/** TexGen mode for S coordinate (TG_NONE, TG_OBJ_LINEAR, TG_EYE_LINEAR). Unit 0 only; see TODO in packFromState. */
|
||||
public int texGenModeS() { return (int)((packed >> BIT_TEXGEN_S) & 0x7); }
|
||||
/** TexGen mode for T coordinate. */
|
||||
public int texGenModeT() { return (int)((packed >> BIT_TEXGEN_T) & 0x7); }
|
||||
/** TexGen mode for R coordinate. */
|
||||
public int texGenModeR() { return (int)((packed >> BIT_TEXGEN_R) & 0x7); }
|
||||
/** TexGen mode for Q coordinate. */
|
||||
public int texGenModeQ() { return (int)((packed >> BIT_TEXGEN_Q) & 0x7); }
|
||||
/** Whether any texgen coordinate is enabled. */
|
||||
public boolean texGenEnabled() { return texGenModeS() != TG_NONE || texGenModeT() != TG_NONE || texGenModeR() != TG_NONE || texGenModeQ() != TG_NONE; }
|
||||
/** Whether any clip plane is enabled. */
|
||||
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
|
||||
/** Whether wide line GS emulation is active for this variant. */
|
||||
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
|
||||
|
||||
/** Whether vertex color replaces material ambient in this variant. */
|
||||
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
|
||||
/** Whether vertex color replaces material diffuse in this variant. */
|
||||
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
|
||||
/** Whether vertex color replaces material specular in this variant. */
|
||||
public boolean cmReplacesSpecular() { return colorMaterialMode() == CM_SPECULAR; }
|
||||
/** Whether vertex color replaces material emission in this variant. */
|
||||
public boolean cmReplacesEmission() { return colorMaterialMode() == CM_EMISSION; }
|
||||
|
||||
private boolean bit(int pos) { return ((packed >> pos) & 1) != 0; }
|
||||
@@ -127,7 +123,7 @@ public final class VertexKey {
|
||||
};
|
||||
}
|
||||
|
||||
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
|
||||
long bits = 0;
|
||||
|
||||
final boolean lighting = GLStateManager.getLightingState().isEnabled();
|
||||
@@ -172,23 +168,18 @@ public final class VertexKey {
|
||||
bits |= (1L << BIT_RESCALE_NORMAL);
|
||||
}
|
||||
|
||||
// Texture unit 0 (base texture)
|
||||
if (GLStateManager.getTextures().getTextureUnitStates(0).isEnabled()) {
|
||||
bits |= (1L << BIT_TEXTURE);
|
||||
}
|
||||
// Texture unit 1 (lightmap)
|
||||
if (GLStateManager.getTextures().getTextureUnitStates(1).isEnabled()) {
|
||||
bits |= (1L << BIT_LIGHTMAP);
|
||||
final TextureUnitArray texUnit = GLStateManager.getTextures();
|
||||
for (int i = 0; i < MAX_UNITS; i++) {
|
||||
if ((fragUnitMask & (1 << i)) != 0) {
|
||||
bits |= (1L << (BIT_UNIT_TEX_BASE + i));
|
||||
}
|
||||
if (!MatrixHelper.isIdentity(texUnit.getTextureUnitMatrix(i))) {
|
||||
bits |= (1L << (BIT_UNIT_TEXMAT_BASE + i));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if texture matrix unit 0 is non-identity
|
||||
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
|
||||
if (!MatrixHelper.isIdentity(texMat)) {
|
||||
bits |= (1L << BIT_TEX_MATRIX);
|
||||
}
|
||||
|
||||
// TexGen (unit 0 only) — encode per-coordinate mode if enabled
|
||||
final var texUnit = GLStateManager.getTextures();
|
||||
// TexGen (unit 0 only) - per-coordinate mode if enabled.
|
||||
// TODO: per-unit texgen for units 2/3. Deferred - no MC code path uses glTexGen on those.
|
||||
final TexGenState tg = texUnit.getTexGenState(0);
|
||||
boolean anyTexGen = false;
|
||||
if (texUnit.getTexGenSStates(0).isEnabled() && tg.getMode(GL11.GL_S) != 0) {
|
||||
@@ -211,9 +202,9 @@ public final class VertexKey {
|
||||
bits |= ((long) m & 0x7) << BIT_TEXGEN_Q;
|
||||
if (m != TG_NONE) anyTexGen = true;
|
||||
}
|
||||
// Force texture matrix on when texgen is active (end portal pattern always uses it)
|
||||
// Force unit-0 texture matrix on when texgen is active (end portal pattern always uses it).
|
||||
if (anyTexGen) {
|
||||
bits |= (1L << BIT_TEX_MATRIX);
|
||||
bits |= (1L << BIT_UNIT_TEXMAT_BASE);
|
||||
}
|
||||
|
||||
// Vertex format flags
|
||||
@@ -222,12 +213,10 @@ public final class VertexKey {
|
||||
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
|
||||
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
|
||||
|
||||
// Clip planes
|
||||
if (GLStateManager.anyClipPlaneEnabled()) {
|
||||
bits |= (1L << BIT_CLIP_PLANES);
|
||||
}
|
||||
|
||||
// Wide line GS emulation
|
||||
if (GLStateManager.wideLineEmulationActive) {
|
||||
bits |= (1L << BIT_WIDE_LINE);
|
||||
}
|
||||
@@ -235,8 +224,8 @@ public final class VertexKey {
|
||||
return bits;
|
||||
}
|
||||
|
||||
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
|
||||
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap));
|
||||
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
|
||||
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragUnitMask));
|
||||
}
|
||||
|
||||
static VertexKey fromPacked(long packed) {
|
||||
@@ -257,9 +246,11 @@ public final class VertexKey {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("FFPVertexKey[0x%09X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%b lm=%b col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b]",
|
||||
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b]",
|
||||
packed, lightingEnabled(), light0Enabled(), light1Enabled(),
|
||||
colorMaterialEnabled(), fogEnabled(), textureEnabled(), lightmapEnabled(),
|
||||
colorMaterialEnabled(), fogEnabled(),
|
||||
unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0,
|
||||
unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0,
|
||||
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(),
|
||||
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation());
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ public final class VertexShaderGenerator {
|
||||
source = source.replace("v_SpecularColor", "v_SpecularColor_gs")
|
||||
.replace("v_TexCoord0", "v_TexCoord0_gs")
|
||||
.replace("v_TexCoord1", "v_TexCoord1_gs")
|
||||
.replace("v_TexCoord2", "v_TexCoord2_gs")
|
||||
.replace("v_TexCoord3", "v_TexCoord3_gs")
|
||||
.replace("v_FogCoord", "v_FogCoord_gs")
|
||||
.replace("v_Color", "v_Color_gs");
|
||||
}
|
||||
@@ -76,11 +78,14 @@ public final class VertexShaderGenerator {
|
||||
sb.append("uniform mat3 u_NormalMatrix;\n");
|
||||
}
|
||||
|
||||
if (key.textureMatrixEnabled()) {
|
||||
sb.append("uniform mat4 u_TextureMatrix0;\n");
|
||||
for (int i = 0; i < VertexKey.MAX_UNITS; i++) {
|
||||
if (i == 1) continue;
|
||||
if (key.unitTexMatEnabled(i)) {
|
||||
sb.append("uniform mat4 u_TextureMatrix").append(i).append(";\n");
|
||||
}
|
||||
}
|
||||
|
||||
// TexGen plane uniforms
|
||||
// TexGen plane uniforms (unit 0 only - TODO: per-unit)
|
||||
if (key.texGenModeS() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneS;\n");
|
||||
if (key.texGenModeS() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneS;\n");
|
||||
if (key.texGenModeT() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneT;\n");
|
||||
@@ -98,9 +103,11 @@ public final class VertexShaderGenerator {
|
||||
sb.append("uniform vec4 u_CurrentColor;\n");
|
||||
}
|
||||
|
||||
if (!key.hasVertexTexCoord() && key.textureEnabled()) {
|
||||
sb.append("uniform vec4 u_CurrentTexCoord;\n");
|
||||
if (key.unitTexCoordEnabled(0) && !key.hasVertexTexCoord()) {
|
||||
sb.append("uniform vec4 u_CurrentTexCoord0;\n");
|
||||
}
|
||||
if (key.unitTexCoordEnabled(2)) sb.append("uniform vec4 u_CurrentTexCoord2;\n");
|
||||
if (key.unitTexCoordEnabled(3)) sb.append("uniform vec4 u_CurrentTexCoord3;\n");
|
||||
|
||||
if (key.lightmapEnabled() && !key.hasVertexLightmap()) {
|
||||
sb.append("uniform vec2 u_CurrentLightmapCoord;\n");
|
||||
@@ -178,12 +185,15 @@ public final class VertexShaderGenerator {
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("out vec3 v_SpecularColor;\n");
|
||||
}
|
||||
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
|
||||
// Per-unit varying outputs. Each enabled texture unit (and texgen, which targets unit 0) gets its own v_TexCoord<i>.
|
||||
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) {
|
||||
sb.append("out vec4 v_TexCoord0;\n");
|
||||
}
|
||||
if (key.lightmapEnabled()) {
|
||||
sb.append("out vec4 v_TexCoord1;\n");
|
||||
}
|
||||
if (key.unitTexCoordEnabled(2)) sb.append("out vec4 v_TexCoord2;\n");
|
||||
if (key.unitTexCoordEnabled(3)) sb.append("out vec4 v_TexCoord3;\n");
|
||||
if (key.fogEnabled()) {
|
||||
sb.append("out float v_FogCoord;\n");
|
||||
}
|
||||
@@ -331,22 +341,18 @@ public final class VertexShaderGenerator {
|
||||
private static void emitTexCoordPassthrough(StringBuilder sb, VertexKey key) {
|
||||
if (key.texGenEnabled()) {
|
||||
emitTexGenCoordGeneration(sb, key);
|
||||
} else if (key.textureEnabled() || key.hasVertexTexCoord()) {
|
||||
sb.append(" // Texture coordinates\n");
|
||||
if (key.hasVertexTexCoord()) {
|
||||
if (key.textureMatrixEnabled()) {
|
||||
sb.append(" v_TexCoord0 = u_TextureMatrix0 * vec4(a_TexCoord0, 0.0, 1.0);\n");
|
||||
} else {
|
||||
sb.append(" v_TexCoord0 = vec4(a_TexCoord0, 0.0, 1.0);\n");
|
||||
}
|
||||
} else if (key.unitTexCoordEnabled(0)) {
|
||||
sb.append(" // Texture coordinates - unit 0\n");
|
||||
final String src = key.hasVertexTexCoord()
|
||||
? "vec4(a_TexCoord0, 0.0, 1.0)"
|
||||
: "u_CurrentTexCoord0";
|
||||
if (key.unitTexMatEnabled(0)) {
|
||||
sb.append(" v_TexCoord0 = u_TextureMatrix0 * ").append(src).append(";\n");
|
||||
} else {
|
||||
if (key.textureMatrixEnabled()) {
|
||||
sb.append(" v_TexCoord0 = u_TextureMatrix0 * u_CurrentTexCoord;\n");
|
||||
} else {
|
||||
sb.append(" v_TexCoord0 = u_CurrentTexCoord;\n");
|
||||
}
|
||||
sb.append(" v_TexCoord0 = ").append(src).append(";\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (key.lightmapEnabled()) {
|
||||
if (key.hasVertexLightmap()) {
|
||||
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
|
||||
@@ -354,6 +360,16 @@ public final class VertexShaderGenerator {
|
||||
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0);\n");
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 2; i < VertexKey.MAX_UNITS; i++) {
|
||||
if (!key.unitTexCoordEnabled(i)) continue;
|
||||
final String src = "u_CurrentTexCoord" + i;
|
||||
if (key.unitTexMatEnabled(i)) {
|
||||
sb.append(" v_TexCoord").append(i).append(" = u_TextureMatrix").append(i).append(" * ").append(src).append(";\n");
|
||||
} else {
|
||||
sb.append(" v_TexCoord").append(i).append(" = ").append(src).append(";\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,7 @@ public class TextureInfo {
|
||||
|
||||
@Getter protected final int id;
|
||||
protected int internalFormat = -1;
|
||||
protected int resolvedInternalFormat = -1;
|
||||
protected int width = -1;
|
||||
protected int height = -1;
|
||||
|
||||
@@ -34,6 +35,17 @@ public class TextureInfo {
|
||||
return internalFormat;
|
||||
}
|
||||
|
||||
public int getResolvedInternalFormat() {
|
||||
if (resolvedInternalFormat == -1) {
|
||||
resolvedInternalFormat = RenderSystem.getTexLevelParameteri(id, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
|
||||
}
|
||||
return resolvedInternalFormat;
|
||||
}
|
||||
|
||||
public boolean needsInternalFormatResolve() {
|
||||
return resolvedInternalFormat == -1;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
if (width == -1) {
|
||||
width = RenderSystem.getTexLevelParameteri(id, 0, GL11.GL_TEXTURE_WIDTH);
|
||||
|
||||
@@ -5,6 +5,9 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL21;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.Buffer;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -45,6 +48,7 @@ public class TextureInfoCache {
|
||||
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
|
||||
if (info == null) return;
|
||||
info.internalFormat = internalformat;
|
||||
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
|
||||
info.width = width;
|
||||
info.height = height;
|
||||
}
|
||||
@@ -55,11 +59,24 @@ public class TextureInfoCache {
|
||||
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
|
||||
if (info == null) return;
|
||||
info.internalFormat = internalformat;
|
||||
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
|
||||
info.width = width;
|
||||
info.height = height;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isGenericCompressedInternalFormat(int internalFormat) {
|
||||
return switch (internalFormat) {
|
||||
case GL30.GL_COMPRESSED_RED,
|
||||
GL30.GL_COMPRESSED_RG,
|
||||
GL13.GL_COMPRESSED_RGB,
|
||||
GL13.GL_COMPRESSED_RGBA,
|
||||
GL21.GL_COMPRESSED_SRGB,
|
||||
GL21.GL_COMPRESSED_SRGB_ALPHA -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
public void onDeleteTexture(int id) {
|
||||
if (id < 0) return;
|
||||
|
||||
|
||||
+96
@@ -6,6 +6,10 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL21;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.EXTTextureCompressionS3TC;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -15,6 +19,98 @@ import static org.junit.jupiter.api.Assertions.*;
|
||||
@ExtendWith(GLSMExtension.class)
|
||||
public class GLSM_TextureInfoCache_UnitTest {
|
||||
|
||||
@Test
|
||||
void testGenericCompressedUploadResolvesToSpecificFormat() {
|
||||
int texId = GL11.glGenTextures();
|
||||
try {
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);
|
||||
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
|
||||
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL13.GL_COMPRESSED_RGBA, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
|
||||
|
||||
int format = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
|
||||
assertNotEquals(-1, format);
|
||||
assertNotEquals(GL30.GL_COMPRESSED_RED, format);
|
||||
assertNotEquals(GL30.GL_COMPRESSED_RG, format);
|
||||
assertNotEquals(GL13.GL_COMPRESSED_RGB, format);
|
||||
assertNotEquals(GL13.GL_COMPRESSED_RGBA, format);
|
||||
assertNotEquals(GL21.GL_COMPRESSED_SRGB, format);
|
||||
assertNotEquals(GL21.GL_COMPRESSED_SRGB_ALPHA, format);
|
||||
} finally {
|
||||
GL11.glDeleteTextures(texId);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNonGenericUploadCachesWithoutRoundtrip() {
|
||||
int texId = GL11.glGenTextures();
|
||||
try {
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);
|
||||
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
|
||||
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
|
||||
|
||||
TextureInfo info = TextureInfoCache.INSTANCE.getInfo(texId);
|
||||
assertFalse(info.needsInternalFormatResolve());
|
||||
assertEquals(GL11.GL_RGBA8, info.getResolvedInternalFormat());
|
||||
assertEquals(GL11.GL_RGBA8, GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
|
||||
} finally {
|
||||
GL11.glDeleteTextures(texId);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenericCompressedPreservesAsPassedInternalFormat() {
|
||||
int texId = GL11.glGenTextures();
|
||||
try {
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);
|
||||
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
|
||||
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL13.GL_COMPRESSED_RGBA, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
|
||||
|
||||
TextureInfo info = TextureInfoCache.INSTANCE.getInfo(texId);
|
||||
assertEquals(GL13.GL_COMPRESSED_RGBA, info.getInternalFormat());
|
||||
assertTrue(info.needsInternalFormatResolve());
|
||||
} finally {
|
||||
GL11.glDeleteTextures(texId);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRepeatedQueryAfterResolveUsesCache() {
|
||||
int texId = GL11.glGenTextures();
|
||||
try {
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);
|
||||
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
|
||||
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL13.GL_COMPRESSED_RGBA, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
|
||||
|
||||
TextureInfo info = TextureInfoCache.INSTANCE.getInfo(texId);
|
||||
assertTrue(info.needsInternalFormatResolve());
|
||||
int first = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
|
||||
assertFalse(info.needsInternalFormatResolve());
|
||||
int second = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
|
||||
assertEquals(first, second);
|
||||
} finally {
|
||||
GL11.glDeleteTextures(texId);
|
||||
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsGenericCompressedInternalFormatHelper() {
|
||||
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL30.GL_COMPRESSED_RED));
|
||||
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL30.GL_COMPRESSED_RG));
|
||||
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL13.GL_COMPRESSED_RGB));
|
||||
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL13.GL_COMPRESSED_RGBA));
|
||||
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL21.GL_COMPRESSED_SRGB));
|
||||
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL21.GL_COMPRESSED_SRGB_ALPHA));
|
||||
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(GL11.GL_RGBA));
|
||||
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(GL11.GL_RGBA8));
|
||||
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT));
|
||||
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(0));
|
||||
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(-1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Texture deletion is server-side; cache must be invalidated regardless of client caching state.
|
||||
*/
|
||||
|
||||
@@ -128,7 +128,7 @@ class FFPCombineShaderTest {
|
||||
setupXaeroState();
|
||||
|
||||
FragmentKey fk = FragmentKey.fromState();
|
||||
VertexKey vk = VertexKey.fromState(true, false, true, false);
|
||||
VertexKey vk = VertexKey.fromState(true, false, true, false, fk.enabledUnitMask());
|
||||
|
||||
String fragSource = FragmentShaderGenerator.generate(fk);
|
||||
String vertSource = VertexShaderGenerator.generate(vk);
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.gtnewhorizons.angelica.lwjgl3.MissingDependencySdl;
|
||||
import com.gtnewhorizons.angelica.mixins.Mixins;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.SharedConfig;
|
||||
import cpw.mods.fml.relauncher.FMLLaunchHandler;
|
||||
import cpw.mods.fml.relauncher.FMLRelaunchLog;
|
||||
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
|
||||
import jss.notfine.asm.AsmTransformers;
|
||||
import jss.notfine.asm.mappings.Namer;
|
||||
@@ -85,7 +86,9 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
|
||||
if (FMLLaunchHandler.side().isClient()) {
|
||||
final boolean rfbLoaded = Launch.blackboard.getOrDefault("angelica.rfbPluginLoaded", Boolean.FALSE) == Boolean.TRUE;
|
||||
if (!rfbLoaded) {
|
||||
Launch.classLoader.registerTransformer("com.gtnewhorizons.angelica.loading.fml.transformers.EarlyRedirectorTransformer");
|
||||
final String transformer = "com.gtnewhorizons.angelica.loading.fml.transformers.EarlyRedirectorTransformer";
|
||||
FMLRelaunchLog.finer("Registering transformer %s", transformer);
|
||||
Launch.classLoader.registerTransformer(transformer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,6 +149,10 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
|
||||
final List<String> notFineTransformers = Arrays.asList(ITransformers.getTransformers(AsmTransformers.class));
|
||||
if (!notFineTransformers.isEmpty()) Namer.initNames();
|
||||
transformers.addAll(notFineTransformers);
|
||||
final boolean rfbLoaded = Launch.blackboard.getOrDefault("angelica.rfbPluginLoaded", Boolean.FALSE) == Boolean.TRUE;
|
||||
if (!rfbLoaded) {
|
||||
transformers.add("com.gtnewhorizons.angelica.loading.fml.transformers.IsbrhTessellatorAbuseTransformer");
|
||||
}
|
||||
transformerClasses = transformers.toArray(new String[0]);
|
||||
}
|
||||
return transformerClasses;
|
||||
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
package com.gtnewhorizons.angelica.loading.fml.transformers;
|
||||
|
||||
import com.gtnewhorizons.angelica.loading.AngelicaTweaker;
|
||||
import com.gtnewhorizons.angelica.loading.shared.AngelicaClassDump;
|
||||
import com.gtnewhorizons.angelica.loading.shared.transformers.AngelicaRedirector;
|
||||
import net.minecraft.launchwrapper.IClassTransformer;
|
||||
|
||||
+1
@@ -8,6 +8,7 @@ import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
/** IClassTransformer wrapper for {@link CeleritasBlockTransform} */
|
||||
public class CeleritasBlockTransformer implements IClassTransformer {
|
||||
|
||||
private final CeleritasBlockTransform inner;
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.gtnewhorizons.angelica.loading.fml.transformers;
|
||||
|
||||
import com.gtnewhorizons.angelica.loading.AngelicaClientTweaker;
|
||||
import com.gtnewhorizons.angelica.loading.shared.AngelicaClassDump;
|
||||
import com.gtnewhorizons.angelica.loading.shared.transformers.IsbrhTessellatorAbuseTransform;
|
||||
import net.minecraft.launchwrapper.IClassTransformer;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
/** IClassTransformer wrapper for {@link IsbrhTessellatorAbuseTransform} */
|
||||
public class IsbrhTessellatorAbuseTransformer implements IClassTransformer {
|
||||
|
||||
private final IsbrhTessellatorAbuseTransform inner = new IsbrhTessellatorAbuseTransform();
|
||||
|
||||
@Override
|
||||
public byte[] transform(String name, String transformedName, byte[] basicClass) {
|
||||
if (!inner.shouldTransform(basicClass)) return basicClass;
|
||||
|
||||
final ClassReader cr = new ClassReader(basicClass);
|
||||
final ClassNode cn = new ClassNode();
|
||||
cr.accept(cn, 0);
|
||||
if (!inner.transformClassNode(cn, AngelicaClientTweaker.isObfEnv())) return basicClass;
|
||||
|
||||
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
cn.accept(cw);
|
||||
final byte[] bytes = cw.toByteArray();
|
||||
AngelicaClassDump.dumpClass(transformedName, basicClass, bytes, this);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@ package com.gtnewhorizons.angelica.loading.fml.tweakers;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.loading.EcosystemNarrowRules;
|
||||
import com.gtnewhorizons.angelica.glsm.loading.TransformerNarrower;
|
||||
import com.gtnewhorizons.angelica.loading.AngelicaClientTweaker;
|
||||
import com.gtnewhorizons.angelica.loading.fml.transformers.IsbrhTessellatorAbuseClassTransformer;
|
||||
import cpw.mods.fml.relauncher.FMLRelaunchLog;
|
||||
import net.minecraft.launchwrapper.IClassTransformer;
|
||||
import net.minecraft.launchwrapper.ITweaker;
|
||||
@@ -62,10 +60,6 @@ public class AngelicaLateTweaker implements ITweaker {
|
||||
final List<IClassTransformer> transformers = (List<IClassTransformer>) transformersField.get(Launch.classLoader);
|
||||
|
||||
transformers.removeIf(t -> t.getClass().getName().equals(EARLY_REDIRECTOR_CLASS));
|
||||
|
||||
final IClassTransformer isbrhAbuse = new IsbrhTessellatorAbuseClassTransformer(AngelicaClientTweaker.isObfEnv());
|
||||
FMLRelaunchLog.finer("Registering transformer %s", isbrhAbuse.getClass().getName());
|
||||
transformers.add(isbrhAbuse);
|
||||
} catch (Exception e) {
|
||||
FMLRelaunchLog.warning("[Angelica] Failed to remove EarlyRedirectorTransformer: %s", e.getMessage());
|
||||
}
|
||||
|
||||
+9
-6
@@ -1,7 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.loading.rfb.transformers;
|
||||
|
||||
import com.gtnewhorizons.angelica.loading.fml.transformers.IsbrhTessellatorAbuseClassTransformer;
|
||||
import com.gtnewhorizons.angelica.loading.shared.AngelicaClassDump;
|
||||
import com.gtnewhorizons.angelica.loading.shared.transformers.IsbrhTessellatorAbuseTransform;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ClassHeaderMetadata;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.api.ExtensibleClassLoader;
|
||||
@@ -12,12 +12,15 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
/** RfbClassTransformer wrapper for {@link IsbrhTessellatorAbuseClassTransformer} */
|
||||
/** RfbClassTransformer wrapper for {@link IsbrhTessellatorAbuseTransform} */
|
||||
public class RFBIsbrhTessellatorAbuseTransformer implements RfbClassTransformer {
|
||||
private final IsbrhTessellatorAbuseClassTransformer inner;
|
||||
|
||||
private final IsbrhTessellatorAbuseTransform inner;
|
||||
private final boolean isObf;
|
||||
|
||||
public RFBIsbrhTessellatorAbuseTransformer(boolean isObf) {
|
||||
inner = new IsbrhTessellatorAbuseClassTransformer(isObf);
|
||||
this.inner = new IsbrhTessellatorAbuseTransform();
|
||||
this.isObf = isObf;
|
||||
}
|
||||
|
||||
@Pattern("[a-z0-9-]+")
|
||||
@@ -42,13 +45,13 @@ public class RFBIsbrhTessellatorAbuseTransformer implements RfbClassTransformer
|
||||
@NotNull ClassNodeHandle classNode) {
|
||||
if (!classNode.isPresent()) return false;
|
||||
final ClassHeaderMetadata metadata = classNode.getOriginalMetadata();
|
||||
return metadata != null && metadata.binaryInterfaceNames.contains(IsbrhTessellatorAbuseClassTransformer.ISBRH);
|
||||
return metadata != null && metadata.binaryInterfaceNames.contains(IsbrhTessellatorAbuseTransform.ISBRH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transformClass(@NotNull ExtensibleClassLoader classLoader, @NotNull RfbClassTransformer.Context context,
|
||||
@Nullable Manifest manifest, @NotNull String className, @NotNull ClassNodeHandle classNode) {
|
||||
if (inner.transformClassNode(className, classNode.getNode())) {
|
||||
if (inner.transformClassNode(classNode.getNode(), this.isObf)) {
|
||||
classNode.computeMaxs();
|
||||
AngelicaClassDump.dumpRFBClass(className, classNode, this);
|
||||
}
|
||||
|
||||
+8
-33
@@ -1,12 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.loading.fml.transformers;
|
||||
package com.gtnewhorizons.angelica.loading.shared.transformers;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser;
|
||||
import com.gtnewhorizons.angelica.loading.shared.AngelicaClassDump;
|
||||
import net.minecraft.launchwrapper.IClassTransformer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
@@ -14,7 +10,8 @@ import org.objectweb.asm.tree.InsnNode;
|
||||
import org.objectweb.asm.tree.MethodInsnNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
public class IsbrhTessellatorAbuseClassTransformer implements IClassTransformer {
|
||||
public final class IsbrhTessellatorAbuseTransform {
|
||||
|
||||
public static final String ISBRH = "cpw/mods/fml/client/registry/ISimpleBlockRenderingHandler";
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("IsbrhTessellatorAbuse");
|
||||
@@ -22,21 +19,11 @@ public class IsbrhTessellatorAbuseClassTransformer implements IClassTransformer
|
||||
private static final String TESSELLATOR = "net/minecraft/client/renderer/Tessellator";
|
||||
private static final String RENDER_WORLD_BLOCK_DESC = "(Lnet/minecraft/world/IBlockAccess;IIILnet/minecraft/block/Block;ILnet/minecraft/client/renderer/RenderBlocks;)Z";
|
||||
|
||||
private final String draw;
|
||||
private final String startDrawingQuads;
|
||||
private final String startDrawing;
|
||||
|
||||
public IsbrhTessellatorAbuseClassTransformer(boolean isObf) {
|
||||
this.draw = isObf ? "func_78381_a" : "draw";
|
||||
this.startDrawingQuads = isObf ? "func_78382_b" : "startDrawingQuads";
|
||||
this.startDrawing = isObf ? "func_78371_b" : "startDrawing";
|
||||
}
|
||||
|
||||
public boolean shouldTransform(byte[] classBytes) {
|
||||
return CST_POOL_PARSER.find(classBytes);
|
||||
}
|
||||
|
||||
public boolean transformClassNode(String transformedName, ClassNode cn) {
|
||||
public boolean transformClassNode(ClassNode cn, boolean isObf) {
|
||||
MethodNode target = null;
|
||||
for (int i = 0, n = cn.methods.size(); i < n; i++) {
|
||||
final MethodNode mn = cn.methods.get(i);
|
||||
@@ -47,6 +34,10 @@ public class IsbrhTessellatorAbuseClassTransformer implements IClassTransformer
|
||||
}
|
||||
if (target == null) return false;
|
||||
|
||||
final String draw = isObf ? "func_78381_a" : "draw";
|
||||
final String startDrawingQuads = isObf ? "func_78382_b" : "startDrawingQuads";
|
||||
final String startDrawing = isObf ? "func_78371_b" : "startDrawing";
|
||||
|
||||
boolean changed = false;
|
||||
AbstractInsnNode insn = target.instructions.getFirst();
|
||||
while (insn != null) {
|
||||
@@ -79,20 +70,4 @@ public class IsbrhTessellatorAbuseClassTransformer implements IClassTransformer
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] transform(String name, String transformedName, byte[] basicClass) {
|
||||
if (basicClass == null || !shouldTransform(basicClass)) return basicClass;
|
||||
|
||||
final ClassReader cr = new ClassReader(basicClass);
|
||||
final ClassNode cn = new ClassNode();
|
||||
cr.accept(cn, 0);
|
||||
if (!transformClassNode(transformedName, cn)) return basicClass;
|
||||
|
||||
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
cn.accept(cw);
|
||||
final byte[] bytes = cw.toByteArray();
|
||||
AngelicaClassDump.dumpClass(transformedName, basicClass, bytes, this);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,9 @@ public enum Mixins implements IMixins {
|
||||
, "angelica.bugfixes.MixinRenderCreeper_AuraDepth"
|
||||
, "angelica.bugfixes.MixinRenderGlobal_DeferredEntityOverlay"
|
||||
, "angelica.bugfixes.MixinRenderGlobal_DestroyBlock"
|
||||
, "angelica.bugfixes.MixinRenderMooshroom_MushroomTint"
|
||||
, "angelica.bugfixes.MixinRendererLivingEntity_DeferredEntityOverlay"
|
||||
, "angelica.bugfixes.MixinRendererLivingEntity_OverlayTint"
|
||||
, "angelica.bugfixes.MixinRenderWither_ArmorCentering"
|
||||
, "angelica.bugfixes.MixinRendererLivingEntity_EyeDepth"
|
||||
, "angelica.debug.MixinMinecraft_FPSCap"
|
||||
@@ -275,6 +277,8 @@ public enum Mixins implements IMixins {
|
||||
, "shaders.MixinRenderNameTag"
|
||||
, "shaders.MixinRenderPlayerArmor"
|
||||
, "shaders.MixinTileEntityBeaconRenderer"
|
||||
, "shaders.MixinWorldNbtCache"
|
||||
, "shaders.MixinNetHandlerPlayClient_NbtRemesh"
|
||||
, "shaders.MixinRenderEndPortal"
|
||||
, "shaders.MixinTileEntityRendererDispatcher"
|
||||
, "shaders.MixinGlProgram"
|
||||
|
||||
@@ -29,6 +29,8 @@ import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasSetup;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.threading.ChunkTaskRegistry;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.threading.DefaultChunkTaskProvider;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.threading.ThreadedChunkTaskProvider;
|
||||
import com.gtnewhorizons.angelica.rendering.items.BlockRenderListManager;
|
||||
import com.gtnewhorizons.angelica.rendering.items.ItemRenderListManager;
|
||||
import com.gtnewhorizons.angelica.utils.AnimationMode;
|
||||
import com.gtnewhorizons.angelica.utils.ManagedEnum;
|
||||
import com.gtnewhorizons.angelica.zoom.Zoom;
|
||||
@@ -145,6 +147,10 @@ public final class ClientProxy extends CommonProxy {
|
||||
if (AngelicaConfig.enableDynamicLights) {
|
||||
EntityLightConfig.init(new java.io.File(mc.mcDataDir, "config"));
|
||||
}
|
||||
if(AngelicaConfig.optimizeInWorldItemRendering){
|
||||
ItemRenderListManager.registerReloadListener();
|
||||
BlockRenderListManager.registerReloadListener();
|
||||
}
|
||||
|
||||
// Register debug commands in dev environment only
|
||||
if (!AngelicaClientTweaker.isObfEnv()) {
|
||||
|
||||
+32
-5
@@ -12,8 +12,10 @@ import com.prupe.mcpatcher.mal.block.RenderBlocksUtils;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceMap;
|
||||
import net.coderbot.iris.block_rendering.BlockMaterialMapping;
|
||||
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
|
||||
import net.coderbot.iris.block_rendering.NbtConditionalIdMap;
|
||||
import net.coderbot.iris.vertices.ExtendedDataHelper;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.RenderBlocks;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
@@ -118,6 +120,10 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
final Map<Block, BlockRenderLayer> blockTypeIds = provider != null ? provider.getBlockTypeIds() : null;
|
||||
final BlockRenderContext blockRenderContext = buildContext.getBlockRenderContext();
|
||||
|
||||
final NbtConditionalIdMap<Block> teMap = BlockRenderingSettings.INSTANCE.getBlockNbtMap();
|
||||
final net.minecraft.world.World mcWorld = Minecraft.getMinecraft().theWorld;
|
||||
final long currentTick = mcWorld != null ? mcWorld.getTotalWorldTime() : 0L;
|
||||
|
||||
final boolean threaded = isThreaded();
|
||||
final List<DeferredBlock> deferredBlocks = threaded ? new ArrayList<>() : null;
|
||||
|
||||
@@ -192,7 +198,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
continue;
|
||||
}
|
||||
|
||||
renderBlock(block, meta, x, y, z, pass, tessellator, renderBlocks, buffers, buildContext, blockRenderContext, minX, minY, minZ, materialOverride, isShaderPackOverride);
|
||||
renderBlock(block, meta, x, y, z, pass, tessellator, renderBlocks, buffers, buildContext, blockRenderContext, minX, minY, minZ, materialOverride, isShaderPackOverride, teMap, currentTick);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +211,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
|
||||
// Process deferred blocks on main thread if any
|
||||
if (deferredBlocks != null && !deferredBlocks.isEmpty()) {
|
||||
processDeferredBlocks(deferredBlocks, buildContext, buffers, region, minX, minY, minZ);
|
||||
processDeferredBlocks(deferredBlocks, buildContext, buffers, region, minX, minY, minZ, teMap, currentTick);
|
||||
}
|
||||
|
||||
final Reference2ReferenceMap<TerrainRenderPass, BuiltSectionMeshParts> meshes =
|
||||
@@ -236,7 +242,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
}
|
||||
}
|
||||
|
||||
protected void renderBlock(Block block, int metadata, int x, int y, int z, int pass, Tessellator tessellator, RenderBlocks renderBlocks, ChunkBuildBuffers buffers, AngelicaChunkBuildContext buildContext, BlockRenderContext blockRenderContext, int originX, int originY, int originZ, Material materialOverride, boolean isShaderPackOverride) {
|
||||
protected void renderBlock(Block block, int metadata, int x, int y, int z, int pass, Tessellator tessellator, RenderBlocks renderBlocks, ChunkBuildBuffers buffers, AngelicaChunkBuildContext buildContext, BlockRenderContext blockRenderContext, int originX, int originY, int originZ, Material materialOverride, boolean isShaderPackOverride, NbtConditionalIdMap<Block> teMap, long currentTick) {
|
||||
|
||||
final var blockMaterial = block.getMaterial();
|
||||
final boolean isFluid = blockMaterial == net.minecraft.block.material.Material.water || blockMaterial == net.minecraft.block.material.Material.lava;
|
||||
@@ -272,6 +278,27 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
}
|
||||
contextEncoder.prepareToRenderBlock(blockRenderContext, block, effectiveMeta,
|
||||
ExtendedDataHelper.BLOCK_RENDER_TYPE, lightValue);
|
||||
// Check for TileEntity NBT-conditional shader block ID override
|
||||
if (teMap != null && teMap.hasConditions(block)) {
|
||||
final long packedPos = BlockRenderingSettings.packBlockPos(x, y, z);
|
||||
int teBlockId = BlockRenderingSettings.getCachedTeNbtId(packedPos, currentTick);
|
||||
|
||||
if (teBlockId == BlockRenderingSettings.CACHE_MISS) {
|
||||
final TileEntity te = getBlockAccess().getTileEntity(x, y, z);
|
||||
if (te != null) {
|
||||
final NBTTagCompound teNbt = new NBTTagCompound();
|
||||
te.writeToNBT(teNbt);
|
||||
teBlockId = teMap.resolve(block, teNbt);
|
||||
} else {
|
||||
teBlockId = -1;
|
||||
}
|
||||
BlockRenderingSettings.cacheTeNbtId(packedPos, teBlockId, currentTick);
|
||||
}
|
||||
|
||||
if (teBlockId != -1) {
|
||||
blockRenderContext.blockId = (short) teBlockId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,7 +321,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
}
|
||||
}
|
||||
|
||||
private void processDeferredBlocks(List<DeferredBlock> deferredBlocks, AngelicaChunkBuildContext buildContext, ChunkBuildBuffers buffers, IBlockAccess region, int minX, int minY, int minZ) {
|
||||
private void processDeferredBlocks(List<DeferredBlock> deferredBlocks, AngelicaChunkBuildContext buildContext, ChunkBuildBuffers buffers, IBlockAccess region, int minX, int minY, int minZ, NbtConditionalIdMap<Block> teMap, long currentTick) {
|
||||
|
||||
final BlockRenderContext blockRenderContext = buildContext.getBlockRenderContext();
|
||||
|
||||
@@ -308,7 +335,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
|
||||
for (DeferredBlock deferred : deferredBlocks) {
|
||||
renderBlock(deferred.block(), deferred.meta(), deferred.x(), deferred.y(), deferred.z(), deferred.pass(),
|
||||
mainTessellator, mainThreadRenderBlocks, buffers, buildContext,
|
||||
blockRenderContext, minX, minY, minZ, deferred.materialOverride(), deferred.isShaderPackOverride());
|
||||
blockRenderContext, minX, minY, minZ, deferred.materialOverride(), deferred.isShaderPackOverride(), teMap, currentTick);
|
||||
}
|
||||
} finally {
|
||||
((StateAwareTessellator)mainTessellator).angelica$setCeleritasMeshing(false);
|
||||
|
||||
@@ -4,6 +4,8 @@ import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.IReloadableResourceManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public class BlockRenderListManager {
|
||||
@@ -46,6 +48,17 @@ public class BlockRenderListManager {
|
||||
displayListMap.put(new BlockMeta(block, meta), list);
|
||||
}
|
||||
|
||||
public static void registerReloadListener(){
|
||||
IReloadableResourceManager resourceManager = (IReloadableResourceManager) Minecraft.getMinecraft()
|
||||
.getResourceManager();
|
||||
resourceManager.registerReloadListener(_ -> {
|
||||
for (int list : displayListMap.values()) {
|
||||
GLStateManager.glDeleteLists(list, 1);
|
||||
}
|
||||
displayListMap.clear();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static final class BlockMeta {
|
||||
private Block block;
|
||||
|
||||
@@ -37,6 +37,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.IReloadableResourceManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public class ItemRenderListManager {
|
||||
@@ -98,6 +99,17 @@ public class ItemRenderListManager {
|
||||
return Minecraft.getMinecraft().thePlayer.ticksExisted;
|
||||
}
|
||||
|
||||
public static void registerReloadListener(){
|
||||
IReloadableResourceManager resourceManager = (IReloadableResourceManager) Minecraft.getMinecraft()
|
||||
.getResourceManager();
|
||||
resourceManager.registerReloadListener(_ -> {
|
||||
for (CachedVBO value : vboCache.values()) {
|
||||
value.delete();
|
||||
}
|
||||
vboCache.clear();
|
||||
});
|
||||
}
|
||||
|
||||
public static final class CachedVBO {
|
||||
private final IVertexArrayObject vertexBuffer;
|
||||
private final IndexBuffer ebo;
|
||||
|
||||
@@ -10,6 +10,7 @@ import cpw.mods.fml.relauncher.SideOnly;
|
||||
import jss.notfine.gui.options.control.NotFineControlValueFormatter;
|
||||
import jss.notfine.gui.options.named.AlwaysNever;
|
||||
import jss.notfine.gui.options.named.BackgroundSelect;
|
||||
import jss.notfine.gui.options.named.BobviewMode;
|
||||
import jss.notfine.gui.options.named.DownfallQuality;
|
||||
import jss.notfine.gui.options.named.FOVMode;
|
||||
import jss.notfine.gui.options.named.GraphicsQualityOff;
|
||||
@@ -50,6 +51,7 @@ public enum Settings {
|
||||
SettingsManager.downfallDistanceUpdated();
|
||||
}
|
||||
},
|
||||
BOBVIEW_MODE(new NotFineOptionCycling<>(BobviewMode.DEFAULT, null)),
|
||||
DYNAMIC_FOV(new NotFineOptionCycling<>(FOVMode.ALL, OptionImpact.VARIES)),
|
||||
HURT_SHAKE(new NotFineOptionSliderPercentage(100, 0, 300, 5, OptionImpact.LOW)),
|
||||
DYNAMIC_LIGHTS(new NotFineOptionCycling<>(DynamicLightsMode.FANCY, OptionImpact.VARIES)){
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package jss.notfine.gui.options.named;
|
||||
|
||||
import me.jellysquid.mods.sodium.client.gui.options.named.NamedState;
|
||||
|
||||
public enum BobviewMode implements NamedState {
|
||||
DEFAULT("generator.default"),
|
||||
HAND("options.bobview.hand"),
|
||||
CAMERA("options.bobview.camera");
|
||||
|
||||
private final String name;
|
||||
|
||||
BobviewMode(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
@@ -143,6 +143,7 @@ public class SodiumGameOptionPages {
|
||||
.setControl(TickBoxControl::new)
|
||||
.setBinding((opts, value) -> opts.viewBobbing = value, opts -> opts.viewBobbing)
|
||||
.build())
|
||||
.add(Settings.BOBVIEW_MODE.option)
|
||||
.add(Settings.DYNAMIC_FOV.option)
|
||||
.add(Settings.HURT_SHAKE.option)
|
||||
.add(Settings.MODE_WATER.option)
|
||||
|
||||
@@ -23,26 +23,33 @@ import java.util.Set;
|
||||
|
||||
public class BlockMaterialMapping {
|
||||
|
||||
public record BlockIdMaps(Reference2ObjectMap<Block, Int2IntMap> blockMetaMap, NbtConditionalIdMap<Block> tileEntityMap) {}
|
||||
|
||||
/** Meta-key bit OR'd in at runtime when a snowy-tagged block has snow above it. */
|
||||
public static final int SNOWY_META_BIT = 0x10;
|
||||
|
||||
/**
|
||||
* Creates a two-level map structure for block material IDs.
|
||||
* Based on Iris's BlockState mapping approach adapted for 1.7.10's metadata system.
|
||||
* Creates the standard block meta ID map, the TileEntity NBT-conditional map, and registers
|
||||
* snowy blocks on {@link BlockRenderingSettings}.
|
||||
*/
|
||||
public static Reference2ObjectMap<Block, Int2IntMap> createBlockMetaIdMap(Int2ObjectMap<List<BlockEntry>> blockPropertiesMap, boolean skipFlattening) {
|
||||
public static BlockIdMaps createBlockIdMaps(Int2ObjectMap<List<BlockEntry>> blockPropertiesMap, boolean skipFlattening) {
|
||||
final Reference2ObjectMap<Block, Int2IntMap> blockMatches = new Reference2ObjectOpenHashMap<>();
|
||||
final NbtConditionalIdMap<Block> tileEntityMap = new NbtConditionalIdMap<>();
|
||||
final ReferenceSet<Block> snowyBlocks = new ReferenceOpenHashSet<>();
|
||||
|
||||
blockPropertiesMap.forEach((intId, entries) -> {
|
||||
for (BlockEntry entry : entries) {
|
||||
addBlockMetas(entry, blockMatches, intId, skipFlattening, snowyBlocks);
|
||||
if (entry.hasNbtProperties()) {
|
||||
addTileEntityEntry(entry, tileEntityMap, intId);
|
||||
} else {
|
||||
addBlockMetas(entry, blockMatches, tileEntityMap, intId, skipFlattening, snowyBlocks);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
BlockRenderingSettings.INSTANCE.setHasSnowyEntries(!snowyBlocks.isEmpty());
|
||||
BlockRenderingSettings.INSTANCE.setSnowyBlocks(snowyBlocks);
|
||||
return blockMatches;
|
||||
return new BlockIdMaps(blockMatches, tileEntityMap);
|
||||
}
|
||||
|
||||
public static Map<Block, BlockRenderLayer> createBlockTypeMap(Map<NamespacedId, BlockRenderType> blockPropertiesMap) {
|
||||
@@ -55,7 +62,8 @@ public class BlockMaterialMapping {
|
||||
if (block == null && "minecraft".equals(id.getNamespace())) {
|
||||
final List<BlockEntry> legacyEntries = FlatteningMap.toLegacy(id.getName(), Map.of());
|
||||
if (legacyEntries != null) {
|
||||
block = resolveBlockOrNull(legacyEntries.getFirst().getId());
|
||||
// Use the first entry for render type (render type applies per-block)
|
||||
block = resolveBlockOrNull(legacyEntries.getFirst().id());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,28 +88,64 @@ public class BlockMaterialMapping {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an NBT-conditional ID map keyed by NamespacedId, for items or entities.
|
||||
*/
|
||||
public static NbtConditionalIdMap<NamespacedId> createNamespacedNbtMap(Int2ObjectMap<List<BlockEntry>> nbtEntries) {
|
||||
NbtConditionalIdMap<NamespacedId> map = new NbtConditionalIdMap<>();
|
||||
|
||||
nbtEntries.forEach((intId, entries) -> {
|
||||
for (BlockEntry entry : entries) {
|
||||
if (entry.hasNbtProperties()) {
|
||||
map.addCondition(entry.id(), entry.nbtProperties(), intId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static void addTileEntityEntry(BlockEntry entry, NbtConditionalIdMap<Block> teMap, int intId) {
|
||||
final NamespacedId id = entry.id();
|
||||
final ResourceLocation resourceLocation = new ResourceLocation(id.getNamespace(), id.getName());
|
||||
|
||||
final Block block = (Block) Block.blockRegistry.getObject(resourceLocation.toString());
|
||||
|
||||
if (block == null || block == Blocks.air) {
|
||||
return;
|
||||
}
|
||||
|
||||
teMap.addCondition(block, entry.nbtProperties(), intId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds block+metadata combinations to the material ID map.
|
||||
* Based on Iris's addBlockStates method, adapted for 1.7.10 metadata system.
|
||||
*/
|
||||
private static void addBlockMetas(BlockEntry entry, Reference2ObjectMap<Block, Int2IntMap> idMap, int intId, boolean skipFlattening, ReferenceSet<Block> snowyBlocks) {
|
||||
final NamespacedId id = entry.getId();
|
||||
final Map<String, String> stateProps = entry.getStateProperties();
|
||||
private static void addBlockMetas(BlockEntry entry, Reference2ObjectMap<Block, Int2IntMap> idMap,
|
||||
NbtConditionalIdMap<Block> tileEntityMap, int intId,
|
||||
boolean skipFlattening, ReferenceSet<Block> snowyBlocks) {
|
||||
final NamespacedId id = entry.id();
|
||||
final Map<String, String> stateProps = entry.stateProperties();
|
||||
final String snowy = stateProps.get("snowy");
|
||||
final int snowyBit = "true".equals(snowy) ? SNOWY_META_BIT : 0;
|
||||
|
||||
// Vanilla modern names go through FlatteningMap; legacy-section packs and modded blocks
|
||||
// resolve directly from the registry.
|
||||
List<BlockEntry> targets = null;
|
||||
if (!skipFlattening && "minecraft".equals(id.getNamespace()) && (!stateProps.isEmpty() || entry.getMetas().isEmpty())) {
|
||||
if (!skipFlattening && "minecraft".equals(id.getNamespace()) && (!stateProps.isEmpty() || entry.metas().isEmpty())) {
|
||||
targets = FlatteningMap.toLegacy(id.getName(), stateProps);
|
||||
}
|
||||
if (targets == null) targets = List.of(entry);
|
||||
|
||||
for (BlockEntry target : targets) {
|
||||
final Block block = resolveBlockOrNull(target.getId());
|
||||
if (target.hasNbtProperties()) {
|
||||
addTileEntityEntry(target, tileEntityMap, intId);
|
||||
continue;
|
||||
}
|
||||
final Block block = resolveBlockOrNull(target.id());
|
||||
if (block == null) continue;
|
||||
applyMetas(block, target.getMetas(), idMap, intId, snowyBit);
|
||||
applyMetas(block, target.metas(), idMap, intId, snowyBit);
|
||||
if (snowy != null) snowyBlocks.add(block);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package net.coderbot.iris.block_rendering;
|
||||
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.BlockRenderLayer;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2LongLinkedOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntFunction;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceSet;
|
||||
@@ -18,16 +19,76 @@ import java.util.Map;
|
||||
public class BlockRenderingSettings {
|
||||
public static final BlockRenderingSettings INSTANCE = new BlockRenderingSettings();
|
||||
|
||||
public static final int CACHE_MISS = Integer.MIN_VALUE;
|
||||
|
||||
private static final int NBT_CACHE_INTERVAL_TICKS = 20;
|
||||
private static final int TE_NBT_CACHE_MAX = 8192;
|
||||
|
||||
private static final Long2LongLinkedOpenHashMap teNbtIdCache = new Long2LongLinkedOpenHashMap(256);
|
||||
private static final Object teNbtIdCacheLock = new Object();
|
||||
|
||||
static {
|
||||
teNbtIdCache.defaultReturnValue(-1L);
|
||||
}
|
||||
|
||||
public static long packBlockPos(int x, int y, int z) {
|
||||
return ((long) x & 0x3FFFFFFL) << 38 | ((long) y & 0xFFFL) << 26 | ((long) z & 0x3FFFFFFL);
|
||||
}
|
||||
|
||||
public static void invalidateTeNbtCache(int x, int y, int z) {
|
||||
final long key = packBlockPos(x, y, z);
|
||||
synchronized (teNbtIdCacheLock) {
|
||||
teNbtIdCache.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public static int getCachedTeNbtId(long packedPos, long currentTick) {
|
||||
final long entry;
|
||||
synchronized (teNbtIdCacheLock) {
|
||||
entry = teNbtIdCache.get(packedPos);
|
||||
}
|
||||
if (entry == -1L) {
|
||||
return CACHE_MISS;
|
||||
}
|
||||
final long entryTick = entry >>> 32;
|
||||
if (currentTick - entryTick >= NBT_CACHE_INTERVAL_TICKS) {
|
||||
return CACHE_MISS;
|
||||
}
|
||||
return (int) entry;
|
||||
}
|
||||
|
||||
public static void cacheTeNbtId(long packedPos, int shaderId, long currentTick) {
|
||||
// Mask tick into low 31 bits so packed value never collides with the -1L sentinel.
|
||||
final long packed = ((currentTick & 0x7FFFFFFFL) << 32) | (shaderId & 0xFFFFFFFFL);
|
||||
synchronized (teNbtIdCacheLock) {
|
||||
teNbtIdCache.put(packedPos, packed);
|
||||
while (teNbtIdCache.size() > TE_NBT_CACHE_MAX) {
|
||||
teNbtIdCache.removeFirstLong();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearTeNbtCache() {
|
||||
synchronized (teNbtIdCacheLock) {
|
||||
teNbtIdCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Getter
|
||||
private boolean reloadRequired;
|
||||
private Reference2ObjectMap<Block, Int2IntMap> blockMetaMatches;
|
||||
private NbtConditionalIdMap<Block> blockNbtMap;
|
||||
private Map<Block, BlockRenderLayer> blockTypeIds;
|
||||
// note: no reload needed, entities are rebuilt every frame.
|
||||
@Setter
|
||||
private Object2IntFunction<NamespacedId> entityIds;
|
||||
@Setter
|
||||
private NbtConditionalIdMap<NamespacedId> entityNbtMap;
|
||||
// note: no reload needed, items are rendered every frame.
|
||||
@Setter
|
||||
private Object2IntFunction<NamespacedId> itemIds;
|
||||
@Setter
|
||||
private NbtConditionalIdMap<NamespacedId> itemNbtMap;
|
||||
@Getter
|
||||
private float ambientOcclusionLevel;
|
||||
private boolean disableDirectionalShading;
|
||||
@@ -41,6 +102,7 @@ public class BlockRenderingSettings {
|
||||
public BlockRenderingSettings() {
|
||||
reloadRequired = false;
|
||||
blockMetaMatches = null;
|
||||
blockNbtMap = null;
|
||||
blockTypeIds = null;
|
||||
ambientOcclusionLevel = 1.0F;
|
||||
disableDirectionalShading = false;
|
||||
@@ -82,11 +144,32 @@ public class BlockRenderingSettings {
|
||||
return itemIds;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public NbtConditionalIdMap<Block> getBlockNbtMap() {
|
||||
return blockNbtMap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public NbtConditionalIdMap<NamespacedId> getItemNbtMap() {
|
||||
return itemNbtMap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public NbtConditionalIdMap<NamespacedId> getEntityNbtMap() {
|
||||
return entityNbtMap;
|
||||
}
|
||||
|
||||
public void setBlockMetaMatches(Reference2ObjectMap<Block, Int2IntMap> blockMetaIds) {
|
||||
this.reloadRequired = true;
|
||||
this.blockMetaMatches = blockMetaIds;
|
||||
}
|
||||
|
||||
public void setBlockNbtMap(NbtConditionalIdMap<Block> blockNbtMap) {
|
||||
this.reloadRequired = true;
|
||||
this.blockNbtMap = blockNbtMap;
|
||||
clearTeNbtCache();
|
||||
}
|
||||
|
||||
public boolean hasSnowyEntries() {
|
||||
return hasSnowyEntries;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
package net.coderbot.iris.block_rendering;
|
||||
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.shaderpack.materialmap.PropertiesTokenizer;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.nbt.NBTBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Maps objects with specific NBT properties to shader IDs. Generic over the
|
||||
* key type so it works for blocks, items, and entities.
|
||||
*
|
||||
* Example:
|
||||
* block.37=flower_pot[Item=minecraft:red_flower,Data=0]
|
||||
* item.10=diamond_sword[display.Name='Pointy Stick']
|
||||
*/
|
||||
public class NbtConditionalIdMap<K> {
|
||||
|
||||
/**
|
||||
* A single property matcher within a condition.
|
||||
*/
|
||||
record PropertyMatcher(String key, String[] path, String expectedValue,
|
||||
long expectedLong, boolean isExpectedLong,
|
||||
double expectedDouble, boolean isExpectedDouble) {
|
||||
boolean isPathMatch() { return path != null; }
|
||||
boolean isExistenceOnly() { return expectedValue == null; }
|
||||
}
|
||||
|
||||
record NbtCondition(PropertyMatcher[] matchers, int shaderId) {}
|
||||
|
||||
private final Map<K, List<NbtCondition>> conditionsByKey;
|
||||
|
||||
public NbtConditionalIdMap() {
|
||||
this.conditionsByKey = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an NBT condition. Unquoted values are resolved via item/block
|
||||
* registries at registration time. Single-quoted values are kept as literals.
|
||||
* Dot notation paths are pre-split here so we prevent allocations.
|
||||
*/
|
||||
public void addCondition(K key, Map<String, PropertiesTokenizer.NbtValue> nbtProperties, int shaderId) {
|
||||
final PropertyMatcher[] matchers = new PropertyMatcher[nbtProperties.size()];
|
||||
int i = 0;
|
||||
|
||||
for (Map.Entry<String, PropertiesTokenizer.NbtValue> entry : nbtProperties.entrySet()) {
|
||||
final String nbtKey = entry.getKey();
|
||||
final PropertiesTokenizer.NbtValue nbtValue = entry.getValue();
|
||||
|
||||
final String resolvedValue;
|
||||
if (nbtValue == null) {
|
||||
resolvedValue = null;
|
||||
|
||||
} else if (nbtValue.literal()) {
|
||||
resolvedValue = nbtValue.value();
|
||||
|
||||
} else {
|
||||
resolvedValue = resolveValue(nbtValue.value());
|
||||
}
|
||||
|
||||
final String[] path = nbtKey.contains(".") ? nbtKey.split("\\.") : null;
|
||||
|
||||
long expectedLong = 0;
|
||||
boolean isExpectedLong = false;
|
||||
double expectedDouble = 0;
|
||||
boolean isExpectedDouble = false;
|
||||
if (resolvedValue != null) {
|
||||
try {
|
||||
expectedLong = Long.parseLong(resolvedValue);
|
||||
isExpectedLong = true;
|
||||
} catch (NumberFormatException ignored) {}
|
||||
try {
|
||||
expectedDouble = Double.parseDouble(resolvedValue);
|
||||
isExpectedDouble = true;
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
matchers[i++] = new PropertyMatcher(nbtKey, path, resolvedValue,
|
||||
expectedLong, isExpectedLong, expectedDouble, isExpectedDouble);
|
||||
}
|
||||
|
||||
conditionsByKey.computeIfAbsent(key, k -> new ArrayList<>())
|
||||
.add(new NbtCondition(matchers, shaderId));
|
||||
}
|
||||
|
||||
public boolean hasConditions(K key) {
|
||||
return conditionsByKey.containsKey(key);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return conditionsByKey.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the shader ID by matching the provided NBT data.
|
||||
*/
|
||||
public int resolve(K key, NBTTagCompound nbt) {
|
||||
final List<NbtCondition> conditions = conditionsByKey.get(key);
|
||||
if (conditions == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (NbtCondition condition : conditions) {
|
||||
if (matchesNbt(nbt, condition.matchers())) {
|
||||
return condition.shaderId;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static boolean matchesNbt(NBTTagCompound nbt, PropertyMatcher[] matchers) {
|
||||
for (PropertyMatcher matcher : matchers) {
|
||||
if (matcher.isPathMatch()) {
|
||||
if (!matchesPath(nbt, matcher.path(), 0, matcher)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (!nbt.hasKey(matcher.key())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!matcher.isExistenceOnly()) {
|
||||
if (!matchesNbtValue(nbt.getTag(matcher.key()), matcher)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search through dot-separated path in NBT data.
|
||||
*/
|
||||
private static boolean matchesPath(NBTBase tag, String[] path, int depth, PropertyMatcher matcher) {
|
||||
if (depth == path.length) {
|
||||
if (matcher.isExistenceOnly()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return matchesNbtValue(tag, matcher);
|
||||
}
|
||||
|
||||
final String segment = path[depth];
|
||||
|
||||
if (tag instanceof NBTTagCompound compound) {
|
||||
if (!compound.hasKey(segment)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return matchesPath(compound.getTag(segment), path, depth + 1, matcher);
|
||||
}
|
||||
|
||||
if (tag instanceof NBTTagList list) {
|
||||
for (int i = 0; i < list.tagCount(); i++) {
|
||||
if (matchesPath(list.getCompoundTagAt(i), path, depth, matcher)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares an NBT tag's value against an expected string using type-specific
|
||||
* accessors to avoid toString() allocations where possible.
|
||||
* Makes it so you don't need to specify the type either in the .properties files.
|
||||
*/
|
||||
private static boolean matchesNbtValue(NBTBase tag, PropertyMatcher matcher) {
|
||||
return switch (tag.getId()) {
|
||||
case 1 -> // TAG_Byte
|
||||
matcher.isExpectedLong() && ((net.minecraft.nbt.NBTTagByte) tag).func_150290_f() == matcher.expectedLong();
|
||||
case 2 -> // TAG_Short
|
||||
matcher.isExpectedLong() && ((net.minecraft.nbt.NBTTagShort) tag).func_150289_e() == matcher.expectedLong();
|
||||
case 3 -> // TAG_Int
|
||||
matcher.isExpectedLong() && ((net.minecraft.nbt.NBTTagInt) tag).func_150287_d() == matcher.expectedLong();
|
||||
case 4 -> // TAG_Long
|
||||
matcher.isExpectedLong() && ((net.minecraft.nbt.NBTTagLong) tag).func_150291_c() == matcher.expectedLong();
|
||||
case 5 -> // TAG_Float
|
||||
matcher.isExpectedDouble() && ((net.minecraft.nbt.NBTTagFloat) tag).func_150288_h() == (float) matcher.expectedDouble();
|
||||
case 6 -> // TAG_Double
|
||||
matcher.isExpectedDouble() && ((net.minecraft.nbt.NBTTagDouble) tag).func_150286_g() == matcher.expectedDouble();
|
||||
case 8 -> // TAG_String
|
||||
matcher.expectedValue().equals(((net.minecraft.nbt.NBTTagString) tag).func_150285_a_());
|
||||
default -> // Compounds, lists, arrays — fall back to toString
|
||||
matcher.expectedValue().equals(PropertiesTokenizer.stripQuotes(tag.toString()));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to resolve a value as a registry name (item or block) to its numeric ID.
|
||||
* If the value is already numeric or can't be resolved, return it unchanged.
|
||||
*/
|
||||
private static String resolveValue(String value) {
|
||||
try {
|
||||
Integer.parseInt(value);
|
||||
return value;
|
||||
} catch (NumberFormatException ignored) {}
|
||||
|
||||
if (!value.contains(":")) {
|
||||
Iris.logger.warn("NBT value '{}' is missing a namespace, please use the full form e.g. 'minecraft:{}'", value, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
final ResourceLocation loc = new ResourceLocation(value);
|
||||
|
||||
final Item item = (Item) Item.itemRegistry.getObject(loc.toString());
|
||||
if (item != null) {
|
||||
return String.valueOf(Item.getIdFromItem(item));
|
||||
}
|
||||
|
||||
final Block block = (Block) Block.blockRegistry.getObject(loc.toString());
|
||||
if (block != null) {
|
||||
return String.valueOf(Block.getIdFromBlock(block));
|
||||
}
|
||||
|
||||
Iris.logger.warn("Could not resolve '{}' to an item or block ID", value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,9 @@ public class GlFramebuffer extends GlResource {
|
||||
public void addDepthAttachmentBypass(int texture) {
|
||||
final int fb = getGlId();
|
||||
RenderSystem.framebufferTexture2D(fb, GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL11.GL_TEXTURE_2D, texture, 0);
|
||||
// Detach any stale stencil left over from a prior combined-stencil attach; otherwise drivers
|
||||
// reject the FBO as UNSUPPORTED when the depth attachment no longer matches the stencil.
|
||||
RenderSystem.framebufferTexture2D(fb, GL30.GL_FRAMEBUFFER, GL30.GL_STENCIL_ATTACHMENT, GL11.GL_TEXTURE_2D, 0, 0);
|
||||
this.hasDepthAttachment = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -277,15 +277,19 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
|
||||
holder -> CommonUniforms.addNonDynamicUniforms(holder, programs.getPack().getIdMap(), programs.getPackDirectives(), this.updateNotifier)
|
||||
);
|
||||
|
||||
BlockRenderingSettings.INSTANCE.setBlockMetaMatches(BlockMaterialMapping.createBlockMetaIdMap(
|
||||
final var blockIdMaps = BlockMaterialMapping.createBlockIdMaps(
|
||||
programs.getPack().getIdMap().getBlockProperties(),
|
||||
programs.getPack().getIdMap().hasLegacySection()));
|
||||
programs.getPack().getIdMap().hasLegacySection());
|
||||
BlockRenderingSettings.INSTANCE.setBlockMetaMatches(blockIdMaps.blockMetaMap());
|
||||
BlockRenderingSettings.INSTANCE.setBlockNbtMap(blockIdMaps.tileEntityMap());
|
||||
BlockRenderingSettings.INSTANCE.setBlockTypeIds(BlockMaterialMapping.createBlockTypeMap(programs.getPack().getIdMap().getBlockRenderTypeMap()));
|
||||
|
||||
BlockRenderingSettings.INSTANCE.setEntityIds(programs.getPack().getIdMap().getEntityIdMap());
|
||||
BlockRenderingSettings.INSTANCE.setEntityNbtMap(BlockMaterialMapping.createNamespacedNbtMap(programs.getPack().getIdMap().getEntityNbtEntries()));
|
||||
|
||||
ItemMaterialHelper.clearCache();
|
||||
BlockRenderingSettings.INSTANCE.setItemIds(programs.getPack().getIdMap().getItemIdMap());
|
||||
BlockRenderingSettings.INSTANCE.setItemNbtMap(BlockMaterialMapping.createNamespacedNbtMap(programs.getPack().getIdMap().getItemNbtEntries()));
|
||||
BlockRenderingSettings.INSTANCE.setAmbientOcclusionLevel(programs.getPackDirectives().getAmbientOcclusionLevel());
|
||||
BlockRenderingSettings.INSTANCE.setDisableDirectionalShading(shouldDisableDirectionalShading());
|
||||
BlockRenderingSettings.INSTANCE.setUseSeparateAo(programs.getPackDirectives().shouldUseSeparateAo());
|
||||
|
||||
@@ -14,6 +14,7 @@ import net.coderbot.iris.shaderpack.materialmap.BlockEntry;
|
||||
import net.coderbot.iris.shaderpack.materialmap.EntityFlatteningMap;
|
||||
import net.coderbot.iris.shaderpack.materialmap.BlockRenderType;
|
||||
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
|
||||
import net.coderbot.iris.shaderpack.materialmap.PropertiesTokenizer;
|
||||
import net.coderbot.iris.shaderpack.option.ShaderPackOptions;
|
||||
import net.coderbot.iris.shaderpack.preprocessor.PropertiesPreprocessor;
|
||||
import net.minecraft.item.Item;
|
||||
@@ -45,11 +46,15 @@ public class IdMap {
|
||||
* Maps a given item ID to an integer ID
|
||||
*/
|
||||
private final Object2IntMap<NamespacedId> itemIdMap;
|
||||
@Getter
|
||||
private final Int2ObjectMap<List<BlockEntry>> itemNbtEntries;
|
||||
|
||||
/**
|
||||
* Maps a given entity ID to an integer ID
|
||||
*/
|
||||
private final Object2IntMap<NamespacedId> entityIdMap;
|
||||
@Getter
|
||||
private final Int2ObjectMap<List<BlockEntry>> entityNbtEntries;
|
||||
|
||||
/**
|
||||
* Maps block states to block ids defined in block.properties
|
||||
@@ -106,8 +111,17 @@ public class IdMap {
|
||||
});
|
||||
}
|
||||
|
||||
itemIdMap = loadProperties(shaderPath, "item.properties", shaderPackOptions, resolvedDefines).map(IdMap::parseItemIdMap).orElse(Object2IntMaps.emptyMap());
|
||||
entityIdMap = loadProperties(shaderPath, "entity.properties", shaderPackOptions, resolvedDefines).map(IdMap::parseEntityIdMap).orElse(Object2IntMaps.emptyMap());
|
||||
final ParsedIdMap parsedItems = loadProperties(shaderPath, "item.properties", shaderPackOptions, resolvedDefines)
|
||||
.map(p -> parseIdMap(p, "item.", "item.properties"))
|
||||
.orElse(new ParsedIdMap(Object2IntMaps.emptyMap(), new Int2ObjectOpenHashMap<>()));
|
||||
itemIdMap = parsedItems.simpleMap();
|
||||
itemNbtEntries = parsedItems.nbtEntries();
|
||||
|
||||
final ParsedIdMap parsedEntities = loadProperties(shaderPath, "entity.properties", shaderPackOptions, resolvedDefines)
|
||||
.map(p -> parseIdMap(p, "entity.", "entity.properties"))
|
||||
.orElse(new ParsedIdMap(Object2IntMaps.emptyMap(), new Int2ObjectOpenHashMap<>()));
|
||||
entityIdMap = augmentEntityIdMap(parsedEntities.simpleMap(), parsedEntities.nbtEntries());
|
||||
entityNbtEntries = parsedEntities.nbtEntries();
|
||||
|
||||
// TODO: Properly override block render layers
|
||||
|
||||
@@ -166,25 +180,37 @@ public class IdMap {
|
||||
}
|
||||
}
|
||||
|
||||
private static Object2IntMap<NamespacedId> parseItemIdMap(Properties properties) {
|
||||
return parseIdMap(properties, "item.", "item.properties");
|
||||
}
|
||||
|
||||
private static Object2IntMap<NamespacedId> parseEntityIdMap(Properties properties) {
|
||||
Object2IntMap<NamespacedId> idMap = parseIdMap(properties, "entity.", "entity.properties");
|
||||
|
||||
// For modern shader packs: translate modern entity names to 1.7.10 names
|
||||
// so runtime lookups match EntityList's registered names directly.
|
||||
/**
|
||||
* For modern shaderpacks: also expose entries under their 1.7.10 entity names
|
||||
* so runtime lookups match EntityList's registered names directly.
|
||||
*
|
||||
* Modern names whose 1.7.10 counterpart is NBT-discriminated (e.g. wither_skeleton
|
||||
* is just Skeleton with SkeletonType=1) are routed into the NBT-conditional map
|
||||
* instead of being aliased into the simple map — otherwise the simple lookup for
|
||||
* "Skeleton" would short-circuit and match every skeleton variant.
|
||||
*/
|
||||
private static Object2IntMap<NamespacedId> augmentEntityIdMap(
|
||||
Object2IntMap<NamespacedId> idMap,
|
||||
Int2ObjectMap<List<BlockEntry>> nbtEntries) {
|
||||
Object2IntMap<NamespacedId> augmented = new Object2IntOpenHashMap<>(idMap);
|
||||
augmented.defaultReturnValue(-1);
|
||||
|
||||
for (Object2IntMap.Entry<NamespacedId> entry : idMap.object2IntEntrySet()) {
|
||||
NamespacedId id = entry.getKey();
|
||||
if ("minecraft".equals(id.getNamespace())) {
|
||||
String legacyName = EntityFlatteningMap.toLegacy(id.getName());
|
||||
if (legacyName != null) {
|
||||
augmented.putIfAbsent(new NamespacedId(legacyName), entry.getIntValue());
|
||||
}
|
||||
if (!"minecraft".equals(id.getNamespace())) {
|
||||
continue;
|
||||
}
|
||||
final int intId = entry.getIntValue();
|
||||
|
||||
BlockEntry nbtMapping = EntityFlatteningMap.toLegacyWithNbt(id.getName());
|
||||
if (nbtMapping != null) {
|
||||
nbtEntries.computeIfAbsent(intId, k -> new ArrayList<>()).add(nbtMapping);
|
||||
continue;
|
||||
}
|
||||
|
||||
String legacyName = EntityFlatteningMap.toLegacy(id.getName());
|
||||
if (legacyName != null) {
|
||||
augmented.putIfAbsent(new NamespacedId(legacyName), intId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,67 +238,18 @@ public class IdMap {
|
||||
* @return List of parsed identifiers
|
||||
*/
|
||||
static List<String> parseIdentifierList(String value, String fileName, String key) {
|
||||
if (value.indexOf('"') == -1) {
|
||||
String[] parts = value.split("\\s+");
|
||||
List<String> result = new ArrayList<>(parts.length);
|
||||
for (String part : parts) {
|
||||
if (!part.isEmpty()) {
|
||||
result.add(part);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Found quote, start of a dumb block ID
|
||||
List<String> result = new ArrayList<>();
|
||||
StringBuilder current = new StringBuilder();
|
||||
boolean inQuotes = false;
|
||||
boolean escaped = false;
|
||||
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
char c = value.charAt(i);
|
||||
|
||||
if (escaped) {
|
||||
current.append(c);
|
||||
escaped = false;
|
||||
} else if (c == '\\') {
|
||||
escaped = true;
|
||||
} else if (c == '"') {
|
||||
inQuotes = !inQuotes;
|
||||
} else if (Character.isWhitespace(c) && !inQuotes) {
|
||||
if (!current.isEmpty()) {
|
||||
result.add(current.toString());
|
||||
current.setLength(0);
|
||||
}
|
||||
} else {
|
||||
current.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
// Didn't close a quote, warn
|
||||
if (inQuotes) {
|
||||
Iris.logger.warn(fileName + " [" + key + "]: Unclosed quote");
|
||||
}
|
||||
|
||||
// Trailing backslash, warn
|
||||
if (escaped) {
|
||||
Iris.logger.warn(fileName + " [" + key + "]: Trailing backslash");
|
||||
}
|
||||
|
||||
// Add final token
|
||||
if (!current.isEmpty()) {
|
||||
result.add(current.toString());
|
||||
}
|
||||
|
||||
return result;
|
||||
return PropertiesTokenizer.tokenizeValues(value, ' ');
|
||||
}
|
||||
|
||||
record ParsedIdMap(Object2IntMap<NamespacedId> simpleMap, Int2ObjectMap<List<BlockEntry>> nbtEntries) {}
|
||||
|
||||
/**
|
||||
* Parses a NamespacedId map in OptiFine format
|
||||
*/
|
||||
private static Object2IntMap<NamespacedId> parseIdMap(Properties properties, String keyPrefix, String fileName) {
|
||||
private static ParsedIdMap parseIdMap(Properties properties, String keyPrefix, String fileName) {
|
||||
Object2IntMap<NamespacedId> idMap = new Object2IntOpenHashMap<>();
|
||||
idMap.defaultReturnValue(-1);
|
||||
Int2ObjectMap<List<BlockEntry>> nbtEntries = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
properties.forEach((keyObject, valueObject) -> {
|
||||
String key = (String) keyObject;
|
||||
@@ -295,6 +272,20 @@ public class IdMap {
|
||||
|
||||
// Parse identifiers
|
||||
for (String part : parseIdentifierList(value, fileName, key)) {
|
||||
if (part.contains("[")) {
|
||||
// NBT-conditional entry
|
||||
try {
|
||||
BlockEntry entry = BlockEntry.parse(part);
|
||||
if (entry.hasNbtProperties()) {
|
||||
nbtEntries.computeIfAbsent(intId, k -> new ArrayList<>()).add(entry);
|
||||
} else {
|
||||
idMap.put(entry.id(), intId);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Iris.logger.warn("Failed to parse NBT entry in " + fileName + " for key " + key + ": " + part, e);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (part.contains("=")) {
|
||||
// Avoid tons of logspam for now
|
||||
Iris.logger.warn("Failed to parse an ResourceLocation in " + fileName + " for the key " + key + ": state properties are currently not supported: " + part);
|
||||
@@ -307,7 +298,7 @@ public class IdMap {
|
||||
}
|
||||
});
|
||||
|
||||
return Object2IntMaps.unmodifiable(idMap);
|
||||
return new ParsedIdMap(Object2IntMaps.unmodifiable(idMap), nbtEntries);
|
||||
}
|
||||
|
||||
private static Int2ObjectMap<List<BlockEntry>> parseBlockMap(Properties properties, String keyPrefix, String fileName) {
|
||||
@@ -408,7 +399,7 @@ public class IdMap {
|
||||
return itemIdMap;
|
||||
}
|
||||
|
||||
public Object2IntFunction<NamespacedId> getEntityIdMap() {
|
||||
public Object2IntFunction<NamespacedId> getEntityIdMap() {
|
||||
return entityIdMap;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,124 +1,45 @@
|
||||
package net.coderbot.iris.shaderpack.materialmap;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.coderbot.iris.Iris;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@Getter
|
||||
public class BlockEntry {
|
||||
private final NamespacedId id;
|
||||
private final Set<Integer> metas;
|
||||
private final Map<String, String> stateProperties;
|
||||
public record BlockEntry(
|
||||
NamespacedId id,
|
||||
Set<Integer> metas,
|
||||
Map<String, String> stateProperties,
|
||||
Map<String, PropertiesTokenizer.NbtValue> nbtProperties
|
||||
) {
|
||||
public BlockEntry(NamespacedId id, Set<Integer> metas) {
|
||||
this(id, metas, Collections.emptyMap(), Collections.emptyMap());
|
||||
}
|
||||
|
||||
public BlockEntry(NamespacedId id, Set<Integer> metas) {
|
||||
this(id, metas, Collections.emptyMap());
|
||||
}
|
||||
public BlockEntry(NamespacedId id, Set<Integer> metas, Map<String, String> stateProperties) {
|
||||
this(id, metas, stateProperties, Collections.emptyMap());
|
||||
}
|
||||
|
||||
public BlockEntry(NamespacedId id, Set<Integer> metas, Map<String, String> stateProperties) {
|
||||
this.id = id;
|
||||
this.metas = metas;
|
||||
this.stateProperties = stateProperties;
|
||||
}
|
||||
public boolean hasNbtProperties() {
|
||||
return !nbtProperties.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a block ID entry.
|
||||
*
|
||||
* @param entry The string representation of the entry. Must not be empty.
|
||||
*/
|
||||
@NotNull
|
||||
public static BlockEntry parse(@NotNull String entry) {
|
||||
if (entry.isEmpty()) {
|
||||
throw new IllegalArgumentException("Called BlockEntry::parse with an empty string");
|
||||
}
|
||||
public boolean hasStateProperties() {
|
||||
return !stateProperties.isEmpty();
|
||||
}
|
||||
|
||||
// We can assume that this array is of at least array length because the input string is non-empty.
|
||||
final String[] splitStates = entry.split(":");
|
||||
|
||||
// Trivial case: no states, no namespace
|
||||
if (splitStates.length == 1) {
|
||||
return new BlockEntry(new NamespacedId("minecraft", entry), Collections.emptySet());
|
||||
}
|
||||
|
||||
// Examples of what we accept:
|
||||
// stone
|
||||
// stone:0
|
||||
// minecraft:stone:0
|
||||
// minecraft:stone:0,1,2
|
||||
// minecraft:furnace:lit=true (blockstate property)
|
||||
// minecraft:oak_log:axis=y (blockstate property)
|
||||
|
||||
// Less trivial case: no metas or state properties, just a namespace
|
||||
if (splitStates.length == 2 && !StringUtils.isNumeric(splitStates[1].substring(0, 1))
|
||||
&& !splitStates[1].contains("=")) {
|
||||
return new BlockEntry(new NamespacedId(splitStates[0], splitStates[1]), Collections.emptySet());
|
||||
}
|
||||
|
||||
// Complex case: metas and/or state properties
|
||||
final int statesStart;
|
||||
final NamespacedId id;
|
||||
|
||||
if (splitStates.length == 2) {
|
||||
// "stone:0" or "stone:lit=true"
|
||||
statesStart = 1;
|
||||
id = new NamespacedId("minecraft", splitStates[0]);
|
||||
} else if (StringUtils.isNumeric(splitStates[1].substring(0, 1)) || splitStates[1].contains("=")) {
|
||||
// "stone:0:something" or "stone:lit=true:something" — unlikely but handle it
|
||||
statesStart = 1;
|
||||
id = new NamespacedId("minecraft", splitStates[0]);
|
||||
} else {
|
||||
// "minecraft:stone:0" or "minecraft:furnace:lit=true"
|
||||
statesStart = 2;
|
||||
id = new NamespacedId(splitStates[0], splitStates[1]);
|
||||
}
|
||||
|
||||
final Set<Integer> metas = new HashSet<>();
|
||||
final Map<String, String> properties = new HashMap<>();
|
||||
|
||||
for (int index = statesStart; index < splitStates.length; index++) {
|
||||
String segment = splitStates[index];
|
||||
|
||||
if (segment.contains("=")) {
|
||||
// Blockstate property: key=value
|
||||
for (String prop : segment.split(",")) {
|
||||
int eq = prop.indexOf('=');
|
||||
if (eq > 0 && eq < prop.length() - 1) {
|
||||
properties.put(prop.substring(0, eq), prop.substring(eq + 1));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Metadata IDs: comma-separated integers
|
||||
for (String metaPart : segment.split(",")) {
|
||||
try {
|
||||
metas.add(Integer.parseInt(metaPart));
|
||||
} catch (NumberFormatException e) {
|
||||
Iris.logger.warn("Warning: the block ID map entry \"" + entry + "\" could not be fully parsed:");
|
||||
Iris.logger.warn("- Metadata ids must be a comma separated list of one or more integers, but " + segment + " is not of that form!");
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parses a block ID entry. All tokenization is delegated to {@link PropertiesTokenizer}.
|
||||
*
|
||||
* @param entry The string representation of the entry. Must not be empty.
|
||||
*/
|
||||
@NotNull
|
||||
public static BlockEntry parse(@NotNull String entry) {
|
||||
if (entry.isEmpty()) {
|
||||
throw new IllegalArgumentException("Called BlockEntry::parse with an empty string");
|
||||
}
|
||||
|
||||
return new BlockEntry(id, metas, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final BlockEntry that = (BlockEntry) o;
|
||||
return Objects.equals(id, that.id) && Objects.equals(metas, that.metas) && Objects.equals(stateProperties, that.stateProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, metas, stateProperties);
|
||||
}
|
||||
final PropertiesTokenizer.ParsedBlockIdentifier parsed = PropertiesTokenizer.parseBlockIdentifier(entry);
|
||||
return new BlockEntry(parsed.id(), parsed.metas(), parsed.stateProperties(), parsed.nbtProperties());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.coderbot.iris.shaderpack.materialmap;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -10,14 +11,15 @@ import java.util.Map;
|
||||
* In 1.7.10, entities are registered with names like "CaveSpider".
|
||||
* Some entities were also renamed or split into multiple types.
|
||||
*
|
||||
* This map is used at lookup time: when a modern shader pack references an entity by its
|
||||
* modern name, we resolve it to the 1.7.10 name that EntityList uses.
|
||||
*/
|
||||
public class EntityFlatteningMap {
|
||||
|
||||
/** Maps modern entity name -> 1.7.10 entity name. */
|
||||
private static final Map<String, String> MODERN_TO_LEGACY = new HashMap<>();
|
||||
|
||||
/** Maps modern entity name -> legacy entity name + NBT. */
|
||||
private static final Map<String, BlockEntry> MODERN_TO_LEGACY_NBT = new HashMap<>();
|
||||
|
||||
static {
|
||||
// Simple case changes
|
||||
simple("Arrow", "arrow");
|
||||
@@ -35,7 +37,6 @@ public class EntityFlatteningMap {
|
||||
simple("Pig", "pig");
|
||||
simple("Sheep", "sheep");
|
||||
simple("Silverfish", "silverfish");
|
||||
simple("Skeleton", "skeleton");
|
||||
simple("Slime", "slime");
|
||||
simple("Snowball", "snowball");
|
||||
simple("Spider", "spider");
|
||||
@@ -49,7 +50,6 @@ public class EntityFlatteningMap {
|
||||
simple("CaveSpider", "cave_spider");
|
||||
simple("EnderCrystal", "end_crystal");
|
||||
simple("EnderDragon", "ender_dragon");
|
||||
simple("EntityHorse", "horse");
|
||||
simple("EyeOfEnderSignal", "eye_of_ender");
|
||||
simple("FallingSand", "falling_block");
|
||||
simple("Fireball", "fireball");
|
||||
@@ -79,6 +79,36 @@ public class EntityFlatteningMap {
|
||||
simple("MinecartHopper", "hopper_minecart");
|
||||
simple("MinecartSpawner", "spawner_minecart");
|
||||
simple("MinecartTNT", "tnt_minecart");
|
||||
|
||||
MODERN_TO_LEGACY_NBT.put("skeleton", new BlockEntry(
|
||||
new NamespacedId("Skeleton"), Collections.emptySet(), Collections.emptyMap(),
|
||||
Map.of("SkeletonType", new PropertiesTokenizer.NbtValue("0", false))));
|
||||
MODERN_TO_LEGACY_NBT.put("wither_skeleton", new BlockEntry(
|
||||
new NamespacedId("Skeleton"), Collections.emptySet(), Collections.emptyMap(),
|
||||
Map.of("SkeletonType", new PropertiesTokenizer.NbtValue("1", false))));
|
||||
|
||||
// 1.7.10 EntityHorse with Type byte: 0=horse, 1=donkey, 2=mule, 3=zombie_horse,
|
||||
// 4=skeleton_horse. 1.11+ split each into its own entity type.
|
||||
MODERN_TO_LEGACY_NBT.put("horse", new BlockEntry(
|
||||
new NamespacedId("EntityHorse"), Collections.emptySet(), Collections.emptyMap(),
|
||||
Map.of("Type", new PropertiesTokenizer.NbtValue("0", false))));
|
||||
MODERN_TO_LEGACY_NBT.put("donkey", new BlockEntry(
|
||||
new NamespacedId("EntityHorse"), Collections.emptySet(), Collections.emptyMap(),
|
||||
Map.of("Type", new PropertiesTokenizer.NbtValue("1", false))));
|
||||
MODERN_TO_LEGACY_NBT.put("mule", new BlockEntry(
|
||||
new NamespacedId("EntityHorse"), Collections.emptySet(), Collections.emptyMap(),
|
||||
Map.of("Type", new PropertiesTokenizer.NbtValue("2", false))));
|
||||
MODERN_TO_LEGACY_NBT.put("zombie_horse", new BlockEntry(
|
||||
new NamespacedId("EntityHorse"), Collections.emptySet(), Collections.emptyMap(),
|
||||
Map.of("Type", new PropertiesTokenizer.NbtValue("3", false))));
|
||||
MODERN_TO_LEGACY_NBT.put("skeleton_horse", new BlockEntry(
|
||||
new NamespacedId("EntityHorse"), Collections.emptySet(), Collections.emptyMap(),
|
||||
Map.of("Type", new PropertiesTokenizer.NbtValue("4", false))));
|
||||
|
||||
// 1.7.10 Zombie with IsVillager.
|
||||
MODERN_TO_LEGACY_NBT.put("zombie_villager", new BlockEntry(
|
||||
new NamespacedId("Zombie"), Collections.emptySet(), Collections.emptyMap(),
|
||||
Map.of("IsVillager", new PropertiesTokenizer.NbtValue("1", false))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,6 +119,10 @@ public class EntityFlatteningMap {
|
||||
return MODERN_TO_LEGACY.get(modernName);
|
||||
}
|
||||
|
||||
public static BlockEntry toLegacyWithNbt(String modernName) {
|
||||
return MODERN_TO_LEGACY_NBT.get(modernName);
|
||||
}
|
||||
|
||||
private static void simple(String legacy, String modern) {
|
||||
MODERN_TO_LEGACY.put(modern, legacy);
|
||||
}
|
||||
|
||||
@@ -158,18 +158,18 @@ public class FlatteningMap {
|
||||
rename("dark_oak_wall_sign", "wall_sign");
|
||||
|
||||
// === Skull / Head blocks (ID 144) ===
|
||||
// In 1.7.10, skull type is stored in tile entity, not metadata.
|
||||
// All modern skull blocks map to the same 1.7.10 block.
|
||||
rename("skeleton_skull", "skull");
|
||||
rename("skeleton_wall_skull", "skull");
|
||||
rename("wither_skeleton_skull", "skull");
|
||||
rename("wither_skeleton_wall_skull", "skull");
|
||||
rename("zombie_head", "skull");
|
||||
rename("zombie_wall_head", "skull");
|
||||
rename("player_head", "skull");
|
||||
rename("player_wall_head", "skull");
|
||||
rename("creeper_head", "skull");
|
||||
rename("creeper_wall_head", "skull");
|
||||
// 1.7.10 stores skull type in TileEntitySkull's SkullType byte (0=skeleton,
|
||||
// 1=wither, 2=zombie, 3=player, 4=creeper).
|
||||
MODERN_TO_LEGACY.put("skeleton_skull", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("0", false)))));
|
||||
MODERN_TO_LEGACY.put("skeleton_wall_skull", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("0", false)))));
|
||||
MODERN_TO_LEGACY.put("wither_skeleton_skull", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("1", false)))));
|
||||
MODERN_TO_LEGACY.put("wither_skeleton_wall_skull", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("1", false)))));
|
||||
MODERN_TO_LEGACY.put("zombie_head", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("2", false)))));
|
||||
MODERN_TO_LEGACY.put("zombie_wall_head", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("2", false)))));
|
||||
MODERN_TO_LEGACY.put("player_head", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("3", false)))));
|
||||
MODERN_TO_LEGACY.put("player_wall_head", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("3", false)))));
|
||||
MODERN_TO_LEGACY.put("creeper_head", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("4", false)))));
|
||||
MODERN_TO_LEGACY.put("creeper_wall_head", List.of(new BlockEntry(new NamespacedId("minecraft", "skull"), Collections.emptySet(), Collections.emptyMap(), Map.of("SkullType", new PropertiesTokenizer.NbtValue("4", false)))));
|
||||
|
||||
// ==========================================
|
||||
// Metadata variants (block split by flattening)
|
||||
@@ -321,29 +321,29 @@ public class FlatteningMap {
|
||||
meta("mossy_cobblestone_wall", "cobblestone_wall", 1);
|
||||
|
||||
// === Flower pot (ID 140) ===
|
||||
// In 1.7.10, flower pot contents are in the tile entity.
|
||||
// In 1.13+, each potted plant is a separate block.
|
||||
rename("potted_oak_sapling", "flower_pot");
|
||||
rename("potted_spruce_sapling", "flower_pot");
|
||||
rename("potted_birch_sapling", "flower_pot");
|
||||
rename("potted_jungle_sapling", "flower_pot");
|
||||
rename("potted_acacia_sapling", "flower_pot");
|
||||
rename("potted_dark_oak_sapling", "flower_pot");
|
||||
rename("potted_fern", "flower_pot");
|
||||
rename("potted_dandelion", "flower_pot");
|
||||
rename("potted_poppy", "flower_pot");
|
||||
rename("potted_blue_orchid", "flower_pot");
|
||||
rename("potted_allium", "flower_pot");
|
||||
rename("potted_azure_bluet", "flower_pot");
|
||||
rename("potted_red_tulip", "flower_pot");
|
||||
rename("potted_orange_tulip", "flower_pot");
|
||||
rename("potted_white_tulip", "flower_pot");
|
||||
rename("potted_pink_tulip", "flower_pot");
|
||||
rename("potted_oxeye_daisy", "flower_pot");
|
||||
rename("potted_red_mushroom", "flower_pot");
|
||||
rename("potted_brown_mushroom", "flower_pot");
|
||||
rename("potted_dead_bush", "flower_pot");
|
||||
rename("potted_cactus", "flower_pot");
|
||||
// 1.7.10 stores potted contents in TileEntityFlowerPot's NBT. 1.13+ has a separate block
|
||||
// per potted plant.
|
||||
MODERN_TO_LEGACY.put("potted_oak_sapling", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:sapling", false), "Data", new PropertiesTokenizer.NbtValue("0", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_spruce_sapling", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:sapling", false), "Data", new PropertiesTokenizer.NbtValue("1", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_birch_sapling", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:sapling", false), "Data", new PropertiesTokenizer.NbtValue("2", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_jungle_sapling", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:sapling", false), "Data", new PropertiesTokenizer.NbtValue("3", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_acacia_sapling", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:sapling", false), "Data", new PropertiesTokenizer.NbtValue("4", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_dark_oak_sapling", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:sapling", false), "Data", new PropertiesTokenizer.NbtValue("5", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_fern", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:tallgrass", false), "Data", new PropertiesTokenizer.NbtValue("2", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_dandelion", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:yellow_flower", false), "Data", new PropertiesTokenizer.NbtValue("0", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_poppy", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("0", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_blue_orchid", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("1", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_allium", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("2", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_azure_bluet", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("3", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_red_tulip", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("4", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_orange_tulip", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("5", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_white_tulip", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("6", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_pink_tulip", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("7", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_oxeye_daisy", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_flower", false), "Data", new PropertiesTokenizer.NbtValue("8", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_red_mushroom", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:red_mushroom", false), "Data", new PropertiesTokenizer.NbtValue("0", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_brown_mushroom", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:brown_mushroom", false), "Data", new PropertiesTokenizer.NbtValue("0", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_dead_bush", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:deadbush", false), "Data", new PropertiesTokenizer.NbtValue("0", false)))));
|
||||
MODERN_TO_LEGACY.put("potted_cactus", List.of(new BlockEntry(new NamespacedId("minecraft", "flower_pot"), Collections.emptySet(), Collections.emptyMap(), Map.of("Item", new PropertiesTokenizer.NbtValue("minecraft:cactus", false), "Data", new PropertiesTokenizer.NbtValue("0", false)))));
|
||||
|
||||
// === Anvil (ID 145) ===
|
||||
// Bits 0-1 = facing, bits 2-3 = damage level
|
||||
@@ -928,26 +928,26 @@ public class FlatteningMap {
|
||||
List<BlockEntry> result = new ArrayList<>(Math.min(a.size(), b.size()));
|
||||
for (BlockEntry entryA : a) {
|
||||
for (BlockEntry entryB : b) {
|
||||
if (!entryA.getId().equals(entryB.getId())) continue;
|
||||
if (!entryA.id().equals(entryB.id())) continue;
|
||||
|
||||
Set<Integer> metasA = entryA.getMetas();
|
||||
Set<Integer> metasB = entryB.getMetas();
|
||||
Set<Integer> metasA = entryA.metas();
|
||||
Set<Integer> metasB = entryB.metas();
|
||||
|
||||
if (metasA.isEmpty() && metasB.isEmpty()) {
|
||||
// Both wildcard -> wildcard
|
||||
result.add(new BlockEntry(entryA.getId(), Collections.emptySet()));
|
||||
result.add(new BlockEntry(entryA.id(), Collections.emptySet()));
|
||||
} else if (metasA.isEmpty()) {
|
||||
// A is wildcard -> use B's constraint
|
||||
result.add(new BlockEntry(entryA.getId(), metasB));
|
||||
result.add(new BlockEntry(entryA.id(), metasB));
|
||||
} else if (metasB.isEmpty()) {
|
||||
// B is wildcard -> use A's constraint
|
||||
result.add(new BlockEntry(entryA.getId(), metasA));
|
||||
result.add(new BlockEntry(entryA.id(), metasA));
|
||||
} else {
|
||||
// Both have specific metas -> intersect
|
||||
Set<Integer> intersection = new IntOpenHashSet(metasA);
|
||||
intersection.retainAll(metasB);
|
||||
if (!intersection.isEmpty()) {
|
||||
result.add(new BlockEntry(entryA.getId(), intersection));
|
||||
result.add(new BlockEntry(entryA.id(), intersection));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,388 @@
|
||||
package net.coderbot.iris.shaderpack.materialmap;
|
||||
|
||||
import net.coderbot.iris.Iris;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
// This got complicated fast
|
||||
|
||||
/**
|
||||
* This class handles all processing of individual block entries per material ID line.
|
||||
*/
|
||||
public final class PropertiesTokenizer {
|
||||
|
||||
private PropertiesTokenizer() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a property value into individual block identifier tokens.
|
||||
*/
|
||||
public static List<String> tokenizeValues(String input, char delimiter) {
|
||||
if (input.indexOf('"') == -1 && input.indexOf('\\') == -1 && input.indexOf('[') == -1) {
|
||||
return tokenizeSimple(input, delimiter);
|
||||
}
|
||||
return tokenizeFull(input, delimiter);
|
||||
}
|
||||
|
||||
// Most blocks should take this path
|
||||
private static List<String> tokenizeSimple(String input, char delimiter) {
|
||||
final List<String> result = new ArrayList<>();
|
||||
final boolean isWhitespaceDelim = Character.isWhitespace(delimiter);
|
||||
|
||||
if (isWhitespaceDelim) {
|
||||
for (String part : input.split("\\s+")) {
|
||||
if (!part.isEmpty()) {
|
||||
result.add(part);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (String part : input.split(String.valueOf(delimiter))) {
|
||||
final String trimmed = part.trim();
|
||||
if (!trimmed.isEmpty()) {
|
||||
result.add(trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Found special characters
|
||||
private static List<String> tokenizeFull(String input, char delimiter) {
|
||||
final boolean isWhitespaceDelim = Character.isWhitespace(delimiter);
|
||||
final List<String> result = new ArrayList<>();
|
||||
final StringBuilder current = new StringBuilder();
|
||||
boolean inQuotes = false;
|
||||
boolean escaped = false;
|
||||
int bracketDepth = 0;
|
||||
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
final char c = input.charAt(i);
|
||||
|
||||
if (escaped) {
|
||||
if (bracketDepth > 0) {
|
||||
// Preserve escapes inside brackets for parseBlockIdentifier
|
||||
current.append('\\');
|
||||
}
|
||||
current.append(c);
|
||||
escaped = false;
|
||||
|
||||
} else if (c == '\\') {
|
||||
escaped = true;
|
||||
|
||||
} else if (bracketDepth > 0) {
|
||||
current.append(c);
|
||||
|
||||
if (c == '[') {
|
||||
bracketDepth++;
|
||||
}
|
||||
|
||||
else if (c == ']') {
|
||||
bracketDepth--;
|
||||
}
|
||||
|
||||
} else if (c == '[' && !inQuotes) {
|
||||
current.append(c);
|
||||
bracketDepth = 1;
|
||||
|
||||
} else if (c == '"') {
|
||||
inQuotes = !inQuotes;
|
||||
|
||||
} else if (!inQuotes && (isWhitespaceDelim ? Character.isWhitespace(c) : c == delimiter)) {
|
||||
if (!current.isEmpty()) {
|
||||
result.add(current.toString());
|
||||
current.setLength(0);
|
||||
}
|
||||
|
||||
} else {
|
||||
current.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (inQuotes) {
|
||||
Iris.logger.warn("Unclosed quote in properties token: {}", input);
|
||||
}
|
||||
|
||||
if (!current.isEmpty()) {
|
||||
result.add(current.toString());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Begin reading the special case block
|
||||
public static ParsedBlockIdentifier parseBlockIdentifier(String entry) {
|
||||
final StringBuilder blockIdBuilder = new StringBuilder();
|
||||
final Map<String, NbtValue> nbt = new LinkedHashMap<>();
|
||||
|
||||
boolean escaped = false;
|
||||
boolean inDoubleQuotes = false;
|
||||
boolean inSingleQuotes = false;
|
||||
boolean inBracket = false;
|
||||
|
||||
// NBT pair
|
||||
StringBuilder nbtKey = new StringBuilder();
|
||||
StringBuilder nbtValue = new StringBuilder();
|
||||
boolean readingValue = false;
|
||||
boolean valueLiteral = false;
|
||||
|
||||
for (int i = 0; i < entry.length(); i++) {
|
||||
final char c = entry.charAt(i);
|
||||
|
||||
// Ignore special characters
|
||||
if (escaped) {
|
||||
appendCurrent(inBracket, readingValue, blockIdBuilder, nbtKey, nbtValue, c);
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
if (c == '\\') {
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Detect special characters
|
||||
if (inSingleQuotes) {
|
||||
if (c == '\'') {
|
||||
inSingleQuotes = false;
|
||||
|
||||
} else {
|
||||
nbtValue.append(c);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inDoubleQuotes) {
|
||||
if (c == '"') {
|
||||
inDoubleQuotes = false;
|
||||
|
||||
} else {
|
||||
appendCurrent(inBracket, readingValue, blockIdBuilder, nbtKey, nbtValue, c);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (c == '"') {
|
||||
inDoubleQuotes = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!inBracket) {
|
||||
if (c == '[') {
|
||||
inBracket = true;
|
||||
nbtKey.setLength(0);
|
||||
nbtValue.setLength(0);
|
||||
readingValue = false;
|
||||
valueLiteral = false;
|
||||
|
||||
} else {
|
||||
blockIdBuilder.append(c);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (c == ']') {
|
||||
flushNbtPair(nbtKey, nbtValue, readingValue, valueLiteral, nbt);
|
||||
inBracket = false;
|
||||
|
||||
} else if (c == '=' && !readingValue) {
|
||||
readingValue = true;
|
||||
valueLiteral = false;
|
||||
|
||||
} else if (c == '\'' && readingValue && nbtValue.isEmpty()) {
|
||||
inSingleQuotes = true;
|
||||
valueLiteral = true;
|
||||
|
||||
} else if (c == ',') {
|
||||
flushNbtPair(nbtKey, nbtValue, readingValue, valueLiteral, nbt);
|
||||
nbtKey.setLength(0);
|
||||
nbtValue.setLength(0);
|
||||
readingValue = false;
|
||||
valueLiteral = false;
|
||||
|
||||
} else {
|
||||
if (readingValue) {
|
||||
nbtValue.append(c);
|
||||
|
||||
} else {
|
||||
nbtKey.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inDoubleQuotes) {
|
||||
Iris.logger.warn("Unclosed double quote in block entry: {}", entry);
|
||||
}
|
||||
if (inSingleQuotes) {
|
||||
Iris.logger.warn("Unclosed single quote in block entry: {}", entry);
|
||||
}
|
||||
if (escaped) {
|
||||
Iris.logger.warn("Trailing backslash in block entry: {}", entry);
|
||||
}
|
||||
if (inBracket) {
|
||||
Iris.logger.warn("Unclosed bracket in block entry: {}", entry);
|
||||
flushNbtPair(nbtKey, nbtValue, readingValue, valueLiteral, nbt);
|
||||
}
|
||||
|
||||
final String baseEntry = blockIdBuilder.toString().trim();
|
||||
final Map<String, NbtValue> nbtProperties = nbt.isEmpty() ? Collections.emptyMap() : nbt;
|
||||
|
||||
return splitBaseEntry(entry, baseEntry, nbtProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the bracket-stripped portion of a block identifier (e.g. {@code minecraft:furnace:lit=true})
|
||||
* into its components.
|
||||
*
|
||||
* Accepted forms:
|
||||
* stone (vanilla block)
|
||||
* stone:0 (vanilla block with meta)
|
||||
* minecraft:stone (block with namespaced id specified)
|
||||
* minecraft:stone:0 (single meta)
|
||||
* minecraft:stone:0,1,2 (multiple metas)
|
||||
* minecraft:furnace:lit=true (blockstate property)
|
||||
* minecraft:oak_log:axis=y,x:variant=oak (multiple blockstate properties)
|
||||
* flower_pot:1[Item=minecraft:red_flower,Data=0] (used together with NBT brackets)
|
||||
*/
|
||||
private static ParsedBlockIdentifier splitBaseEntry(String entry, String baseEntry, Map<String, NbtValue> nbtProperties) {
|
||||
final Set<Integer> metas = new HashSet<>();
|
||||
final Map<String, String> stateProperties = new LinkedHashMap<>();
|
||||
|
||||
if (baseEntry.isEmpty()) {
|
||||
return new ParsedBlockIdentifier(
|
||||
new NamespacedId("minecraft", baseEntry),
|
||||
Collections.emptySet(),
|
||||
Collections.emptyMap(),
|
||||
nbtProperties
|
||||
);
|
||||
}
|
||||
|
||||
final String[] splitStates = baseEntry.split(":");
|
||||
|
||||
// Vanilla block (minecraft namespaced)
|
||||
if (splitStates.length == 1) {
|
||||
return new ParsedBlockIdentifier(
|
||||
new NamespacedId("minecraft", baseEntry),
|
||||
Collections.emptySet(),
|
||||
Collections.emptyMap(),
|
||||
nbtProperties
|
||||
);
|
||||
}
|
||||
|
||||
// Two-segment with no metas/state properties: "minecraft:stone"
|
||||
if (splitStates.length == 2
|
||||
&& !StringUtils.isNumeric(splitStates[1].substring(0, 1))
|
||||
&& !splitStates[1].contains("=")) {
|
||||
return new ParsedBlockIdentifier(
|
||||
new NamespacedId(splitStates[0], splitStates[1]),
|
||||
Collections.emptySet(),
|
||||
Collections.emptyMap(),
|
||||
nbtProperties
|
||||
);
|
||||
}
|
||||
|
||||
// Metas and/or state properties
|
||||
final NamespacedId id;
|
||||
final int statesStart;
|
||||
|
||||
if (splitStates.length == 2) {
|
||||
// "stone:0" or "stone:lit=true"
|
||||
id = new NamespacedId("minecraft", splitStates[0]);
|
||||
statesStart = 1;
|
||||
|
||||
} else if (StringUtils.isNumeric(splitStates[1].substring(0, 1)) || splitStates[1].contains("=")) {
|
||||
// "stone:0:something" or "stone:lit=true:something", likely will need this for snowy blocks in the future.
|
||||
id = new NamespacedId("minecraft", splitStates[0]);
|
||||
statesStart = 1;
|
||||
|
||||
} else {
|
||||
// "minecraft:stone:0" or "minecraft:furnace:lit=true"
|
||||
id = new NamespacedId(splitStates[0], splitStates[1]);
|
||||
statesStart = 2;
|
||||
}
|
||||
|
||||
for (int index = statesStart; index < splitStates.length; index++) {
|
||||
final String segment = splitStates[index];
|
||||
|
||||
if (segment.contains("=")) {
|
||||
// Blockstate property segment: key=value[,key=value...]
|
||||
for (String prop : segment.split(",")) {
|
||||
final int eq = prop.indexOf('=');
|
||||
if (eq > 0 && eq < prop.length() - 1) {
|
||||
stateProperties.put(prop.substring(0, eq), prop.substring(eq + 1));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Metadata segment (stone:[1,2,3,4,5])
|
||||
for (String metaPart : segment.split(",")) {
|
||||
try {
|
||||
metas.add(Integer.parseInt(metaPart));
|
||||
} catch (NumberFormatException e) {
|
||||
Iris.logger.warn("Warning: the block ID map entry \"{}\" could not be fully parsed:", entry);
|
||||
Iris.logger.warn("- Metadata ids must be a comma separated list of one or more integers, but {} is not of that form!", segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ParsedBlockIdentifier(
|
||||
id,
|
||||
metas.isEmpty() ? Collections.emptySet() : metas,
|
||||
stateProperties.isEmpty() ? Collections.emptyMap() : stateProperties,
|
||||
nbtProperties
|
||||
);
|
||||
}
|
||||
|
||||
private static void appendCurrent(boolean inBracket, boolean readingValue,
|
||||
StringBuilder blockIdBuilder, StringBuilder nbtKey,
|
||||
StringBuilder nbtValue, char c) {
|
||||
if (!inBracket) {
|
||||
blockIdBuilder.append(c);
|
||||
|
||||
} else if (readingValue) {
|
||||
nbtValue.append(c);
|
||||
|
||||
} else {
|
||||
nbtKey.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
private static void flushNbtPair(StringBuilder key, StringBuilder value,
|
||||
boolean readingValue, boolean valueLiteral,
|
||||
Map<String, NbtValue> nbt) {
|
||||
final String k = key.toString().trim();
|
||||
if (k.isEmpty()) return;
|
||||
|
||||
if (!readingValue) {
|
||||
// No = was found, existence is good enough
|
||||
nbt.put(k, null);
|
||||
return;
|
||||
}
|
||||
|
||||
nbt.put(k, new NbtValue(value.toString().trim(), valueLiteral));
|
||||
}
|
||||
|
||||
public static String stripQuotes(String value) {
|
||||
if (value.length() >= 2 && value.charAt(0) == '"' && value.charAt(value.length() - 1) == '"') {
|
||||
return value.substring(1, value.length() - 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public record NbtValue(String value, boolean literal) {
|
||||
}
|
||||
|
||||
public record ParsedBlockIdentifier(
|
||||
NamespacedId id,
|
||||
Set<Integer> metas,
|
||||
Map<String, String> stateProperties,
|
||||
Map<String, NbtValue> nbtProperties
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
package net.coderbot.iris.uniforms;
|
||||
|
||||
import it.unimi.dsi.fastutil.ints.Int2LongLinkedOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntFunction;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
|
||||
import net.coderbot.iris.block_rendering.NbtConditionalIdMap;
|
||||
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityList;
|
||||
@@ -21,10 +27,16 @@ public final class EntityIdHelper {
|
||||
private static final NamespacedId LIGHTNING_BOLT_ID = new NamespacedId("minecraft", "lightning_bolt");
|
||||
|
||||
private static final Object2IntMap<Class<?>> entityIdCache = new Object2IntOpenHashMap<>();
|
||||
private static final Map<Class<?>, NamespacedId> entityNameCache = new IdentityHashMap<>();
|
||||
private static Object2IntFunction<NamespacedId> cachedEntityIdMap;
|
||||
|
||||
private static final Int2LongLinkedOpenHashMap entityNbtCache = new Int2LongLinkedOpenHashMap();
|
||||
private static final int NBT_CACHE_INTERVAL_TICKS = 20;
|
||||
private static final int ENTITY_NBT_CACHE_MAX = 256;
|
||||
|
||||
static {
|
||||
entityIdCache.defaultReturnValue(Integer.MIN_VALUE);
|
||||
entityNbtCache.defaultReturnValue(-1L);
|
||||
}
|
||||
|
||||
private EntityIdHelper() {
|
||||
@@ -42,17 +54,48 @@ public final class EntityIdHelper {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Invalidate cache if the map changed (shader reload)
|
||||
// Invalidate caches if the map changed (shader reload)
|
||||
if (entityIdMap != cachedEntityIdMap) {
|
||||
entityIdCache.clear();
|
||||
entityNameCache.clear();
|
||||
entityNbtCache.clear();
|
||||
cachedEntityIdMap = entityIdMap;
|
||||
}
|
||||
|
||||
// Check NBT-conditional match first
|
||||
final NbtConditionalIdMap<NamespacedId> entityNbtMap = BlockRenderingSettings.INSTANCE.getEntityNbtMap();
|
||||
if (entityNbtMap != null && !entityNbtMap.isEmpty() && entity.worldObj != null) {
|
||||
final NamespacedId namespacedId = getCachedEntityName(entity);
|
||||
if (namespacedId != null && entityNbtMap.hasConditions(namespacedId)) {
|
||||
final int entityRuntimeId = entity.getEntityId();
|
||||
final long currentTick = entity.worldObj.getTotalWorldTime();
|
||||
final long cached = entityNbtCache.get(entityRuntimeId);
|
||||
|
||||
final int nbtId;
|
||||
if (cached != -1L && (currentTick - (cached >>> 32)) < NBT_CACHE_INTERVAL_TICKS) {
|
||||
nbtId = (int) cached;
|
||||
} else {
|
||||
final NBTTagCompound nbt = new NBTTagCompound();
|
||||
entity.writeToNBT(nbt);
|
||||
nbtId = entityNbtMap.resolve(namespacedId, nbt);
|
||||
final long packed = ((currentTick & 0x7FFFFFFFL) << 32) | (nbtId & 0xFFFFFFFFL);
|
||||
entityNbtCache.put(entityRuntimeId, packed);
|
||||
while (entityNbtCache.size() > ENTITY_NBT_CACHE_MAX) {
|
||||
entityNbtCache.removeFirstLong();
|
||||
}
|
||||
}
|
||||
|
||||
if (nbtId != -1) {
|
||||
return nbtId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normal entity type lookup
|
||||
int normalId = getNormalEntityId(entity, entityIdMap);
|
||||
final int normalId = getNormalEntityId(entity, entityIdMap);
|
||||
|
||||
// Check for special entity type overrides
|
||||
int specialId = getSpecialEntityId(entity, entityIdMap);
|
||||
final int specialId = getSpecialEntityId(entity, entityIdMap);
|
||||
if (specialId != -1) {
|
||||
return specialId;
|
||||
}
|
||||
@@ -134,4 +177,24 @@ public final class EntityIdHelper {
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a cached NamespacedId for the entity's registered type.
|
||||
*/
|
||||
private static NamespacedId getCachedEntityName(Entity entity) {
|
||||
Class<?> entityClass = entity.getClass();
|
||||
NamespacedId cached = entityNameCache.get(entityClass);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
String entityType = EntityList.getEntityString(entity);
|
||||
if (entityType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
cached = new NamespacedId(entityType);
|
||||
entityNameCache.put(entityClass, cached);
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntFunction;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
|
||||
import net.coderbot.iris.block_rendering.NbtConditionalIdMap;
|
||||
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.item.Item;
|
||||
@@ -20,6 +21,7 @@ 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;
|
||||
private static final Reference2ObjectMap<Item, NamespacedId> ITEM_NAME_CACHE = new Reference2ObjectOpenHashMap<>();
|
||||
/**
|
||||
* Get the material ID for an ItemStack.
|
||||
* For ItemBlock: checks block.properties first, then item.properties.
|
||||
@@ -32,6 +34,19 @@ public class ItemMaterialHelper {
|
||||
if (itemStack == null || itemStack.getItem() == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check item NBT conditions first
|
||||
final NbtConditionalIdMap<NamespacedId> itemNbtMap = BlockRenderingSettings.INSTANCE.getItemNbtMap();
|
||||
if (itemNbtMap != null && !itemNbtMap.isEmpty() && itemStack.hasTagCompound()) {
|
||||
NamespacedId namespacedId = getCachedItemName(itemStack.getItem());
|
||||
if (namespacedId != null && itemNbtMap.hasConditions(namespacedId)) {
|
||||
int nbtId = itemNbtMap.resolve(namespacedId, itemStack.getTagCompound());
|
||||
if (nbtId != -1) {
|
||||
return nbtId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return getMaterialId(itemStack.getItem(), itemStack.getItemDamage());
|
||||
}
|
||||
|
||||
@@ -73,8 +88,7 @@ public class ItemMaterialHelper {
|
||||
*/
|
||||
private static int lookupMaterialId(Item item, int metadata) {
|
||||
// For ItemBlock: check block.properties first
|
||||
if (item instanceof ItemBlock) {
|
||||
final ItemBlock itemBlock = (ItemBlock) item;
|
||||
if (item instanceof ItemBlock itemBlock) {
|
||||
final Block block = itemBlock.field_150939_a; // The block this item places
|
||||
|
||||
if (block != null) {
|
||||
@@ -94,7 +108,7 @@ public class ItemMaterialHelper {
|
||||
// Fall back to item.properties
|
||||
Object2IntFunction<NamespacedId> itemIds = BlockRenderingSettings.INSTANCE.getItemIds();
|
||||
if (itemIds != null) {
|
||||
String itemIdString = (String) Item.itemRegistry.getNameForObject(item);
|
||||
String itemIdString = Item.itemRegistry.getNameForObject(item);
|
||||
if (itemIdString != null) {
|
||||
ResourceLocation itemId = new ResourceLocation(itemIdString);
|
||||
return itemIds.applyAsInt(new NamespacedId(itemId.getResourceDomain(), itemId.getResourcePath()));
|
||||
@@ -110,5 +124,23 @@ public class ItemMaterialHelper {
|
||||
*/
|
||||
public static void clearCache() {
|
||||
MATERIAL_CACHE.clear();
|
||||
ITEM_NAME_CACHE.clear();
|
||||
}
|
||||
|
||||
private static NamespacedId getCachedItemName(Item item) {
|
||||
NamespacedId cached = ITEM_NAME_CACHE.get(item);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
String itemIdString = Item.itemRegistry.getNameForObject(item);
|
||||
if (itemIdString == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ResourceLocation itemId = new ResourceLocation(itemIdString);
|
||||
cached = new NamespacedId(itemId.getResourceDomain(), itemId.getResourcePath());
|
||||
ITEM_NAME_CACHE.put(item, cached);
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,10 @@ options.terrain_fog=Terrain Fog
|
||||
options.terrain_fog.tooltip=Terrain Fog (aka View Distance Fog)
|
||||
options.fog_near_distance=Near Fog Distance
|
||||
options.fog_near_distance.tooltip=Distance away from the player when the view distance fog will start (0%% opacity then linear increase to render distance)
|
||||
options.bobview_mode=View Bobbing Mode
|
||||
options.bobview_mode.tooltip=Change the view bobbing behaviour to bob only the hand or camera.
|
||||
options.bobview.hand=Hand Only
|
||||
options.bobview.camera=Camera Only
|
||||
options.fov.all=Enabled
|
||||
options.fov.mods=Mods Only
|
||||
options.fov.none=Disabled
|
||||
|
||||
+12
@@ -3,7 +3,9 @@ package com.gtnewhorizons.angelica.mixins.early.angelica;
|
||||
import com.gtnewhorizons.angelica.compat.mojang.Camera;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.EntityRendererAccessor;
|
||||
import com.gtnewhorizons.angelica.rendering.RenderingState;
|
||||
import com.llamalad7.mixinextras.injector.v2.WrapWithCondition;
|
||||
import jss.notfine.core.Settings;
|
||||
import jss.notfine.gui.options.named.BobviewMode;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.EntityRenderer;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
@@ -54,4 +56,14 @@ public abstract class MixinEntityRenderer implements EntityRendererAccessor {
|
||||
int value = (int) Settings.HURT_SHAKE.option.getStore();
|
||||
return orig * (value/100F);
|
||||
}
|
||||
|
||||
@WrapWithCondition(method = "setupCameraTransform", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/EntityRenderer;setupViewBobbing(F)V"))
|
||||
private boolean angelica$cameraBobbing(EntityRenderer entity, float partialTicks) {
|
||||
return Settings.BOBVIEW_MODE.option.getStore() != BobviewMode.HAND;
|
||||
}
|
||||
|
||||
@WrapWithCondition(method = "renderHand", at= @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/EntityRenderer;setupViewBobbing(F)V"))
|
||||
private boolean angelica$handBobbing(EntityRenderer entity, float partialTicks) {
|
||||
return Settings.BOBVIEW_MODE.option.getStore() != BobviewMode.CAMERA;
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -42,10 +42,10 @@ public class MixinItemRenderer_EdgeDepth {
|
||||
)
|
||||
private void angelica$stencilWriteStart(CallbackInfo ci) {
|
||||
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
|
||||
GLStateManager.glStencilMask(0xFF);
|
||||
GLStateManager.glStencilMask(0x03);
|
||||
GLStateManager.glClearStencil(0);
|
||||
GLStateManager.glClear(GL11.GL_STENCIL_BUFFER_BIT);
|
||||
GLStateManager.glStencilFunc(GL11.GL_ALWAYS, 1, 0xFF);
|
||||
GLStateManager.glStencilFunc(GL11.GL_ALWAYS, 1, 0x03);
|
||||
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ public class MixinItemRenderer_EdgeDepth {
|
||||
private void angelica$glintStart(int func) {
|
||||
angelica$glintMode = true;
|
||||
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
|
||||
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 0xFF);
|
||||
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 0x03);
|
||||
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public class MixinItemRenderer_EdgeDepth {
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
|
||||
GLStateManager.glStencilMask(0xFF);
|
||||
GLStateManager.glStencilMask(0x03);
|
||||
}
|
||||
|
||||
// Stencil pre-pass inside renderItemIn2D when in glint mode
|
||||
@@ -102,21 +102,21 @@ public class MixinItemRenderer_EdgeDepth {
|
||||
GLStateManager.glColorMask(false, false, false, false);
|
||||
GLStateManager.glDepthMask(false);
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
GLStateManager.glStencilMask(0xFF);
|
||||
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 0xFF);
|
||||
GLStateManager.glStencilMask(0x03);
|
||||
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 0x03);
|
||||
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_INCR);
|
||||
original.call(tess, minU, minV, maxU, maxV, w, h, thickness);
|
||||
|
||||
// Color pass: draw only at pre-pass pixels.
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 2, 0xFF);
|
||||
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 2, 0x03);
|
||||
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_DECR);
|
||||
GLStateManager.glDepthFunc(GL11.GL_ALWAYS);
|
||||
original.call(tess, minU, minV, maxU, maxV, w, h, thickness);
|
||||
|
||||
// Restore state for the next glint renderItemIn2D call
|
||||
GLStateManager.glStencilMask(0x00);
|
||||
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 0xFF);
|
||||
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 0x03);
|
||||
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.pipeline.FixedFunctionWorldRenderingPipeline;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
import net.minecraft.client.renderer.entity.RenderMooshroom;
|
||||
import net.minecraft.entity.passive.EntityMooshroom;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Make it so that the mushrooms on the mooshroom are also tinted when the mob is damaged.
|
||||
*/
|
||||
@Mixin(RenderMooshroom.class)
|
||||
public class MixinRenderMooshroom_MushroomTint {
|
||||
|
||||
@Unique private static final float ANGELICA$RED_MIX = 1.0F - (0xB2 / 255.0F);
|
||||
|
||||
@Unique private boolean angelica$mushroomTintActive;
|
||||
@Unique private boolean angelica$mushroomTintShaderPath;
|
||||
|
||||
@Inject(
|
||||
method = "renderEquippedItems(Lnet/minecraft/entity/passive/EntityMooshroom;F)V",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/RenderBlocks;renderBlockAsItem(Lnet/minecraft/block/Block;IF)V",
|
||||
ordinal = 0),
|
||||
require = 1, expect = 1
|
||||
)
|
||||
private void angelica$applyMushroomTint(EntityMooshroom entity, float partialTick, CallbackInfo ci) {
|
||||
if (entity.hurtTime <= 0 && entity.deathTime <= 0) return;
|
||||
angelica$mushroomTintShaderPath = angelica$isShaderPackActive();
|
||||
if (angelica$mushroomTintShaderPath) {
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
|
||||
} else {
|
||||
GLStateManager.setOverlayColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
|
||||
}
|
||||
angelica$mushroomTintActive = true;
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "renderEquippedItems(Lnet/minecraft/entity/passive/EntityMooshroom;F)V",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/RenderBlocks;renderBlockAsItem(Lnet/minecraft/block/Block;IF)V",
|
||||
ordinal = 2,
|
||||
shift = At.Shift.AFTER),
|
||||
require = 1, expect = 1
|
||||
)
|
||||
private void angelica$clearMushroomTint(EntityMooshroom entity, float partialTick, CallbackInfo ci) {
|
||||
if (!angelica$mushroomTintActive) return;
|
||||
if (angelica$mushroomTintShaderPath) {
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
} else {
|
||||
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
}
|
||||
angelica$mushroomTintActive = false;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean angelica$isShaderPackActive() {
|
||||
if (!AngelicaConfig.enableIris) return false;
|
||||
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
|
||||
return pipeline != null && !(pipeline instanceof FixedFunctionWorldRenderingPipeline);
|
||||
}
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
|
||||
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
|
||||
import net.coderbot.iris.Iris;
|
||||
import net.coderbot.iris.pipeline.FixedFunctionWorldRenderingPipeline;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
import net.minecraft.client.model.ModelBase;
|
||||
import net.minecraft.client.renderer.entity.RendererLivingEntity;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.EntityLivingBase;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
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.Slice;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
/**
|
||||
* Single-pass damage-overlay tint, modeled on modern Minecraft's OverlayTexture.
|
||||
*/
|
||||
@Mixin(RendererLivingEntity.class)
|
||||
public class MixinRendererLivingEntity_OverlayTint {
|
||||
|
||||
@Unique private static final float ANGELICA$RED_MIX = 1.0F - (0xB2 / 255.0F);
|
||||
|
||||
@Unique private boolean angelica$skipReRender;
|
||||
@Unique private boolean angelica$ffpOverlayActive;
|
||||
@Unique private boolean angelica$shaderEntityColorActive;
|
||||
|
||||
@Unique private boolean angelica$emissivePass;
|
||||
|
||||
@Inject(
|
||||
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderModel(Lnet/minecraft/entity/EntityLivingBase;FFFFFF)V"),
|
||||
require = 1, expect = 1
|
||||
)
|
||||
private void angelica$setupOverlay(EntityLivingBase entity, double x, double y, double z,
|
||||
float yaw, float partialTick, CallbackInfo ci) {
|
||||
GLStateManager.glDisable(GL11.GL_BLEND);
|
||||
GLStateManager.glEnable(GL11.GL_ALPHA_TEST);
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
GLStateManager.glDepthMask(true);
|
||||
|
||||
if (entity.hurtTime <= 0 && entity.deathTime <= 0) return;
|
||||
if (angelica$isShaderPackActive()) {
|
||||
// Iris path
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
|
||||
angelica$shaderEntityColorActive = true;
|
||||
} else {
|
||||
// FFP path
|
||||
GLStateManager.setOverlayColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
|
||||
angelica$ffpOverlayActive = true;
|
||||
}
|
||||
angelica$skipReRender = true;
|
||||
}
|
||||
|
||||
@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"),
|
||||
require = 1, expect = 1
|
||||
)
|
||||
private void angelica$teardownOverlay(EntityLivingBase entity, double x, double y, double z,
|
||||
float yaw, float partialTick, CallbackInfo ci) {
|
||||
if (angelica$ffpOverlayActive) {
|
||||
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
angelica$ffpOverlayActive = false;
|
||||
}
|
||||
if (angelica$shaderEntityColorActive) {
|
||||
// Don't tint held items
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
angelica$shaderEntityColorActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip the vanilla re-render block.
|
||||
*/
|
||||
@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"),
|
||||
slice = @Slice(
|
||||
from = @At(value = "FIELD",
|
||||
target = "Lnet/minecraft/entity/EntityLivingBase;hurtTime:I",
|
||||
ordinal = 1)
|
||||
),
|
||||
require = 1, expect = 1
|
||||
)
|
||||
private void angelica$skipOverlayReRender(ModelBase model, Entity entity,
|
||||
float p1, float p2, float p3, float p4, float p5, float p6, Operation<Void> original) {
|
||||
if (angelica$skipReRender) return;
|
||||
original.call(model, entity, p1, p2, p3, p4, p5, p6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent bleed by saving last state and comparing with current blend state.
|
||||
*/
|
||||
@WrapOperation(
|
||||
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
|
||||
at = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;shouldRenderPass(Lnet/minecraft/entity/EntityLivingBase;IF)I"),
|
||||
require = 1, expect = 1
|
||||
)
|
||||
private int angelica$detectEmissivePass(RendererLivingEntity self, EntityLivingBase entity, int pass,
|
||||
float partialTick, Operation<Integer> original) {
|
||||
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
final int result = original.call(self, entity, pass, partialTick);
|
||||
final int srcAfter = GLStateManager.glGetInteger(GL14.GL_BLEND_SRC_RGB);
|
||||
final int dstAfter = GLStateManager.glGetInteger(GL14.GL_BLEND_DST_RGB);
|
||||
angelica$emissivePass = srcAfter == GL11.GL_ONE && dstAfter == GL11.GL_ONE;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip the overlay on emissive eye pass.
|
||||
*/
|
||||
@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"),
|
||||
slice = @Slice(
|
||||
from = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;shouldRenderPass(Lnet/minecraft/entity/EntityLivingBase;IF)I"),
|
||||
to = @At(value = "INVOKE",
|
||||
target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V")
|
||||
),
|
||||
require = 1, expect = 1
|
||||
)
|
||||
private void angelica$untintEmissivePass(ModelBase model, Entity entity,
|
||||
float p1, float p2, float p3, float p4, float p5, float p6, Operation<Void> original) {
|
||||
if (!angelica$skipReRender || !angelica$emissivePass) {
|
||||
original.call(model, entity, p1, p2, p3, p4, p5, p6);
|
||||
return;
|
||||
}
|
||||
if (angelica$ffpOverlayActive) {
|
||||
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
original.call(model, entity, p1, p2, p3, p4, p5, p6);
|
||||
GLStateManager.setOverlayColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
|
||||
} else if (angelica$shaderEntityColorActive) {
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
original.call(model, entity, p1, p2, p3, p4, p5, p6);
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityColor(1.0F, 0.0F, 0.0F, ANGELICA$RED_MIX);
|
||||
} else {
|
||||
original.call(model, entity, p1, p2, p3, p4, p5, p6);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
|
||||
at = @At("RETURN")
|
||||
)
|
||||
private void angelica$reset(CallbackInfo ci) {
|
||||
if (angelica$ffpOverlayActive) {
|
||||
GLStateManager.setOverlayColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
angelica$ffpOverlayActive = false;
|
||||
}
|
||||
if (angelica$shaderEntityColorActive) {
|
||||
CapturedRenderingState.INSTANCE.setCurrentEntityColor(0.0F, 0.0F, 0.0F, 0.0F);
|
||||
angelica$shaderEntityColorActive = false;
|
||||
}
|
||||
angelica$skipReRender = false;
|
||||
angelica$emissivePass = false;
|
||||
}
|
||||
|
||||
@Unique
|
||||
private static boolean angelica$isShaderPackActive() {
|
||||
if (!AngelicaConfig.enableIris) return false;
|
||||
final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable();
|
||||
return pipeline != null && !(pipeline instanceof FixedFunctionWorldRenderingPipeline);
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.shaders;
|
||||
|
||||
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
|
||||
import net.coderbot.iris.block_rendering.NbtConditionalIdMap;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.network.NetHandlerPlayClient;
|
||||
import net.minecraft.network.play.server.S35PacketUpdateTileEntity;
|
||||
import net.minecraft.world.World;
|
||||
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(NetHandlerPlayClient.class)
|
||||
public class MixinNetHandlerPlayClient_NbtRemesh {
|
||||
|
||||
// Needed or TEs will be missed when meshing
|
||||
@Inject(method = "handleUpdateTileEntity", at = @At("RETURN"))
|
||||
private void angelica$remeshOnTileEntityUpdate(S35PacketUpdateTileEntity packet, CallbackInfo ci) {
|
||||
final NbtConditionalIdMap<Block> teMap = BlockRenderingSettings.INSTANCE.getBlockNbtMap();
|
||||
if (teMap == null || teMap.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final World world = Minecraft.getMinecraft().theWorld;
|
||||
if (world == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int x = packet.func_148856_c/*getX*/() ;
|
||||
final int y = packet.func_148855_d/*getY*/();
|
||||
final int z = packet.func_148854_e/*getZ*/();
|
||||
final Block block = world.getBlock(x, y, z);
|
||||
if (block == null || !teMap.hasConditions(block)) {
|
||||
return;
|
||||
}
|
||||
|
||||
world.markBlockForUpdate(x, y, z);
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.shaders;
|
||||
|
||||
import net.coderbot.iris.block_rendering.BlockRenderingSettings;
|
||||
import net.minecraft.world.World;
|
||||
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(World.class)
|
||||
public class MixinWorldNbtCache {
|
||||
|
||||
@Inject(method = "markBlockForUpdate", at = @At("HEAD"))
|
||||
private void angelica$onMarkBlockForUpdate(int x, int y, int z, CallbackInfo ci) {
|
||||
BlockRenderingSettings.invalidateTeNbtCache(x, y, z);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user