From 7c28a69bfe85a79750f3e433d49d0c7749e601f6 Mon Sep 17 00:00:00 2001 From: Eclipse-Sol Date: Sat, 26 Sep 2026 19:21:21 -0400 Subject: [PATCH] Optimize Enchant Glint Rendering (#2140) * initial commit for enchant-glint-optimizations * Fix spacing issues and remove GlintCompatibility.java --- .../angelica/glsm/ffp/CombinedGlint.java | 55 +++ .../angelica/glsm/ffp/FragmentKey.java | 10 +- .../glsm/ffp/FragmentShaderGenerator.java | 10 + .../angelica/shaders/combined_glint.frag.glsl | 56 +++ .../gtnewhorizons/angelica/mixins/Mixins.java | 1 + .../mixins/interfaces/ArmorEnchantCache.java | 10 + .../angelica/profiling/TracyFramePlots.java | 10 + .../angelica/rendering/GlintClock.java | 16 + .../angelica/rendering/SkippedGlintBlock.java | 45 ++ .../rendering/items/DroppedItemInstancer.java | 56 ++- .../rendering/items/HeldItemGlint.java | 102 +++++ .../rendering/tesr/BatchEligibility.java | 1 + .../rendering/tesr/EntityMaterials.java | 12 +- .../angelica/rendering/tesr/GlintCapture.java | 217 +++++++++ .../angelica/rendering/tesr/InstanceRing.java | 8 + .../tesr/InstancedTemplateRenderer.java | 94 +++- .../rendering/tesr/ModelPartBatcher.java | 201 +++++++-- .../rendering/tesr/RetainedTesrGroups.java | 410 +++++++++++++++--- .../bugfixes/MixinItemRenderer_EdgeDepth.java | 215 ++++----- .../entity/MixinItemRenderer_Instanced.java | 61 ++- .../entity/MixinRenderItem_Instanced.java | 9 +- ...xinRendererLivingEntity_EquippedDraws.java | 37 +- .../MixinRendererLivingEntity_GlintClock.java | 142 ++++++ ...inRendererLivingEntity_ModelPassDraws.java | 38 +- .../tracy/MixinRenderGlobal_Tracy.java | 9 + .../MixinEntityLiving_ArmorEnchantCache.java | 26 ++ .../notfine/glint/MixinItemRenderer.java | 9 +- .../early/notfine/glint/MixinRenderBiped.java | 6 +- .../angelica/rendering/GlintClockTest.java | 14 + .../rendering/tesr/BatchEligibilityTest.java | 2 +- .../tesr/RetainedTesrGroupsGLTest.java | 97 +++++ 31 files changed, 1685 insertions(+), 294 deletions(-) create mode 100644 glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/CombinedGlint.java create mode 100644 glsm/src/main/resources/assets/angelica/shaders/combined_glint.frag.glsl create mode 100644 src/main/java/com/gtnewhorizons/angelica/mixins/interfaces/ArmorEnchantCache.java create mode 100644 src/main/java/com/gtnewhorizons/angelica/rendering/SkippedGlintBlock.java create mode 100644 src/main/java/com/gtnewhorizons/angelica/rendering/items/HeldItemGlint.java create mode 100644 src/main/java/com/gtnewhorizons/angelica/rendering/tesr/GlintCapture.java create mode 100644 src/mixin/java/com/gtnewhorizons/angelica/mixins/early/notfine/glint/MixinEntityLiving_ArmorEnchantCache.java diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/CombinedGlint.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/CombinedGlint.java new file mode 100644 index 000000000..053dc99a0 --- /dev/null +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/CombinedGlint.java @@ -0,0 +1,55 @@ +package com.gtnewhorizons.angelica.glsm.ffp; + +import com.gtnewhorizons.angelica.glsm.GLStateManager; +import org.joml.Matrix4f; +import org.joml.Matrix4fc; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL14; + +public final class CombinedGlint { + private static final Matrix4f savedMatrix = new Matrix4f(); + private static final Matrix4f inverse = new Matrix4f(); + private static final Matrix4f relative = new Matrix4f(); + private static boolean active; + private static boolean replaceAlpha; + + private CombinedGlint() {} + + public static boolean isActive() { + return active; + } + + public static boolean replacesAlpha() { + return active && replaceAlpha; + } + + public static boolean begin(Matrix4fc secondTextureMatrix) { + if (active || GLStateManager.getActiveProgram() != 0 || GLStateManager.getLightingState().isEnabled() || GLStateManager.getColorSumState().isEnabled() || !GLStateManager.glIsEnabled(GL11.GL_BLEND)) return false; + final var blend = GLStateManager.getBlendState(); + final boolean lastAlpha = blend.getSrcAlpha() == GL11.GL_ONE && blend.getDstAlpha() == GL11.GL_ZERO && blend.getEquationAlpha() == GL14.GL_FUNC_ADD; + final boolean preserveAlpha = blend.getSrcAlpha() == GL11.GL_ZERO && blend.getDstAlpha() == GL11.GL_ONE && (blend.getEquationAlpha() == GL14.GL_FUNC_ADD || blend.getEquationAlpha() == GL14.GL_FUNC_REVERSE_SUBTRACT); + if (blend.getSrcRgb() != GL11.GL_SRC_COLOR || blend.getDstRgb() != GL11.GL_ONE + || (!lastAlpha && !preserveAlpha && (blend.getSrcAlpha() != GL11.GL_SRC_COLOR || blend.getDstAlpha() != GL11.GL_ONE || blend.getEquationAlpha() != blend.getEquationRgb())) + || (blend.getEquationRgb() != GL14.GL_FUNC_ADD && blend.getEquationRgb() != GL14.GL_FUNC_REVERSE_SUBTRACT)) return false; + final var textures = GLStateManager.getTextures(); + if (!textures.getTextureUnitStates(0).isEnabled() || textures.getTextureUnitBindings(0).getBinding() == 0 || textures.getTexEnvState(0).mode != GL11.GL_MODULATE) return false; + if (textures.getTextureUnitStates(1).isEnabled() && textures.getTextureUnitBindings(1).getBinding() != 0 && textures.getTexEnvState(1).mode != GL11.GL_MODULATE) return false; + for (int unit = 2; unit < 4; unit++) { + if (textures.getTextureUnitStates(unit).isEnabled() && textures.getTextureUnitBindings(unit).getBinding() != 0) return false; + } + inverse.set(textures.getTextureUnitMatrix(0)).invert(); + relative.set(secondTextureMatrix).mul(inverse); + if (!relative.isFinite()) return false; + savedMatrix.set(textures.getTextureUnitMatrix(3)); + GLStateManager.setTextureMatrix(3, relative); + replaceAlpha = lastAlpha; + active = true; + return true; + } + + public static void end() { + if (!active) return; + active = false; + GLStateManager.setTextureMatrix(3, savedMatrix); + } +} diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/FragmentKey.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/FragmentKey.java index 2a2b54251..0aa455c94 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/FragmentKey.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/FragmentKey.java @@ -66,6 +66,8 @@ public final class FragmentKey { public static final int FOG_EXP2 = 3; private static final int GLOBAL_BITS = 14; + private static final int BIT_COMBINED_GLINT = 61; + private static final int BIT_GLINT_REPLACE_ALPHA = 62; 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 @@ -111,6 +113,8 @@ public final class FragmentKey { public static int packFromState(long[] out, GLContextState glCtx) { long global = 0; + if (CombinedGlint.isActive()) global |= 1L << BIT_COMBINED_GLINT; + if (CombinedGlint.replacesAlpha()) global |= 1L << BIT_GLINT_REPLACE_ALPHA; // Fog if (glCtx.fogMode.isEnabled()) { @@ -248,6 +252,8 @@ public final class FragmentKey { } public int fogMode() { return (int) (packed[0] & 0x3); } + public boolean combinedGlint() { return (packed[0] & (1L << BIT_COMBINED_GLINT)) != 0; } + public boolean glintReplaceAlpha() { return (packed[0] & (1L << BIT_GLINT_REPLACE_ALPHA)) != 0; } public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; } public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); } public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; } @@ -389,7 +395,9 @@ public final class FragmentKey { default -> "?"; }; final StringBuilder sb = new StringBuilder(); - sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d", fogName, alphaTestEnabled(), alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-", separateSpecular(), colorSum(), overlayEnabled() || overlayInstanced(), nrEnabledUnits())); + sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d", fogName, alphaTestEnabled(), + alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-", separateSpecular(), colorSum(), + overlayEnabled() || overlayInstanced(), nrEnabledUnits())); for (int i = 0; i < nrEnabledUnits(); i++) { if (!unitEnabled(i)) { sb.append(String.format(" u%d=OFF", i)); diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/FragmentShaderGenerator.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/FragmentShaderGenerator.java index bbe95c705..cb9350183 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/FragmentShaderGenerator.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/ffp/FragmentShaderGenerator.java @@ -1,6 +1,7 @@ package com.gtnewhorizons.angelica.glsm.ffp; import org.lwjgl.opengl.GL11; +import org.embeddedt.embeddium.impl.render.shader.ShaderLoader; /** * Generates GLSL 330 core fragment shaders for FFP emulation. @@ -17,6 +18,15 @@ public final class FragmentShaderGenerator { emitInputs(sb, key); emitUniforms(sb, key); + if (key.combinedGlint()) { + sb.append("#define GLINT_REPLACE_ALPHA ").append(key.glintReplaceAlpha() ? 1 : 0).append('\n'); + sb.append("#define GLINT_FOG_MODE ").append(key.fogMode()).append('\n'); + sb.append("#define GLINT_ALPHA_FUNC ").append(key.alphaTestEnabled() ? key.alphaTestFunc() : 7).append('\n'); + sb.append("#define GLINT_OVERLAY ").append(key.overlayInstanced() ? 2 : key.overlayEnabled() ? 1 : 0).append('\n'); + sb.append("#define GLINT_LIGHTMAP ").append(key.nrEnabledUnits() > 1 && key.unitEnabled(1) ? 1 : 0).append('\n'); + sb.append(ShaderLoader.getShaderSource("angelica:combined_glint.frag.glsl")); + return sb.toString(); + } sb.append("out vec4 fragColor;\n\n"); sb.append("void main() {\n"); diff --git a/glsm/src/main/resources/assets/angelica/shaders/combined_glint.frag.glsl b/glsm/src/main/resources/assets/angelica/shaders/combined_glint.frag.glsl new file mode 100644 index 000000000..2902c73b5 --- /dev/null +++ b/glsm/src/main/resources/assets/angelica/shaders/combined_glint.frag.glsl @@ -0,0 +1,56 @@ +out vec4 fragColor; + +vec4 glintLayer(vec4 coord, out bool visible) { + vec4 color = textureProj(u_Sampler0, coord) * v_Color; +#if GLINT_OVERLAY == 2 + color.rgb = mix(color.rgb, v_Overlay.rgb, v_Overlay.a); +#elif GLINT_OVERLAY == 1 + color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a); +#endif +#if GLINT_LIGHTMAP + color *= textureProj(u_Sampler1, v_TexCoord1); +#endif + float qa = round(color.a * 255.0); + float ref = u_AlphaRef * 255.0; +#if GLINT_ALPHA_FUNC == 0 + visible = false; +#elif GLINT_ALPHA_FUNC == 1 + visible = qa < ref; +#elif GLINT_ALPHA_FUNC == 2 + visible = qa == round(ref); +#elif GLINT_ALPHA_FUNC == 3 + visible = qa <= ref; +#elif GLINT_ALPHA_FUNC == 4 + visible = qa > ref; +#elif GLINT_ALPHA_FUNC == 5 + visible = qa != round(ref); +#elif GLINT_ALPHA_FUNC == 6 + visible = qa >= ref; +#else + visible = true; +#endif + if (!visible) return vec4(0.0); +#if GLINT_FOG_MODE == 1 + float fog = v_FogCoord * u_FogParams.x + u_FogParams.y; +#elif GLINT_FOG_MODE == 2 + float fog = exp2(-(v_FogCoord * u_FogParams.z)); +#elif GLINT_FOG_MODE == 3 + float fogDistance = v_FogCoord * u_FogParams.w; + float fog = exp2(-(fogDistance * fogDistance)); +#endif +#if GLINT_FOG_MODE != 0 + color.rgb = mix(u_FogColor.rgb, color.rgb, clamp(fog, 0.0, 1.0)); +#endif + return color; +} + +void main() { + bool visible0, visible1; + vec4 a = glintLayer(v_TexCoord0, visible0); + vec4 b = glintLayer(u_TextureMatrix3 * v_TexCoord0, visible1); + if (!visible0 && !visible1) discard; + fragColor = sqrt(a * a + b * b); +#if GLINT_REPLACE_ALPHA + fragColor.a = visible1 ? b.a : a.a; +#endif +} diff --git a/src/main/java/com/gtnewhorizons/angelica/mixins/Mixins.java b/src/main/java/com/gtnewhorizons/angelica/mixins/Mixins.java index eb10d6613..a470037f3 100644 --- a/src/main/java/com/gtnewhorizons/angelica/mixins/Mixins.java +++ b/src/main/java/com/gtnewhorizons/angelica/mixins/Mixins.java @@ -680,6 +680,7 @@ public enum Mixins implements IMixins { "gui.MixinGuiSlot", "glint.MixinRenderBiped", + "glint.MixinEntityLiving_ArmorEnchantCache", "glint.MixinRenderPlayer", "optimization.MixinRenderItemFrame", diff --git a/src/main/java/com/gtnewhorizons/angelica/mixins/interfaces/ArmorEnchantCache.java b/src/main/java/com/gtnewhorizons/angelica/mixins/interfaces/ArmorEnchantCache.java new file mode 100644 index 000000000..81c849c09 --- /dev/null +++ b/src/main/java/com/gtnewhorizons/angelica/mixins/interfaces/ArmorEnchantCache.java @@ -0,0 +1,10 @@ +package com.gtnewhorizons.angelica.mixins.interfaces; + +import net.minecraft.item.ItemStack; + +/** + * Whether a mob's worn armor piece is enchanted, cached per stack instance. + */ +public interface ArmorEnchantCache { + boolean angelica$isArmorEnchanted(int slot, ItemStack stack); +} diff --git a/src/main/java/com/gtnewhorizons/angelica/profiling/TracyFramePlots.java b/src/main/java/com/gtnewhorizons/angelica/profiling/TracyFramePlots.java index ec3a926ca..98cc439d9 100644 --- a/src/main/java/com/gtnewhorizons/angelica/profiling/TracyFramePlots.java +++ b/src/main/java/com/gtnewhorizons/angelica/profiling/TracyFramePlots.java @@ -91,6 +91,11 @@ public final class TracyFramePlots { private static final PlotDelta P_TESR_CACHE_HITS = new PlotDelta("tesr.cacheHits"); private static final PlotDelta P_TESR_CACHE_MISSES = new PlotDelta("tesr.cacheMisses"); private static final PlotDelta P_TESR_MODEL_PARTS = new PlotDelta("tesr.modelParts"); + private static final PlotDelta P_ARMOR_BASE_REUSES = new PlotDelta("glint.armorBaseReuses"); + private static final PlotDelta P_GLINT_PASS_REUSES = new PlotDelta("glint.passReuses"); + private static final PlotDelta P_ARMOR_LOOP_SKIPS = new PlotDelta("glint.armorLoopSkips"); + private static final PlotDelta P_ARMOR_SECTION_SKIPS = new PlotDelta("glint.armorSectionSkips"); + private static final PlotDelta P_REDRAWN_INSTANCES = new PlotDelta("glint.redrawnInstances"); private static final PlotDelta P_TESR_LIVE_FALLBACKS = new PlotDelta("tesr.liveFallbacks"); private static final PlotDelta P_ITEMS_INSTANCED = new PlotDelta("items.instanced"); private static final PlotDelta P_ITEMS_FALLBACK = new PlotDelta("items.fallback"); @@ -239,6 +244,11 @@ public final class TracyFramePlots { P_TESR_CACHE_HITS.plot(AngelicaTesrMeshCache.cacheHits); P_TESR_CACHE_MISSES.plot(AngelicaTesrMeshCache.cacheMisses); P_TESR_MODEL_PARTS.plot(batcher.statParts()); + P_ARMOR_BASE_REUSES.plot(batcher.statArmorBaseReuses()); + P_GLINT_PASS_REUSES.plot(batcher.statGlintPassReuses()); + P_ARMOR_LOOP_SKIPS.plot(batcher.statArmorLoopSkips()); + P_ARMOR_SECTION_SKIPS.plot(batcher.statArmorSectionSkips()); + P_REDRAWN_INSTANCES.plot(batcher.statRedrawnInstances()); P_TESR_LIVE_FALLBACKS.plot(batcher.statLiveFallbacks()); P_ITEMS_INSTANCED.plot(DroppedItemInstancer.statInstanced()); P_ITEMS_FALLBACK.plot(DroppedItemInstancer.statFallback()); diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/GlintClock.java b/src/main/java/com/gtnewhorizons/angelica/rendering/GlintClock.java index c39197c47..e9317664d 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/GlintClock.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/GlintClock.java @@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.rendering; import com.gtnewhorizons.angelica.glsm.GLStateManager; import org.joml.Matrix4fc; +import org.joml.Matrix4f; public final class GlintClock { @@ -14,6 +15,7 @@ public final class GlintClock { private static long millis; private static float armorU0, armorV0, armorU1, armorV1; + private static final Matrix4f armorFirst = new Matrix4f(), armorSecond = new Matrix4f(); public static void beginFrame(long nowMillis) { millis = nowMillis; @@ -30,6 +32,20 @@ public final class GlintClock { final double dv1 = frac(f1 * COS1 / 3.0); armorU1 = (float) (3.0 * (COS1 * du1 + SIN1 * dv1)); armorV1 = (float) (3.0 * (-SIN1 * du1 + COS1 * dv1)); + armorFirst.scaling(0.33333334f).rotateZ((float) Math.toRadians(30)).translate(armorU0, armorV0, 0); + armorSecond.scaling(0.33333334f).rotateZ((float) Math.toRadians(-30)).translate(armorU1, armorV1, 0); + } + + public static Matrix4f secondArmorMatrix(Matrix4fc first) { + return armorFirst.equals(first, 0.000001f) ? armorSecond : null; + } + + public static Matrix4f firstArmorMatrix() { + return armorFirst; + } + + public static Matrix4f secondArmorMatrix() { + return armorSecond; } public static long millis() { diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/SkippedGlintBlock.java b/src/main/java/com/gtnewhorizons/angelica/rendering/SkippedGlintBlock.java new file mode 100644 index 000000000..4792eef43 --- /dev/null +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/SkippedGlintBlock.java @@ -0,0 +1,45 @@ +package com.gtnewhorizons.angelica.rendering; + +import com.gtnewhorizon.gtnhlib.client.renderer.MatrixHelper; +import com.gtnewhorizons.angelica.glsm.GLStateManager; +import com.gtnewhorizons.angelica.glsm.states.BlendState; +import com.gtnewhorizons.angelica.glsm.states.Color4; +import org.lwjgl.opengl.GL11; + +/** + * The state a vanilla glint block leaves behind, for callers that skip the block. GLSM is only called where its effective state differs. + */ +public final class SkippedGlintBlock { + + private static final BlendState blend = new BlendState(); + + private SkippedGlintBlock() {} + + public static void applyArmorExitState() { + applyExitState(1.0F, 1.0F, 1.0F, 1.0F, GL11.GL_SRC_COLOR, GL11.GL_ONE, GL11.GL_SRC_COLOR, GL11.GL_ONE, true, true); + } + + public static void applyHeldItemExitState() { + applyExitState(0.5F * 0.76F, 0.25F * 0.76F, 0.8F * 0.76F, 1.0F, GL11.GL_SRC_COLOR, GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ZERO, false, false); + } + + private static void applyExitState(float red, float green, float blue, float alpha, int srcRgb, int dstRgb, int srcAlpha, int dstAlpha, boolean resetTextureMatrix, boolean restoreDepthMask) { + final Color4 color = GLStateManager.getColor(); + if (color.getRed() != red || color.getGreen() != green || color.getBlue() != blue || color.getAlpha() != alpha) { + GLStateManager.glColor4f(red, green, blue, alpha); + } + if (resetTextureMatrix && !MatrixHelper.isIdentity(GLStateManager.getTextures().getTextureUnitMatrix(0))) { + GLStateManager.glMatrixMode(GL11.GL_TEXTURE); + GLStateManager.glLoadIdentity(); + } + if (GLStateManager.getMatrixMode().getMode() != GL11.GL_MODELVIEW) GLStateManager.glMatrixMode(GL11.GL_MODELVIEW); + if (restoreDepthMask && !GLStateManager.isEffectiveDepthMaskEnabled()) GLStateManager.glDepthMask(true); + if (!GLStateManager.getLightingState().isEnabled()) GLStateManager.glEnable(GL11.GL_LIGHTING); + GLStateManager.getEffectiveBlendState(blend); + if (GLStateManager.getBlendState().isFuncUnknown() || blend.getSrcRgb() != srcRgb || blend.getDstRgb() != dstRgb || blend.getSrcAlpha() != srcAlpha || blend.getDstAlpha() != dstAlpha) { + GLStateManager.tryBlendFuncSeparate(srcRgb, dstRgb, srcAlpha, dstAlpha); + } + if (GLStateManager.isEffectiveBlendEnabled()) GLStateManager.glDisable(GL11.GL_BLEND); + if (GLStateManager.getDepthState().getFunc() != GL11.GL_LEQUAL) GLStateManager.glDepthFunc(GL11.GL_LEQUAL); + } +} diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/items/DroppedItemInstancer.java b/src/main/java/com/gtnewhorizons/angelica/rendering/items/DroppedItemInstancer.java index 2a4c80665..f7fbcf939 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/items/DroppedItemInstancer.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/items/DroppedItemInstancer.java @@ -12,6 +12,7 @@ import com.gtnewhorizons.angelica.rendering.tesr.AngelicaTesrMeshCache; import com.gtnewhorizons.angelica.rendering.tesr.BakedTransformCapture; import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility; import com.gtnewhorizons.angelica.rendering.tesr.EntityMaterials; +import com.gtnewhorizons.angelica.rendering.tesr.GlintCapture; import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher; import com.gtnewhorizons.angelica.rendering.tesr.TemplateBuffer; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; @@ -34,6 +35,12 @@ public final class DroppedItemInstancer { private static final Object[] BLOCK_ARGS = new Object[4]; private static BakedTransformCapture blockBackend; private static boolean basePart; + private static final GlintCapture glintCapture = new GlintCapture(); + private static boolean glintSeen; + private static final ItemPropCache.ItemProp glintProperties = new ItemPropCache.ItemProp(); + private static final ItemPropCache.ItemProp currentGlintProperties = new ItemPropCache.ItemProp(); + private static TemplateBuffer glintTemplate; + private static int glintTemplateWidth, glintTemplateHeight; private static long instanced; private static long glintInstanced; @@ -81,7 +88,8 @@ public final class DroppedItemInstancer { } private static boolean glintEligible(ItemStack stack) { - return stack != null && stack.getItem() != null && ModelPartBatcher.INSTANCE.isActive() && !GLStateManager.isRecordingDisplayList() && !TessellatorManager.isCurrentlyCapturing() && !TessellatorManager.shouldInterceptDraw(Tessellator.instance); + return stack != null && stack.getItem() != null && ModelPartBatcher.INSTANCE.isActive() && !GLStateManager.isRecordingDisplayList() + && !TessellatorManager.isCurrentlyCapturing() && !TessellatorManager.shouldInterceptDraw(Tessellator.instance); } static TesrMaterial material(ItemStack stack, boolean unfilteredAtlas) { @@ -100,6 +108,8 @@ public final class DroppedItemInstancer { } public static void icon(ItemStack stack, Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation original) { + glintCapture.reset(); + glintSeen = false; final TesrMaterial material = material(stack, true); if (material == null) { basePart = false; @@ -131,10 +141,11 @@ public final class DroppedItemInstancer { } callIcon(ICON_ARGS, original, t, maxU, minV, minU, maxV, width, height, thickness); BatchEligibility.onPartFallback(drawsBefore, GLStateManager.drawCalls); - return true; + return false; } - public static void glint(Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation original) { + /** Held glints use ITEM_GLINT for its stencil guard; dropped items draw with culling on and need none. */ + public static void glint(Tessellator t, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, TesrMaterial material, Operation original) { if (!basePart) { callIcon(GLINT_ARGS, original, t, maxU, minV, minU, maxV, width, height, thickness); return; @@ -146,8 +157,22 @@ public final class DroppedItemInstancer { } final long drawsBefore = GLStateManager.drawCalls; if (allowed) { + currentGlintProperties.set(maxU, minV, minU, maxV, width, height, thickness); + if (glintSeen && glintProperties.equals(currentGlintProperties) && ModelPartBatcher.INSTANCE.replayGlint(glintCapture)) { + glintInstanced++; + return; + } final TemplateBuffer template = iconTemplate(GLINT_ARGS, maxU, minV, minU, maxV, width, height, thickness, original); - if (template != null && ModelPartBatcher.INSTANCE.queueTemplate(template, EntityMaterials.GLINT)) { + final boolean capture = !glintSeen && ModelPartBatcher.INSTANCE.beginGlintCapture(glintCapture); + if (capture) glintProperties.set(currentGlintProperties); + glintSeen = true; + final boolean queued; + try { + queued = template != null && ModelPartBatcher.INSTANCE.queueTemplate(template, material); + } finally { + if (capture) glintCapture.end(); + } + if (queued) { glintInstanced++; BatchEligibility.onPartQueued(); return; @@ -161,6 +186,22 @@ public final class DroppedItemInstancer { BatchEligibility.onPartFallback(drawsBefore, GLStateManager.drawCalls); } + public static boolean queueSkippedGlint(int iconWidth, int iconHeight) { + if (!basePart || !BatchEligibility.batchingAllowed()) return false; + if (glintTemplate == null || glintTemplateWidth != iconWidth || glintTemplateHeight != iconHeight) { + final IconMesh hit = icons.get(0.0F, 0.0F, 1.0F, 1.0F, iconWidth, iconHeight, 0.0625F); + if (hit == null || hit.template == null) return false; + glintTemplate = hit.template; + glintTemplateWidth = iconWidth; + glintTemplateHeight = iconHeight; + } + if (!ModelPartBatcher.INSTANCE.queueSkippedHeldGlint(glintTemplate)) return false; + glintInstanced += 2; + BatchEligibility.onPartQueued(); + BatchEligibility.onPartQueued(); + return true; + } + public static void block(ItemStack stack, RenderBlocks rb, Block block, int meta, float brightness, boolean unfilteredAtlas, Operation original) { final TesrMaterial material = material(stack, unfilteredAtlas); if (material == null) { @@ -211,6 +252,9 @@ public final class DroppedItemInstancer { blocks.clear(); blockKey.block = null; basePart = false; + glintCapture.reset(); + glintSeen = false; + glintTemplate = null; if (blockBackend != null) { blockBackend.delete(); blockBackend = null; @@ -292,7 +336,9 @@ public final class DroppedItemInstancer { private static BailReason blockStateBail(RenderBlocks renderBlocks, Block block, float brightness) { if (BlockRenderListManager.isISBRH(block.getRenderType())) return BailReason.ISBRH; - if (renderBlocks.enableAO || renderBlocks.overrideBlockTexture != null || brightness != 1.0F || !renderBlocks.useInventoryTint || renderBlocks.blockAccess != null || (renderBlocks.uvRotateEast | renderBlocks.uvRotateWest | renderBlocks.uvRotateSouth | renderBlocks.uvRotateNorth | renderBlocks.uvRotateTop | renderBlocks.uvRotateBottom) != 0) return BailReason.BLOCK_STATE; + if (renderBlocks.enableAO || renderBlocks.overrideBlockTexture != null || brightness != 1.0F || !renderBlocks.useInventoryTint + || renderBlocks.blockAccess != null || (renderBlocks.uvRotateEast | renderBlocks.uvRotateWest | renderBlocks.uvRotateSouth + | renderBlocks.uvRotateNorth | renderBlocks.uvRotateTop | renderBlocks.uvRotateBottom) != 0) return BailReason.BLOCK_STATE; return null; } diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/items/HeldItemGlint.java b/src/main/java/com/gtnewhorizons/angelica/rendering/items/HeldItemGlint.java new file mode 100644 index 000000000..5c36f6ff0 --- /dev/null +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/items/HeldItemGlint.java @@ -0,0 +1,102 @@ +package com.gtnewhorizons.angelica.rendering.items; + +import com.gtnewhorizons.angelica.config.AngelicaConfig; +import com.gtnewhorizons.angelica.glsm.GLStateManager; +import com.gtnewhorizons.angelica.glsm.ffp.CombinedGlint; +import com.gtnewhorizons.angelica.rendering.GlintClock; +import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher; +import jss.notfine.core.Settings; +import net.minecraft.client.Minecraft; +import net.minecraft.client.resources.IReloadableResourceManager; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import org.joml.Matrix4f; +import org.lwjgl.opengl.GL11; + +import javax.imageio.ImageIO; +import java.io.IOException; + +/** One draw for the two vanilla held-item layers; modified glint sequences retain their original draws. */ +public final class HeldItemGlint { + private static final ResourceLocation TEXTURE = new ResourceLocation("textures/misc/enchanted_item_glint.png"); + private static final Matrix4f first = new Matrix4f(); + private static final Matrix4f second = new Matrix4f(); + private static long matrixTime = Long.MIN_VALUE; + private static boolean listening, checked, opaque; + + private HeldItemGlint() {} + + private static boolean batchedGlintGuarded() { + return ModelPartBatcher.INSTANCE.entityPassActive() && ModelPartBatcher.INSTANCE.canCaptureGlint() && canResetStencil(); + } + + public static boolean needsImmediateBase(ItemStack stack, int pass) { + return stack.hasEffect(pass) && (boolean) Settings.MODE_GLINT_WORLD.option.getStore() && !ModelPartBatcher.INSTANCE.isShadowPass() && !batchedGlintGuarded(); + } + + public static boolean layersCoverSamePixels() { + return opaqueTexture(Minecraft.getMinecraft()); + } + + public static boolean canResetStencil() { + if (!GLStateManager.getStencilTest().isEnabled()) return true; + final var stencil = GLStateManager.getStencilState(); + return stencil.getFuncFront() == GL11.GL_ALWAYS && stencil.getFuncBack() == GL11.GL_ALWAYS && stencil.getFailOpFront() == GL11.GL_KEEP && stencil.getFailOpBack() == GL11.GL_KEEP && stencil.getZFailOpFront() == GL11.GL_KEEP + && stencil.getZFailOpBack() == GL11.GL_KEEP && stencil.getZPassOpFront() == GL11.GL_KEEP && stencil.getZPassOpBack() == GL11.GL_KEEP; + } + + public static boolean eligible() { + return AngelicaConfig.enableEntityBatching && GLStateManager.getActiveProgram() == 0 && (boolean) Settings.MODE_GLINT_WORLD.option.getStore() && opaqueTexture(Minecraft.getMinecraft()); + } + + public static boolean begin() { + if (!eligible()) return false; + final var mc = Minecraft.getMinecraft(); + final var texture = mc.getTextureManager().getTexture(TEXTURE); + if (texture == null || texture.getGlTextureId() != GLStateManager.getTextures().getTextureUnitBindings(0).getBinding()) return false; + return firstMatrix().equals(GLStateManager.getTextures().getTextureUnitMatrix(0), 0.000001f) && CombinedGlint.begin(secondMatrix()); + } + + public static ResourceLocation texture() { + return TEXTURE; + } + + public static Matrix4f firstMatrix() { + updateMatrices(); + return first; + } + + public static Matrix4f secondMatrix() { + updateMatrices(); + return second; + } + + private static void updateMatrices() { + final long time = GlintClock.millis(); + if (matrixTime == time) return; + matrixTime = time; + first.scaling(0.125f).translate((float) (time % 3000L) / 3000.0f * 8.0f, 0, 0).rotate((float) Math.toRadians(-50), 0, 0, 1); + second.scaling(0.125f).translate(-(float) (time % 4873L) / 4873.0f * 8.0f, 0, 0).rotate((float) Math.toRadians(10), 0, 0, 1); + } + + private static boolean opaqueTexture(Minecraft mc) { + if (!listening) { + listening = true; + ((IReloadableResourceManager) mc.getResourceManager()).registerReloadListener(manager -> checked = false); + } + if (checked) return opaque; + checked = true; + opaque = false; + try (var stream = mc.getResourceManager().getResource(TEXTURE).getInputStream()) { + final var image = ImageIO.read(stream); + if (image == null) return false; + for (int y = 0; y < image.getHeight(); y++) for (int x = 0; x < image.getWidth(); x++) { + if ((image.getRGB(x, y) >>> 24) != 255) return false; + } + opaque = true; + } catch (IOException | RuntimeException e) { + GLStateManager.LOGGER.debug("Could not inspect held glint texture; keeping separate layers", e); + } + return opaque; + } +} diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/BatchEligibility.java b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/BatchEligibility.java index 147bec802..a408da639 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/BatchEligibility.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/BatchEligibility.java @@ -28,6 +28,7 @@ public final class BatchEligibility { expectedDraws = 0L; foreignDraws = 0L; parts = 0; + bracketDepth = 0; allowed = state == SAFE; return allowed; } diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/EntityMaterials.java b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/EntityMaterials.java index ae7fa85ba..4d11ef602 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/EntityMaterials.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/EntityMaterials.java @@ -27,8 +27,8 @@ public final class EntityMaterials { public static final TesrMaterial HELD_BLOCK_CUTOUT = TesrMaterial.builder().cutout(0.1f).stream().build(); public static final TesrMaterial HELD_BLOCK_TRANSLUCENT = TesrMaterial.builder().translucent().cutout(0.1f).stream().build(); static final TesrMaterial OVERLAY = TesrMaterial.builder().translucent().depthEqual().stream().build(); - public static final TesrMaterial GLINT = TesrMaterial.builder().glint().depthEqual().noDepthWrite().unlit().stream() - .special(TesrMaterial.SpecialRender.GLINT).build(); + public static final TesrMaterial GLINT = TesrMaterial.builder().glint().depthEqual().noDepthWrite().unlit().stream().special(TesrMaterial.SpecialRender.GLINT).build(); + public static final TesrMaterial ITEM_GLINT = TesrMaterial.builder().glint().depthEqual().noDepthWrite().unlit().stream().special(TesrMaterial.SpecialRender.GLINT).build(); private static final BlendState effectiveBlend = new BlendState(); private static final AlphaState effectiveAlpha = new AlphaState(); @@ -39,13 +39,17 @@ public final class EntityMaterials { final boolean textured = GLStateManager.getTextures().getTextureUnitStates(0).isEnabled(); final BlendState blend = GLStateManager.getEffectiveBlendState(effectiveBlend); final AlphaState alpha = GLStateManager.getEffectiveAlphaState(effectiveAlpha); - return fromState(textured, textured && !texIdentity, GLStateManager.isEffectiveBlendEnabled(), blend.getSrcRgb(), blend.getDstRgb(), GLStateManager.isEffectiveAlphaTestEnabled(), alpha.getFunction(), alpha.getReference(), GLStateManager.getDepthState().getFunc(),GLStateManager.isEffectiveDepthMaskEnabled()); + return fromState(textured, textured && !texIdentity, GLStateManager.isEffectiveBlendEnabled(), blend.getSrcRgb(), blend.getDstRgb(), + GLStateManager.isEffectiveAlphaTestEnabled(), alpha.getFunction(), alpha.getReference(), GLStateManager.getDepthState().getFunc(), + GLStateManager.isEffectiveDepthMaskEnabled()); } public static TesrMaterial itemFromCurrentState(boolean unfilteredAtlas) { final BlendState blend = GLStateManager.getEffectiveBlendState(effectiveBlend); final AlphaState alpha = GLStateManager.getEffectiveAlphaState(effectiveAlpha); - return itemFromState(unfilteredAtlas, GLStateManager.isEffectiveBlendEnabled(), blend.getSrcRgb(), blend.getDstRgb(), GLStateManager.isEffectiveAlphaTestEnabled(), alpha.getFunction(), alpha.getReference(), GLStateManager.getDepthState().getFunc(),GLStateManager.isEffectiveDepthMaskEnabled()); + return itemFromState(unfilteredAtlas, GLStateManager.isEffectiveBlendEnabled(), blend.getSrcRgb(), blend.getDstRgb(), + GLStateManager.isEffectiveAlphaTestEnabled(), alpha.getFunction(), alpha.getReference(), GLStateManager.getDepthState().getFunc(), + GLStateManager.isEffectiveDepthMaskEnabled()); } static TesrMaterial itemFromState(boolean unfilteredAtlas, boolean blend, int srcRgb, int dstRgb, boolean alphaTest, int alphaFunc, float alphaRef, int depthFunc, boolean depthMask) { diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/GlintCapture.java b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/GlintCapture.java new file mode 100644 index 000000000..dc43ac57b --- /dev/null +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/GlintCapture.java @@ -0,0 +1,217 @@ +package com.gtnewhorizons.angelica.rendering.tesr; + +import com.gtnewhorizons.angelica.glsm.GLStateManager; +import com.gtnewhorizons.angelica.glsm.ffp.FragmentKey; +import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig; +import com.gtnewhorizons.angelica.glsm.states.BlendState; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource; +import net.minecraft.util.ResourceLocation; +import org.joml.Matrix4f; + +import java.util.Arrays; + +/** Reuses captured instance records across armor and item glint material passes. */ +public final class GlintCapture { + private static final class Range { + RetainedTesrGroups.Group group; + RetainedTesrGroups.InstanceColumns columns; + RetainedTesrGroups.TexRun source; + int start, end, instances; + } + + private final ObjectArrayList ranges = new ObjectArrayList<>(); + private final Matrix4f parent = new Matrix4f(); + private final long[] fragment = new long[4], scratch = new long[4]; + private final BlendState blend = new BlendState(), scratchBlend = new BlendState(); + private boolean blendEnabled, depthMask; + private int depthFunc; + private int used, texture, color, light, overlay, fragmentLength; + private long draws; + private long pass; + private RetainedTesrGroups owner; + private boolean valid; + private boolean base; + private boolean trusted; + private boolean poseRestored; + private int restoredGeneration; + private boolean uniformLight; + + public void reset() { + if (owner != null && owner.glintCapture == this) owner.glintCapture = null; + for (int i = 0; i < used; i++) { + final Range range = ranges.get(i); + range.group = null; + range.columns = null; + range.source = null; + } + used = 0; + valid = false; + owner = null; + base = false; + trusted = false; + poseRestored = false; + } + + boolean begin(RetainedTesrGroups groups) { + if (!open(groups)) return false; + parent.set(GLStateManager.getModelViewMatrix()); + texture = GLStateManager.getBoundTextureForServerState(); + color = currentColor(); + light = GLSMConfig.packedLastBrightness(); + overlay = currentOverlay(); + fragmentLength = FragmentKey.packFromState(fragment); + GLStateManager.getEffectiveBlendState(blend); + blendEnabled = GLStateManager.isEffectiveBlendEnabled(); + depthMask = GLStateManager.isEffectiveDepthMaskEnabled(); + depthFunc = GLStateManager.getDepthState().getFunc(); + return true; + } + + /** + * Base passes are only copied under the same parent pose. Light and overlay are recorded so the glint can draw the base instances in place when every part still carries them. + */ + boolean beginBase(RetainedTesrGroups groups) { + if (!open(groups)) return false; + parent.set(GLStateManager.getModelViewMatrix()); + light = GLSMConfig.packedLastBrightness(); + overlay = currentOverlay(); + base = true; + return true; + } + + /** For records queued without running model or mod code; the caller replays before any GL state changes. */ + boolean beginTrusted(RetainedTesrGroups groups) { + if (!open(groups)) return false; + trusted = true; + return true; + } + + private boolean open(RetainedTesrGroups groups) { + if (groups.glintCapture != null) return false; + reset(); + owner = groups; + pass = groups.captureVersion(); + groups.glintCapture = this; + draws = GLStateManager.drawCalls; + valid = true; + uniformLight = true; + return true; + } + + void invalidate() { valid = false; } + void allowBase() { base = true; } + boolean capturesBase() { return base && valid; } + + void record(RetainedTesrGroups.Group group, RetainedTesrGroups.InstanceColumns columns, RetainedTesrGroups.TexRun source, int start, int end, int instances) { + if (!valid) return; + if (!base && group.material != EntityMaterials.GLINT && group.material != EntityMaterials.ITEM_GLINT) { + invalidate(); + return; + } + Range range = used > 0 ? ranges.get(used - 1) : null; + if (range == null || range.group != group || range.columns != columns || range.source != source || range.end != start) { + if (used == ranges.size()) ranges.add(new Range()); + range = ranges.get(used++); + range.group = group; + range.columns = columns; + range.source = source; + range.start = start; + range.instances = 0; + } + range.end = end; + range.instances += instances; + if (base && uniformLight) { + for (int i = start; i < end; i++) { + if (columns.lights.getInt(i) != light || columns.overlays.getInt(i) != overlay) { + uniformLight = false; + break; + } + } + } + } + + public void end() { + close(false); + } + + public void endWithRestoredPose() { + close(true); + } + + private void close(boolean poseRestored) { + if (owner == null || owner.glintCapture != this) return; + owner.glintCapture = null; + valid &= GLStateManager.drawCalls == draws && used > 0; + this.poseRestored = poseRestored; + restoredGeneration = GLStateManager.getMvGeneration(); + } + + private boolean sameParentPose() { + return poseRestored && restoredGeneration == GLStateManager.getMvGeneration() || parent.equals(GLStateManager.getModelViewMatrix()); + } + + boolean replay(RetainedTesrGroups groups) { + return replay(groups, GLStateManager.getTextures().getTextureUnitMatrix(0)); + } + + boolean replay(RetainedTesrGroups groups, Matrix4f textureMatrix) { + if (base || !valid || owner != groups || pass != groups.captureVersion() || groups.glintCapture != null || GLStateManager.drawCalls != draws) return false; + if (!trusted && (blendEnabled != GLStateManager.isEffectiveBlendEnabled() || depthMask != GLStateManager.isEffectiveDepthMaskEnabled() || depthFunc != GLStateManager.getDepthState().getFunc() + || !blend.sameAs(GLStateManager.getEffectiveBlendState(scratchBlend)) || !sameParentPose() || texture != GLStateManager.getBoundTextureForServerState() || color != currentColor() || light != GLSMConfig.packedLastBrightness() + || fragmentLength != FragmentKey.packFromState(scratch) || !Arrays.equals(fragment, 0, fragmentLength, scratch, 0, fragmentLength))) return false; + for (int i = 0; i < used; i++) { + final Range range = ranges.get(i); + if (!groups.captureStillValid(range.group)) return false; + } + for (int i = 0; i < used; i++) { + final Range range = ranges.get(i); + range.group.replayRange(range.columns, range.source, range.start, range.end, range.instances, textureMatrix); + } + valid = false; + return true; + } + + boolean copyBase(ModelPartBatcher batcher, RetainedTesrGroups groups) { + if (!baseStillValid(groups) || EntityMaterials.fromCurrentState(false) != EntityMaterials.GLINT || !batcher.prepareArmorGlint()) return false; + final boolean inPlace = sameLightAndOverlay(); + for (int i = 0; i < used; i++) { + final Range range = ranges.get(i); + batcher.copyArmorGlint(range.columns, range.start, range.end, inPlace); + } + valid = false; + return true; + } + + boolean queueBothLayers(ModelPartBatcher batcher, RetainedTesrGroups groups, ResourceLocation glintTexture) { + if (!baseStillValid(groups) || !sameLightAndOverlay() || !batcher.prepareSkippedArmorGlint(glintTexture)) return false; + for (int i = 0; i < used; i++) { + final Range range = ranges.get(i); + batcher.queueArmorGlintLayers(range.columns, range.source, range.start, range.end); + } + valid = false; + return true; + } + + private boolean baseStillValid(RetainedTesrGroups groups) { + if (!valid || !base || owner != groups || pass != groups.captureVersion() || !groups.canCopyInstances(EntityMaterials.GLINT) || !sameParentPose()) return false; + for (int i = 0; i < used; i++) { + final Range range = ranges.get(i); + if (!groups.captureStillValid(range.group) || range.end > range.columns.size) return false; + } + return true; + } + + private boolean sameLightAndOverlay() { + return uniformLight && light == GLSMConfig.packedLastBrightness() && overlay == currentOverlay(); + } + + private static int currentColor() { + final var c = GLStateManager.getColor(); + return AngelicaBufferSource.packAbgr(c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + } + + private static int currentOverlay() { + return AngelicaBufferSource.packAbgr(GLStateManager.getOverlayR(), GLStateManager.getOverlayG(), GLStateManager.getOverlayB(), GLStateManager.getOverlayA()); + } +} diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/InstanceRing.java b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/InstanceRing.java index 432b55b19..40898ef52 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/InstanceRing.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/InstanceRing.java @@ -11,6 +11,7 @@ public final class InstanceRing { private OrphanStreamingBuffer orphanRing; private boolean initialized; private boolean usedThisFrame; + private boolean lastUploadKept; private int bufferId; public long upload(ByteBuffer data, int stride) { @@ -23,15 +24,21 @@ public final class InstanceRing { final int index = persistentRing.upload(data, stride); if (index >= 0) { bufferId = persistentRing.getBufferId(); + lastUploadKept = true; return (long) index * stride; } } if (orphanRing == null) orphanRing = new OrphanStreamingBuffer(); orphanRing.upload(data); bufferId = orphanRing.getBufferId(); + lastUploadKept = false; return 0; } + public int keptUploadsEpoch() { + return lastUploadKept && persistentRing != null ? persistentRing.getWraps() : -1; + } + public int bufferId() { return bufferId; } @@ -54,6 +61,7 @@ public final class InstanceRing { } initialized = false; usedThisFrame = false; + lastUploadKept = false; bufferId = 0; } } diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/InstancedTemplateRenderer.java b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/InstancedTemplateRenderer.java index 1fb62aa1d..64167dd1c 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/InstancedTemplateRenderer.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/InstancedTemplateRenderer.java @@ -65,10 +65,15 @@ final class InstancedTemplateRenderer { } int drawTemplates(InstanceColumns cols, TexRun run, long nowMs) { + return drawTemplates(cols, run, nowMs, false); + } + + int drawTemplates(InstanceColumns cols, TexRun run, long nowMs, boolean recordUpload) { + final InstanceColumns data = run.source != null ? run.source : cols; final long epoch = ++bucketEpochSource; for (TexRun seg = run; seg != null; seg = seg.next) { for (int i = seg.start, end = seg.end; i < end; i++) { - final TemplateBuffer template = cols.templates.get(i); + final TemplateBuffer template = data.templates.get(i); final Bucket bucket; if (template.bucketEpoch == epoch) { bucket = liveBuckets.get(template.bucketIndex); @@ -90,12 +95,19 @@ final class InstancedTemplateRenderer { final IntArrayList indices = liveBuckets.get(b).indices; for (int j = 0, m = indices.size(); j < m; j++) { final int i = indices.getInt(j); - InstancedAttribs.writeHead(ptr, cols.matrices, i * 16, cols.colors.getInt(i), cols.overlays.getInt(i), cols.infos.getLong(i)); - InstancedAttribs.writeLightmap(ptr + InstancedAttribs.OFFSET_LIGHTMAP, cols.lights.getInt(i)); + final int color = run.source != null ? run.color : data.colors.getInt(i); + final long info = run.source != null ? 0L : data.infos.getLong(i); + InstancedAttribs.writeHead(ptr, data.matrices, i * 16, color, data.overlays.getInt(i), info); + InstancedAttribs.writeLightmap(ptr + InstancedAttribs.OFFSET_LIGHTMAP, data.lights.getInt(i)); ptr += InstancedAttribs.STRIDE; } } final long ringBase = uploadStaging(base, ptr, InstancedAttribs.STRIDE); + if (recordUpload) { + run.uploadOffset = ringBase; + run.uploadMeshes.clear(); + run.uploadCounts.clear(); + } int draws = 0; long recordOffset = ringBase; @@ -104,6 +116,10 @@ final class InstancedTemplateRenderer { for (int b = 0, n = liveBuckets.size(); b < n; b++) { final Bucket bucket = liveBuckets.get(b); final TemplateMesh mesh = bucket.mesh; + if (recordUpload) { + run.uploadMeshes.add(mesh); + run.uploadCounts.add(bucket.indices.size()); + } bindRingTo(mesh.vao, TEMPLATE_FLAGS); InstancedAttribs.pointTemplate(recordOffset); GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); @@ -120,22 +136,52 @@ final class InstancedTemplateRenderer { return draws; } - void drawCubes(InstanceColumns cols, TexRun run) { + /** + * Draws a base run's recorded template upload again, every instance in one color. + */ + int redrawTemplates(TexRun base, int colorABGR) { + long recordOffset = base.uploadOffset; + neutralizeExtendedAttribs(); + GLStateManager.ffpInstancing = Instancing.TEMPLATE; + for (int b = 0, n = base.uploadMeshes.size(); b < n; b++) { + final TemplateMesh mesh = (TemplateMesh) base.uploadMeshes.get(b); + final int count = base.uploadCounts.getInt(b); + bindRingTo(mesh.vao, TEMPLATE_FLAGS); + InstancedAttribs.pointTemplate(recordOffset); + GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + setConstantColor(colorABGR); + GLStateManager.glDrawArraysInstanced(mesh.drawMode, 0, mesh.vertexCount, count); + GLStateManager.glEnableVertexAttribArray(InstancedAttribs.LOC_COLOR); + recordOffset += (long) count * InstancedAttribs.STRIDE; + } + GLStateManager.ffpInstancing = Instancing.NONE; + GLStateManager.glBindVertexArray(0); + return base.uploadMeshes.size(); + } + + private static void setConstantColor(int colorABGR) { + GLStateManager.glDisableVertexAttribArray(InstancedAttribs.LOC_COLOR); + GLStateManager.glVertexAttrib4f(InstancedAttribs.LOC_COLOR, (colorABGR & 0xFF) / 255.0f, (colorABGR >>> 8 & 0xFF) / 255.0f, (colorABGR >>> 16 & 0xFF) / 255.0f, (colorABGR >>> 24) / 255.0f); + } + + /** Returns the ring offset of the uploaded instances, for {@link #redrawCubes}. */ + long drawCubes(InstanceColumns cols, TexRun run) { + final InstanceColumns data = run.source != null ? run.source : cols; staging = MeshBuffer.ensureCapacity(staging, run.instances * CubeInstancedAttribs.STRIDE, false); final long base = memAddress0(staging); long ptr = base; for (TexRun seg = run; seg != null; seg = seg.next) { for (int i = seg.start, end = seg.end; i < end; i++) { final int off = i * 16; - final CubeParams[] partCubes = cols.cubes.get(i); - final float scale = cols.scales.getFloat(i); - final int color = cols.colors.getInt(i); - final int light = cols.lights.getInt(i); - final int overlay = cols.overlays.getInt(i); - final long info = cols.infos.getLong(i); + final CubeParams[] partCubes = data.cubes.get(i); + final float scale = data.scales.getFloat(i); + final int color = run.source != null ? run.color : data.colors.getInt(i); + final int light = data.lights.getInt(i); + final int overlay = data.overlays.getInt(i); + final long info = run.source != null ? 0L : data.infos.getLong(i); for (int p = 0, n = partCubes.length; p < n; p++) { final CubeParams cube = partCubes[p]; - cube.writeRows(ptr, cols.matrices, off, scale); + cube.writeRows(ptr, data.matrices, off, scale); InstancedAttribs.writeTail(ptr, color, overlay, info); InstancedAttribs.writeLightmap(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE, light); cube.writeTexture(ptr); @@ -153,6 +199,32 @@ final class InstancedTemplateRenderer { GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, UnitCubeMesh.VERTEX_COUNT, run.instances); GLStateManager.ffpInstancing = Instancing.NONE; GLStateManager.glBindVertexArray(0); + return ringBase; + } + + boolean canRedraw(int ringEpoch, int buffer) { + return ringEpoch >= 0 && ringEpoch == ring.keptUploadsEpoch() && buffer == ring.bufferId(); + } + + int ringEpoch() { + return ring.keptUploadsEpoch(); + } + + int ringBuffer() { + return ring.bufferId(); + } + + void redrawCubes(long offset, int instances, int colorABGR) { + neutralizeExtendedAttribs(); + bindRingTo(UnitCubeMesh.vao(), UnitCubeMesh.VERTEX_FLAGS); + CubeInstancedAttribs.pointInstanceAttribs(offset); + GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); + setConstantColor(colorABGR); + GLStateManager.ffpInstancing = Instancing.CUBE; + GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, UnitCubeMesh.VERTEX_COUNT, instances); + GLStateManager.ffpInstancing = Instancing.NONE; + GLStateManager.glEnableVertexAttribArray(InstancedAttribs.LOC_COLOR); + GLStateManager.glBindVertexArray(0); } private long uploadStaging(long base, long end, int stride) { diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/ModelPartBatcher.java b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/ModelPartBatcher.java index 2401d3b38..01edb1623 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/ModelPartBatcher.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/ModelPartBatcher.java @@ -20,6 +20,8 @@ import com.gtnewhorizons.angelica.glsm.states.Color4; import com.gtnewhorizons.angelica.glsm.states.PolygonState; import com.gtnewhorizons.angelica.mixins.interfaces.ModelBoxData; import com.gtnewhorizons.angelica.profiling.BailClassCounts; +import com.gtnewhorizons.angelica.rendering.GlintClock; +import com.gtnewhorizons.angelica.rendering.items.HeldItemGlint; import com.gtnewhorizons.angelica.shadercompat.ShaderGlint; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectIterator; @@ -41,6 +43,7 @@ import net.minecraft.world.World; import org.joml.Matrix4f; import org.lwjgl.opengl.GL11; +import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -91,17 +94,27 @@ public final class ModelPartBatcher { private boolean instancedThisCycle; private DeferredWorldRenderingPipeline shaderPipeline; private long lastSweepMs; + private RenderLayer armorGlintLayer; + private final Matrix4f armorGlintTexture = new Matrix4f(); + private int armorGlintColor, armorGlintLight, armorGlintOverlay; + private static final int VANILLA_GLINT_COLOR = AngelicaBufferSource.packAbgr(0.5F * 0.76F, 0.25F * 0.76F, 0.8F * 0.76F, 1.0F); long parts; long liveFallbacks; + private long armorBaseReuses, glintPassReuses, armorLoopSkips, armorSectionSkips; private final long[] bails = new long[BailReason.VALUES.length]; private long lastParts, lastLiveFallbacks; public long statParts() { return parts; } + public long statArmorBaseReuses() { return armorBaseReuses; } + public long statGlintPassReuses() { return glintPassReuses; } + public long statArmorLoopSkips() { return armorLoopSkips; } + public long statArmorSectionSkips() { return armorSectionSkips; } public long statLiveFallbacks() { return liveFallbacks; } public long statInstancedDraws() { return groups.instancedDraws + shadowGroups.instancedDraws; } public long statInstancedInstances() { return groups.instancedInstances + shadowGroups.instancedInstances; } public long statCubeInstances() { return groups.cubeInstances + shadowGroups.cubeInstances; } + public long statRedrawnInstances() { return groups.redrawnInstances + shadowGroups.redrawnInstances; } public long statTexMatrixRuns() { return groups.texMatrixRuns + shadowGroups.texMatrixRuns; } public long statBail(BailReason reason) { return bails[reason.ordinal()]; } public long statShadowQuads() { return shadows.statQuads(); } @@ -131,6 +144,11 @@ public final class ModelPartBatcher { int cull; boolean lit; + boolean matches(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cull, boolean lit) { + return this.texture == texture && this.material == material && pass.equals(this.pass) && this.offsetFactor == offsetFactor + && this.offsetUnits == offsetUnits && this.glintSlot == glintSlot && this.cull == cull && this.lit == lit; + } + LayerKey set(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cull, boolean lit) { this.texture = texture; this.material = material; @@ -167,14 +185,10 @@ public final class ModelPartBatcher { private final Object2ObjectOpenHashMap layers = new Object2ObjectOpenHashMap<>(); private final LayerKey scratchKey = new LayerKey(); - private ResourceLocation lastLayerTexture; - private TesrMaterial lastLayerMaterial; - private PassOverride lastLayerPass; - private float lastLayerOffsetFactor; - private float lastLayerOffsetUnits; - private int lastLayerGlintSlot; - private int lastLayerCull; - private boolean lastLayerLit; + private static final int RECENT_LAYERS = 8; + private final LayerKey[] recentKeys = new LayerKey[RECENT_LAYERS]; + private final RenderLayer[] recentLayers = new RenderLayer[RECENT_LAYERS]; + private int recentNext; private RenderLayer lastLayer; private static ResourceLocation lastBoundLocation; @@ -262,6 +276,126 @@ public final class ModelPartBatcher { return active; } + public boolean beginGlintCapture(GlintCapture capture) { + return canCaptureGlint() && capture.begin(activeGroups); + } + + public boolean canCaptureGlint() { + return active && instancedThisCycle && shaderPipeline == null && !shadow && BatchEligibility.batchingAllowed() && GLStateManager.getActiveProgram() == 0 && !GLStateManager.isRecordingDisplayList(); + } + + public boolean replayGlint(GlintCapture capture) { + return replayGlint(capture, GLStateManager.getTextures().getTextureUnitMatrix(0)); + } + + public boolean queueSecondArmorGlint(GlintCapture capture) { + final Matrix4f second = GlintClock.secondArmorMatrix(GLStateManager.getTextures().getTextureUnitMatrix(0)); + if (second == null || !replayGlint(capture, second)) return false; + armorLoopSkips++; + return true; + } + + private boolean replayGlint(GlintCapture capture, Matrix4f textureMatrix) { + if (!active || shaderPipeline != null || shadow || !BatchEligibility.batchingAllowed() || GLStateManager.getActiveProgram() != 0 || GLStateManager.isRecordingDisplayList()) return false; + if (!capture.replay(activeGroups, textureMatrix)) return false; + glintPassReuses++; + BatchEligibility.onPartQueued(); + return true; + } + + public boolean beginArmorCapture(GlintCapture capture) { + return canCaptureGlint() && capture.beginBase(activeGroups); + } + + public boolean reuseArmorBase(GlintCapture base, GlintCapture glint) { + if (!canCaptureGlint() || !glint.beginTrusted(activeGroups)) return false; + final boolean copied; + try { + copied = base.copyBase(this, activeGroups); + } finally { + glint.end(); + } + if (!copied) { + glint.reset(); + return false; + } + armorBaseReuses++; + return true; + } + + public boolean queueSkippedArmorGlint(GlintCapture base, ResourceLocation glintTexture) { + if (!canCaptureGlint() || GLStateManager.getActiveTextureUnit() != 0 || !GLStateManager.getTextures().getTextureUnitStates(0).isEnabled() || !base.queueBothLayers(this, activeGroups, glintTexture)) return false; + armorBaseReuses++; + glintPassReuses++; + armorSectionSkips++; + BatchEligibility.onPartQueued(); + return true; + } + + public boolean queueSkippedHeldGlint(TemplateBuffer template) { + if (!canCaptureGlint() || GLStateManager.getActiveTextureUnit() != 0 || !activeGroups.canCopyInstances(EntityMaterials.ITEM_GLINT) || !MatrixHelper.isIdentity(GLStateManager.getTextures().getTextureUnitMatrix(0))) return false; + final boolean offset = GLStateManager.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL); + final PolygonState polygon = GLStateManager.getPolygonState(); + final int cullCode = EntityMaterials.ITEM_GLINT.isNoCull() ? DrawState.DISABLED : DrawState.liveCull(); + final RenderLayer layer = layerFor(HeldItemGlint.texture(), EntityMaterials.ITEM_GLINT, PassOverride.NONE, offset ? polygon.getOffsetFactor() : 0, offset ? polygon.getOffsetUnits() : 0, ShaderGlint.NO_TINT, cullCode, false); + activeGroups.queueGlintLayers(template, layer, EntityMaterials.ITEM_GLINT, currentModelView(), GLSMConfig.packedLastBrightness(), VANILLA_GLINT_COLOR, currentOverlay(), HeldItemGlint.firstMatrix(), HeldItemGlint.secondMatrix()); + parts++; + glintPassReuses++; + return true; + } + + private Matrix4f currentModelView() { + final int mvGen = GLStateManager.getMvGeneration(); + if (!mvCaptured || mvGen != lastMvGeneration) { + modelView.set(GLStateManager.getModelViewMatrix()); + lastMvGeneration = mvGen; + mvCaptured = true; + } + return modelView; + } + + private static int currentOverlay() { + return AngelicaBufferSource.packAbgr(GLStateManager.getOverlayR(), GLStateManager.getOverlayG(), GLStateManager.getOverlayB(), GLStateManager.getOverlayA()); + } + + boolean prepareArmorGlint() { + if (GLStateManager.getBoundTextureForServerState() != lastBoundGlId || lastBoundLocation == null) return false; + final Color4 color = GLStateManager.getColor(); + setArmorGlint(lastBoundLocation, lastBoundGlintSlot, DrawState.liveLit(EntityMaterials.GLINT), GLStateManager.getTextures().getTextureUnitMatrix(0), + AngelicaBufferSource.packAbgr(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha())); + return true; + } + + boolean prepareSkippedArmorGlint(ResourceLocation glintTexture) { + setArmorGlint(glintTexture, ShaderGlint.NO_TINT, false, GlintClock.firstArmorMatrix(), VANILLA_GLINT_COLOR); + return true; + } + + private void setArmorGlint(ResourceLocation texture, int glintSlot, boolean lit, Matrix4f textureMatrix, int color) { + final boolean offset = GLStateManager.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL); + final PolygonState polygon = GLStateManager.getPolygonState(); + final int cullCode = EntityMaterials.GLINT.isNoCull() ? DrawState.DISABLED : DrawState.liveCull(); + armorGlintLayer = layerFor(texture, EntityMaterials.GLINT, PassOverride.NONE, offset ? polygon.getOffsetFactor() : 0, offset ? polygon.getOffsetUnits() : 0, glintSlot, cullCode, lit); + armorGlintTexture.set(textureMatrix); + armorGlintColor = color; + armorGlintLight = GLSMConfig.packedLastBrightness(); + armorGlintOverlay = currentOverlay(); + } + + void copyArmorGlint(RetainedTesrGroups.InstanceColumns base, int start, int end, boolean inPlace) { + if (inPlace) { + activeGroups.referenceInstances(base, start, end, armorGlintLayer, EntityMaterials.GLINT, armorGlintColor, armorGlintTexture); + } else { + activeGroups.copyInstances(base, start, end, armorGlintLayer, EntityMaterials.GLINT, armorGlintLight, armorGlintColor, armorGlintOverlay, armorGlintTexture); + } + parts += end - start; + } + + void queueArmorGlintLayers(RetainedTesrGroups.InstanceColumns base, RetainedTesrGroups.TexRun baseRun, int start, int end) { + activeGroups.referenceGlintLayers(base, baseRun, start, end, armorGlintLayer, EntityMaterials.GLINT, armorGlintColor, GlintClock.firstArmorMatrix(), GlintClock.secondArmorMatrix()); + parts += end - start; + } + public boolean entityPassActive() { return active && mode == Mode.ENTITIES && !shadow; } @@ -359,16 +493,12 @@ public final class ModelPartBatcher { final int entityId = groupId(shaderPipeline != null, mode == Mode.ENTITIES || pass.isEntityPhase(), CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity()); final Color4 color = GLStateManager.getColor(); final int colorABGR = AngelicaBufferSource.packAbgr(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); - final int overlayABGR = shaderPipeline != null ? AngelicaBufferSource.packEntityColor(CapturedRenderingState.INSTANCE.getCurrentEntityColor()) : AngelicaBufferSource.packAbgr(GLStateManager.getOverlayR(), GLStateManager.getOverlayG(), GLStateManager.getOverlayB(), GLStateManager.getOverlayA()); - final long entityInfo = shaderPipeline != null ? InstancedAttribs.packEntityInfo(CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedItem()) : 0L; + final int overlayABGR = shaderPipeline != null ? AngelicaBufferSource.packEntityColor(CapturedRenderingState.INSTANCE.getCurrentEntityColor()) + : AngelicaBufferSource.packAbgr(GLStateManager.getOverlayR(), GLStateManager.getOverlayG(), GLStateManager.getOverlayB(), GLStateManager.getOverlayA()); + final long entityInfo = shaderPipeline != null ? InstancedAttribs.packEntityInfo(CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(), + CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedItem()) : 0L; final int packedLight = GLSMConfig.packedLastBrightness(); - final int mvGen = GLStateManager.getMvGeneration(); - if (!mvCaptured || mvGen != lastMvGeneration) { - modelView.set(GLStateManager.getModelViewMatrix()); - lastMvGeneration = mvGen; - mvCaptured = true; - } - activeGroups.queue(template, cubes, scale, layer, material, modelView, packedLight, colorABGR, overlayABGR, entityInfo, entityId, texMatrix); + activeGroups.queue(template, cubes, scale, layer, material, currentModelView(), packedLight, colorABGR, overlayABGR, entityInfo, entityId, texMatrix); parts++; return true; } @@ -383,7 +513,10 @@ public final class ModelPartBatcher { final boolean textured = GLStateManager.getTextures().getTextureUnitStates(0).isEnabled(); final BlendState blendState = GLStateManager.getEffectiveBlendState(bailBlend); final AlphaState alphaState = GLStateManager.getEffectiveAlphaState(bailAlpha); - AngelicaMod.LOGGER.info("TESR MATERIAL bail: cls={} shadow={} textured={} texAnimated={} blend={} src={} dst={} alphaTest={} alphaFunc={} alphaRef={} depthFunc={} depthMask={}", cls.getName(), shadow, textured, textured && !unitTexIdentity(), GLStateManager.isEffectiveBlendEnabled(), blendState.getSrcRgb(), blendState.getDstRgb(), GLStateManager.isEffectiveAlphaTestEnabled(), alphaState.getFunction(), alphaState.getReference(), GLStateManager.getDepthState().getFunc(),GLStateManager.isEffectiveDepthMaskEnabled()); + AngelicaMod.LOGGER.info("TESR MATERIAL bail: cls={} shadow={} textured={} texAnimated={} blend={} src={} dst={} alphaTest={} alphaFunc={} alphaRef={} depthFunc={} depthMask={}", + cls.getName(), shadow, textured, textured && !unitTexIdentity(), GLStateManager.isEffectiveBlendEnabled(), blendState.getSrcRgb(), + blendState.getDstRgb(), GLStateManager.isEffectiveAlphaTestEnabled(), alphaState.getFunction(), alphaState.getReference(), + GLStateManager.getDepthState().getFunc(), GLStateManager.isEffectiveDepthMaskEnabled()); } static int groupId(boolean piped, boolean entityPhase, int entityId, int blockEntityId) { @@ -458,26 +591,22 @@ public final class ModelPartBatcher { } private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cullCode, boolean lit) { - if (texture == lastLayerTexture && material == lastLayerMaterial && pass.equals(lastLayerPass) - && offsetFactor == lastLayerOffsetFactor && offsetUnits == lastLayerOffsetUnits && glintSlot == lastLayerGlintSlot - && cullCode == lastLayerCull && lit == lastLayerLit) { - return lastLayer; + for (int i = 0; i < RECENT_LAYERS; i++) { + final RenderLayer recent = recentLayers[i]; + if (recent != null && recentKeys[i].matches(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit)) { + return lastLayer = recent; + } } RenderLayer layer = layers.get(scratchKey.set(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit)); if (layer == null) { layer = RenderLayer.tesr(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit); layers.put(new LayerKey().set(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit), layer); } - lastLayerTexture = texture; - lastLayerMaterial = material; - lastLayerPass = pass; - lastLayerOffsetFactor = offsetFactor; - lastLayerOffsetUnits = offsetUnits; - lastLayerGlintSlot = glintSlot; - lastLayerCull = cullCode; - lastLayerLit = lit; - lastLayer = layer; - return layer; + if (recentKeys[recentNext] == null) recentKeys[recentNext] = new LayerKey(); + recentKeys[recentNext].set(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit); + recentLayers[recentNext] = layer; + recentNext = (recentNext + 1) % RECENT_LAYERS; + return lastLayer = layer; } private void sweepTemplates(long now) { @@ -501,12 +630,8 @@ public final class ModelPartBatcher { shaderPipeline = null; layers.clear(); scratchKey.set(null, null, null, 0.0f, 0.0f, ShaderGlint.NO_TINT, DrawState.DISABLED, false); - lastLayerTexture = null; - lastLayerMaterial = null; - lastLayerPass = null; - lastLayerGlintSlot = ShaderGlint.NO_TINT; - lastLayerCull = DrawState.DISABLED; - lastLayerLit = false; + Arrays.fill(recentLayers, null); + recentNext = 0; lastLayer = null; loggedMaterialBails.clear(); lastBoundLocation = null; diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/RetainedTesrGroups.java b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/RetainedTesrGroups.java index a69b58de4..49c8a7c77 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/RetainedTesrGroups.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/RetainedTesrGroups.java @@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.api.tesr.TesrMaterial; import com.gtnewhorizons.angelica.compat.mojang.RenderLayer; import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.glsm.ffp.CubeParams; +import com.gtnewhorizons.angelica.glsm.ffp.CombinedGlint; import com.gtnewhorizons.angelica.glsm.ffp.Instancing; import com.gtnewhorizons.angelica.glsm.profiling.Tracy; import it.unimi.dsi.fastutil.floats.FloatArrayList; @@ -19,6 +20,7 @@ import net.minecraft.util.ResourceLocation; import org.joml.Matrix4f; import org.joml.Matrix4fc; import org.joml.Vector3f; +import org.lwjgl.opengl.GL11; import java.nio.ByteBuffer; import java.util.Arrays; @@ -51,6 +53,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { private final float[] matScratch = new float[16]; private double camX, camY, camZ; private long frameMark; + GlintCapture glintCapture; long rebuilds; long retainedDraws; @@ -59,6 +62,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { long instancedDraws; long instancedInstances; long cubeInstances; + long redrawnInstances; long texMatrixRuns; private InstancedTemplateRenderer instanced; @@ -73,6 +77,17 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { int start, end; int parts, instances; TexRun next, last; + TexRun reusedFrom; + InstanceColumns source; + int color; + Matrix4fc matrixSource; + boolean glintBase; + TexRun sourceRun; + long uploadOffset; + long uploadPass = -1; + int uploadRingEpoch, uploadBuffer; + final ObjectArrayList uploadMeshes = new ObjectArrayList<>(); + final IntArrayList uploadCounts = new IntArrayList(); } static final class InstanceColumns { @@ -86,6 +101,11 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { final ObjectArrayList runs = new ObjectArrayList<>(); float[] matrices = new float[16 * 64]; int size; + int referencedParts; + + boolean hasInstances() { + return size > 0 || referencedParts > 0; + } void add(TemplateBuffer templateOrNull, Matrix4fc mv, int light, int color, int overlay, long info) { final int base = size * 16; @@ -107,6 +127,33 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { add(templateOrNull, mv, light, color, overlay, info); } + void appendRange(InstanceColumns src, int start, int end, int light, int color, int overlay) { + final int n = end - start; + final int need = (size + n) * 16; + if (need > matrices.length) { + matrices = Arrays.copyOf(matrices, Math.max(need, matrices.length << 1)); + } + System.arraycopy(src.matrices, start * 16, matrices, size * 16, n * 16); + if (src.templates.size() == src.size) { + for (int i = start; i < end; i++) templates.add(src.templates.get(i)); + } + if (src.cubes.size() == src.size) { + for (int i = start; i < end; i++) cubes.add(src.cubes.get(i)); + scales.addElements(scales.size(), src.scales.elements(), start, n); + } + fill(lights, n, light); + fill(colors, n, color); + fill(overlays, n, overlay); + infos.size(infos.size() + n); + size += n; + } + + private static void fill(IntArrayList list, int n, int value) { + final int from = list.size(); + list.size(from + n); + Arrays.fill(list.elements(), from, from + n, value); + } + void clear() { templates.clear(); cubes.clear(); @@ -117,6 +164,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { infos.clear(); runs.clear(); size = 0; + referencedParts = 0; } } @@ -168,14 +216,23 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { } TexRun openOrExtendRun(InstanceColumns cols, Matrix4f texMatrix) { + return openOrExtendRun(cols, texMatrix, false, false); + } + + TexRun openOrExtendRun(InstanceColumns cols, Matrix4f texMatrix, boolean stableMatrix, boolean glintBase) { final ObjectArrayList list = cols.runs; final int part = cols.size; final boolean identity = texMatrix == null; final int size = list.size(); - final int floor = Math.max(0, mergeRuns ? size - MERGE_SCAN : size - 1); + int floor = Math.max(0, mergeRuns ? size - MERGE_SCAN : size - 1); for (int i = size - 1; i >= floor; i--) { final TexRun head = list.get(i); - if (head.identity != identity || (!identity && !head.matrix.equals(texMatrix))) continue; + if (head.reusedFrom != null || head.source != null) continue; + if (head.glintBase != glintBase) { + if (opaque) floor = Math.max(0, floor - 1); + continue; + } + if (head.identity != identity || (!identity && head.matrixSource != texMatrix && !head.matrix.equals(texMatrix))) continue; if (head.last.end != part) { final TexRun segment = pooledRun(part); head.last.next = segment; @@ -186,10 +243,69 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { final TexRun head = pooledRun(part); head.identity = identity; if (!identity) head.matrix.set(texMatrix); + if (stableMatrix) head.matrixSource = texMatrix; + head.glintBase = glintBase; list.add(head); return head; } + TexRun referenceRun(InstanceColumns cols, InstanceColumns src, TexRun sourceRun, int color, Matrix4f texMatrix, boolean stableMatrix, int start) { + final ObjectArrayList list = cols.runs; + for (int i = list.size() - 1; i >= 0; i--) { + final TexRun head = list.get(i); + if (head.source != src || head.reusedFrom != null || head.color != color || head.matrixSource != texMatrix && !head.matrix.equals(texMatrix)) continue; + if (head.last.end != start) { + final TexRun segment = pooledRun(start); + head.last.next = segment; + head.last = segment; + } + if (head.sourceRun != sourceRun) head.sourceRun = null; + return head; + } + final TexRun head = pooledRun(start); + head.identity = false; + head.matrix.set(texMatrix); + head.source = src; + head.sourceRun = sourceRun; + head.color = color; + if (stableMatrix) head.matrixSource = texMatrix; + list.add(head); + return head; + } + + void replayRange(InstanceColumns cols, TexRun original, int start, int end, int instances, Matrix4f matrix) { + replayRange(cols, original, start, end, instances, matrix, false); + } + + void replayRange(InstanceColumns cols, TexRun original, int start, int end, int instances, Matrix4f matrix, boolean stableMatrix) { + TexRun head = null; + for (int i = cols.runs.size() - 1; i >= 0; i--) { + final TexRun candidate = cols.runs.get(i); + if (candidate.reusedFrom == original && (candidate.matrixSource == matrix || candidate.matrix.equals(matrix))) { + head = candidate; + break; + } + } + if (head == null) { + head = pooledRun(start); + head.matrix.set(matrix); + head.identity = false; + head.reusedFrom = original; + head.source = original.source; + head.color = original.color; + if (stableMatrix) head.matrixSource = matrix; + cols.runs.add(head); + } else if (head.last.end != start) { + final TexRun segment = pooledRun(start); + head.last.next = segment; + head.last = segment; + } + head.sourceRun = original.sourceRun; + head.last.end = end; + head.parts += end - start; + head.instances += instances; + } + private TexRun pooledRun(int part) { if (runPoolUsed == runPool.size()) { runPool.add(new TexRun()); @@ -200,6 +316,12 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { run.parts = 0; run.instances = 0; run.next = null; + run.reusedFrom = null; + run.source = null; + run.matrixSource = null; + run.glintBase = false; + run.sourceRun = null; + run.uploadPass = -1; run.last = run; return run; } @@ -225,7 +347,9 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { private final Reference2ObjectOpenHashMap>> groups = new Reference2ObjectOpenHashMap<>(); private final Reference2ObjectOpenHashMap> byLayer = new Reference2ObjectOpenHashMap<>(); - private Group lastGroup; + private static final int RECENT_GROUPS = 4; + private final Group[] recentGroups = new Group[RECENT_GROUPS]; + private int recentGroupNext; RetainedTesrGroups(AngelicaBufferSource source) { this(source, System::currentTimeMillis); @@ -245,6 +369,8 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { } void beginPass(Matrix4f base, double camX, double camY, double camZ, InstancedTemplateRenderer instancedRenderer, TesrInstancingPipeline deferred) { + if (glintCapture != null) glintCapture.invalidate(); + glintCapture = null; baseMV.set(base); baseMVInv.set(base).invert(); this.camX = camX; @@ -257,10 +383,23 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { nowMs = clock.getAsLong(); } + boolean captureStillValid(Group group) { + return group.frameMark == frameMark; + } + + long captureVersion() { return frameMark; } + private Group groupFor(RenderLayer layer, TesrMaterial material, int id) { final int key = instanceableMaterial(material) ? 0 : id; - Group group = lastGroup; - if (group == null || group.layer != layer || group.material != material || group.blockEntityId != key) { + Group group = null; + for (int i = 0; i < RECENT_GROUPS; i++) { + final Group recent = recentGroups[i]; + if (recent != null && recent.layer == layer && recent.material == material && recent.blockEntityId == key) { + group = recent; + break; + } + } + if (group == null) { Reference2ObjectOpenHashMap> byMaterial = groups.get(layer); if (byMaterial == null) { byMaterial = new Reference2ObjectOpenHashMap<>(); @@ -278,7 +417,8 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { byId.put(key, group); byLayer.computeIfAbsent(layer, l -> new ObjectArrayList<>()).add(group); } - lastGroup = group; + recentGroups[recentGroupNext] = group; + recentGroupNext = (recentGroupNext + 1) % RECENT_GROUPS; } if (group.renderableClass == null) { group.renderableClass = TesrAttribution.currentRenderable; @@ -296,6 +436,124 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { void queue(TemplateBuffer template, CubeParams[] cubes, float scale, RenderLayer layer, TesrMaterial material, Matrix4f currentMV, int packedLight, int colorABGR, int overlayABGR, long entityInfo, int id, Matrix4f texMatrix) { final Group group = groupFor(layer, material, id); + touch(group, layer); + if (group.stream) { + final boolean instanceable = instanceable(group); + final boolean capturingBase = glintCapture != null && glintCapture.capturesBase(); + if (instanceable && cubes != null) { + final TexRun run = group.openOrExtendRun(group.cubeColumns, texMatrix, false, capturingBase && group.opaque); + group.cubeColumns.addCubes(deferred != null ? template : null, cubes, scale, currentMV, packedLight, colorABGR, overlayABGR, entityInfo); + run.last.end = group.cubeColumns.size; + run.parts++; + run.instances += cubes.length; + if (glintCapture != null) glintCapture.record(group, group.cubeColumns, run, group.cubeColumns.size - 1, group.cubeColumns.size, cubes.length); + return; + } + if (instanceable) { + final TexRun run = group.openOrExtendRun(group.templateColumns, texMatrix, false, capturingBase && group.opaque); + group.templateColumns.add(template, currentMV, packedLight, colorABGR, overlayABGR, entityInfo); + run.last.end = group.templateColumns.size; + run.parts++; + run.instances++; + if (glintCapture != null) glintCapture.record(group, group.templateColumns, run, group.templateColumns.size - 1, group.templateColumns.size, 1); + return; + } + if (glintCapture != null) glintCapture.invalidate(); + if (template.drawMode == layer.getDrawMode()) { + source.getBuffer(layer, id).addTemplateInstance(template, currentMV, scratchVec, colorABGR, packedLight, texMatrix); + streamedInstances++; + return; + } + } + scratchMat.set(baseMVInv).mul(currentMV); + TesrAnchorMath.toAnchorRelative(scratchMat, camX, camY, camZ, group.anchorX, group.anchorY, group.anchorZ); + long hash = TesrAnchorMath.instanceHash(System.identityHashCode(template), scratchMat, packedLight, colorABGR); + if (texMatrix != null) { + hash += TesrAnchorMath.texMatrixHash(texMatrix); + } + if (group.streaming && template.drawMode == layer.getDrawMode()) { + group.frameHash += hash; + source.getBuffer(layer, id).addTemplateInstance(template, currentMV, scratchVec, colorABGR, packedLight, texMatrix); + streamedInstances++; + return; + } + group.hashAcc += hash; + group.templateColumns.add(template, scratchMat, packedLight, colorABGR, overlayABGR, entityInfo); + group.addTexMatrix(texMatrix); + } + + boolean canCopyInstances(TesrMaterial material) { + return material.isStream() && instanceableMaterial(material); + } + + void copyInstances(InstanceColumns src, int start, int end, RenderLayer layer, TesrMaterial material, int packedLight, int colorABGR, int overlayABGR, Matrix4f texMatrix) { + final Group group = groupFor(layer, material, 0); + touch(group, layer); + final boolean cubes = !src.cubes.isEmpty(); + final InstanceColumns dst = cubes ? group.cubeColumns : group.templateColumns; + final TexRun run = group.openOrExtendRun(dst, texMatrix); + final int first = dst.size; + dst.appendRange(src, start, end, packedLight, colorABGR, overlayABGR); + int instances = end - start; + if (cubes) { + instances = 0; + for (int i = start; i < end; i++) instances += src.cubes.get(i).length; + } + run.last.end = dst.size; + run.parts += end - start; + run.instances += instances; + if (glintCapture != null) glintCapture.record(group, dst, run, first, dst.size, instances); + } + + void referenceInstances(InstanceColumns src, int start, int end, RenderLayer layer, TesrMaterial material, int colorABGR, Matrix4f texMatrix) { + final Group group = groupFor(layer, material, 0); + touch(group, layer); + final boolean cubes = !src.cubes.isEmpty(); + final InstanceColumns dst = cubes ? group.cubeColumns : group.templateColumns; + final int instances = instanceCount(src, start, end, cubes); + final TexRun run = group.referenceRun(dst, src, null, colorABGR, texMatrix, false, start); + run.last.end = end; + run.parts += end - start; + run.instances += instances; + dst.referencedParts += end - start; + if (glintCapture != null) glintCapture.record(group, dst, run, start, end, instances); + } + + void referenceGlintLayers(InstanceColumns src, TexRun sourceRun, int start, int end, RenderLayer layer, TesrMaterial material, int colorABGR, Matrix4f first, Matrix4f second) { + final Group group = groupFor(layer, material, 0); + touch(group, layer); + final boolean cubes = !src.cubes.isEmpty(); + final InstanceColumns dst = cubes ? group.cubeColumns : group.templateColumns; + final int instances = instanceCount(src, start, end, cubes); + final TexRun run = group.referenceRun(dst, src, sourceRun, colorABGR, first, true, start); + run.last.end = end; + run.parts += end - start; + run.instances += instances; + group.replayRange(dst, run, start, end, instances, second, true); + dst.referencedParts += end - start; + } + + void queueGlintLayers(TemplateBuffer template, RenderLayer layer, TesrMaterial material, Matrix4f currentMV, int packedLight, int colorABGR, int overlayABGR, Matrix4f first, Matrix4f second) { + final Group group = groupFor(layer, material, 0); + touch(group, layer); + final InstanceColumns dst = group.templateColumns; + final int index = dst.size; + final TexRun run = group.openOrExtendRun(dst, first, true, false); + dst.add(template, currentMV, packedLight, colorABGR, overlayABGR, 0L); + run.last.end = dst.size; + run.parts++; + run.instances++; + group.replayRange(dst, run, index, index + 1, 1, second, true); + } + + private static int instanceCount(InstanceColumns src, int start, int end, boolean cubes) { + if (!cubes) return end - start; + int instances = 0; + for (int i = start; i < end; i++) instances += src.cubes.get(i).length; + return instances; + } + + private void touch(Group group, RenderLayer layer) { if (group.frameMark != frameMark) { if (frameMark - group.frameMark > IDLE_RESET_FRAMES) { group.streaming = group.stream; @@ -330,45 +588,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { } source.declareUse(layer); } - if (group.stream) { - final boolean instanceable = instanceable(group); - if (instanceable && cubes != null) { - final TexRun run = group.openOrExtendRun(group.cubeColumns, texMatrix); - group.cubeColumns.addCubes(deferred != null ? template : null, cubes, scale, currentMV, packedLight, colorABGR, overlayABGR, entityInfo); - run.last.end = group.cubeColumns.size; - run.parts++; - run.instances += cubes.length; - return; - } - if (instanceable) { - final TexRun run = group.openOrExtendRun(group.templateColumns, texMatrix); - group.templateColumns.add(template, currentMV, packedLight, colorABGR, overlayABGR, entityInfo); - run.last.end = group.templateColumns.size; - run.parts++; - run.instances++; - return; - } - if (template.drawMode == layer.getDrawMode()) { - source.getBuffer(layer, id).addTemplateInstance(template, currentMV, scratchVec, colorABGR, packedLight, texMatrix); - streamedInstances++; - return; - } - } - scratchMat.set(baseMVInv).mul(currentMV); - TesrAnchorMath.toAnchorRelative(scratchMat, camX, camY, camZ, group.anchorX, group.anchorY, group.anchorZ); - long hash = TesrAnchorMath.instanceHash(System.identityHashCode(template), scratchMat, packedLight, colorABGR); - if (texMatrix != null) { - hash += TesrAnchorMath.texMatrixHash(texMatrix); - } - if (group.streaming && template.drawMode == layer.getDrawMode()) { - group.frameHash += hash; - source.getBuffer(layer, id).addTemplateInstance(template, currentMV, scratchVec, colorABGR, packedLight, texMatrix); - streamedInstances++; - return; - } - group.hashAcc += hash; - group.templateColumns.add(template, scratchMat, packedLight, colorABGR, overlayABGR, entityInfo); - group.addTexMatrix(texMatrix); } @Override @@ -383,6 +602,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { @Override public void drawLayer(RenderLayer layer) { + if (glintCapture != null) glintCapture.invalidate(); final ObjectArrayList list = byLayer.get(layer); if (list == null) return; boolean matrixAvailable = true; @@ -411,7 +631,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { private static final int NEED_CUBE = 2; private boolean live(Group g) { - return g.frameMark == frameMark && (g.templateColumns.size > 0 || g.cubeColumns.size > 0); + return g.frameMark == frameMark && (g.templateColumns.hasInstances() || g.cubeColumns.hasInstances()); } private boolean anyInstanceable(ObjectArrayList list) { @@ -431,14 +651,14 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { drawRetained(group); continue; } - if (group.templateColumns.size > 0) { + if (group.templateColumns.hasInstances()) { if (matrixAvailable) { need |= NEED_MATRIX; } else { drawInstancesCpu(group, group.templateColumns); } } - if (group.cubeColumns.size > 0) { + if (group.cubeColumns.hasInstances()) { if (cubeAvailable) { need |= NEED_CUBE; } else if (matrixAvailable) { @@ -455,11 +675,12 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { for (int i = 0, n = list.size(); i < n; i++) { final Group group = list.get(i); if (!live(group) || !instanceable(group)) continue; - if (group.templateColumns.size > 0) { - drawTemplateRuns(group.templateColumns); + if (group.templateColumns.hasInstances()) { + final boolean fixedFunction = deferred == null && GLStateManager.getActiveProgram() == 0; + drawTemplateRuns(group.templateColumns, group.material.special() == TesrMaterial.SpecialRender.GLINT && fixedFunction, group.material == EntityMaterials.ITEM_GLINT && fixedFunction); } - if (!cubeAvailable && group.cubeColumns.size > 0) { - drawTemplateRuns(group.cubeColumns); + if (!cubeAvailable && group.cubeColumns.hasInstances()) { + drawTemplateRuns(group.cubeColumns, group.material == EntityMaterials.GLINT && deferred == null, false); } } restoreTexMatrix(); @@ -468,7 +689,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { private void drawCubeVariant(ObjectArrayList list) { for (int i = 0, n = list.size(); i < n; i++) { final Group group = list.get(i); - if (!live(group) || !instanceable(group) || group.cubeColumns.size == 0) continue; + if (!live(group) || !instanceable(group) || !group.cubeColumns.hasInstances()) continue; drawCubeInstances(group); } restoreTexMatrix(); @@ -497,12 +718,34 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { } } - private void drawTemplateRuns(InstanceColumns cols) { + private void drawTemplateRuns(InstanceColumns cols, boolean combineGlint, boolean itemStencil) { for (int r = 0, n = cols.runs.size(); r < n; r++) { final TexRun run = cols.runs.get(r); applyTexMatrix(run); - instancedDraws += instanced.drawTemplates(cols, run, nowMs); - instancedInstances += run.parts; + final boolean combined = combineGlint && r + 1 < n && combineRuns(cols, run, cols.runs.get(r + 1)); + if (itemStencil) { + GLStateManager.glPushAttrib(GL11.GL_STENCIL_BUFFER_BIT); + GLStateManager.glEnable(GL11.GL_STENCIL_TEST); + GLStateManager.glStencilMask(1); + GLStateManager.glClearStencil(0); + GLStateManager.glClear(GL11.GL_STENCIL_BUFFER_BIT); + GLStateManager.glStencilFunc(GL11.GL_EQUAL, 0, 1); + GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_INCR); + } + try { + if (canRedraw(run)) { + instancedDraws += instanced.redrawTemplates(run.sourceRun, run.color); + redrawnInstances += run.parts; + } else { + instancedDraws += instanced.drawTemplates(cols, run, nowMs, run.glintBase); + if (run.glintBase) recordUpload(run); + } + instancedInstances += run.parts; + } finally { + if (itemStencil) GLStateManager.glPopAttrib(); + if (combined) CombinedGlint.end(); + } + if (combined) r++; } } @@ -511,12 +754,43 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { for (int r = 0, n = cols.runs.size(); r < n; r++) { final TexRun run = cols.runs.get(r); applyTexMatrix(run); - instanced.drawCubes(cols, run); - instancedDraws++; - cubeInstances += run.instances; + final boolean combined = group.material == EntityMaterials.GLINT && deferred == null && r + 1 < n && combineRuns(cols, run, cols.runs.get(r + 1)); + try { + if (canRedraw(run)) { + instanced.redrawCubes(run.sourceRun.uploadOffset, run.instances, run.color); + redrawnInstances += run.instances; + } else { + run.uploadOffset = instanced.drawCubes(cols, run); + if (run.glintBase) recordUpload(run); + } + instancedDraws++; + cubeInstances += run.instances; + } finally { + if (combined) CombinedGlint.end(); + } + if (combined) r++; } } + private boolean canRedraw(TexRun run) { + final TexRun base = run.sourceRun; + return base != null && base.uploadPass == frameMark && base.parts == run.parts && base.instances == run.instances && instanced.canRedraw(base.uploadRingEpoch, base.uploadBuffer); + } + + private void recordUpload(TexRun run) { + run.uploadPass = frameMark; + run.uploadRingEpoch = instanced.ringEpoch(); + run.uploadBuffer = instanced.ringBuffer(); + } + + private static boolean combineRuns(InstanceColumns cols, TexRun first, TexRun second) { + return matchingGlintGeometry(cols, first, second) && CombinedGlint.begin(second.identity ? IDENTITY : second.matrix); + } + + static boolean matchingGlintGeometry(InstanceColumns cols, TexRun first, TexRun second) { + return second.reusedFrom == first && first.parts == second.parts && first.instances == second.instances; + } + private void applyTexMatrix(TexRun run) { if (run.identity && !texApplied) return; if (!texApplied) { @@ -551,18 +825,19 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { saveMV(); GLStateManager.setModelViewMatrix(IDENTITY); final VertexFormat format = group.layer.getVertexFormat(); - final ObjectArrayList templates = cols.templates; for (int r = 0, rn = cols.runs.size(); r < rn; r++) { final TexRun head = cols.runs.get(r); final Matrix4f texMatrix = head.identity ? null : head.matrix; + final InstanceColumns data = head.source != null ? head.source : cols; + final ObjectArrayList templates = data.templates; for (TexRun seg = head; seg != null; seg = seg.next) { final int runEnd = seg.end; int sub = seg.start; while (sub < runEnd) { - final long info = cols.infos.getLong(sub); - final int overlay = cols.overlays.getInt(sub); + final long info = head.source != null ? 0L : data.infos.getLong(sub); + final int overlay = data.overlays.getInt(sub); int subEnd = sub; - while (subEnd < runEnd && cols.infos.getLong(subEnd) == info && cols.overlays.getInt(subEnd) == overlay) { + while (subEnd < runEnd && (head.source != null || data.infos.getLong(subEnd) == info) && data.overlays.getInt(subEnd) == overlay) { subEnd++; } AngelicaBufferSource.setEntityColor(overlay); @@ -581,9 +856,10 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { final long base = memAddress0(fallbackScratch); long ptr = base; for (int i = start; i < end; i++) { - System.arraycopy(cols.matrices, i * 16, matScratch, 0, 16); + System.arraycopy(data.matrices, i * 16, matScratch, 0, 16); scratchMat.set(matScratch); - ptr = VertexTransform.writeInstance(ptr, format, templates.get(i), scratchMat, scratchVec, cols.colors.getInt(i), cols.lights.getInt(i), texMatrix); + final int color = head.source != null ? head.color : data.colors.getInt(i); + ptr = VertexTransform.writeInstance(ptr, format, templates.get(i), scratchMat, scratchVec, color, data.lights.getInt(i), texMatrix); } fallbackScratch.position((int) (ptr - base)); fallbackScratch.flip(); @@ -612,7 +888,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { } void sweep(long now) { - lastGroup = null; + Arrays.fill(recentGroups, null); for (final ObjectArrayList list : byLayer.values()) { for (int i = list.size() - 1; i >= 0; i--) { final Group group = list.get(i); @@ -638,7 +914,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook { } byLayer.clear(); groups.clear(); - lastGroup = null; + Arrays.fill(recentGroups, null); fallbackMesh.delete(); fallbackScratch = null; texApplied = false; diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/bugfixes/MixinItemRenderer_EdgeDepth.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/bugfixes/MixinItemRenderer_EdgeDepth.java index 1d1ad958a..394c5823b 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/bugfixes/MixinItemRenderer_EdgeDepth.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/bugfixes/MixinItemRenderer_EdgeDepth.java @@ -1,59 +1,88 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.bugfixes; +import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager; import com.gtnewhorizons.angelica.glsm.GLStateManager; +import com.gtnewhorizons.angelica.glsm.ffp.CombinedGlint; +import com.gtnewhorizons.angelica.glsm.profiling.Tracy; +import com.gtnewhorizons.angelica.rendering.items.HeldItemGlint; import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.sugar.Local; import net.coderbot.iris.pipeline.ShadowRenderer; import net.minecraft.client.renderer.ItemRenderer; import net.minecraft.client.renderer.Tessellator; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.ItemStack; +import net.minecraft.util.IIcon; +import net.minecraftforge.client.IItemRenderer.ItemRenderType; import org.lwjgl.opengl.GL11; 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.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -/** - * Fix enchantment glint z-fighting on held item edges using stencil masking. - * - * Vanilla uses GL_EQUAL to restrict the glint to item pixels, but GPU depth - * non-determinism between draw calls causes GL_EQUAL to fail on edge quads. - * - * This mixin uses a stencil-based check instead of a GL_EQUAL check. - * 1. During item render: write stencil value 1 wherever the item draws. - * 2. During each glint renderItemIn2D call: - * a. Pre-pass: no color/depth writes, GL_LEQUAL depth test, increment the stencil 1→2 - * where depth passes. This determines pixel ownership without corrupting the depth buffer (damn potions...) - * b. Color pass: draw only where stencil == 2, GL_LEQUAL depth test, decrement the stencil - * 2→1 to reset for the next glint pass. - * - * For the shadow pass, all stencil operations are skipped. Shadow FBOs have no stencil attachment. - * On AMD Windows, glClear(GL_STENCIL_BUFFER_BIT) on a depth-only FBO causes the driver to - * promote it to GL_DEPTH24_STENCIL8 internally, corrupting depth values and breaking shadows. - * Someone needs to yell at AMD to fix this decade old bug. - */ +/** Match item depth and prevent coplanar extrusion faces from blending the same glint layer twice. */ @Mixin(value = ItemRenderer.class, priority = 1100) public class MixinItemRenderer_EdgeDepth { - @Unique private static boolean angelica$glintMode = false; - @Unique private static boolean angelica$inPrepass = false; + @Unique private static final Tracy.ZoneId angelica$Z_HELD_ITEM = Tracy.zoneId("heldItem", Tracy.COLOR_CLIENT); + @Unique private static IIcon angelica$glintIcon; + @Unique private static boolean angelica$combinedGlint; + @Unique private static boolean angelica$stencilPrepared; + @Unique private static boolean angelica$preparingStencil; + @Unique private static boolean angelica$firstLayerMarked; + @Unique private static int angelica$nesting; + @Unique private static final IIcon[] angelica$savedIcons = new IIcon[8]; + @Unique private static final boolean[] angelica$savedCombined = new boolean[8]; + @Unique private static final boolean[] angelica$savedPrepared = new boolean[8]; - // Item render: write stencil value 1 at all visible item pixels + @Inject(method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V", + at = @At("HEAD"), remap = false) + private void angelica$enterRenderItem(CallbackInfo ci) { + final int depth = angelica$nesting++; + if (depth < angelica$savedIcons.length) { + angelica$savedIcons[depth] = angelica$glintIcon; + angelica$savedCombined[depth] = angelica$combinedGlint; + angelica$savedPrepared[depth] = angelica$stencilPrepared; + } + angelica$glintIcon = null; + angelica$combinedGlint = false; + angelica$stencilPrepared = false; + angelica$firstLayerMarked = false; + if (Tracy.FINE_ZONES) Tracy.beginZone(angelica$Z_HELD_ITEM); + } + + @Inject(method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V", + at = @At("RETURN"), remap = false) + private void angelica$exitRenderItem(CallbackInfo ci) { + if (Tracy.FINE_ZONES) Tracy.endZone(); + final int depth = --angelica$nesting; + final boolean saved = depth >= 0 && depth < angelica$savedIcons.length; + angelica$glintIcon = saved ? angelica$savedIcons[depth] : null; + angelica$combinedGlint = saved && angelica$savedCombined[depth]; + angelica$stencilPrepared = saved && angelica$savedPrepared[depth]; + angelica$firstLayerMarked = false; + if (saved) angelica$savedIcons[depth] = null; + if (depth < 0) angelica$nesting = 0; + } @Inject( method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V", ordinal = 0, remap = true), - remap = false - ) - private void angelica$stencilWriteStart(CallbackInfo ci) { - if (ShadowRenderer.ACTIVE) return; - GLStateManager.glEnable(GL11.GL_STENCIL_TEST); - GLStateManager.glStencilMask(0x03); - GLStateManager.glClearStencil(0); - GLStateManager.glClear(GL11.GL_STENCIL_BUFFER_BIT); - GLStateManager.glStencilFunc(GL11.GL_ALWAYS, 1, 0x03); - GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V", ordinal = 0, remap = true), remap = false) + private void angelica$prepareGlintStencil(EntityLivingBase entity, ItemStack stack, int pass, ItemRenderType type, CallbackInfo ci, + @Local Tessellator tess) { + angelica$preparingStencil = !ShadowRenderer.ACTIVE && !GLStateManager.isRecordingDisplayList() + && !TessellatorManager.isCurrentlyCapturing() && !TessellatorManager.shouldInterceptDraw(tess) + && GLStateManager.getDepthTest().isEnabled() && GLStateManager.getDepthState().isEnabled() + && HeldItemGlint.canResetStencil() && HeldItemGlint.eligible() && HeldItemGlint.needsImmediateBase(stack, pass); + if (angelica$preparingStencil) { + GLStateManager.glPushAttrib(GL11.GL_STENCIL_BUFFER_BIT); + GLStateManager.glEnable(GL11.GL_STENCIL_TEST); + GLStateManager.glStencilMask(1); + GLStateManager.glStencilFunc(GL11.GL_ALWAYS, 0, 1); + GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); + } } @Inject( @@ -61,81 +90,65 @@ public class MixinItemRenderer_EdgeDepth { at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V", ordinal = 0, remap = true, shift = At.Shift.AFTER), remap = false ) - private void angelica$stencilWriteEnd(CallbackInfo ci) { - if (ShadowRenderer.ACTIVE) return; - GLStateManager.glStencilMask(0x00); - GLStateManager.glDisable(GL11.GL_STENCIL_TEST); + private void angelica$finishGlintStencil(CallbackInfo ci) { + if (angelica$preparingStencil) GLStateManager.glPopAttrib(); + angelica$stencilPrepared = angelica$preparingStencil; + angelica$preparingStencil = false; } - // Glint section: replace GL_EQUAL with stencil-based masking - - @Redirect( + @Inject( method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V", - at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDepthFunc(I)V", ordinal = 0, remap = false), + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/ItemRenderer;renderItemIn2D(Lnet/minecraft/client/renderer/Tessellator;FFFFIIF)V", ordinal = 0, remap = true, shift = At.Shift.AFTER), remap = false ) - private void angelica$glintStart(int func) { - if (ShadowRenderer.ACTIVE) { - GLStateManager.glDepthFunc(func); - return; - } - angelica$glintMode = true; - GLStateManager.glEnable(GL11.GL_STENCIL_TEST); - GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 0x03); - GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); + private void angelica$rememberGlintIcon(CallbackInfo ci, @Local IIcon icon) { + angelica$glintIcon = icon; } - @Redirect( - method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V", - at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glDepthFunc(I)V", ordinal = 1, remap = false), - remap = false - ) - private void angelica$glintEnd(int func) { - if (ShadowRenderer.ACTIVE) { - GLStateManager.glDepthFunc(func); - return; - } - angelica$glintMode = false; - GLStateManager.glDepthFunc(GL11.GL_LEQUAL); - GLStateManager.glDepthMask(true); - GLStateManager.glDisable(GL11.GL_STENCIL_TEST); - GLStateManager.glStencilMask(0x03); - } - - // Stencil pre-pass inside renderItemIn2D when in glint mode - + // Wrap the geometry method so mods redirecting the caller retain their glint color and blending. @WrapMethod(method = "renderItemIn2D") - private static void angelica$glintPrepass(Tessellator tess, float minU, float minV, float maxU, float maxV, - int w, int h, float thickness, Operation original) { - if (ShadowRenderer.ACTIVE || !angelica$glintMode || angelica$inPrepass) { - original.call(tess, minU, minV, maxU, maxV, w, h, thickness); - return; + private static void angelica$matchGlintGeometry(Tessellator tess, float minU, float minV, float maxU, float maxV, + int width, int height, float thickness, Operation original) { + if (angelica$glintIcon != null && thickness == 0.0625F + && ((width == 256 && height == 256) + || (width == angelica$glintIcon.getIconWidth() && height == angelica$glintIcon.getIconHeight())) + && minU == 0 && minV == 0 && maxU == 1 && maxV == 1) { + width = angelica$glintIcon.getIconWidth(); + height = angelica$glintIcon.getIconHeight(); + if (!ShadowRenderer.ACTIVE && !GLStateManager.isRecordingDisplayList() + && !TessellatorManager.isCurrentlyCapturing() && !TessellatorManager.shouldInterceptDraw(tess)) { + if (angelica$combinedGlint) return; + final boolean secondLayer = angelica$firstLayerMarked; + final boolean combined = !secondLayer && HeldItemGlint.begin(); + // Depth selects the surface, stencil limits it to one blend. + // The base draw's stencil reset or the first layer's marks avoid a masked full-screen clear. + GLStateManager.glPushAttrib(GL11.GL_STENCIL_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); + try { + GLStateManager.glDepthMask(false); + GLStateManager.glEnable(GL11.GL_STENCIL_TEST); + GLStateManager.glStencilMask(1); + if (secondLayer) { + // Same pixels as the first layer, so removing its marks leaves the stencil reset. + GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 1); + GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_DECR); + } else { + if (!angelica$stencilPrepared) { + GLStateManager.glClearStencil(0); + GLStateManager.glClear(GL11.GL_STENCIL_BUFFER_BIT); + } + GLStateManager.glStencilFunc(GL11.GL_EQUAL, 0, 1); + GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_INCR); + } + original.call(tess, minU, minV, maxU, maxV, width, height, thickness); + } finally { + GLStateManager.glPopAttrib(); + if (combined) CombinedGlint.end(); + } + angelica$combinedGlint = combined; + angelica$firstLayerMarked = !combined && !secondLayer && HeldItemGlint.layersCoverSamePixels(); + return; + } } - - angelica$inPrepass = true; - - // Pre-pass: determine pixel ownership via GL_LEQUAL depth test. - GLStateManager.glColorMask(false, false, false, false); - GLStateManager.glDepthMask(false); - GLStateManager.glDepthFunc(GL11.GL_LEQUAL); - 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, 0x03); - GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_DECR); - GLStateManager.glDepthFunc(GL11.GL_LEQUAL); - 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, 0x03); - GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); - GLStateManager.glDepthFunc(GL11.GL_LEQUAL); - - angelica$inPrepass = false; + original.call(tess, minU, minV, maxU, maxV, width, height, thickness); } } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinItemRenderer_Instanced.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinItemRenderer_Instanced.java index 718341c74..c613796a5 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinItemRenderer_Instanced.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinItemRenderer_Instanced.java @@ -1,22 +1,47 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.entity; +import com.gtnewhorizons.angelica.rendering.SkippedGlintBlock; +import com.gtnewhorizons.angelica.glsm.profiling.Tracy; import com.gtnewhorizons.angelica.rendering.GlintClock; import com.gtnewhorizons.angelica.rendering.items.DroppedItemInstancer; +import com.gtnewhorizons.angelica.rendering.items.HeldItemGlint; +import com.gtnewhorizons.angelica.rendering.tesr.EntityMaterials; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import com.llamalad7.mixinextras.sugar.Local; import net.minecraft.block.Block; import net.minecraft.client.renderer.ItemRenderer; import net.minecraft.client.renderer.RenderBlocks; import net.minecraft.client.renderer.Tessellator; +import net.minecraft.entity.EntityLivingBase; import net.minecraft.item.ItemStack; import net.minecraftforge.client.IItemRenderer.ItemRenderType; 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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(ItemRenderer.class) public abstract class MixinItemRenderer_Instanced { + @Unique private static final Tracy.ZoneId angelica$Z_HELD_ICON = Tracy.zoneId("heldIcon", Tracy.COLOR_CLIENT); + @Unique private static final Tracy.ZoneId angelica$Z_HELD_GLINT = Tracy.zoneId("heldGlint", Tracy.COLOR_CLIENT); + @Unique private static ItemStack angelica$stack; + @Unique private static int angelica$pass; + @Unique private static ItemRenderType angelica$type; + @Unique private static int angelica$iconWidth, angelica$iconHeight; + + @Inject( + method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V", + at = @At("HEAD"), + remap = false + ) + private void angelica$rememberItem(EntityLivingBase entity, ItemStack stack, int pass, ItemRenderType type, CallbackInfo ci) { + angelica$stack = stack; + angelica$pass = pass; + angelica$type = type; + } @WrapOperation( method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V", @@ -26,8 +51,27 @@ public abstract class MixinItemRenderer_Instanced { ordinal = 0 ) ) - private void angelica$instanceHeldIcon(Tessellator tessellator, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation original, @Local(argsOnly = true) ItemStack stack, @Local(argsOnly = true) ItemRenderType type) { - DroppedItemInstancer.icon(angelica$batchable(type) ? stack : null, tessellator, maxU, minV, minU, maxV, width, height, thickness, original); + private void angelica$instanceHeldIcon(Tessellator tessellator, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation original) { + angelica$iconWidth = width; + angelica$iconHeight = height; + final ItemStack stack = angelica$stack; + if (Tracy.FINE_ZONES) Tracy.beginZone(angelica$Z_HELD_ICON); + try { + DroppedItemInstancer.icon(angelica$batchable(angelica$type) && !HeldItemGlint.needsImmediateBase(stack, angelica$pass) ? stack : null, tessellator, maxU, minV, minU, maxV, width, height, thickness, original); + } finally { + if (Tracy.FINE_ZONES) Tracy.endZone(); + } + } + + @ModifyExpressionValue( + method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;hasEffect(I)Z"), + remap = false + ) + private boolean angelica$queueHeldGlintBlock(boolean hasEffect) { + if (!hasEffect || !HeldItemGlint.eligible() || !DroppedItemInstancer.queueSkippedGlint(angelica$iconWidth, angelica$iconHeight)) return hasEffect; + SkippedGlintBlock.applyHeldItemExitState(); + return false; } @WrapOperation( @@ -46,7 +90,12 @@ public abstract class MixinItemRenderer_Instanced { } ) private void angelica$instanceHeldGlint(Tessellator tessellator, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation original) { - DroppedItemInstancer.glint(tessellator, maxU, minV, minU, maxV, width, height, thickness, original); + if (Tracy.FINE_ZONES) Tracy.beginZone(angelica$Z_HELD_GLINT); + try { + DroppedItemInstancer.glint(tessellator, maxU, minV, minU, maxV, angelica$iconWidth, angelica$iconHeight, thickness, EntityMaterials.ITEM_GLINT, original); + } finally { + if (Tracy.FINE_ZONES) Tracy.endZone(); + } } @WrapOperation( @@ -56,8 +105,8 @@ public abstract class MixinItemRenderer_Instanced { target = "Lnet/minecraft/client/renderer/RenderBlocks;renderBlockAsItem(Lnet/minecraft/block/Block;IF)V" ) ) - private void angelica$instanceHeldBlock(RenderBlocks renderBlocks, Block block, int meta, float brightness, Operation original, @Local(argsOnly = true) ItemStack stack, @Local(argsOnly = true) ItemRenderType type) { - DroppedItemInstancer.block(angelica$batchable(type) ? stack : null, renderBlocks, block, meta, brightness, false, original); + private void angelica$instanceHeldBlock(RenderBlocks renderBlocks, Block block, int meta, float brightness, Operation original) { + DroppedItemInstancer.block(angelica$batchable(angelica$type) ? angelica$stack : null, renderBlocks, block, meta, brightness, false, original); } @Redirect( diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRenderItem_Instanced.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRenderItem_Instanced.java index 77799df29..bf13d37a6 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRenderItem_Instanced.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRenderItem_Instanced.java @@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.rendering.GlintClock; import com.gtnewhorizons.angelica.rendering.items.DroppedItemInstancer; import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility; +import com.gtnewhorizons.angelica.rendering.tesr.EntityMaterials; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Local; @@ -49,7 +50,7 @@ public abstract class MixinRenderItem_Instanced { } ) private void angelica$instanceDroppedGlint(Tessellator tessellator, float maxU, float minV, float minU, float maxV, int width, int height, float thickness, Operation original) { - DroppedItemInstancer.glint(tessellator, maxU, minV, minU, maxV, width, height, thickness, original); + DroppedItemInstancer.glint(tessellator, maxU, minV, minU, maxV, width, height, thickness, EntityMaterials.GLINT, original); } @Redirect( @@ -64,7 +65,8 @@ public abstract class MixinRenderItem_Instanced { method = "doRender(Lnet/minecraft/entity/item/EntityItem;DDDFF)V", at = @At( value = "INVOKE", - target = "Lnet/minecraftforge/client/ForgeHooksClient;renderEntityItem(Lnet/minecraft/entity/item/EntityItem;Lnet/minecraft/item/ItemStack;FFLjava/util/Random;Lnet/minecraft/client/renderer/texture/TextureManager;Lnet/minecraft/client/renderer/RenderBlocks;I)Z", + target = "Lnet/minecraftforge/client/ForgeHooksClient;renderEntityItem(Lnet/minecraft/entity/item/EntityItem;Lnet/minecraft/item/ItemStack;" + + "FFLjava/util/Random;Lnet/minecraft/client/renderer/texture/TextureManager;Lnet/minecraft/client/renderer/RenderBlocks;I)Z", remap = false ) ) @@ -76,7 +78,8 @@ public abstract class MixinRenderItem_Instanced { method = "doRender(Lnet/minecraft/entity/item/EntityItem;DDDFF)V", at = @At( value = "INVOKE", - target = "Lnet/minecraftforge/client/ForgeHooksClient;renderEntityItem(Lnet/minecraft/entity/item/EntityItem;Lnet/minecraft/item/ItemStack;FFLjava/util/Random;Lnet/minecraft/client/renderer/texture/TextureManager;Lnet/minecraft/client/renderer/RenderBlocks;I)Z", + target = "Lnet/minecraftforge/client/ForgeHooksClient;renderEntityItem(Lnet/minecraft/entity/item/EntityItem;Lnet/minecraft/item/ItemStack;" + + "FFLjava/util/Random;Lnet/minecraft/client/renderer/texture/TextureManager;Lnet/minecraft/client/renderer/RenderBlocks;I)Z", shift = At.Shift.AFTER, remap = false ) diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRendererLivingEntity_EquippedDraws.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRendererLivingEntity_EquippedDraws.java index ec99e04d6..3be53aafe 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRendererLivingEntity_EquippedDraws.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRendererLivingEntity_EquippedDraws.java @@ -1,40 +1,29 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.entity; import com.gtnewhorizons.angelica.glsm.GLStateManager; -import com.gtnewhorizons.angelica.rendering.OperationArgs; import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.client.renderer.entity.RendererLivingEntity; -import net.minecraft.entity.EntityLivingBase; 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; @Mixin(RendererLivingEntity.class) public abstract class MixinRendererLivingEntity_EquippedDraws { - @Unique private static final Object[] angelica$equippedArgs = new Object[3]; - - @WrapOperation( + @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" - ) + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderEquippedItems(Lnet/minecraft/entity/EntityLivingBase;F)V") ) - private void angelica$countEquippedDraws(RendererLivingEntity self, EntityLivingBase entity, float partialTicks, Operation original) { - final Object[] args = angelica$equippedArgs; - args[0] = self; - args[1] = entity; - args[2] = OperationArgs.boxed(args[2], partialTicks); + private void angelica$beginEquippedDraws(CallbackInfo ci) { BatchEligibility.beginExpectedDraws(GLStateManager.drawCalls); - try { - original.call(args); - } finally { - args[0] = null; - args[1] = null; - BatchEligibility.endExpectedDraws(GLStateManager.drawCalls); - } + } + + @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", shift = At.Shift.AFTER) + ) + private void angelica$endEquippedDraws(CallbackInfo ci) { + BatchEligibility.endExpectedDraws(GLStateManager.drawCalls); } } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRendererLivingEntity_GlintClock.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRendererLivingEntity_GlintClock.java index 065fd9828..e16688c37 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRendererLivingEntity_GlintClock.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRendererLivingEntity_GlintClock.java @@ -1,14 +1,156 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.entity; +import com.gtnewhorizons.angelica.rendering.SkippedGlintBlock; +import com.gtnewhorizons.angelica.helpers.RendererLivingEntityHelper; import com.gtnewhorizons.angelica.rendering.GlintClock; +import com.gtnewhorizons.angelica.glsm.GLStateManager; +import com.gtnewhorizons.angelica.glsm.profiling.Tracy; +import com.gtnewhorizons.angelica.rendering.tesr.GlintCapture; +import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher; +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.llamalad7.mixinextras.sugar.Local; +import net.minecraft.client.model.ModelBase; +import net.minecraft.entity.Entity; import net.minecraft.client.renderer.entity.RendererLivingEntity; +import net.minecraft.util.ResourceLocation; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Constant; +import org.spongepowered.asm.mixin.injection.ModifyConstant; import org.spongepowered.asm.mixin.injection.Group; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(RendererLivingEntity.class) public abstract class MixinRendererLivingEntity_GlintClock { + @Shadow @Final private static ResourceLocation RES_ITEM_GLINT; + @Unique private static final Tracy.ZoneId angelica$Z_ARMOR_BASE = Tracy.zoneId("armorBase", Tracy.COLOR_CLIENT); + @Unique private static final Tracy.ZoneId angelica$Z_ARMOR_GLINT = Tracy.zoneId("armorGlint", Tracy.COLOR_CLIENT); + @Unique private final GlintCapture angelica$armorGlint = new GlintCapture(); + @Unique private final GlintCapture angelica$armorBase = new GlintCapture(); + @Unique private static final int GLINT_LAYER_QUEUED = 0, GLINT_LAYER_DRAWN = 1, GLINT_LAYER_CAPTURED = 2; + @Unique private ModelBase angelica$baseModel; + @Unique private boolean angelica$capturingBase; + @Unique private int angelica$armorPassFlags; + @Unique private int angelica$glintLayer; + @Unique private boolean angelica$secondArmorQueued; + @Unique private boolean angelica$zoneOpen; + + @ModifyConstant(method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V", + slice = @Slice(from = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;RES_ITEM_GLINT:Lnet/minecraft/util/ResourceLocation;")), + constant = @Constant(intValue = 2, ordinal = 0)) + private int angelica$armorGlintLayers(int vanilla) { + return angelica$secondArmorQueued ? 1 : vanilla; + } + + @ModifyConstant(method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V", + slice = @Slice( + from = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V", ordinal = 0), + to = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;RES_ITEM_GLINT:Lnet/minecraft/util/ResourceLocation;")), + constant = @Constant(intValue = 15, ordinal = 0)) + private int angelica$queueArmorGlintBlock(int mask) { + final ModelBase model = ((RendererLivingEntity) (Object) this).renderPassModel; + if ((angelica$armorPassFlags & 15) != 15 || model != angelica$baseModel || GLStateManager.getOverlayA() != 0.0F || RendererLivingEntityHelper.hasEyePass(this) + || !ModelPartBatcher.INSTANCE.queueSkippedArmorGlint(angelica$armorBase, RES_ITEM_GLINT)) return mask; + SkippedGlintBlock.applyArmorExitState(); + return 0; + } + + @Inject(method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V", + at = { + @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V", ordinal = 0), + @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V", ordinal = 1) + }) + private void angelica$beginArmorBase(CallbackInfo ci, @Local(ordinal = 0) int passFlags) { + if (Tracy.FINE_ZONES) { + Tracy.beginZone(angelica$Z_ARMOR_BASE); + angelica$zoneOpen = true; + } + angelica$armorPassFlags = passFlags; + angelica$baseModel = null; + angelica$capturingBase = (passFlags & 15) == 15 && ModelPartBatcher.INSTANCE.beginArmorCapture(angelica$armorBase); + if (angelica$capturingBase) GLStateManager.glPushMatrix(); + } + + @Inject(method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V", + at = { + @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V", ordinal = 0, shift = At.Shift.AFTER), + @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V", ordinal = 1, shift = At.Shift.AFTER) + }) + private void angelica$endArmorBase(CallbackInfo ci) { + if (angelica$capturingBase) { + angelica$capturingBase = false; + GLStateManager.glPopMatrix(); + angelica$armorBase.endWithRestoredPose(); + angelica$baseModel = ((RendererLivingEntity) (Object) this).renderPassModel; + } + angelica$endZone(); + } + + @WrapWithCondition(method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V", + slice = @Slice(from = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;RES_ITEM_GLINT:Lnet/minecraft/util/ResourceLocation;")), + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V", ordinal = 0)) + private boolean angelica$renderArmorGlintLayer(ModelBase model, Entity entity, float swing, float amount, float age, float yaw, float pitch, float scale, @Local(ordinal = 2) int layer) { + if (Tracy.FINE_ZONES) { + Tracy.beginZone(angelica$Z_ARMOR_GLINT); + angelica$zoneOpen = true; + } + final ModelPartBatcher batcher = ModelPartBatcher.INSTANCE; + if (layer == 0) angelica$secondArmorQueued = false; + angelica$glintLayer = GLINT_LAYER_QUEUED; + if (layer == 0 && model == angelica$baseModel && batcher.reuseArmorBase(angelica$armorBase, angelica$armorGlint)) { + angelica$secondArmorQueued = batcher.queueSecondArmorGlint(angelica$armorGlint); + if (!angelica$secondArmorQueued) angelica$armorGlint.reset(); + return false; + } + if (layer == 1 && batcher.replayGlint(angelica$armorGlint)) return false; + angelica$glintLayer = layer == 0 && batcher.beginGlintCapture(angelica$armorGlint) ? GLINT_LAYER_CAPTURED : GLINT_LAYER_DRAWN; + GLStateManager.glPushMatrix(); + return true; + } + + @Inject(method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V", + slice = @Slice(from = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;RES_ITEM_GLINT:Lnet/minecraft/util/ResourceLocation;")), + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/model/ModelBase;render(Lnet/minecraft/entity/Entity;FFFFFF)V", ordinal = 0, shift = At.Shift.AFTER)) + private void angelica$endArmorGlintLayer(CallbackInfo ci) { + if (angelica$glintLayer != GLINT_LAYER_QUEUED) { + GLStateManager.glPopMatrix(); + if (angelica$glintLayer == GLINT_LAYER_CAPTURED) { + angelica$armorGlint.endWithRestoredPose(); + angelica$secondArmorQueued = ModelPartBatcher.INSTANCE.queueSecondArmorGlint(angelica$armorGlint); + } + angelica$glintLayer = GLINT_LAYER_QUEUED; + } + angelica$endZone(); + } + + @Inject(method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V", + at = @At(value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;error(Ljava/lang/String;Ljava/lang/Throwable;)V", remap = false)) + private void angelica$closeAfterFailedRender(CallbackInfo ci) { + if (angelica$capturingBase) { + angelica$capturingBase = false; + GLStateManager.glPopMatrix(); + angelica$armorBase.reset(); + } + if (angelica$glintLayer != GLINT_LAYER_QUEUED) { + angelica$glintLayer = GLINT_LAYER_QUEUED; + GLStateManager.glPopMatrix(); + angelica$armorGlint.reset(); + } + angelica$endZone(); + } + + @Unique + private void angelica$endZone() { + if (!angelica$zoneOpen) return; + angelica$zoneOpen = false; + Tracy.endZone(); + } @Group(name = "angelica$globalGlintPhase", min = 1, max = 1) @Redirect( diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRendererLivingEntity_ModelPassDraws.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRendererLivingEntity_ModelPassDraws.java index 5e884f1a6..458041b1d 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRendererLivingEntity_ModelPassDraws.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/entity/MixinRendererLivingEntity_ModelPassDraws.java @@ -1,43 +1,29 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.entity; import com.gtnewhorizons.angelica.glsm.GLStateManager; -import com.gtnewhorizons.angelica.rendering.OperationArgs; import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import net.minecraft.client.model.ModelBase; import net.minecraft.client.renderer.entity.RendererLivingEntity; -import net.minecraft.entity.Entity; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(RendererLivingEntity.class) public abstract class MixinRendererLivingEntity_ModelPassDraws { - @Unique private static final Object[] angelica$modelPassArgs = new Object[8]; - - @WrapOperation( + @Inject( 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") ) - private void angelica$countModelPassDraws(ModelBase model, Entity entity, float limbSwing, float limbSwingAmount, float ageInTicks, float headYaw, float headPitch, float scale, Operation original) { - final Object[] args = angelica$modelPassArgs; - args[0] = model; - args[1] = entity; - args[2] = OperationArgs.boxed(args[2], limbSwing); - args[3] = OperationArgs.boxed(args[3], limbSwingAmount); - args[4] = OperationArgs.boxed(args[4], ageInTicks); - args[5] = OperationArgs.boxed(args[5], headYaw); - args[6] = OperationArgs.boxed(args[6], headPitch); - args[7] = OperationArgs.boxed(args[7], scale); + private void angelica$beginModelPassDraws(CallbackInfo ci) { BatchEligibility.beginExpectedDraws(GLStateManager.drawCalls); - try { - original.call(args); - } finally { - args[0] = null; - args[1] = null; - BatchEligibility.endExpectedDraws(GLStateManager.drawCalls); - } + } + + @Inject( + 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", shift = At.Shift.AFTER) + ) + private void angelica$endModelPassDraws(CallbackInfo ci) { + BatchEligibility.endExpectedDraws(GLStateManager.drawCalls); } } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/tracy/MixinRenderGlobal_Tracy.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/tracy/MixinRenderGlobal_Tracy.java index 62fc98fc7..66fdedea4 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/tracy/MixinRenderGlobal_Tracy.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/angelica/tracy/MixinRenderGlobal_Tracy.java @@ -16,6 +16,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(RenderGlobal.class) public class MixinRenderGlobal_Tracy { private static final Tracy.ZoneId Z_ENTITY_DISPATCH = Tracy.zoneId("entityDispatch", Tracy.COLOR_CLIENT); + private static final Tracy.ZoneId Z_ENTITY_RENDER = Tracy.zoneId("entityRender", Tracy.COLOR_CLIENT); @Inject(method = "renderEntities", at = @At(value = "INVOKE_STRING", target = "Lnet/minecraft/profiler/Profiler;endStartSection(Ljava/lang/String;)V", args = "ldc=entities", shift = At.Shift.AFTER)) private void angelica$beginEntityDispatchZone(CallbackInfo ci) { @@ -33,9 +34,17 @@ public class MixinRenderGlobal_Tracy { if (TesrAttribution.currentRenderable == null && entity != null) { TesrAttribution.currentRenderable = entity.getClass(); } + if (Tracy.FINE_ZONES) { + Tracy.setGpuZonesEnabled(false); + Tracy.beginZone(Z_ENTITY_RENDER); + } try { return original.call(instance, entity, partialTicks); } finally { + if (Tracy.FINE_ZONES) { + Tracy.endZone(); + Tracy.setGpuZonesEnabled(true); + } TesrAttribution.currentRenderable = null; RenderClassTimings.ENTITY.add(entity.getClass(), System.nanoTime() - start); } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/notfine/glint/MixinEntityLiving_ArmorEnchantCache.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/notfine/glint/MixinEntityLiving_ArmorEnchantCache.java new file mode 100644 index 000000000..484e7958e --- /dev/null +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/notfine/glint/MixinEntityLiving_ArmorEnchantCache.java @@ -0,0 +1,26 @@ +package com.gtnewhorizons.angelica.mixins.early.notfine.glint; + +import com.gtnewhorizons.angelica.mixins.interfaces.ArmorEnchantCache; +import net.minecraft.entity.EntityLiving; +import net.minecraft.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; + +@Mixin(EntityLiving.class) +public abstract class MixinEntityLiving_ArmorEnchantCache implements ArmorEnchantCache { + @Unique private ItemStack[] angelica$checkedArmor; + @Unique private int angelica$enchantedSlots; + + @Override + public boolean angelica$isArmorEnchanted(int slot, ItemStack stack) { + if (slot < 0 || slot >= 4) return stack.isItemEnchanted(); + ItemStack[] checked = angelica$checkedArmor; + if (checked == null) checked = angelica$checkedArmor = new ItemStack[4]; + final int bit = 1 << slot; + if (checked[slot] != stack) { + checked[slot] = stack; + angelica$enchantedSlots = stack.isItemEnchanted() ? angelica$enchantedSlots | bit : angelica$enchantedSlots & ~bit; + } + return (angelica$enchantedSlots & bit) != 0; + } +} diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/notfine/glint/MixinItemRenderer.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/notfine/glint/MixinItemRenderer.java index c5ee0efbf..092767c84 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/notfine/glint/MixinItemRenderer.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/notfine/glint/MixinItemRenderer.java @@ -1,16 +1,15 @@ package com.gtnewhorizons.angelica.mixins.early.notfine.glint; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import jss.notfine.core.Settings; import net.minecraft.client.renderer.ItemRenderer; -import net.minecraft.item.ItemStack; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; @Mixin(value = ItemRenderer.class) public abstract class MixinItemRenderer { - @Redirect( + @ModifyExpressionValue( method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V", at = @At( value = "INVOKE", @@ -18,8 +17,8 @@ public abstract class MixinItemRenderer { ), remap = false ) - private boolean notFine$toggleGlint(ItemStack stack, int pass) { - return (boolean)Settings.MODE_GLINT_WORLD.option.getStore() && stack.hasEffect(pass); + private boolean notFine$toggleGlint(boolean hasEffect) { + return hasEffect && (boolean) Settings.MODE_GLINT_WORLD.option.getStore(); } } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/notfine/glint/MixinRenderBiped.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/notfine/glint/MixinRenderBiped.java index 62c5d73ed..dfde0a0a7 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/notfine/glint/MixinRenderBiped.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/notfine/glint/MixinRenderBiped.java @@ -1,7 +1,9 @@ package com.gtnewhorizons.angelica.mixins.early.notfine.glint; +import com.gtnewhorizons.angelica.mixins.interfaces.ArmorEnchantCache; import jss.notfine.core.Settings; import net.minecraft.client.renderer.entity.RenderBiped; +import net.minecraft.entity.EntityLiving; import net.minecraft.item.ItemStack; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -17,8 +19,8 @@ public abstract class MixinRenderBiped { target = "Lnet/minecraft/item/ItemStack;isItemEnchanted()Z" ) ) - private boolean notFine$toggleGlint(ItemStack stack) { - return (boolean)Settings.MODE_GLINT_WORLD.option.getStore() && stack.isItemEnchanted(); + private boolean notFine$toggleGlint(ItemStack stack, EntityLiving entity, int slot, float partialTicks) { + return (boolean)Settings.MODE_GLINT_WORLD.option.getStore() && ((ArmorEnchantCache) entity).angelica$isArmorEnchanted(slot, stack); } } diff --git a/src/test/java/com/gtnewhorizons/angelica/rendering/GlintClockTest.java b/src/test/java/com/gtnewhorizons/angelica/rendering/GlintClockTest.java index 2d19077ef..0598aeb6d 100644 --- a/src/test/java/com/gtnewhorizons/angelica/rendering/GlintClockTest.java +++ b/src/test/java/com/gtnewhorizons/angelica/rendering/GlintClockTest.java @@ -11,9 +11,23 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNull; class GlintClockTest { + @Test + void cachedSecondLayerMatchesTheLegacyTransformAndRejectsModifiedFirstLayers() { + for (long time : new long[] {0, 1000003, 864000000123L}) { + GlintClock.beginFrame(time); + final Matrix4f first = new Matrix4f().scaling(0.33333334f).rotateZ((float) Math.toRadians(30)) + .translate(armorU0(), armorV0(), 0); + final Matrix4f second = new Matrix4f().scaling(0.33333334f).rotateZ((float) Math.toRadians(-30)) + .translate(armorU1(), armorV1(), 0); + assertTrue(second.equals(GlintClock.secondArmorMatrix(first), 1e-6f)); + assertNull(GlintClock.secondArmorMatrix(first.translate(0.1f, 0, 0))); + } + } + @BeforeEach void resetArmorPathTable() { Reflect.setStatic(ArmorTexturePaths.class, "table", new String[0]); diff --git a/src/test/java/com/gtnewhorizons/angelica/rendering/tesr/BatchEligibilityTest.java b/src/test/java/com/gtnewhorizons/angelica/rendering/tesr/BatchEligibilityTest.java index a88f03cb7..9e6fb71af 100644 --- a/src/test/java/com/gtnewhorizons/angelica/rendering/tesr/BatchEligibilityTest.java +++ b/src/test/java/com/gtnewhorizons/angelica/rendering/tesr/BatchEligibilityTest.java @@ -172,7 +172,7 @@ class BatchEligibilityTest { }; BatchEligibility.begin(SAFE, 0L); - DroppedItemInstancer.glint(new Tessellator(), 1f, 0f, 0f, 1f, 16, 16, 0.0625f, original); + DroppedItemInstancer.glint(new Tessellator(), 1f, 0f, 0f, 1f, 16, 16, 0.0625f, EntityMaterials.GLINT, original); assertEquals(SAFE, BatchEligibility.end(SAFE, 0L)); assertFalse(originalCalled[0], "the shadow-pass early return must not fall back to the original draw"); diff --git a/src/test/java/com/gtnewhorizons/angelica/rendering/tesr/RetainedTesrGroupsGLTest.java b/src/test/java/com/gtnewhorizons/angelica/rendering/tesr/RetainedTesrGroupsGLTest.java index 72c6d00c3..b098faec5 100644 --- a/src/test/java/com/gtnewhorizons/angelica/rendering/tesr/RetainedTesrGroupsGLTest.java +++ b/src/test/java/com/gtnewhorizons/angelica/rendering/tesr/RetainedTesrGroupsGLTest.java @@ -44,6 +44,7 @@ import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.X_ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; @GLCoreTest class RetainedTesrGroupsGLTest { @@ -518,6 +519,102 @@ class RetainedTesrGroupsGLTest { CubeParityFixture.assertPixelParity(reference, cubeCandidate(), SIZE, BACKGROUND, RetainedTesrGroupsGLTest::describe); } + @Test + void replayedGlintTemplatesMatchIndependentPasses() { + glintState(); + final int[] reference = templateReference(false); + drawBaseTemplates(); + setGlintBlendState(); + groups.beginPass(new Matrix4f(), 0, 0, 0, instanced); + final GlintCapture capture = new GlintCapture(); + for (Matrix4f pose : new Matrix4f[] {templateE1, templateE2}) { + applyGlintTexMatrix(glintMatrixA); + assertTrue(capture.begin(groups)); + groups.queue(glintTemplate, layer, EntityMaterials.GLINT, pose, 0, -1, 0, 0L, 0, glintMatrixA); + capture.end(); + applyGlintTexMatrix(glintMatrixB); + GLStateManager.glColor4f(0.5f, 1, 1, 1); + assertFalse(capture.replay(groups), "changed layer color must retain the original render call"); + GLStateManager.glColor4f(1, 1, 1, 1); + GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE); + assertFalse(capture.replay(groups), "changed layer blending must retain the original render call"); + setGlintBlendState(); + assertTrue(capture.replay(groups)); + assertFalse(capture.replay(groups), "a captured pass is replayed only once"); + } + groups.drawLayer(layer); + instanced.endFrame(); + CubeParityFixture.assertPixelParity(reference, FfpFixture.readRegion(SIZE), SIZE, BACKGROUND, RetainedTesrGroupsGLTest::describe); + } + + @Test + void replayedGlintCubesMatchIndependentPasses() { + glintState(); + final int[] reference = cubeReference(); + drawBaseCubes(); + setGlintBlendState(); + groups.beginPass(new Matrix4f(), 0, 0, 0, instanced); + final GlintCapture capture = new GlintCapture(); + for (Matrix4f pose : new Matrix4f[] {cubeE1, cubeE2}) { + applyGlintTexMatrix(glintMatrixA); + assertTrue(capture.begin(groups)); + groups.queue(null, glintCubes, CUBE_SCALE, layer, EntityMaterials.GLINT, pose, 0, -1, 0, 0L, 0, glintMatrixA); + capture.end(); + applyGlintTexMatrix(glintMatrixB); + assertTrue(capture.replay(groups)); + } + groups.drawLayer(layer); + instanced.endFrame(); + CubeParityFixture.assertPixelParity(reference, FfpFixture.readRegion(SIZE), SIZE, BACKGROUND, RetainedTesrGroupsGLTest::describe); + } + + @Test + void armorGlintUsesTheBasePoseWithTheGlintMaterial() { + glintState(); + drawBaseTemplates(); + setGlintBlendState(); + GLStateManager.glBlendFunc(GL11.GL_SRC_COLOR, GL11.GL_ONE); + applyGlintTexMatrix(glintMatrixA); + drawSingleInstance(instanced, glintTemplate, templateE1, 0xFFFFFFFF); + applyGlintTexMatrix(glintMatrixB); + drawSingleInstance(instanced, glintTemplate, templateE1, 0xFFFFFFFF); + final int[] reference = FfpFixture.readRegion(SIZE); + drawBaseTemplates(); + groups.beginPass(new Matrix4f(), 0, 0, 0, instanced); + final GlintCapture base = new GlintCapture(); + assertTrue(base.begin(groups)); + base.allowBase(); + groups.queue(glintTemplate, layer, EntityMaterials.CUTOUT, templateE1, 0, 0xFF123456, 0, 0L, 0, null); + base.end(); + setGlintBlendState(); + GLStateManager.glBlendFunc(GL11.GL_SRC_COLOR, GL11.GL_ONE); + applyGlintTexMatrix(glintMatrixA); + ModelPartBatcher.onTextureBind(new ResourceLocation("angelica", "test/glint"), glintTexture); + final ModelPartBatcher batcher = ModelPartBatcher.INSTANCE; + final RetainedTesrGroups previousGroups = Reflect.get(batcher, "activeGroups"); + Reflect.set(batcher, "activeGroups", groups); + try { + final GlintCapture glint = new GlintCapture(); + assertTrue(glint.begin(groups)); + GLStateManager.glTranslatef(0.1f, 0, 0); + assertFalse(base.copyBase(batcher, groups), "a different parent pose must use the original model call"); + GLStateManager.glLoadIdentity(); + assertTrue(base.copyBase(batcher, groups)); + glint.end(); + assertTrue(glint.replay(groups, glintMatrixB), "the second layer can be queued without rerunning legacy texture setup"); + groups.drawLayer(Reflect.get(batcher, "lastLayer")); + instanced.endFrame(); + final int[] actual = FfpFixture.readRegion(SIZE); + int error = 0; + for (int p = 0; p < actual.length; p++) for (int shift = 0; shift < 24; shift += 8) { + error = Math.max(error, Math.abs(((actual[p] >>> shift) & 255) - ((reference[p] >>> shift) & 255))); + } + assertTrue(error <= 2, "reusing the base pose must retain the glint color; error " + error); + } finally { + Reflect.set(batcher, "activeGroups", previousGroups); + } + } + @Test void mirroredModelViewUnderCullFrontBatchesLikeTheImmediateDraw() { GLStateManager.glViewport(0, 0, SIZE, SIZE);