From 22c9479946ab5bbd24470becafdb2154758f98ad Mon Sep 17 00:00:00 2001 From: Eclipse-Sol Date: Wed, 19 Aug 2026 11:55:43 -0400 Subject: [PATCH] Many bug fixes (#2017) Co-authored-by: Martin Robertz --- dependencies.gradle.kts | 2 + .../angelica/glsm/GLContextState.java | 4 + .../angelica/glsm/GLStateManager.java | 128 +++- .../glsm/hooks/DeferredBlendHandler.java | 1 + .../glsm/hooks/DeferredDepthColorHandler.java | 1 + .../angelica/glsm/hooks/GLSMConfig.java | 8 + .../angelica/glsm/hooks/GLSMHooks.java | 4 + .../glsm/hooks/VanillaBooleanLayer.java | 8 + .../glsm/hooks/VanillaStateLayer.java | 22 + .../hooks/events/ForeignDrawEndEvent.java | 6 + .../hooks/events/VanillaBlendChangeEvent.java | 6 + .../angelica/glsm/stacks/AlphaStateStack.java | 17 +- .../angelica/glsm/stacks/BlendStateStack.java | 19 +- .../glsm/stacks/BooleanStateStack.java | 30 +- .../angelica/glsm/stacks/ColorMaskStack.java | 11 +- .../angelica/glsm/stacks/DepthStateStack.java | 21 +- .../angelica/glsm/states/BlendState.java | 9 + .../angelica/glsm/states/DepthState.java | 6 + .../streaming/TessellatorStreamingDrawer.java | 2 +- .../client/font/BatchingFontRenderer.java | 546 ++++++++++++++---- .../angelica/compat/mojang/RenderLayer.java | 29 +- .../angelica/compat/mojang/RenderPhase.java | 19 + .../angelica/config/AngelicaConfig.java | 15 + .../angelica/config/FontConfig.java | 4 +- .../angelica/iris/IrisGLSMBridge.java | 127 +++- .../gtnewhorizons/angelica/mixins/Mixins.java | 20 + .../angelica/mixins/TargetedMod.java | 1 + .../interfaces/ItemRendererAccessor.java | 8 + .../angelica/render/SelectionBoxRenderer.java | 3 + .../rendering/DeferredEntityOverlay.java | 4 +- .../rendering/ParticleRunSplitter.java | 63 ++ .../rendering/PlayerReflectionCapture.java | 7 +- .../celeritas/AngelicaChunkBuildContext.java | 57 +- .../celeritas/VertexArrayQuadView.java | 10 +- .../celeritas/light/LightDataCache.java | 13 +- .../rendering/celeritas/world/WorldSlice.java | 11 + .../rendering/tesr/ModelPartBatcher.java | 58 +- .../rendering/tesr/TesrBatchRenderer.java | 80 ++- .../client/gui/SodiumGameOptionPages.java | 34 +- .../control/ControlValueFormatter.java | 12 + .../impl/AngelicaBufferSource.java | 26 +- src/main/java/net/coderbot/iris/Iris.java | 6 + .../celeritas/CeleritasTerrainPipeline.java | 37 +- .../fantastic/IrisParticleRenderTypes.java | 22 - .../matching/RenderCondition.java | 2 + .../matching/TranslucentBlendMatcher.java | 24 + .../iris/gl/blending/AlphaTestStorage.java | 135 +++-- .../iris/gl/blending/BlendModeStorage.java | 257 +++++---- .../iris/gl/blending/DepthColorStorage.java | 119 ++-- .../gui/element/ShaderPackSelectionList.java | 6 +- .../iris/gui/screen/ShaderPackScreen.java | 86 ++- .../coderbot/iris/layer/GbufferPrograms.java | 214 ++++++- .../net/coderbot/iris/layer/PassOverride.java | 48 ++ .../DeferredWorldRenderingPipeline.java | 145 ++++- .../FixedFunctionWorldRenderingPipeline.java | 6 + .../coderbot/iris/pipeline/HandRenderer.java | 111 ++-- .../iris/pipeline/ShadowRenderer.java | 34 +- .../iris/pipeline/WorldRenderingPipeline.java | 2 + .../coderbot/iris/shaderpack/ProgramSet.java | 49 +- .../iris/shaderpack/loading/ProgramId.java | 6 +- .../option/menu/OptionMenuContainer.java | 4 + .../iris/shadows/ShadowGraphGate.java | 22 +- .../iris/uniforms/CapturedRenderingState.java | 11 +- .../coderbot/iris/uniforms/IdMapUniforms.java | 12 +- .../coderbot/iris/uniforms/ItemIdManager.java | 2 +- .../iris/uniforms/ItemMaterialHelper.java | 66 +-- .../resources/assets/angelica/lang/en_US.lang | 7 + .../MixinEffectRenderer_Particles.java | 35 ++ .../early/shaders/MixinEntityPickupFX.java | 8 + .../early/shaders/MixinEntityRenderer.java | 23 + .../early/shaders/MixinItemRenderer.java | 68 +-- .../shaders/MixinItemRendererBackhand.java | 36 +- .../shaders/MixinItemRenderer_ItemId.java | 75 +++ .../early/shaders/MixinRenderDragon.java | 33 +- .../early/shaders/MixinRenderEndPortal.java | 9 +- .../early/shaders/MixinRenderEntityFlame.java | 4 +- .../mixins/early/shaders/MixinRenderItem.java | 25 +- .../early/shaders/MixinRenderManager.java | 38 +- .../early/shaders/MixinRenderManagerDAPI.java | 24 +- .../early/shaders/MixinRenderNameTag.java | 18 +- .../early/shaders/MixinRenderPlayerArmor.java | 10 +- .../shaders/MixinRendererLivingEntity.java | 36 +- .../MixinTileEntityRendererDispatcher.java | 5 + .../chisel/MixinRenderCarvableBeacon.java | 5 + .../MixinThrottleableEffectRenderer.java | 33 ++ .../MixinTileEntityNewBeaconRenderer.java | 27 + .../MixinTileEntityTrophyRenderer.java | 38 ++ .../rendering/tesr/TesrLayerKeyTest.java | 52 +- .../iris/pipeline/ShadowGraphGateTest.java | 68 ++- 89 files changed, 2820 insertions(+), 735 deletions(-) create mode 100644 glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/VanillaBooleanLayer.java create mode 100644 glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/VanillaStateLayer.java create mode 100644 glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/events/ForeignDrawEndEvent.java create mode 100644 glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/events/VanillaBlendChangeEvent.java create mode 100644 src/main/java/com/gtnewhorizons/angelica/mixins/interfaces/ItemRendererAccessor.java create mode 100644 src/main/java/com/gtnewhorizons/angelica/rendering/ParticleRunSplitter.java delete mode 100644 src/main/java/net/coderbot/iris/fantastic/IrisParticleRenderTypes.java create mode 100644 src/main/java/net/coderbot/iris/gbuffer_overrides/matching/TranslucentBlendMatcher.java create mode 100644 src/main/java/net/coderbot/iris/layer/PassOverride.java create mode 100644 src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEffectRenderer_Particles.java create mode 100644 src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinItemRenderer_ItemId.java create mode 100644 src/mixin/java/com/gtnewhorizons/angelica/mixins/late/client/dragonapi/MixinThrottleableEffectRenderer.java create mode 100644 src/mixin/java/com/gtnewhorizons/angelica/mixins/late/client/etfuturum/MixinTileEntityNewBeaconRenderer.java create mode 100644 src/mixin/java/com/gtnewhorizons/angelica/mixins/late/client/openblocks/MixinTileEntityTrophyRenderer.java diff --git a/dependencies.gradle.kts b/dependencies.gradle.kts index b90ea9f21..699ad6ac3 100644 --- a/dependencies.gradle.kts +++ b/dependencies.gradle.kts @@ -161,6 +161,8 @@ DependencyHandlerScope.of(dependencies).apply { compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684")) compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228")) compileOnly("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.49-GTNH:dev") + compileOnly("com.github.GTNewHorizons:OpenBlocks:1.12.19-GTNH:dev") + compileOnly("com.github.GTNewHorizons:Chisel:2.17.31-GTNH:dev") compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612")) devOnlyNonPublishable("com.github.GTNewHorizons:CosmeticArmorReworked:1.0.6-GTNH:dev") diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLContextState.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLContextState.java index c44082c42..5f2d2b314 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLContextState.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLContextState.java @@ -18,6 +18,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack; import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack; import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack; import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack; +import com.gtnewhorizons.angelica.glsm.states.BlendState; import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState; import com.gtnewhorizons.angelica.glsm.states.Color4; import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState; @@ -181,6 +182,9 @@ class GLContextState { public int clientAttribStackPointer = 0; public final boolean[] restoreUnitChanged = new boolean[GLStateManager.MAX_TEXTURE_UNITS]; public boolean restoreDepthChanged, restoreBlendChanged, restoreColorMaskChanged, restoreClearColorChanged, restoreDrawBufferChanged, restoreLogicOpChanged, restoreStencilChanged, restoreViewportChanged, restoreLineChanged, restorePointChanged, restorePolygonChanged, restoreActiveUnitChanged; + public final BlendState vanillaBlendBefore = new BlendState(); + public final BlendState vanillaBlendAfter = new BlendState(); + public boolean vanillaBlendEnabledBefore; public int drawFramebuffer = 0; public int readFramebuffer = 0; public int texGenGeneration; diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLStateManager.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLStateManager.java index 12bd864dc..49cef8bf7 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLStateManager.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/GLStateManager.java @@ -45,6 +45,8 @@ import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack; import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack; import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack; import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack; +import com.gtnewhorizons.angelica.glsm.states.AlphaState; +import com.gtnewhorizons.angelica.glsm.states.BlendState; import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState; import com.gtnewhorizons.angelica.glsm.states.Color4; import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState; @@ -1302,12 +1304,16 @@ public class GLStateManager { return; } } + final BooleanStateStack blend = ctx().blendMode; + final boolean wasEnabled = blend.isEffectivelyEnabled(); final DeferredBlendHandler bh = GLSMHooks.blendHandler; if (bh != null && bh.isBlendLocked()) { + blend.beforeModify(); bh.deferBlendModeToggle(true); - return; + } else { + blend.enable(); } - ctx().blendMode.enable(); + if (!wasEnabled && blend.isEffectivelyEnabled()) postVanillaBlendChange(); } public static void disableBlend() { @@ -1318,12 +1324,80 @@ public class GLStateManager { return; } } + final BooleanStateStack blend = ctx().blendMode; + final boolean wasEnabled = blend.isEffectivelyEnabled(); final DeferredBlendHandler bh = GLSMHooks.blendHandler; if (bh != null && bh.isBlendLocked()) { + blend.beforeModify(); bh.deferBlendModeToggle(false); - return; + } else { + blend.disable(); } - ctx().blendMode.disable(); + if (wasEnabled && !blend.isEffectivelyEnabled()) postVanillaBlendChange(); + } + + private static void postVanillaBlendChange() { + if (GLSMHooks.VANILLA_BLEND_CHANGE.hasListeners()) { + GLSMHooks.VANILLA_BLEND_CHANGE.post(GLSMHooks.vanillaBlendChangeEvent); + } + } + + private static boolean snapshotVanillaBlendFunc() { + if (!GLSMHooks.VANILLA_BLEND_CHANGE.hasListeners()) return false; + final GLContextState glCtx = ctx(); + glCtx.blendState.readEffective(glCtx.vanillaBlendBefore); + glCtx.vanillaBlendEnabledBefore = glCtx.blendMode.isEffectivelyEnabled(); + return true; + } + + private static void postVanillaBlendChangeIfMoved(boolean snapshotted) { + if (!snapshotted) return; + final GLContextState glCtx = ctx(); + final BlendState before = glCtx.vanillaBlendBefore; + final BlendState after = glCtx.blendState.readEffective(glCtx.vanillaBlendAfter); + if (glCtx.blendMode.isEffectivelyEnabled() != glCtx.vanillaBlendEnabledBefore + || after.getSrcRgb() != before.getSrcRgb() + || after.getDstRgb() != before.getDstRgb() + || after.getSrcAlpha() != before.getSrcAlpha() + || after.getDstAlpha() != before.getDstAlpha()) { + postVanillaBlendChange(); + } + } + + public static boolean isEffectiveBlendEnabled() { + return ctx().blendMode.isEffectivelyEnabled(); + } + + public static BlendState getEffectiveBlendState(BlendState out) { + return ctx().blendState.readEffective(out); + } + + public static boolean isEffectiveDepthMaskEnabled() { + return ctx().depthState.isEffectiveMaskEnabled(); + } + + public static boolean isEffectiveAlphaTestEnabled() { + return ctx().alphaTest.isEffectivelyEnabled(); + } + + public static AlphaState getEffectiveAlphaState(AlphaState out) { + return ctx().alphaState.readEffective(out); + } + + private static int foreignDrawDepth; + + public static void beginForeignDraw() { + foreignDrawDepth++; + } + + public static void endForeignDraw() { + if (foreignDrawDepth > 0 && --foreignDrawDepth == 0 && GLSMHooks.FOREIGN_DRAW_END.hasListeners()) { + GLSMHooks.FOREIGN_DRAW_END.post(GLSMHooks.foreignDrawEndEvent); + } + } + + public static boolean isForeignDraw() { + return foreignDrawDepth > 0; } public static void enableScissorTest() { @@ -1357,9 +1431,11 @@ public class GLStateManager { return; } } + final boolean snapshotted = snapshotVanillaBlendFunc(); final DeferredBlendHandler bh = GLSMHooks.blendHandler; if (bh != null && bh.isBlendLocked()) { bh.deferBlendFunc(srcFactor, dstFactor, srcFactor, dstFactor); + postVanillaBlendChangeIfMoved(snapshotted); return; } final boolean caching = isCachingEnabled(); @@ -1373,6 +1449,7 @@ public class GLStateManager { GLSMHooks.blendFuncChangeEvent.dstAlpha = GL11.GL_ONE_MINUS_SRC_ALPHA; GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent); } + postVanillaBlendChangeIfMoved(snapshotted); return; } final boolean bypass = !caching; @@ -1391,6 +1468,7 @@ public class GLStateManager { GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent); } } + postVanillaBlendChangeIfMoved(snapshotted); } public static void glBlendEquation(int mode) { @@ -1430,9 +1508,11 @@ public class GLStateManager { return; } } + final boolean snapshotted = snapshotVanillaBlendFunc(); final DeferredBlendHandler bh = GLSMHooks.blendHandler; if (bh != null && bh.isBlendLocked()) { bh.deferBlendFunc(srcRgb, dstRgb, srcAlpha, dstAlpha); + postVanillaBlendChangeIfMoved(snapshotted); return; } if (GLSMConfig.hudCacheOverride && dstAlpha != GL11.GL_ONE_MINUS_SRC_ALPHA) { @@ -1452,6 +1532,7 @@ public class GLStateManager { GLSMHooks.BLEND_FUNC_CHANGE.post(GLSMHooks.blendFuncChangeEvent); } } + postVanillaBlendChangeIfMoved(snapshotted); } public static void glBlendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha) { @@ -1746,6 +1827,7 @@ public class GLStateManager { } final DeferredAlphaHandler ah = GLSMHooks.alphaHandler; if (ah != null && ah.isAlphaTestLocked()) { + glCtx.alphaTest.beforeModify(); ah.deferAlphaTestToggle(true); return; } @@ -1767,6 +1849,7 @@ public class GLStateManager { } final DeferredAlphaHandler ah = GLSMHooks.alphaHandler; if (ah != null && ah.isAlphaTestLocked()) { + glCtx.alphaTest.beforeModify(); ah.deferAlphaTestToggle(false); return; } @@ -3532,6 +3615,9 @@ public class GLStateManager { final int mask = glCtx.attribs.popInt(); glCtx.attribDepth--; + // Snapshot before the stacks pop, so applyRestoredState can tell whether vanilla blend actually moved. + final boolean blendSnapshotted = (mask & (GL11.GL_COLOR_BUFFER_BIT | GL11.GL_ENABLE_BIT)) != 0 && snapshotVanillaBlendFunc(); + // First: restore BooleanStateStack states that were actually modified (fast path) // These use lazy copy-on-write with global depth tracking final List> modified = glCtx.modifiedAtDepth[glCtx.attribDepth]; @@ -3550,7 +3636,12 @@ public class GLStateManager { // Third: apply restored state to the GL driver. BooleanStateStacks already issue GL calls via setEnabled(); non-boolean stacks are pure data // containers, so we must explicitly drive GL here. - applyRestoredState(mask); + applyRestoredState(mask, blendSnapshotted); + } + + private static boolean isDepthColorOverridden() { + final DeferredDepthColorHandler dch = GLSMHooks.depthColorHandler; + return dch != null && dch.isOverrideHeld(); } private static void captureRestoreChanges(int mask) { @@ -3639,20 +3730,25 @@ public class GLStateManager { /** * After popping GLSM stacks, apply restored state to the GL driver. */ - private static void applyRestoredState(int mask) { + private static void applyRestoredState(int mask, boolean blendSnapshotted) { final GLContextState glCtx = ctx(); if ((mask & GL11.GL_DEPTH_BUFFER_BIT) != 0 && glCtx.restoreDepthChanged) { RENDER_BACKEND.depthFunc(glCtx.depthState.getFunc()); - RENDER_BACKEND.depthMask(glCtx.depthState.isEnabled()); + if (!isDepthColorOverridden()) { + RENDER_BACKEND.depthMask(glCtx.depthState.isEnabled()); + } RENDER_BACKEND.clearDepth(glCtx.depthState.getClearValue()); } if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) { if (glCtx.restoreBlendChanged) { - RENDER_BACKEND.blendFuncSeparate(glCtx.blendState.getSrcRgb(), glCtx.blendState.getDstRgb(), glCtx.blendState.getSrcAlpha(), glCtx.blendState.getDstAlpha()); + final DeferredBlendHandler restoreBlendHandler = GLSMHooks.blendHandler; + if (restoreBlendHandler == null || !restoreBlendHandler.isOverrideHeld()) { + RENDER_BACKEND.blendFuncSeparate(glCtx.blendState.getSrcRgb(), glCtx.blendState.getDstRgb(), glCtx.blendState.getSrcAlpha(), glCtx.blendState.getDstAlpha()); + } RENDER_BACKEND.blendEquationSeparate(glCtx.blendState.getEquationRgb(), glCtx.blendState.getEquationAlpha()); RENDER_BACKEND.blendColor(glCtx.blendState.getBlendColorR(), glCtx.blendState.getBlendColorG(), glCtx.blendState.getBlendColorB(), glCtx.blendState.getBlendColorA()); } - if (glCtx.restoreColorMaskChanged) { + if (glCtx.restoreColorMaskChanged && !isDepthColorOverridden()) { RENDER_BACKEND.colorMask(glCtx.colorMask.red, glCtx.colorMask.green, glCtx.colorMask.blue, glCtx.colorMask.alpha); } if (glCtx.restoreClearColorChanged) { @@ -3737,6 +3833,8 @@ public class GLStateManager { glCtx.dirtyTexCoordAttrib = true; } } + + postVanillaBlendChangeIfMoved(blendSnapshotted); } public static void glClear(int mask) { @@ -4925,16 +5023,22 @@ public class GLStateManager { final ShaderManager ffp = ShaderManager.getInstance(); if (program == 0 && ffp.isEnabled()) { final int prev = glCtx.activeProgram; - final boolean caching = isCachingEnabled(); - if (prev != 0) glCtx.programGeneration++; + final boolean changed = prev != 0; + if (changed) glCtx.programGeneration++; glCtx.activeProgram = 0; // Track that FFP was requested - if (GLSMHooks.PROGRAM_CHANGE.hasListeners()) { + if (changed && GLSMHooks.PROGRAM_CHANGE.hasListeners()) { GLSMHooks.programChangeEvent.previousProgram = prev; GLSMHooks.programChangeEvent.newProgram = 0; GLSMHooks.programChangeEvent.postBind = false; GLSMHooks.PROGRAM_CHANGE.post(GLSMHooks.programChangeEvent); } ffp.activate(); + if (changed && GLSMHooks.PROGRAM_CHANGE.hasListeners()) { + GLSMHooks.programChangeEvent.previousProgram = prev; + GLSMHooks.programChangeEvent.newProgram = 0; + GLSMHooks.programChangeEvent.postBind = true; + GLSMHooks.PROGRAM_CHANGE.post(GLSMHooks.programChangeEvent); + } return; } diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/DeferredBlendHandler.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/DeferredBlendHandler.java index 6d62cbc49..942dc5886 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/DeferredBlendHandler.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/DeferredBlendHandler.java @@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.hooks; public interface DeferredBlendHandler { boolean isBlendLocked(); + boolean isOverrideHeld(); void deferBlendModeToggle(boolean enabled); void deferBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha); void flushDeferredBlend(); diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/DeferredDepthColorHandler.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/DeferredDepthColorHandler.java index 1e715c089..45ef37631 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/DeferredDepthColorHandler.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/DeferredDepthColorHandler.java @@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.hooks; public interface DeferredDepthColorHandler { boolean isDepthColorLocked(); + boolean isOverrideHeld(); void deferDepthEnable(boolean enabled); void deferColorMask(boolean r, boolean g, boolean b, boolean a); } diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/GLSMConfig.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/GLSMConfig.java index a6dd9e5c6..06ded9457 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/GLSMConfig.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/GLSMConfig.java @@ -6,6 +6,14 @@ public final class GLSMConfig { public static float lastBrightnessX; public static float lastBrightnessY; + public static int packBrightness(float x, float y) { + return ((int) y << 16) | ((int) x & 0xFFFF); + } + + public static int packedLastBrightness() { + return packBrightness(lastBrightnessX, lastBrightnessY); + } + public static boolean hudCacheOverride; public static volatile boolean expandVertexFormats; diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/GLSMHooks.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/GLSMHooks.java index fb2ad5891..0cf7a9188 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/GLSMHooks.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/GLSMHooks.java @@ -25,6 +25,8 @@ public final class GLSMHooks { public static final EventBus LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class); public static final EventBus ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.class); public static final EventBus SHADER_COLOR_CHANGE = EventBus.create(ShaderColorChangeEvent.class); + public static final EventBus FOREIGN_DRAW_END = EventBus.create(ForeignDrawEndEvent.class); + public static final EventBus VANILLA_BLEND_CHANGE = EventBus.create(VanillaBlendChangeEvent.class); public static final EventBus LOADING_CHECKPOINT = EventBus.create(LoadingCheckpointEvent.class); // Reusable event instances @@ -38,6 +40,8 @@ public final class GLSMHooks { public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent(); public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent(); public static final ShaderColorChangeEvent shaderColorChangeEvent = new ShaderColorChangeEvent(); + public static final ForeignDrawEndEvent foreignDrawEndEvent = new ForeignDrawEndEvent(); + public static final VanillaBlendChangeEvent vanillaBlendChangeEvent = new VanillaBlendChangeEvent(); public static final LoadingCheckpointEvent loadingCheckpointEvent = new LoadingCheckpointEvent(); private GLSMHooks() {} diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/VanillaBooleanLayer.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/VanillaBooleanLayer.java new file mode 100644 index 000000000..1160003e3 --- /dev/null +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/VanillaBooleanLayer.java @@ -0,0 +1,8 @@ +package com.gtnewhorizons.angelica.glsm.hooks; + +public interface VanillaBooleanLayer { + + boolean isOverrideHeld(); + boolean getVanilla(); + void setVanilla(boolean enabled); +} diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/VanillaStateLayer.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/VanillaStateLayer.java new file mode 100644 index 000000000..a048d8684 --- /dev/null +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/VanillaStateLayer.java @@ -0,0 +1,22 @@ +package com.gtnewhorizons.angelica.glsm.hooks; + +public interface VanillaStateLayer { + + boolean isOverrideHeld(); + void readVanilla(T into); + void writeVanilla(T from); + + static boolean isHeld(VanillaStateLayer layer) { + return layer != null && layer.isOverrideHeld(); + } + + static void capture(VanillaStateLayer layer, T slot) { + if (isHeld(layer)) layer.readVanilla(slot); + } + + static boolean restore(VanillaStateLayer layer, T saved) { + if (!isHeld(layer)) return false; + layer.writeVanilla(saved); + return true; + } +} diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/events/ForeignDrawEndEvent.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/events/ForeignDrawEndEvent.java new file mode 100644 index 000000000..83380bd4a --- /dev/null +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/events/ForeignDrawEndEvent.java @@ -0,0 +1,6 @@ +package com.gtnewhorizons.angelica.glsm.hooks.events; + +import net.minecraftforge.eventbus.api.event.MutableEvent; + +public final class ForeignDrawEndEvent extends MutableEvent { +} diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/events/VanillaBlendChangeEvent.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/events/VanillaBlendChangeEvent.java new file mode 100644 index 000000000..175008f8f --- /dev/null +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/hooks/events/VanillaBlendChangeEvent.java @@ -0,0 +1,6 @@ +package com.gtnewhorizons.angelica.glsm.hooks.events; + +import net.minecraftforge.eventbus.api.event.MutableEvent; + +public final class VanillaBlendChangeEvent extends MutableEvent { +} diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/stacks/AlphaStateStack.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/stacks/AlphaStateStack.java index f062233b0..a2da8fb9b 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/stacks/AlphaStateStack.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/stacks/AlphaStateStack.java @@ -2,7 +2,9 @@ package com.gtnewhorizons.angelica.glsm.stacks; import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack; import com.gtnewhorizons.angelica.glsm.GLStateManager; +import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer; import com.gtnewhorizons.angelica.glsm.states.AlphaState; +import lombok.Setter; public class AlphaStateStack extends AlphaState implements IStateStack { @@ -10,6 +12,8 @@ public class AlphaStateStack extends AlphaState implements IStateStack vanillaLayer; + public AlphaStateStack() { stack = new AlphaState[GLStateManager.MAX_ATTRIB_STACK_DEPTH]; for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) { @@ -22,7 +26,7 @@ public class AlphaStateStack extends AlphaState implements IStateStack { @@ -10,6 +12,8 @@ public class BlendStateStack extends BlendState implements IStateStack vanillaLayer; + public BlendStateStack() { stack = new BlendState[GLStateManager.MAX_ATTRIB_STACK_DEPTH]; for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) { @@ -22,7 +26,7 @@ public class BlendStateStack extends BlendState implements IStateStack { protected final boolean[] stack; + @Setter private VanillaBooleanLayer vanillaLayer; /** * The depth at which state has been saved. Compared against GLStateManager.getAttribDepth() @@ -56,7 +59,7 @@ public class BooleanStateStack extends BooleanState implements IStateStack= stack.length) { throw new IllegalStateException("Stack overflow size " + (savedDepth + 1) + " reached"); } - stack[savedDepth++] = enabled; + stack[savedDepth++] = vanillaValue(); return this; } @@ -65,12 +68,26 @@ public class BooleanStateStack extends BooleanState implements IStateStack 0) { - final boolean oldValue = stack[--savedDepth]; - setEnabledDirect(oldValue); + restore(stack[--savedDepth]); } return this; } @@ -106,7 +122,7 @@ public class BooleanStateStack extends BooleanState implements IStateStack { @@ -10,6 +12,8 @@ public class ColorMaskStack extends ColorMask implements IStateStack vanillaLayer; + public ColorMaskStack() { stack = new ColorMask[GLStateManager.MAX_ATTRIB_STACK_DEPTH]; for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) { @@ -22,7 +26,7 @@ public class ColorMaskStack extends ColorMask implements IStateStack { @@ -10,6 +12,10 @@ public class DepthStateStack extends DepthState implements IStateStack vanillaLayer; + + private final DepthState effective = new DepthState(); + public DepthStateStack() { stack = new DepthState[GLStateManager.MAX_ATTRIB_STACK_DEPTH]; for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) { @@ -22,7 +28,7 @@ public class DepthStateStack extends DepthState implements IStateStack { return this; } + public void setExceptFunc(BlendState state) { + final int sRgb = srcRgb, dRgb = dstRgb, sAlpha = srcAlpha, dAlpha = dstAlpha; + set(state); + this.srcRgb = sRgb; + this.dstRgb = dRgb; + this.srcAlpha = sAlpha; + this.dstAlpha = dAlpha; + } + @Override public boolean sameAs(Object state) { if (this == state) return true; diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/states/DepthState.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/states/DepthState.java index b8521986b..136b8f029 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/states/DepthState.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/states/DepthState.java @@ -18,6 +18,12 @@ public class DepthState implements ISettableState { return this; } + public void setExceptMask(DepthState state) { + final boolean mask = enabled; + set(state); + this.enabled = mask; + } + @Override public boolean sameAs(Object state) { if (this == state) return true; diff --git a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/streaming/TessellatorStreamingDrawer.java b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/streaming/TessellatorStreamingDrawer.java index baeb88c74..f39a457b5 100644 --- a/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/streaming/TessellatorStreamingDrawer.java +++ b/glsm/src/main/java/com/gtnewhorizons/angelica/glsm/streaming/TessellatorStreamingDrawer.java @@ -108,7 +108,7 @@ public class TessellatorStreamingDrawer { final int defaultBrightness; if (!tess.hasBrightness) { - defaultBrightness = ((int) GLSMConfig.lastBrightnessY << 16) | ((int) GLSMConfig.lastBrightnessX & 0xFFFF); + defaultBrightness = GLSMConfig.packedLastBrightness(); } else { defaultBrightness = 0; } diff --git a/src/main/java/com/gtnewhorizons/angelica/client/font/BatchingFontRenderer.java b/src/main/java/com/gtnewhorizons/angelica/client/font/BatchingFontRenderer.java index 107dc8f39..76db4472c 100644 --- a/src/main/java/com/gtnewhorizons/angelica/client/font/BatchingFontRenderer.java +++ b/src/main/java/com/gtnewhorizons/angelica/client/font/BatchingFontRenderer.java @@ -8,7 +8,9 @@ import com.gtnewhorizons.angelica.config.AngelicaConfig; import com.gtnewhorizons.angelica.config.FontConfig; import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.glsm.ffp.FFPVertexLighting; +import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager; import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig; +import com.gtnewhorizons.angelica.glsm.states.BlendState; import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer; import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader; import com.gtnewhorizons.angelica.hudcaching.HUDCaching; @@ -17,9 +19,15 @@ import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher; import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import lombok.Setter; +import net.coderbot.iris.Iris; import net.coderbot.iris.gl.program.Program; import net.coderbot.iris.gl.program.ProgramBuilder; +import net.coderbot.iris.layer.GbufferPrograms; +import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline; +import net.coderbot.iris.pipeline.PipelineManager; +import net.coderbot.iris.uniforms.CapturedRenderingState; import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.renderer.Tessellator; import net.minecraft.util.MathHelper; import net.minecraft.util.ResourceLocation; import org.embeddedt.embeddium.impl.render.shader.ShaderLoader; @@ -41,6 +49,8 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush; import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0; import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc; import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree; +import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte; +import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetFloat; import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutByte; import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat; import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutShort; @@ -87,6 +97,7 @@ public class BatchingFontRenderer { private static class FontAAShader { private static Program fontShader = null; + public static Program getProgram() { if (fontShader == null) { final String vsh = ShaderLoader.getShaderSource("angelica:fontFilter.vsh"); @@ -172,6 +183,9 @@ public class BatchingFontRenderer { private int blendSrcRGB = GL11.GL_SRC_ALPHA; private int blendDstRGB = GL11.GL_ONE_MINUS_SRC_ALPHA; + private final BlendState blendStateBefore = new BlendState(); + private static final BlendState deferredBlendStateBefore = new BlendState(); + private float lightmapU = 0.0f; private float lightmapV = 0.0f; private boolean lightmapActive = false; @@ -217,6 +231,7 @@ public class BatchingFontRenderer { private static final class TextSegment { final Matrix4f mvp = new Matrix4f(); + final Matrix4f modelView = new Matrix4f(); BatchingFontRenderer owner; int cmdStart; int cmdEnd; @@ -224,6 +239,9 @@ public class BatchingFontRenderer { float lightmapV; boolean lightmapActive; int lightmapTexture; + int packedLight; + float normalX, normalY, normalZ; + int blockEntityId; } // 16-bit EBO index range @@ -235,7 +253,6 @@ public class BatchingFontRenderer { private int deferredCmdWatermark; private static final ObjectArrayList deferredSegments = ObjectArrayList.wrap(new TextSegment[16], 0); private static final ObjectArrayList deferredSegmentPool = ObjectArrayList.wrap(new TextSegment[16], 0); - private boolean deferCurrentBatch; private final Matrix4f batchProj = new Matrix4f(); private final Matrix4f batchModelView = new Matrix4f(); @@ -467,6 +484,10 @@ public class BatchingFontRenderer { } } + private void resolveModelView(Matrix4f dest) { + dest.set(batchMatrixValid ? batchModelView : GLStateManager.getModelViewMatrix()); + } + private void sealBatchSegment() { final int end = batchCommands.size(); if (end == batchSealedEnd) return; @@ -475,10 +496,17 @@ public class BatchingFontRenderer { segment.cmdStart = batchSealedEnd; segment.cmdEnd = end; resolveMvp(segment.mvp); + resolveModelView(segment.modelView); segment.lightmapActive = lightmapActive; segment.lightmapU = lightmapU; segment.lightmapV = lightmapV; segment.lightmapTexture = lightmapTextureId; + segment.packedLight = GLSMConfig.packedLastBrightness(); + final Vector3f normal = ShaderManager.getCurrentNormal(); + segment.normalX = normal.x; + segment.normalY = normal.y; + segment.normalZ = normal.z; + segment.blockEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity(); batchSegments.add(segment); batchSealedEnd = end; } @@ -499,27 +527,29 @@ public class BatchingFontRenderer { } batchDepth--; if (batchDepth == 0) { - // We finished any nested batches - flushBatch(); + // We finished any nested batches. + if (shouldDeferNow()) { + deferBatch(); + } else { + flushBatch(); + } } } + private static boolean shouldDeferNow() { + if (!GLStateManager.isMainThread()) return false; + return TesrBatchRenderer.INSTANCE.hasPendingGeometry() || ModelPartBatcher.INSTANCE.isActive(); + } + public void beginDeferredBatch() { - deferCurrentBatch = batchDepth == 0 && TesrBatchRenderer.INSTANCE.hasActivePass() || ModelPartBatcher.INSTANCE.isActive(); - if (deferCurrentBatch && deferredIdxPos / 6 > QUAD_BUDGET / 2) { + if (batchDepth == 0 && shouldDeferNow() && deferredIdxPos / 6 > QUAD_BUDGET / 2) { flushDeferredText(); } beginBatch(); } public void endDeferredBatch() { - if (deferCurrentBatch && batchDepth == 1) { - deferCurrentBatch = false; - batchDepth = 0; - deferBatch(); - } else { - endBatch(); - } + endBatch(); } private void deferBatch() { @@ -544,38 +574,123 @@ public class BatchingFontRenderer { public static void flushDeferredText() { if (deferredSegments.isEmpty()) return; + if (deferredSegments.getFirst().owner.shouldDrawThroughPipeline()) { + flushDeferredThroughPipeline(); + return; + } + + GLStateManager.beginForeignDraw(); + try { + flushDeferredTextInner(); + } finally { + GLStateManager.endForeignDraw(); + } + } + + private static void flushDeferredThroughPipeline() { + final BatchingFontRenderer first = deferredSegments.getFirst().owner; + final boolean isTextureEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D); + final boolean isBlendEnabledBefore = GLStateManager.isEffectiveBlendEnabled(); + final boolean isAlphaTestEnabledBefore = GLStateManager.isEffectiveAlphaTestEnabled(); + GLStateManager.getEffectiveBlendState(deferredBlendStateBefore); + final int boundTextureBefore = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D); + final boolean depthMaskBefore = GLStateManager.isEffectiveDepthMaskEnabled(); + final int prevBlockEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity(); + boolean textureChanged = false; + + GLStateManager.enableTexture(); + GLStateManager.enableAlphaTest(); + GLStateManager.enableBlend(); + GLStateManager.tryBlendFuncSeparate(first.blendSrcRGB, first.blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO); + GLStateManager.glDepthMask(false); + + final Boolean prevTranslucency = GbufferPrograms.beginTranslucencyDeclaration(Boolean.TRUE); + GbufferPrograms.setBlockEntityDefaults(); + + GLStateManager.glMatrixMode(GL11.GL_MODELVIEW); + GLStateManager.glPushMatrix(); + try { + for (final TextSegment segment : deferredSegments) { + final BatchingFontRenderer owner = segment.owner; + if (segment.cmdStart == segment.cmdEnd) continue; + + GLStateManager.setModelViewMatrix(segment.modelView); + CapturedRenderingState.INSTANCE.setCurrentBlockEntity(segment.blockEntityId); + + final FontDrawCmd[] cmdsData = owner.batchCommands.elements(); + Arrays.sort(cmdsData, segment.cmdStart, segment.cmdEnd, FontDrawCmd.DRAW_ORDER_COMPARATOR); + + textureChanged |= emitRangeThroughPipeline(owner, cmdsData, segment.cmdStart, segment.cmdEnd, + segment.packedLight, segment.normalX, segment.normalY, segment.normalZ); + } + } finally { + GLStateManager.glPopMatrix(); + CapturedRenderingState.INSTANCE.setCurrentBlockEntity(prevBlockEntityId); + GbufferPrograms.endTranslucencyDeclaration(prevTranslucency); + + GLStateManager.glDepthMask(depthMaskBefore); + if (isTextureEnabledBefore) { + GLStateManager.enableTexture(); + } else { + GLStateManager.disableTexture(); + } + if (isBlendEnabledBefore) { + GLStateManager.enableBlend(); + } else { + GLStateManager.disableBlend(); + } + GLStateManager.tryBlendFuncSeparate(deferredBlendStateBefore.getSrcRgb(), deferredBlendStateBefore.getDstRgb(), + deferredBlendStateBefore.getSrcAlpha(), deferredBlendStateBefore.getDstAlpha()); + if (isAlphaTestEnabledBefore) { + GLStateManager.enableAlphaTest(); + } else { + GLStateManager.disableAlphaTest(); + } + if (textureChanged) { + GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, boundTextureBefore); + } + discardDeferredText(); + } + } + + private static void flushDeferredTextInner() { uploadVertexData(deferredVertexPos); final int prevProgram = GLStateManager.glGetInteger(GL20.GL_CURRENT_PROGRAM); final boolean isTextureEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D); - final boolean isBlendEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_BLEND); + final boolean isBlendEnabledBefore = GLStateManager.isEffectiveBlendEnabled(); + final boolean isAlphaTestEnabledBefore = GLStateManager.isEffectiveAlphaTestEnabled(); + GLStateManager.getEffectiveBlendState(deferredBlendStateBefore); + final int shadeModelBefore = GLStateManager.glGetInteger(GL11.GL_SHADE_MODEL); final int boundTextureBefore = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D); final int lightmapBindingBefore = boundLightmapTexture(); final boolean depthTestBefore = GLStateManager.getDepthTest().isEnabled(); - final boolean depthMaskBefore = GLStateManager.getDepthState().isEnabled(); + final boolean depthMaskBefore = GLStateManager.isEffectiveDepthMaskEnabled(); deferredSegments.get(0).owner.setupFontDrawState(); GLStateManager.glDepthMask(false); flushLastTexture = DUMMY_RESOURCE_LOCATION; flushTextureChanged = false; resetFlushLightmap(); - try (MemoryStack stack = stackPush()) { - final FloatBuffer mvpBuf = stack.mallocFloat(16); - for (int i = 0, n = deferredSegments.size(); i < n; i++) { - final TextSegment segment = deferredSegments.get(i); - mvpBuf.clear(); - segment.mvp.get(mvpBuf); - GLStateManager.glUniformMatrix4(segment.owner.mvpMatrixLocation, false, mvpBuf); - uploadLightmap(segment.owner.lightmapLocation, segment.lightmapActive, segment.lightmapU, - segment.lightmapV, segment.lightmapTexture); - drawCommands(segment.owner.batchCommands.elements(), segment.cmdStart, segment.cmdEnd, segment.owner); + try { + try (MemoryStack stack = stackPush()) { + final FloatBuffer mvpBuf = stack.mallocFloat(16); + for (final TextSegment segment : deferredSegments) { + mvpBuf.clear(); + segment.mvp.get(mvpBuf); + GLStateManager.glUniformMatrix4(segment.owner.mvpMatrixLocation, false, mvpBuf); + uploadLightmap(segment.owner.lightmapLocation, segment.lightmapActive, segment.lightmapU, + segment.lightmapV, segment.lightmapTexture); + drawCommands(segment.owner.batchCommands.elements(), segment.cmdStart, segment.cmdEnd, segment.owner); + } } + } finally { + restoreFontDrawState(prevProgram, isTextureEnabledBefore, isBlendEnabledBefore, boundTextureBefore, + lightmapBindingBefore, isAlphaTestEnabledBefore, deferredBlendStateBefore, shadeModelBefore); + restoreDepth(depthTestBefore, depthMaskBefore); + discardDeferredText(); } - restoreFontDrawState(prevProgram, isTextureEnabledBefore, isBlendEnabledBefore, boundTextureBefore, lightmapBindingBefore); - restoreDepth(depthTestBefore, depthMaskBefore); - - discardDeferredText(); } public static void discardDeferredText() { @@ -614,18 +729,47 @@ public class BatchingFontRenderer { } private static final Matrix4f scratchMvp = new Matrix4f(); + private static final Matrix4f scratchModelView = new Matrix4f(); private int fontAAModeLast = -1; private int fontAAStrengthLast = -1; private void flushBatch() { final boolean locked = GLStateManager.acquireDrawLock(); try { - flushBatchInner(); + if (shouldDrawThroughPipeline()) { + flushBatchThroughPipeline(); + } else { + GLStateManager.beginForeignDraw(); + try { + flushBatchInner(); + } finally { + GLStateManager.endForeignDraw(); + } + } } finally { if (locked) GLStateManager.releaseDrawLock(); } } + private boolean shouldDrawThroughPipeline() { + if (FontConfig.fontAAMode != 0) { + return false; + } + + if (!GLStateManager.isMainThread()) { + return false; + } + + final PipelineManager pipelines = Iris.getPipelineManagerNullable(); + if (pipelines == null) { + return false; + } + + return !Tessellator.instance.isDrawing + && pipelines.getPipelineNullable() instanceof DeferredWorldRenderingPipeline drp + && drp.shouldOverrideShaders(); + } + private void flushBatchInner() { final int cmdCount = batchCommands.size(); final int segmentCount = batchSegments.size(); @@ -646,11 +790,14 @@ public class BatchingFontRenderer { Arrays.sort(cmds, batchSealedEnd, cmdCount, FontDrawCmd.DRAW_ORDER_COMPARATOR); final boolean isTextureEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D); - final boolean isBlendEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_BLEND); + final boolean isBlendEnabledBefore = GLStateManager.isEffectiveBlendEnabled(); + final boolean isAlphaTestEnabledBefore = GLStateManager.isEffectiveAlphaTestEnabled(); + GLStateManager.getEffectiveBlendState(blendStateBefore); + final int shadeModelBefore = GLStateManager.glGetInteger(GL11.GL_SHADE_MODEL); final int boundTextureBefore = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D); final int lightmapBindingBefore = boundLightmapTexture(); final boolean depthTestBefore = GLStateManager.getDepthTest().isEnabled(); - final boolean depthMaskBefore = GLStateManager.getDepthState().isEnabled(); + final boolean depthMaskBefore = GLStateManager.isEffectiveDepthMaskEnabled(); setupFontDrawState(); flushLastTexture = DUMMY_RESOURCE_LOCATION; @@ -675,7 +822,8 @@ public class BatchingFontRenderer { drawCommands(cmds, batchSealedEnd, cmdCount, this); } } - restoreFontDrawState(prevProgram, isTextureEnabledBefore, isBlendEnabledBefore, boundTextureBefore, lightmapBindingBefore); + restoreFontDrawState(prevProgram, isTextureEnabledBefore, isBlendEnabledBefore, boundTextureBefore, + lightmapBindingBefore, isAlphaTestEnabledBefore, blendStateBefore, shadeModelBefore); restoreDepth(depthTestBefore, depthMaskBefore); truncateBatchToWatermark(); @@ -783,7 +931,9 @@ public class BatchingFontRenderer { } } - private static void restoreFontDrawState(int prevProgram, boolean textureEnabledBefore, boolean blendEnabledBefore, int boundTextureBefore, int lightmapBindingBefore) { + private static void restoreFontDrawState(int prevProgram, boolean textureEnabledBefore, boolean blendEnabledBefore, + int boundTextureBefore, int lightmapBindingBefore, boolean alphaTestEnabledBefore, BlendState blendBefore, + int shadeModelBefore) { GLStateManager.glUseProgram(prevProgram); GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); @@ -792,9 +942,19 @@ public class BatchingFontRenderer { if (textureEnabledBefore) { GLStateManager.glEnable(GL11.GL_TEXTURE_2D); } - if (!blendEnabledBefore) { + if (blendEnabledBefore) { + GLStateManager.enableBlend(); + } else { GLStateManager.disableBlend(); } + GLStateManager.tryBlendFuncSeparate(blendBefore.getSrcRgb(), blendBefore.getDstRgb(), + blendBefore.getSrcAlpha(), blendBefore.getDstAlpha()); + if (alphaTestEnabledBefore) { + GLStateManager.enableAlphaTest(); + } else { + GLStateManager.disableAlphaTest(); + } + GLStateManager.glShadeModel(shadeModelBefore); if (flushTextureChanged) { GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, boundTextureBefore); } @@ -803,11 +963,156 @@ public class BatchingFontRenderer { } } + private void flushBatchThroughPipeline() { + final int cmdCount = batchCommands.size(); + final int segmentCount = batchSegments.size(); + if (segmentCount == 0 && (vertexDataPos == deferredVertexPos || cmdCount == deferredCmdWatermark)) { + truncateBatchToWatermark(); + return; + } + + final boolean isTextureEnabledBefore = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D); + final boolean isBlendEnabledBefore = GLStateManager.isEffectiveBlendEnabled(); + final boolean isAlphaTestEnabledBefore = GLStateManager.isEffectiveAlphaTestEnabled(); + GLStateManager.getEffectiveBlendState(blendStateBefore); + final int boundTextureBefore = GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D); + final int prevBlockEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity(); + boolean textureChanged = false; + + GLStateManager.enableTexture(); + GLStateManager.enableAlphaTest(); + GLStateManager.enableBlend(); + GLStateManager.tryBlendFuncSeparate(blendSrcRGB, blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO); + + final Boolean prevTranslucency = GbufferPrograms.beginTranslucencyDeclaration(Boolean.TRUE); + GbufferPrograms.setBlockEntityDefaults(); + + final FontDrawCmd[] cmdsData = batchCommands.elements(); + for (int i = 0; i < segmentCount; i++) { + final TextSegment segment = batchSegments.get(i); + Arrays.sort(cmdsData, segment.cmdStart, segment.cmdEnd, FontDrawCmd.DRAW_ORDER_COMPARATOR); + } + Arrays.sort(cmdsData, batchSealedEnd, cmdCount, FontDrawCmd.DRAW_ORDER_COMPARATOR); + + // The unsealed tail still belongs to the modelview live at flush time; capture it before the segments move it. + resolveModelView(scratchModelView); + + GLStateManager.glMatrixMode(GL11.GL_MODELVIEW); + GLStateManager.glPushMatrix(); + try { + for (int i = 0; i < segmentCount; i++) { + final TextSegment segment = batchSegments.get(i); + if (segment.cmdStart == segment.cmdEnd) continue; + GLStateManager.setModelViewMatrix(segment.modelView); + CapturedRenderingState.INSTANCE.setCurrentBlockEntity(segment.blockEntityId); + textureChanged |= emitRangeThroughPipeline(this, cmdsData, segment.cmdStart, segment.cmdEnd, + segment.packedLight, segment.normalX, segment.normalY, segment.normalZ); + } + if (batchSealedEnd < cmdCount) { + GLStateManager.setModelViewMatrix(scratchModelView); + CapturedRenderingState.INSTANCE.setCurrentBlockEntity(prevBlockEntityId); + final Vector3f normal = ShaderManager.getCurrentNormal(); + textureChanged |= emitRangeThroughPipeline(this, cmdsData, batchSealedEnd, cmdCount, + GLSMConfig.packedLastBrightness(), normal.x, normal.y, normal.z); + } + } finally { + GLStateManager.glPopMatrix(); + CapturedRenderingState.INSTANCE.setCurrentBlockEntity(prevBlockEntityId); + GbufferPrograms.endTranslucencyDeclaration(prevTranslucency); + + if (isTextureEnabledBefore) { + GLStateManager.enableTexture(); + } else { + GLStateManager.disableTexture(); + } + if (isBlendEnabledBefore) { + GLStateManager.enableBlend(); + } else { + GLStateManager.disableBlend(); + } + GLStateManager.tryBlendFuncSeparate(blendStateBefore.getSrcRgb(), blendStateBefore.getDstRgb(), + blendStateBefore.getSrcAlpha(), blendStateBefore.getDstAlpha()); + if (isAlphaTestEnabledBefore) { + GLStateManager.enableAlphaTest(); + } else { + GLStateManager.disableAlphaTest(); + } + if (textureChanged) { + GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, boundTextureBefore); + } + + truncateBatchToWatermark(); + } + } + + /** Replays one sealed command range through the Tessellator so Iris sees it as ordinary geometry. */ + private static boolean emitRangeThroughPipeline(BatchingFontRenderer owner, FontDrawCmd[] cmdsData, int from, int to, + int packedLight, float normalX, float normalY, float normalZ) { + final Tessellator tessellator = Tessellator.instance; + ResourceLocation lastTexture = DUMMY_RESOURCE_LOCATION; + boolean textureChanged = false; + boolean drawing = false; + try { + for (int i = from; i < to; i++) { + final FontDrawCmd cmd = cmdsData[i]; + if (!Objects.equals(lastTexture, cmd.texture)) { + if (drawing) { + tessellator.draw(); + drawing = false; + } + if (cmd.texture == null) { + GLStateManager.disableTexture(); + } else { + GLStateManager.enableTexture(); + ((FontRendererAccessor) owner.underlying).angelica$bindTexture(cmd.texture); + textureChanged = true; + } + lastTexture = cmd.texture; + } + if (!drawing) { + tessellator.startDrawingQuads(); + tessellator.setBrightness(packedLight); + tessellator.setNormal(normalX, normalY, normalZ); + drawing = true; + } + owner.emitCommand(tessellator, cmd); + } + } finally { + if (drawing) { + tessellator.draw(); + } + } + return textureChanged; + } + + private void emitCommand(Tessellator tessellator, FontDrawCmd cmd) { + final int firstVertex = cmd.startVtx / 6 * 4; + final int quadCount = cmd.idxCount / 6; + + for (int q = 0; q < quadCount; q++) { + final long base = vertexDataAddress + (long) (firstVertex + q * 4) * VERTEX_SIZE; + emitVertex(tessellator, base); + emitVertex(tessellator, base + VERTEX_SIZE); + emitVertex(tessellator, base + 3L * VERTEX_SIZE); + emitVertex(tessellator, base + 2L * VERTEX_SIZE); + } + } + + private void emitVertex(Tessellator tessellator, long ptr) { + tessellator.setColorRGBA( + memGetByte(ptr + 16) & 0xFF, + memGetByte(ptr + 17) & 0xFF, + memGetByte(ptr + 18) & 0xFF, + memGetByte(ptr + 19) & 0xFF); + tessellator.addVertexWithUV(memGetFloat(ptr), memGetFloat(ptr + 4), 0.0D, + memGetFloat(ptr + 8), memGetFloat(ptr + 12)); + } + private static void restoreDepth(boolean testBefore, boolean maskBefore) { if (testBefore != GLStateManager.getDepthTest().isEnabled()) { if (testBefore) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest(); } - if (maskBefore != GLStateManager.getDepthState().isEnabled()) { + if (maskBefore != GLStateManager.isEffectiveDepthMaskEnabled()) { GLStateManager.glDepthMask(maskBefore); } } @@ -865,7 +1170,7 @@ public class BatchingFontRenderer { } private static int hsvToRgb(float hue, float sat, float val) { - int h = (int)(hue / 60f) % 6; + int h = (int) (hue / 60f) % 6; float f = hue / 60f - h; float p = val * (1 - sat); float q = val * (1 - f * sat); @@ -879,11 +1184,12 @@ public class BatchingFontRenderer { case 4: r=t; g=p; b=val; break; default: r=val; g=p; b=q; break; } - return ((int)(r*255) << 16) | ((int)(g*255) << 8) | (int)(b*255); + return ((int) (r * 255) << 16) | ((int) (g * 255) << 8) | (int) (b * 255); } private static final int RAINBOW_LUT_SIZE = 24; private static final int[] RAINBOW_LUT = new int[RAINBOW_LUT_SIZE]; + static { for (int i = 0; i < RAINBOW_LUT_SIZE; i++) { RAINBOW_LUT[i] = hsvToRgb(i * 15f, 1f, 1f); @@ -918,7 +1224,7 @@ public class BatchingFontRenderer { private static final float WAVE_FREQUENCY = 0.5f; public float drawString(final float anchorX, final float anchorY, final int color, final boolean enableShadow, - final boolean unicodeFlag, final CharSequence string, int stringOffset, int stringLength) { + final boolean unicodeFlag, final CharSequence string, int stringOffset, int stringLength) { // noinspection SizeReplaceableByIsEmpty if (string == null || string.length() == 0) { return anchorX + (enableShadow ? 1.0f : 0.0f); @@ -1007,86 +1313,86 @@ public class BatchingFontRenderer { charIdx += SECTION_X_PAYLOAD; } } else { - final boolean is09 = charInRange(fmtCode, '0', '9'); - final boolean isAF = charInRange(fmtCode, 'a', 'f'); - if (is09 || isAF) { - curRandom = false; - curBold = false; - curStrikethrough = false; - curUnderline = false; - curItalic = false; - curRainbow = false; - curGradient = false; - // wave/dinnerbone NOT reset — they're positional effects, independent of color - - final int colorIdx = is09 ? (fmtCode - '0') : (fmtCode - 'a' + 10); - final int rgb = this.colorCode[colorIdx]; - curColor = (curColor & 0xFF000000) | (rgb & 0x00FFFFFF); - final int shadowRgb = this.colorCode[colorIdx + 16]; - curShadowColor = (curShadowColor & 0xFF000000) | (shadowRgb & 0x00FFFFFF); - } else if (fmtCode == 'k') { - curRandom = true; - } else if (fmtCode == 'l') { - curBold = true; - } else if (fmtCode == 'm') { - curStrikethrough = true; - strikethroughStartX = curX - 1.0f; - strikethroughEndX = strikethroughStartX; - } else if (fmtCode == 'n') { - curUnderline = true; - underlineStartX = curX - 1.0f; - underlineEndX = underlineStartX; - } else if (fmtCode == 'o') { - curItalic = true; - } else if (fmtCode == 'q' && AngelicaConfig.enableRainbow) { - curRainbow = true; - curGradient = false; - rainbowCharIndex = 0; - } else if (fmtCode == 'z' && AngelicaConfig.enableWaveText) { - curWave = !curWave; - } else if (fmtCode == 'v' && AngelicaConfig.enableDinnerboneText) { - curDinnerbone = !curDinnerbone; - } else if (fmtCode == 'u' && AngelicaConfig.enableDropShadow) { - int customRgb = (charIdx + SECTION_X_LENGTH < stringEnd) - ? ColorCodeUtils.parseSectionXAt(string, charIdx + 1) - : -1; - if (customRgb != -1) { - curShadow = true; - curShadowCustomColor = true; - curShadowColorOverride = customRgb; - charIdx += SECTION_X_LENGTH; - } else { - curShadow = !curShadow; - if (!curShadow) curShadowCustomColor = false; - } - } else if (fmtCode == 'g' && AngelicaConfig.enableGradients && charIdx + GRADIENT_PAYLOAD < stringEnd) { - int color1 = ColorCodeUtils.parseSectionXAt(string, charIdx + 1); - int color2 = ColorCodeUtils.parseSectionXAt(string, charIdx + 1 + SECTION_X_LENGTH); - if (color1 != -1 && color2 != -1) { - curGradient = true; + final boolean is09 = charInRange(fmtCode, '0', '9'); + final boolean isAF = charInRange(fmtCode, 'a', 'f'); + if (is09 || isAF) { + curRandom = false; + curBold = false; + curStrikethrough = false; + curUnderline = false; + curItalic = false; curRainbow = false; - gradientStartRgb = color1; - gradientEndRgb = color2; - gradientCharIndex = 0; - gradientTotalChars = countVisibleChars(string, charIdx + 1 + GRADIENT_PAYLOAD, stringEnd); - gradientStep = gradientTotalChars > 1 ? 1f / (gradientTotalChars - 1) : 0f; - charIdx += GRADIENT_PAYLOAD; + curGradient = false; + // wave/dinnerbone NOT reset — they're positional effects, independent of color + + final int colorIdx = is09 ? (fmtCode - '0') : (fmtCode - 'a' + 10); + final int rgb = this.colorCode[colorIdx]; + curColor = (curColor & 0xFF000000) | (rgb & 0x00FFFFFF); + final int shadowRgb = this.colorCode[colorIdx + 16]; + curShadowColor = (curShadowColor & 0xFF000000) | (shadowRgb & 0x00FFFFFF); + } else if (fmtCode == 'k') { + curRandom = true; + } else if (fmtCode == 'l') { + curBold = true; + } else if (fmtCode == 'm') { + curStrikethrough = true; + strikethroughStartX = curX - 1.0f; + strikethroughEndX = strikethroughStartX; + } else if (fmtCode == 'n') { + curUnderline = true; + underlineStartX = curX - 1.0f; + underlineEndX = underlineStartX; + } else if (fmtCode == 'o') { + curItalic = true; + } else if (fmtCode == 'q' && AngelicaConfig.enableRainbow) { + curRainbow = true; + curGradient = false; + rainbowCharIndex = 0; + } else if (fmtCode == 'z' && AngelicaConfig.enableWaveText) { + curWave = !curWave; + } else if (fmtCode == 'v' && AngelicaConfig.enableDinnerboneText) { + curDinnerbone = !curDinnerbone; + } else if (fmtCode == 'u' && AngelicaConfig.enableDropShadow) { + int customRgb = (charIdx + SECTION_X_LENGTH < stringEnd) + ? ColorCodeUtils.parseSectionXAt(string, charIdx + 1) + : -1; + if (customRgb != -1) { + curShadow = true; + curShadowCustomColor = true; + curShadowColorOverride = customRgb; + charIdx += SECTION_X_LENGTH; + } else { + curShadow = !curShadow; + if (!curShadow) curShadowCustomColor = false; + } + } else if (fmtCode == 'g' && AngelicaConfig.enableGradients && charIdx + GRADIENT_PAYLOAD < stringEnd) { + int color1 = ColorCodeUtils.parseSectionXAt(string, charIdx + 1); + int color2 = ColorCodeUtils.parseSectionXAt(string, charIdx + 1 + SECTION_X_LENGTH); + if (color1 != -1 && color2 != -1) { + curGradient = true; + curRainbow = false; + gradientStartRgb = color1; + gradientEndRgb = color2; + gradientCharIndex = 0; + gradientTotalChars = countVisibleChars(string, charIdx + 1 + GRADIENT_PAYLOAD, stringEnd); + gradientStep = gradientTotalChars > 1 ? 1f / (gradientTotalChars - 1) : 0f; + charIdx += GRADIENT_PAYLOAD; + } + } else if (fmtCode == 'r') { + curRandom = false; + curBold = false; + curStrikethrough = false; + curUnderline = false; + curItalic = false; + curRainbow = false; + curWave = false; + curDinnerbone = false; + curGradient = false; + curShadow = false; + curShadowCustomColor = false; + curColor = color; + curShadowColor = shadowColor; } - } else if (fmtCode == 'r') { - curRandom = false; - curBold = false; - curStrikethrough = false; - curUnderline = false; - curItalic = false; - curRainbow = false; - curWave = false; - curDinnerbone = false; - curGradient = false; - curShadow = false; - curShadowCustomColor = false; - curColor = color; - curShadowColor = shadowColor; - } } // close else block for non-§x codes continue; @@ -1130,9 +1436,9 @@ public class BatchingFontRenderer { } if (curGradient && gradientTotalChars > 0) { float t = Math.min(gradientCharIndex * gradientStep, 1f); - int gr = (int)((gradientStartRgb >> 16 & 0xFF) * (1-t) + (gradientEndRgb >> 16 & 0xFF) * t); - int gg = (int)((gradientStartRgb >> 8 & 0xFF) * (1-t) + (gradientEndRgb >> 8 & 0xFF) * t); - int gb = (int)((gradientStartRgb & 0xFF) * (1-t) + (gradientEndRgb & 0xFF) * t); + int gr = (int) ((gradientStartRgb >> 16 & 0xFF) * (1 - t) + (gradientEndRgb >> 16 & 0xFF) * t); + int gg = (int) ((gradientStartRgb >> 8 & 0xFF) * (1 - t) + (gradientEndRgb >> 8 & 0xFF) * t); + int gb = (int) ((gradientStartRgb & 0xFF) * (1 - t) + (gradientEndRgb & 0xFF) * t); int rgbEffect = (gr << 16) | (gg << 8) | gb; curColor = (curColor & 0xFF000000) | rgbEffect; curShadowColor = (curShadowColor & 0xFF000000) | ((rgbEffect & 0xFCFCFC) >> 2); @@ -1155,7 +1461,7 @@ public class BatchingFontRenderer { // Wave: Y offset via sine wave float renderY = heightNorth; if (curWave) { - float time = HUDCaching.renderingCacheOverride ? 0f : (float)((System.nanoTime() & 0xFFFFFFFFFFFFL) * WAVE_TIME_SCALE); + float time = HUDCaching.renderingCacheOverride ? 0f : (float) ((System.nanoTime() & 0xFFFFFFFFFFFFL) * WAVE_TIME_SCALE); renderY += (float) Math.sin(visibleCharIndex * WAVE_FREQUENCY + time) * AngelicaConfig.waveAmplitude; } diff --git a/src/main/java/com/gtnewhorizons/angelica/compat/mojang/RenderLayer.java b/src/main/java/com/gtnewhorizons/angelica/compat/mojang/RenderLayer.java index e17554422..135669928 100644 --- a/src/main/java/com/gtnewhorizons/angelica/compat/mojang/RenderLayer.java +++ b/src/main/java/com/gtnewhorizons/angelica/compat/mojang/RenderLayer.java @@ -15,6 +15,7 @@ import net.coderbot.batchedentityrendering.impl.BlendingStateHolder; import net.coderbot.batchedentityrendering.impl.TransparencyType; import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition; import net.coderbot.iris.layer.GbufferPrograms; +import net.coderbot.iris.layer.PassOverride; import net.minecraft.util.ResourceLocation; import org.lwjgl.opengl.GL11; @@ -68,7 +69,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris return TRANSLUCENT; } - private static MultiPhaseParameters.Builder tesrMaterialPhases(ResourceLocation texture, TesrMaterial material) { + private static MultiPhaseParameters.Builder tesrMaterialPhases(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits) { final MultiPhaseParameters.Builder b = MultiPhaseParameters.builder(); b.texture(texture != null ? new RenderPhase.Texture(texture, false, false) : NO_TEXTURE); if (material.isNoCull()) { @@ -92,6 +93,7 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris case GLINT -> b.transparency(GLINT_TRANSPARENCY); case OPAQUE -> b.transparency(NO_TRANSPARENCY); } + boolean shaderPhaseTaken = true; switch (material.special()) { case GLINT -> b.shader(SPECIAL_GLINT); case BEACON_BEAM -> b.shader(SPECIAL_BEACON_BEAM); @@ -99,9 +101,15 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris final TesrShader shader = material.shader(); if (shader != null) { b.shader(new RenderPhase.Shader("angelica_tesr_shader_" + shader.name(), shader.bind(), shader.release())); + } else { + shaderPhaseTaken = false; } } } + if (!shaderPhaseTaken && pass != PassOverride.NONE) { + b.shader(new RenderPhase.Shader("angelica_tesr_pass" + pass.nameSuffix(), pass::apply, pass::clear)); + } + b.layering(polygonOffset(offsetFactor, offsetUnits)); return b; } @@ -113,12 +121,23 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris GbufferPrograms::teardownSpecialRenderCondition); public static RenderLayer tesr(ResourceLocation texture, TesrMaterial material) { - final MultiPhaseParameters.Builder b = tesrMaterialPhases(texture, material).shadeModel(SMOOTH_SHADE_MODEL); + return tesr(texture, material, PassOverride.NONE, 0.0f, 0.0f); + } + + public static RenderLayer tesr(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits) { + final MultiPhaseParameters.Builder b = tesrMaterialPhases(texture, material, pass, offsetFactor, offsetUnits).shadeModel(SMOOTH_SHADE_MODEL); final boolean sortsTranslucent = material.transparency() != TesrMaterial.Transparency.OPAQUE; - return of("angelica_tesr_" + material.transparency().name().toLowerCase(Locale.ROOT), + return of("angelica_tesr_" + material.transparency().name().toLowerCase(Locale.ROOT) + pass.nameSuffix() + + (offsetFactor == 0.0f && offsetUnits == 0.0f ? "" : "_offset" + offsetFactor + "_" + offsetUnits), BatchVertexFormats.POSITION_COLOR_TEXTURE_LIGHTF_NORMAL, GL11.GL_QUADS, 65536, true, sortsTranslucent, b.build(false)); } + public static RenderLayer tesrNoPass(ResourceLocation texture, TesrMaterial material) { + return of("angelica_tesr_nopass_" + material.transparency().name().toLowerCase(Locale.ROOT), + BatchVertexFormats.POSITION_COLOR_TEXTURE_LIGHTF_NORMAL, GL11.GL_QUADS, 65536, true, false, + tesrMaterialPhases(texture, material, PassOverride.NONE, 0.0f, 0.0f).build(false)); + } + public static RenderLayer getOutline(ResourceLocation texture, RenderPhase.Cull cull) { return of("outline", DefaultVertexFormat.POSITION_COLOR_TEXTURE, 7, 256, RenderLayer.MultiPhaseParameters.builder().texture(new RenderPhase.Texture(texture, false, false)).cull(cull).depthTest(ALWAYS_DEPTH_TEST).alpha(ONE_TENTH_ALPHA).fog(NO_FOG).target(OUTLINE_TARGET).build(RenderLayer.OutlineMode.IS_OUTLINE)); } @@ -313,6 +332,10 @@ public abstract class RenderLayer extends RenderPhase { // Aka: RenderType (Iris return this; } + public void layering(Layering layering) { + this.layering = layering; + } + public Builder shadeModel(RenderPhase.ShadeModel shadeModel) { this.shadeModel = shadeModel; return this; diff --git a/src/main/java/com/gtnewhorizons/angelica/compat/mojang/RenderPhase.java b/src/main/java/com/gtnewhorizons/angelica/compat/mojang/RenderPhase.java index 738a8bcd3..37c70685c 100644 --- a/src/main/java/com/gtnewhorizons/angelica/compat/mojang/RenderPhase.java +++ b/src/main/java/com/gtnewhorizons/angelica/compat/mojang/RenderPhase.java @@ -9,7 +9,9 @@ import net.minecraft.util.ResourceLocation; import org.lwjgl.opengl.GL11; import javax.annotation.Nullable; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.Optional; @@ -77,6 +79,7 @@ public abstract class RenderPhase { protected static final WriteMaskState COLOR_MASK; protected static final WriteMaskState DEPTH_MASK; protected static final Layering NO_LAYERING; + protected static final Fog NO_FOG; protected static final Fog FOG; protected static final Fog BLACK_FOG; @@ -85,6 +88,22 @@ public abstract class RenderPhase { protected static final Target TRANSLUCENT_TARGET; protected static final Shader NO_SHADER = new Shader("no_shader", () -> {}, () -> {}); + private static final Map POLYGON_OFFSET_LAYERINGS = new ConcurrentHashMap<>(); + + public static Layering polygonOffset(float factor, float units) { + if (factor == 0.0f && units == 0.0f) { + return NO_LAYERING; + } + final long key = ((long) Float.floatToIntBits(factor) << 32) | (Float.floatToIntBits(units) & 0xFFFFFFFFL); + return POLYGON_OFFSET_LAYERINGS.computeIfAbsent(key, k -> new Layering("polygon_offset_" + factor + "_" + units, () -> { + GLStateManager.glEnable(GL11.GL_POLYGON_OFFSET_FILL); + GLStateManager.glPolygonOffset(factor, units); + }, () -> { + GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL); + GLStateManager.glPolygonOffset(0.0f, 0.0f); + })); + } + public RenderPhase(String name, Runnable beginAction, Runnable endAction) { this.name = name; this.beginAction = beginAction; diff --git a/src/main/java/com/gtnewhorizons/angelica/config/AngelicaConfig.java b/src/main/java/com/gtnewhorizons/angelica/config/AngelicaConfig.java index a0d432c11..b41816362 100644 --- a/src/main/java/com/gtnewhorizons/angelica/config/AngelicaConfig.java +++ b/src/main/java/com/gtnewhorizons/angelica/config/AngelicaConfig.java @@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.config; import com.gtnewhorizon.gtnhlib.config.Config; import com.gtnewhorizons.angelica.rendering.culling.GpuCulling; +import net.coderbot.iris.shadows.ShadowGraphGate; @Config(modid = "angelica", filename = "angelica-modules") public class AngelicaConfig { @@ -347,6 +348,20 @@ public class AngelicaConfig { @Config.DefaultBoolean(true) public static boolean shadowSkipInMeshTileEntities; + @Config.Comment({ + "How much a celestial body can move before the shadow map can update.", + "", + "Note this option is disabled when a shader pack utilizes voxelization features." + }) + @Config.DefaultFloat(ShadowGraphGate.DEFAULT_ANGLE_DELTA_DEGREES) + @Config.RangeFloat(min = 0f, max = 0.1f) + public static float shadowGraphAngleDelta; + + @Config.Comment("Adjusts the rate the shadow map rebuilds when when a celestial body is near the horizon. Rate is sinusoidal") + @Config.DefaultFloat(ShadowGraphGate.DEFAULT_HORIZON_SCALE) + @Config.RangeFloat(min = 0f, max = 1.0f) + public static float shadowGraphHorizonScale; + @Config.Comment("ASM transformer exclusion narrowing for mod compatibility. Disable per-mod if narrowing causes class loading issues.") public static TransformerCompat transformerCompat = new TransformerCompat(); diff --git a/src/main/java/com/gtnewhorizons/angelica/config/FontConfig.java b/src/main/java/com/gtnewhorizons/angelica/config/FontConfig.java index 2173cecc1..3f0fb8db7 100644 --- a/src/main/java/com/gtnewhorizons/angelica/config/FontConfig.java +++ b/src/main/java/com/gtnewhorizons/angelica/config/FontConfig.java @@ -56,7 +56,9 @@ public class FontConfig { @Config.RangeFloat(min = -2F, max = 2F) public static float glyphSpacing; - @Config.Comment("Controls font antialiasing. 0 = none, 1 = 4x MSAA, 2 = 16x MSAA.") + @Config.Comment({ + "Controls font antialiasing. 0 = none, 1 = 4x MSAA, 2 = 16x MSAA.", + "Turning off AA lets shaders pick up the text."}) @Config.DefaultInt(2) @Config.RangeInt(min = 0, max = 2) public static int fontAAMode; diff --git a/src/main/java/com/gtnewhorizons/angelica/iris/IrisGLSMBridge.java b/src/main/java/com/gtnewhorizons/angelica/iris/IrisGLSMBridge.java index 25e17c7fc..04b312933 100644 --- a/src/main/java/com/gtnewhorizons/angelica/iris/IrisGLSMBridge.java +++ b/src/main/java/com/gtnewhorizons/angelica/iris/IrisGLSMBridge.java @@ -10,6 +10,9 @@ import com.gtnewhorizons.angelica.glsm.hooks.ShaderTransformPostProcessor; import com.gtnewhorizons.angelica.glsm.shader.ShaderType; import org.taumc.glsl.grammar.GLSLParser; import com.gtnewhorizons.angelica.glsm.hooks.ShaderWorkSubmitter; +import com.gtnewhorizons.angelica.glsm.hooks.VanillaBooleanLayer; +import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer; +import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate; import net.coderbot.iris.Iris; import java.util.concurrent.CompletableFuture; @@ -38,7 +41,20 @@ public class IrisGLSMBridge { private static Runnable fogDensityListener = null; private static Runnable colorModulatorListener = null; + private static boolean inputsDeferred = false; + + private static boolean blendDeferred = false; + + private static boolean programRestoreDeferred = false; + private static final Int2IntOpenHashMap programLastUpdatedFrame = new Int2IntOpenHashMap(); + + private static void refreshBlendCondition() { + if (Iris.getPipelineManager().getPipelineNullable() instanceof DeferredWorldRenderingPipeline drp) { + drp.onVanillaBlendChanged(); + } + } + static { programLastUpdatedFrame.defaultReturnValue(-1); StateUpdateNotifiers.alphaFuncNotifier = listener -> alphaFuncListener = listener; @@ -72,6 +88,44 @@ public class IrisGLSMBridge { }; } + private static VanillaBooleanLayer gated(VanillaBooleanLayer layer) { + return new VanillaBooleanLayer() { + @Override + public boolean isOverrideHeld() { + return Iris.enabled && layer.isOverrideHeld(); + } + + @Override + public boolean getVanilla() { + return layer.getVanilla(); + } + + @Override + public void setVanilla(boolean enabled) { + layer.setVanilla(enabled); + } + }; + } + + private static VanillaStateLayer gated(VanillaStateLayer layer) { + return new VanillaStateLayer<>() { + @Override + public boolean isOverrideHeld() { + return Iris.enabled && layer.isOverrideHeld(); + } + + @Override + public void readVanilla(T into) { + layer.readVanilla(into); + } + + @Override + public void writeVanilla(T from) { + layer.writeVanilla(from); + } + }; + } + public static void register() { GLSMConfig.expandVertexFormats = Iris.enabled; IrisSamplers.initRenderer(); @@ -88,6 +142,11 @@ public class IrisGLSMBridge { return Iris.enabled && BlendModeStorage.isBlendLocked(); } + @Override + public boolean isOverrideHeld() { + return Iris.enabled && BlendModeStorage.isOverrideHeld(); + } + @Override public void deferBlendModeToggle(boolean enabled) { BlendModeStorage.deferBlendModeToggle(enabled); @@ -104,6 +163,13 @@ public class IrisGLSMBridge { } }; + GLStateManager.getBlendMode().setVanillaLayer(gated(BlendModeStorage.ENABLE_LAYER)); + GLStateManager.getBlendState().setVanillaLayer(gated(BlendModeStorage.FUNC_LAYER)); + GLStateManager.getAlphaTest().setVanillaLayer(gated(AlphaTestStorage.ENABLE_LAYER)); + GLStateManager.getAlphaState().setVanillaLayer(gated(AlphaTestStorage.FUNC_LAYER)); + GLStateManager.getDepthState().setVanillaLayer(gated(DepthColorStorage.DEPTH_LAYER)); + GLStateManager.getColorMask().setVanillaLayer(gated(DepthColorStorage.COLOR_LAYER)); + GLSMHooks.alphaHandler = new DeferredAlphaHandler() { @Override public boolean isAlphaTestLocked() { @@ -127,6 +193,11 @@ public class IrisGLSMBridge { return Iris.enabled && DepthColorStorage.isDepthColorLocked(); } + @Override + public boolean isOverrideHeld() { + return Iris.enabled && DepthColorStorage.isOverrideHeld(); + } + @Override public void deferDepthEnable(boolean enabled) { DepthColorStorage.deferDepthEnable(enabled); @@ -190,9 +261,41 @@ public class IrisGLSMBridge { StateTracker.INSTANCE.lightmapSampler = event.enabled; updatePipeline = true; } - if (updatePipeline) { + if (!updatePipeline) return; + + if (GLStateManager.isForeignDraw()) { + inputsDeferred = true; + return; + } + Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setInputs(StateTracker.INSTANCE.getInputs())); + }); + + GLSMHooks.VANILLA_BLEND_CHANGE.addListener(event -> { + if (!Iris.enabled) return; + if (GLStateManager.isForeignDraw()) { + blendDeferred = true; + return; + } + refreshBlendCondition(); + }); + + GLSMHooks.FOREIGN_DRAW_END.addListener(event -> { + if (!Iris.enabled) return; + if (inputsDeferred) { + inputsDeferred = false; Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setInputs(StateTracker.INSTANCE.getInputs())); } + if (blendDeferred) { + blendDeferred = false; + refreshBlendCondition(); + } + if (programRestoreDeferred) { + programRestoreDeferred = false; + final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable(); + if (pipeline instanceof DeferredWorldRenderingPipeline drp && drp.shouldOverrideShaders()) { + drp.restorePassAfterModProgram(GLStateManager.getActiveProgram()); + } + } }); GLSMHooks.PROGRAM_CHANGE.addListener(event -> { @@ -208,15 +311,29 @@ public class IrisGLSMBridge { final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable(); if (!(pipeline instanceof DeferredWorldRenderingPipeline drp)) return; if (!drp.shouldOverrideShaders()) return; - if (drp.getActivePassProgramId() == -1) return; + DepthColorStorage.unlockDepthColor(); - if (DepthColorStorage.isOwnedProgram(event.newProgram)) { - DepthColorStorage.unlockDepthColor(); - } else { + if (event.newProgram != 0 && !DepthColorStorage.isOwnedProgram(event.newProgram)) { drp.onModProgramOverride(); } }); + GLSMHooks.PROGRAM_CHANGE.addListener(event -> { + if (!Iris.enabled) return; + if (!event.postBind) return; + + final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable(); + if (!(pipeline instanceof DeferredWorldRenderingPipeline drp)) return; + if (!drp.shouldOverrideShaders()) return; + + if (GLStateManager.isForeignDraw()) { + programRestoreDeferred = true; + return; + } + + drp.restorePassAfterModProgram(event.newProgram); + }); + GLSMHooks.PROGRAM_CHANGE.addListener(event -> { if (!Iris.enabled) return; if (!event.postBind) return; diff --git a/src/main/java/com/gtnewhorizons/angelica/mixins/Mixins.java b/src/main/java/com/gtnewhorizons/angelica/mixins/Mixins.java index da4811c62..fbf306df5 100644 --- a/src/main/java/com/gtnewhorizons/angelica/mixins/Mixins.java +++ b/src/main/java/com/gtnewhorizons/angelica/mixins/Mixins.java @@ -417,11 +417,13 @@ public enum Mixins implements IMixins { .addClientMixins( "shaders.MixinDroppedItemGlintEdges" , "shaders.MixinHeldItemGlintEdges" + , "shaders.MixinEffectRenderer_Particles" , "shaders.MixinEntityPickupFX" , "shaders.MixinEntityRenderer" , "shaders.MixinGuiIngameForge" , "shaders.MixinFramebuffer" , "shaders.MixinItem" + , "shaders.MixinItemRenderer_ItemId" , "shaders.MixinLocale" , "shaders.MixinRender" , "shaders.MixinRenderBiped" @@ -744,6 +746,24 @@ public enum Mixins implements IMixins { .setApplyIf(() -> AngelicaConfig.enableIris) .addClientMixins("client.etfuturum.MixinLayerBetterElytra") ), + ET_FUTURUM_BEACON_BEAM(new MixinBuilder("Iris Et Futurum Beacon Beam") + .setPhase(Phase.LATE) + .addRequiredMod(TargetedMod.ET_FUTURUM_REQUIEM) + .setApplyIf(() -> AngelicaConfig.enableIris) + .addClientMixins("client.etfuturum.MixinTileEntityNewBeaconRenderer") + ), + OPENBLOCKS_TROPHY_ENTITY_GBUFFER(new MixinBuilder("Render OpenBlocks trophies with the entity gbuffer programs") + .setPhase(Phase.LATE) + .addRequiredMod(TargetedMod.OPENBLOCKS) + .setApplyIf(() -> AngelicaConfig.enableIris) + .addClientMixins("client.openblocks.MixinTileEntityTrophyRenderer") + ), + DRAGONAPI_PARTICLE_SPLIT(new MixinBuilder("Split DragonAPI's replacement particle renderer between the particle gbuffer programs") + .setPhase(Phase.LATE) + .addRequiredMod(TargetedMod.DRAGON_API) + .setApplyIf(() -> AngelicaConfig.enableIris) + .addClientMixins("client.dragonapi.MixinThrottleableEffectRenderer") + ), MCPATCHER_FORGE(new MixinBuilder() .setPhase(Phase.EARLY) .setApplyIf(() -> AngelicaConfig.enableMCPatcherForgeFeatures) diff --git a/src/main/java/com/gtnewhorizons/angelica/mixins/TargetedMod.java b/src/main/java/com/gtnewhorizons/angelica/mixins/TargetedMod.java index 45b745870..3402fe1bc 100644 --- a/src/main/java/com/gtnewhorizons/angelica/mixins/TargetedMod.java +++ b/src/main/java/com/gtnewhorizons/angelica/mixins/TargetedMod.java @@ -23,6 +23,7 @@ public enum TargetedMod implements ITargetMod { NTM_SPACE("com.hbm.dim.SolarSystem"), IC2("ic2.core.coremod.IC2core", "IC2"), NATURA(null, "Natura"), + OPENBLOCKS(null, "OpenBlocks"), SECURITYCRAFT(null, "securitycraft"), STARMINER("jp.mc.ancientred.starminer.core.SMPlugin", "modJ_StarMiner"), SUPERNOVA("com.mitchej123.supernova.core.SupernovaCore", "supernova"), diff --git a/src/main/java/com/gtnewhorizons/angelica/mixins/interfaces/ItemRendererAccessor.java b/src/main/java/com/gtnewhorizons/angelica/mixins/interfaces/ItemRendererAccessor.java new file mode 100644 index 000000000..025987bde --- /dev/null +++ b/src/main/java/com/gtnewhorizons/angelica/mixins/interfaces/ItemRendererAccessor.java @@ -0,0 +1,8 @@ +package com.gtnewhorizons.angelica.mixins.interfaces; + +import net.minecraft.item.ItemStack; + +public interface ItemRendererAccessor { + + ItemStack angelica$getItemToRender(); +} diff --git a/src/main/java/com/gtnewhorizons/angelica/render/SelectionBoxRenderer.java b/src/main/java/com/gtnewhorizons/angelica/render/SelectionBoxRenderer.java index 86d781872..5eeac5963 100644 --- a/src/main/java/com/gtnewhorizons/angelica/render/SelectionBoxRenderer.java +++ b/src/main/java/com/gtnewhorizons/angelica/render/SelectionBoxRenderer.java @@ -73,6 +73,9 @@ public final class SelectionBoxRenderer { public static void draw(AxisAlignedBB aabb, int color) { if (program == null || vao == null) return; + GLStateManager.enableDepthTest(); + GLStateManager.glDepthFunc(GL11.GL_LEQUAL); + final float minX = (float) aabb.minX, minY = (float) aabb.minY, minZ = (float) aabb.minZ; final float extX = (float) (aabb.maxX - aabb.minX); final float extY = (float) (aabb.maxY - aabb.minY); diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/DeferredEntityOverlay.java b/src/main/java/com/gtnewhorizons/angelica/rendering/DeferredEntityOverlay.java index 197505b5e..40a47671b 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/DeferredEntityOverlay.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/DeferredEntityOverlay.java @@ -123,11 +123,11 @@ public class DeferredEntityOverlay { GbufferPrograms.beginEntities(); try { for (DeferredEntry entry : deferred) { - CapturedRenderingState.INSTANCE.setCurrentEntity(EntityIdHelper.getEntityId(entry.entity)); + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(EntityIdHelper.getEntityId(entry.entity), 0); renderOverlay(entry); } } finally { - CapturedRenderingState.INSTANCE.setCurrentEntity(-1); + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(-1, 0); GbufferPrograms.endEntities(); } recycle(); diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/ParticleRunSplitter.java b/src/main/java/com/gtnewhorizons/angelica/rendering/ParticleRunSplitter.java new file mode 100644 index 000000000..e8b170eae --- /dev/null +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/ParticleRunSplitter.java @@ -0,0 +1,63 @@ +package com.gtnewhorizons.angelica.rendering; + +import com.gtnewhorizons.angelica.client.rendering.DeferredDrawBatcher; +import com.gtnewhorizons.angelica.rendering.celeritas.SpriteExtension; +import net.coderbot.iris.layer.GbufferPrograms; +import net.minecraft.client.particle.EntityCloudFX; +import net.minecraft.client.particle.EntityFX; +import net.minecraft.client.particle.EntityFireworkOverlayFX; +import net.minecraft.client.particle.EntityFireworkSparkFX; +import net.minecraft.client.particle.EntityFireworkStarterFX; +import net.minecraft.client.particle.EntitySpellParticleFX; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import org.embeddedt.embeddium.impl.render.chunk.sprite.SpriteTransparencyLevel; + +/** + * Splits a particle run between {@code gbuffers_particles} and {@code gbuffers_particles_translucent}. + */ +public final class ParticleRunSplitter { + + private static boolean runTranslucent; + + private ParticleRunSplitter() {} + + public static void beginRun() { + runTranslucent = false; + GbufferPrograms.setTranslucencyDeclaration(Boolean.FALSE); + } + + public static void splitIfNeeded(EntityFX particle, Tessellator tessellator, float partialTicks) { + final boolean translucent = isTranslucent(particle); + if (translucent == runTranslucent) { + return; + } + + final boolean batcherWasActive = DeferredDrawBatcher.isActive(); + if (batcherWasActive) { + DeferredDrawBatcher.exitAndFlush(); + } + tessellator.draw(); + + runTranslucent = translucent; + GbufferPrograms.setTranslucencyDeclaration(translucent); + + tessellator.startDrawingQuads(); + tessellator.setBrightness(particle.getBrightnessForRender(partialTicks)); + if (batcherWasActive) { + DeferredDrawBatcher.enter(); + } + } + + public static boolean isTranslucent(EntityFX particle) { + if (particle.particleIcon instanceof TextureAtlasSprite sprite) { + return ((SpriteExtension) sprite).celeritas$getTransparencyLevel() == SpriteTransparencyLevel.TRANSLUCENT; + } + + return particle instanceof EntityFireworkSparkFX + || particle instanceof EntityFireworkOverlayFX + || particle instanceof EntityFireworkStarterFX + || particle instanceof EntitySpellParticleFX + || particle instanceof EntityCloudFX; + } +} diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/PlayerReflectionCapture.java b/src/main/java/com/gtnewhorizons/angelica/rendering/PlayerReflectionCapture.java index ad1377945..8c62753c7 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/PlayerReflectionCapture.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/PlayerReflectionCapture.java @@ -71,6 +71,7 @@ public final class PlayerReflectionCapture { private static final Matrix4f pendingModelView = new Matrix4f(); private static int pendingSkin; private static int pendingEntityId; + private static int pendingItemId; private static int vbo; private static int vao; @@ -134,6 +135,7 @@ public final class PlayerReflectionCapture { pendingModelView.set(GLStateManager.getModelViewMatrix()); pendingSkin = GLStateManager.getBoundTextureForServerState(0); pendingEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(); + pendingItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem(); pending = true; } @@ -328,9 +330,10 @@ public final class PlayerReflectionCapture { final boolean prevDepthWrite = GLStateManager.getDepthState().isEnabled(); final int prevEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(); + final int prevItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem(); savedModelView.set(GLStateManager.getModelViewMatrix()); - CapturedRenderingState.INSTANCE.setCurrentEntity(pendingEntityId); + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(pendingEntityId, pendingItemId); GLStateManager.setModelViewMatrix(pendingModelView); if (maskable) { GLStateManager.glColorMask(false, false, false, false); @@ -344,7 +347,7 @@ public final class PlayerReflectionCapture { GLStateManager.glColorMask(pr, pg, pb, pa); } GLStateManager.setModelViewMatrix(savedModelView); - CapturedRenderingState.INSTANCE.setCurrentEntity(prevEntityId); + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(prevEntityId, prevItemId); GLStateManager.glBindVertexArray(0); GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, prevTexture); diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkBuildContext.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkBuildContext.java index 736a1a98d..0ae495e1a 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkBuildContext.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/AngelicaChunkBuildContext.java @@ -28,6 +28,7 @@ import org.embeddedt.embeddium.impl.model.light.data.QuadLightData; import org.embeddedt.embeddium.impl.model.light.flat.FlatLightPipeline; import org.embeddedt.embeddium.impl.model.light.smooth.SmoothLightPipeline; import org.embeddedt.embeddium.impl.model.quad.properties.ModelQuadFacing; +import org.embeddedt.embeddium.impl.model.quad.properties.ModelQuadFlags; import org.embeddedt.embeddium.impl.render.chunk.ChunkColorWriter; import org.embeddedt.embeddium.impl.render.chunk.RenderPassConfiguration; import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildBuffers; @@ -43,6 +44,9 @@ import java.util.List; public class AngelicaChunkBuildContext extends ChunkBuildContext { public static final int NUM_PASSES = 2; + // Yoinked from Celeritas + private static final int[] REVERSED_QUAD_ORDER = { 0, 3, 2, 1 }; + private final TextureMapExtension textureAtlas; private final ChunkVertexEncoder.Vertex[] vertices = ChunkVertexEncoder.Vertex.uninitializedQuad(); @Getter @@ -62,6 +66,8 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext { private boolean lightPipelineReady = false; private int originX, originY, originZ; private float cachedSkylightSubtracted; + private final float[] fluidQuadPos = new float[12]; + private ModelQuadFacing fluidQuadLightFace; public AngelicaChunkBuildContext(RenderPassConfiguration renderPassConfiguration, WorldClient world) { super(renderPassConfiguration); @@ -160,6 +166,7 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext { boolean hasCachedMaterial = false; TextureAtlasSprite lastSprite = null; Material lastMaterial = null; + this.fluidQuadLightFace = null; for (int quadIdx = 0; quadIdx < numQuads; quadIdx++) { float uSum = 0, vSum = 0; @@ -201,11 +208,8 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext { // Otherwise, we need to use the block's lightmap values as they are as the ISBRH may have // specified its own lightmaps for visual purposes (e.g. fullbright) if (useAoCalculation && (quadState & StateAwareTessellator.RENDERED_WITH_VANILLA_AO) != 0) { - quadView.setup(trueNormal, blockX, blockY, blockZ); - final ModelQuadFacing lightFace = quadView.getLightFace(); - final LightPipeline pipeline = blockAllowsSmoothLighting ? smoothLightPipeline : flatLightPipeline; - final ModelQuadFacing cullFace = quadView.getCullFace(); - pipeline.calculate(quadView, worldX, worldY, worldZ, quadLightData, cullFace, lightFace, shouldApplyDiffuse, true); + calculateQuadLight(trueNormal, blockX, blockY, blockZ, worldX, worldY, worldZ, isFluid, + blockAllowsSmoothLighting, shouldApplyDiffuse); for (int vIdx = 0; vIdx < 4; vIdx++) { final var vertex = vertices[vIdx]; @@ -227,11 +231,8 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext { } if (useAoCalculation && isFluid) { - quadView.setup(trueNormal, blockX, blockY, blockZ); - final ModelQuadFacing lightFace = quadView.getLightFace(); - final LightPipeline pipeline = blockAllowsSmoothLighting ? smoothLightPipeline : flatLightPipeline; - final ModelQuadFacing cullFace = quadView.getCullFace(); - pipeline.calculate(quadView, worldX, worldY, worldZ, quadLightData, cullFace, lightFace, shouldApplyDiffuse, true); + calculateQuadLight(trueNormal, blockX, blockY, blockZ, worldX, worldY, worldZ, true, + blockAllowsSmoothLighting, shouldApplyDiffuse); for (int vIdx = 0; vIdx < 4; vIdx++) { vertices[vIdx].light = quadLightData.lm[vIdx]; } @@ -274,6 +275,42 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext { } } + private void calculateQuadLight(int trueNormal, int blockX, int blockY, int blockZ, int worldX, int worldY, + int worldZ, boolean isFluid, boolean blockAllowsSmoothLighting, + boolean shouldApplyDiffuse) { + final boolean isFlippedFluidCopy = isFluid && fluidQuadLightFace != null && isReversedFluidQuad(); + + quadView.setup(trueNormal, blockX, blockY, blockZ, isFluid ? ModelQuadFlags.IS_VANILLA_SHADED : 0, + isFlippedFluidCopy ? fluidQuadLightFace : null); + + final LightPipeline pipeline = blockAllowsSmoothLighting ? smoothLightPipeline : flatLightPipeline; + pipeline.calculate(quadView, worldX, worldY, worldZ, quadLightData, quadView.getCullFace(), + quadView.getLightFace(), shouldApplyDiffuse, true); + + if (isFluid && !isFlippedFluidCopy) { + fluidQuadLightFace = quadView.getLightFace(); + for (int vIdx = 0; vIdx < 4; vIdx++) { + final var vertex = vertices[vIdx]; + fluidQuadPos[vIdx * 3] = vertex.x; + fluidQuadPos[vIdx * 3 + 1] = vertex.y; + fluidQuadPos[vIdx * 3 + 2] = vertex.z; + } + } else if (!isFluid) { + fluidQuadLightFace = null; + } + } + + private boolean isReversedFluidQuad() { + for (int vIdx = 0; vIdx < 4; vIdx++) { + final int src = REVERSED_QUAD_ORDER[vIdx] * 3; + final var vertex = vertices[vIdx]; + if (vertex.x != fluidQuadPos[src] || vertex.y != fluidQuadPos[src + 1] || vertex.z != fluidQuadPos[src + 2]) { + return false; + } + } + return true; + } + private void applyBlockLightTint(int x, int y, int z, ChunkVertexEncoder.Vertex[] vertices) { if (!hasColoredLight) return; final ExtQuadLightData ext = (ExtQuadLightData) quadLightData; diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/VertexArrayQuadView.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/VertexArrayQuadView.java index 03475bc79..a0dd28b3d 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/VertexArrayQuadView.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/VertexArrayQuadView.java @@ -13,18 +13,24 @@ public class VertexArrayQuadView implements ModelQuadView { private int blockX, blockY, blockZ; private ModelQuadFacing lightFace; private int flags; + private int extraFlags; public VertexArrayQuadView(ChunkVertexEncoder.Vertex[] vertices) { this.vertices = vertices; } public void setup(int trueNormal, int blockX, int blockY, int blockZ) { + setup(trueNormal, blockX, blockY, blockZ, 0, null); + } + + public void setup(int trueNormal, int blockX, int blockY, int blockZ, int extraFlags, ModelQuadFacing forcedLightFace) { this.trueNormal = trueNormal; this.blockX = blockX; this.blockY = blockY; this.blockZ = blockZ; - this.lightFace = null; // Reset cached lightFace + this.lightFace = forcedLightFace; this.flags = 0; + this.extraFlags = extraFlags; } @Override @@ -65,7 +71,7 @@ public class VertexArrayQuadView implements ModelQuadView { @Override public int getFlags() { if (flags == 0) { - flags = ModelQuadFlags.getQuadFlags(this, getLightFace()); + flags = ModelQuadFlags.getQuadFlags(this, getLightFace(), extraFlags); } return flags; } diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/light/LightDataCache.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/light/LightDataCache.java index fa07385cd..dade5b337 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/light/LightDataCache.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/light/LightDataCache.java @@ -1,5 +1,6 @@ package com.gtnewhorizons.angelica.rendering.celeritas.light; +import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice; import lombok.Setter; import net.minecraft.block.Block; import net.minecraft.world.IBlockAccess; @@ -9,6 +10,16 @@ import org.embeddedt.embeddium.impl.model.light.data.LightDataAccess; public class LightDataCache extends LightDataAccess { private IBlockAccess world; + /** + * Doing this to force packs like Steadfast's water to work properly. + */ + private static int skyLight(IBlockAccess world, Block block, int x, int y, int z, int neighborMaxed) { + if (world instanceof WorldSlice slice && block.getMaterial().isLiquid()) { + return slice.getRawSkyLight(x, y, z); + } + return neighborMaxed; + } + @Override protected int compute(int x, int y, int z) { final IBlockAccess world = this.world; @@ -26,7 +37,7 @@ public class LightDataCache extends LightDataAccess { } else { final int packed = world.getLightBrightnessForSkyBlocks(x, y, z, 0); bl = (packed >> 4) & 0xF; - sl = (packed >> 20) & 0xF; + sl = skyLight(world, block, x, y, z, (packed >> 20) & 0xF); } // AO value - blocks that emit light don't contribute to AO diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/world/WorldSlice.java b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/world/WorldSlice.java index 0b3643280..2ad195053 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/world/WorldSlice.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/celeritas/world/WorldSlice.java @@ -276,6 +276,17 @@ public class WorldSlice implements IBlockAccessExtended, FLBlockAccess { return skyBrightness << 20 | blockBrightness << 4; } + public int getRawSkyLight(int x, int y, int z) { + final int minHeight = ModStatus.isCubicChunksLoaded ? CubicChunksAPI.getMinHeight(this.world) : 0; + final int maxHeight = ModStatus.isCubicChunksLoaded ? CubicChunksAPI.getMaxHeight(this.world) : 256; + + if (y < minHeight || y >= maxHeight || x < -30_000_000 || z < -30_000_000 || x >= 30_000_000 || z >= 30_000_000) { + return 15; + } + + return this.getLightLevel(EnumSkyBlock.Sky, x, y, z); + } + private int getSkyBlockTypeBrightness(EnumSkyBlock skyBlock, int x, int y, int z) { if (this.getBlock(x, y, z).getUseNeighborBrightness()) { int yp = this.getLightLevel(skyBlock, x, y + 1, z); 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 56306abdb..953375112 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/ModelPartBatcher.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/ModelPartBatcher.java @@ -15,6 +15,7 @@ import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig; import com.gtnewhorizons.angelica.glsm.profiling.Tracy; import com.gtnewhorizons.angelica.glsm.states.BlendState; import com.gtnewhorizons.angelica.glsm.states.Color4; +import com.gtnewhorizons.angelica.glsm.states.PolygonState; import com.gtnewhorizons.angelica.profiling.BailClassCounts; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectIterator; @@ -24,6 +25,7 @@ import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource; import net.coderbot.batchedentityrendering.impl.TransparencyType; import net.coderbot.iris.Iris; import net.coderbot.iris.layer.GbufferPrograms; +import net.coderbot.iris.layer.PassOverride; import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline; import net.coderbot.iris.uniforms.CapturedRenderingState; import net.minecraft.client.model.ModelBox; @@ -105,22 +107,34 @@ public final class ModelPartBatcher { private static final class LayerKey { ResourceLocation texture; TesrMaterial material; + PassOverride pass; + float offsetFactor; + float offsetUnits; - LayerKey set(ResourceLocation texture, TesrMaterial material) { + LayerKey set(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits) { this.texture = texture; this.material = material; + this.pass = pass; + this.offsetFactor = offsetFactor; + this.offsetUnits = offsetUnits; return this; } @Override public boolean equals(Object o) { if (!(o instanceof LayerKey other)) return false; - return Objects.equals(texture, other.texture) && Objects.equals(material, other.material); + return Objects.equals(texture, other.texture) && Objects.equals(material, other.material) + && Objects.equals(pass, other.pass) + && Float.floatToIntBits(offsetFactor) == Float.floatToIntBits(other.offsetFactor) + && Float.floatToIntBits(offsetUnits) == Float.floatToIntBits(other.offsetUnits); } @Override public int hashCode() { - return Objects.hashCode(texture) * 31 + Objects.hashCode(material); + int h = (Objects.hashCode(texture) * 31 + Objects.hashCode(material)) * 31 + Objects.hashCode(pass); + h = h * 31 + Float.floatToIntBits(offsetFactor); + h = h * 31 + Float.floatToIntBits(offsetUnits); + return h; } } @@ -128,6 +142,9 @@ public final class ModelPartBatcher { private final LayerKey scratchKey = new LayerKey(); private ResourceLocation lastLayerTexture; private TesrMaterial lastLayerMaterial; + private PassOverride lastLayerPass; + private float lastLayerOffsetFactor; + private float lastLayerOffsetUnits; private RenderLayer lastLayer; private static ResourceLocation lastBoundLocation; @@ -177,7 +194,9 @@ public final class ModelPartBatcher { if (instancedThisCycle) { TesrBatchRenderer.INSTANCE.instancedRenderer.endFrame(); } - BatchingFontRenderer.flushDeferredText(); + if (!TesrBatchRenderer.INSTANCE.hasPendingGeometry()) { + BatchingFontRenderer.flushDeferredText(); + } return; } final boolean inEntityLoop = mode == Mode.ENTITIES && Iris.enabled && GbufferPrograms.isEntityLoopActive(); @@ -193,6 +212,9 @@ public final class ModelPartBatcher { if (instancedThisCycle) { TesrBatchRenderer.INSTANCE.instancedRenderer.endFrame(); } + if (!TesrBatchRenderer.INSTANCE.hasPendingGeometry()) { + BatchingFontRenderer.flushDeferredText(); + } } public boolean isActive() { @@ -278,11 +300,19 @@ public final class ModelPartBatcher { } } } - final RenderLayer layer = layerFor(texture, material); - final int entityId = mode == Mode.ENTITIES ? Math.max(0, CapturedRenderingState.INSTANCE.getCurrentRenderedEntity()) : CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity(); + final PassOverride pass = shaderPipeline != null ? PassOverride.capture() : PassOverride.NONE; + final boolean offset = GLStateManager.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL); + final PolygonState polygon = GLStateManager.getPolygonState(); + final float offsetFactor = offset ? polygon.getOffsetFactor() : 0.0f; + final float offsetUnits = offset ? polygon.getOffsetUnits() : 0.0f; + final RenderLayer layer = layerFor(texture, material, pass, offsetFactor, offsetUnits); + // Entities nested inside a TESR (mob spawner, OpenBlocks trophy) run in the block entity pass but carry an entity id + final int entityId = mode == Mode.ENTITIES || pass.isEntityPhase() + ? Math.max(0, CapturedRenderingState.INSTANCE.getCurrentRenderedEntity()) + : CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity(); final Color4 color = GLStateManager.getColor(); final int colorABGR = ColorABGR.pack(color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha()); - final int packedLight = ((int) GLSMConfig.lastBrightnessY << 16) | ((int) GLSMConfig.lastBrightnessX & 0xFFFF); + final int packedLight = GLSMConfig.packedLastBrightness(); final int mvGen = GLStateManager.getMvGeneration(); if (!mvCaptured || mvGen != lastMvGeneration) { modelView.set(GLStateManager.getModelViewMatrix()); @@ -349,17 +379,21 @@ public final class ModelPartBatcher { return entry; } - private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material) { - if (texture == lastLayerTexture && material == lastLayerMaterial) { + private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits) { + if (texture == lastLayerTexture && material == lastLayerMaterial && pass.equals(lastLayerPass) + && offsetFactor == lastLayerOffsetFactor && offsetUnits == lastLayerOffsetUnits) { return lastLayer; } - RenderLayer layer = layers.get(scratchKey.set(texture, material)); + RenderLayer layer = layers.get(scratchKey.set(texture, material, pass, offsetFactor, offsetUnits)); if (layer == null) { - layer = RenderLayer.tesr(texture, material); - layers.put(new LayerKey().set(texture, material), layer); + layer = RenderLayer.tesr(texture, material, pass, offsetFactor, offsetUnits); + layers.put(new LayerKey().set(texture, material, pass, offsetFactor, offsetUnits), layer); } lastLayerTexture = texture; lastLayerMaterial = material; + lastLayerPass = pass; + lastLayerOffsetFactor = offsetFactor; + lastLayerOffsetUnits = offsetUnits; lastLayer = layer; return layer; } diff --git a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/TesrBatchRenderer.java b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/TesrBatchRenderer.java index f4ec217d3..446efc224 100644 --- a/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/TesrBatchRenderer.java +++ b/src/main/java/com/gtnewhorizons/angelica/rendering/tesr/TesrBatchRenderer.java @@ -12,6 +12,7 @@ import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager; import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig; import com.gtnewhorizons.angelica.glsm.profiling.Tracy; import com.gtnewhorizons.angelica.glsm.states.Color4; +import com.gtnewhorizons.angelica.glsm.states.PolygonState; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource; @@ -19,6 +20,7 @@ import net.coderbot.batchedentityrendering.impl.SegmentedBufferBuilder; import net.coderbot.batchedentityrendering.impl.TransparencyType; import net.coderbot.iris.Iris; import net.coderbot.iris.layer.GbufferPrograms; +import net.coderbot.iris.layer.PassOverride; import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline; import net.coderbot.iris.pipeline.FixedFunctionWorldRenderingPipeline; import net.coderbot.iris.pipeline.WorldRenderingPhase; @@ -129,13 +131,23 @@ public final class TesrBatchRenderer { return activePass >= 0; } + public boolean hasPendingGeometry() { + return activePass >= 0 || deferredFlushPending; + } + public void queue(TemplateBuffer template, ResourceLocation texture, TesrMaterial material) { modelView.set(GLStateManager.getModelViewMatrix()); final int packedLight = currentPackedLight(material); final int colorABGR = resolveColor(material); if (activePass >= 0) { - final RenderLayer layer = layerFor(texture, material); - final int blockEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity(); + final boolean offset = GLStateManager.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL); + final PolygonState polygon = GLStateManager.getPolygonState(); + final PassOverride pass = PassOverride.capture(); + final RenderLayer layer = layerFor(texture, material, pass, + offset ? polygon.getOffsetFactor() : 0.0f, offset ? polygon.getOffsetUnits() : 0.0f); + final int blockEntityId = pass.isEntityPhase() + ? Math.max(0, CapturedRenderingState.INSTANCE.getCurrentRenderedEntity()) + : CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity(); retained[activePass].queue(template, layer, material, modelView, packedLight, colorABGR, blockEntityId, captureTextureMatrix(), false); } else { drawImmediate(template, texture, material, packedLight, colorABGR); @@ -158,8 +170,12 @@ public final class TesrBatchRenderer { boolean depthEqual; TesrMaterial.SpecialRender special; TesrShader shader; + boolean noPass; + PassOverride pass; + float offsetFactor; + float offsetUnits; - LayerKey set(ResourceLocation texture, TesrMaterial.Transparency transparency, boolean noCull, boolean unlit, boolean noDepthWrite, boolean depthOnly, float cutoutAlpha, boolean depthEqual, TesrMaterial.SpecialRender special, TesrShader shader) { + LayerKey set(ResourceLocation texture, TesrMaterial.Transparency transparency, boolean noCull, boolean unlit, boolean noDepthWrite, boolean depthOnly, float cutoutAlpha, boolean depthEqual, TesrMaterial.SpecialRender special, TesrShader shader, boolean noPass, PassOverride pass, float offsetFactor, float offsetUnits) { this.texture = texture; this.transparency = transparency; this.noCull = noCull; @@ -170,11 +186,15 @@ public final class TesrBatchRenderer { this.depthEqual = depthEqual; this.special = special; this.shader = shader; + this.noPass = noPass; + this.pass = pass; + this.offsetFactor = offsetFactor; + this.offsetUnits = offsetUnits; return this; } LayerKey copy() { - return new LayerKey().set(texture, transparency, noCull, unlit, noDepthWrite, depthOnly, cutoutAlpha, depthEqual, special, shader); + return new LayerKey().set(texture, transparency, noCull, unlit, noDepthWrite, depthOnly, cutoutAlpha, depthEqual, special, shader, noPass, pass, offsetFactor, offsetUnits); } @Override @@ -184,7 +204,10 @@ public final class TesrBatchRenderer { && noCull == other.noCull && unlit == other.unlit && noDepthWrite == other.noDepthWrite && depthOnly == other.depthOnly && Float.floatToIntBits(cutoutAlpha) == Float.floatToIntBits(other.cutoutAlpha) - && depthEqual == other.depthEqual && special == other.special && shader == other.shader; + && depthEqual == other.depthEqual && special == other.special && shader == other.shader && noPass == other.noPass + && Objects.equals(pass, other.pass) + && Float.floatToIntBits(offsetFactor) == Float.floatToIntBits(other.offsetFactor) + && Float.floatToIntBits(offsetUnits) == Float.floatToIntBits(other.offsetUnits); } @Override @@ -199,6 +222,10 @@ public final class TesrBatchRenderer { h = h * 31 + (depthEqual ? 1 : 0); h = h * 31 + Objects.hashCode(special); h = h * 31 + System.identityHashCode(shader); + h = h * 31 + (noPass ? 1 : 0); + h = h * 31 + Objects.hashCode(pass); + h = h * 31 + Float.floatToIntBits(offsetFactor); + h = h * 31 + Float.floatToIntBits(offsetUnits); return h; } } @@ -207,24 +234,45 @@ public final class TesrBatchRenderer { private final LayerKey scratchKey = new LayerKey(); private ResourceLocation lastLayerTexture; private TesrMaterial lastLayerMaterial; + private PassOverride lastLayerPass; + private float lastLayerOffsetFactor; + private float lastLayerOffsetUnits; private RenderLayer lastLayer; + private ResourceLocation lastImmediateTexture; + private TesrMaterial lastImmediateMaterial; + private RenderLayer lastImmediateLayer; - private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material) { - if (texture == lastLayerTexture && material == lastLayerMaterial) { + private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits) { + if (texture == lastLayerTexture && material == lastLayerMaterial && pass.equals(lastLayerPass) + && offsetFactor == lastLayerOffsetFactor && offsetUnits == lastLayerOffsetUnits) { return lastLayer; } - final RenderLayer layer = layerLookup(texture, material); + final RenderLayer layer = layerLookup(texture, material, false, pass, offsetFactor, offsetUnits); lastLayerTexture = texture; lastLayerMaterial = material; + lastLayerPass = pass; + lastLayerOffsetFactor = offsetFactor; + lastLayerOffsetUnits = offsetUnits; lastLayer = layer; return layer; } - private RenderLayer layerLookup(ResourceLocation texture, TesrMaterial material) { - final LayerKey key = scratchKey.set(texture, material.transparency(), material.isNoCull(), material.isUnlit(), material.isNoDepthWrite(), material.isDepthOnly(), material.cutoutAlpha(), material.isDepthEqual(), material.special(), material.shader()); + private RenderLayer noPassLayerFor(ResourceLocation texture, TesrMaterial material) { + if (texture == lastImmediateTexture && material == lastImmediateMaterial) { + return lastImmediateLayer; + } + final RenderLayer layer = layerLookup(texture, material, true, PassOverride.NONE, 0.0f, 0.0f); + lastImmediateTexture = texture; + lastImmediateMaterial = material; + lastImmediateLayer = layer; + return layer; + } + + private RenderLayer layerLookup(ResourceLocation texture, TesrMaterial material, boolean noPass, PassOverride pass, float offsetFactor, float offsetUnits) { + final LayerKey key = scratchKey.set(texture, material.transparency(), material.isNoCull(), material.isUnlit(), material.isNoDepthWrite(), material.isDepthOnly(), material.cutoutAlpha(), material.isDepthEqual(), material.special(), material.shader(), noPass, pass, offsetFactor, offsetUnits); RenderLayer layer = layers.get(key); if (layer == null) { - layer = RenderLayer.tesr(texture, material); + layer = noPass ? RenderLayer.tesrNoPass(texture, material) : RenderLayer.tesr(texture, material, pass, offsetFactor, offsetUnits); layers.put(key.copy(), layer); } return layer; @@ -232,9 +280,9 @@ public final class TesrBatchRenderer { private static int currentPackedLight(TesrMaterial material) { if (material.hasLightmap()) { - return ((int) material.lightmapY() << 16) | ((int) material.lightmapX() & 0xFFFF); + return GLSMConfig.packBrightness(material.lightmapX(), material.lightmapY()); } - return ((int) GLSMConfig.lastBrightnessY << 16) | ((int) GLSMConfig.lastBrightnessX & 0xFFFF); + return GLSMConfig.packedLastBrightness(); } private static int resolveColor(TesrMaterial material) { @@ -290,6 +338,7 @@ public final class TesrBatchRenderer { try { final EntityRenderer entityRenderer = Minecraft.getMinecraft().entityRenderer; final boolean savedDepthMask = GLStateManager.getDepthState().isEnabled(); + final boolean savedDepthTest = GLStateManager.getDepthTest().isEnabled(); final boolean savedBlend = GLStateManager.getBlendMode().isEnabled(); final int savedSrcRgb = GLStateManager.getBlendState().getSrcRgb(); final int savedDstRgb = GLStateManager.getBlendState().getDstRgb(); @@ -312,11 +361,12 @@ public final class TesrBatchRenderer { bufferSource.endBatch(hook); instancedRenderer.endFrame(); AngelicaBufferSource.rebindPass(); - BatchingFontRenderer.flushDeferredText(); + BatchingFontRenderer.flushDeferredText(); } finally { if (wrap) GbufferPrograms.endBlockEntities(); entityRenderer.disableLightmap(0); GLStateManager.glDepthMask(savedDepthMask); + if (savedDepthTest) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest(); if (savedBlend) GLStateManager.enableBlend(); else GLStateManager.disableBlend(); GLStateManager.tryBlendFuncSeparate(savedSrcRgb, savedDstRgb, savedSrcAlpha, savedDstAlpha); if (savedAlphaTest) GLStateManager.enableAlphaTest(); else GLStateManager.disableAlphaTest(); @@ -452,7 +502,7 @@ public final class TesrBatchRenderer { final long end = VertexTransform.writeInstance(addr, format, template, identity, scratchVec, colorABGR, packedLight, null); immediateScratch.position((int) (end - addr)); immediateScratch.flip(); - final RenderLayer layer = layerFor(texture, material); + final RenderLayer layer = noPassLayerFor(texture, material); GLStateManager.glPushAttrib(AngelicaBufferSource.SAVED_STATE_BITS); GLStateManager.glActiveTexture(GL13.GL_TEXTURE0); layer.startDrawing(); diff --git a/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptionPages.java b/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptionPages.java index fb503a9b2..73dad21ef 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptionPages.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptionPages.java @@ -11,8 +11,6 @@ import net.minecraft.client.resources.I18n; import net.minecraft.client.settings.GameSettings; import net.minecraft.util.MathHelper; -import org.lwjgl.opengl.Display; - import com.cardinalstar.cubicchunks.api.compat.CubicChunksVideoSettings; import com.google.common.collect.ImmutableList; import com.gtnewhorizons.angelica.compat.ModStatus; @@ -21,7 +19,6 @@ import com.gtnewhorizons.angelica.rendering.celeritas.MultiDrawModeResolver; import com.gtnewhorizons.angelica.rendering.culling.GpuCulling; import com.gtnewhorizons.angelica.config.GpuCullingMode; import com.gtnewhorizons.angelica.glsm.GLStateManager; -import com.gtnewhorizons.angelica.glsm.RenderSystem; import cpw.mods.fml.common.Optional.Method; import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader; import jss.notfine.core.Settings; @@ -48,6 +45,7 @@ import me.jellysquid.mods.sodium.client.gui.options.storage.MinecraftOptionsStor import me.jellysquid.mods.sodium.client.gui.options.storage.SodiumOptionsStorage; import net.coderbot.iris.Iris; import net.coderbot.iris.gui.option.IrisVideoSettings; +import net.coderbot.iris.pipeline.ShadowRenderer; import org.embeddedt.embeddium.impl.render.chunk.occlusion.AsyncOcclusionMode; public class SodiumGameOptionPages { @@ -519,6 +517,36 @@ public class SodiumGameOptionPages { .build()); + if (Iris.enabled) { + final OptionImpl shadowGraphAngleDelta = + OptionImpl.createBuilder(int.class, angelicaOpts) + .setName(I18n.format("options.angelica.shadowGraphAngleDelta")) + .setTooltip(I18n.format("options.angelica.shadowGraphAngleDelta.tooltip")) + .setControl(o -> new SliderControl(o, 0, 100, 1, ControlValueFormatter.shadowGraphAngleDelta())) + .setImpact(OptionImpact.MEDIUM) + .setBinding((opts, value) -> AngelicaConfig.shadowGraphAngleDelta = value * 0.001f, + opts -> Math.round(AngelicaConfig.shadowGraphAngleDelta * 1000.0f)) + .build(); + shadowGraphAngleDelta.iris$dynamicallyEnable(ShadowRenderer::shadowGraphGateIsNotBypassed); + + final OptionImpl shadowGraphHorizonScale = + OptionImpl.createBuilder(int.class, angelicaOpts) + .setName(I18n.format("options.angelica.shadowGraphHorizonScale")) + .setTooltip(I18n.format("options.angelica.shadowGraphHorizonScale.tooltip")) + .setControl(o -> new SliderControl(o, 0, 100, 1, ControlValueFormatter.shadowGraphHorizonScale())) + .setImpact(OptionImpact.LOW) + .setBinding((opts, value) -> AngelicaConfig.shadowGraphHorizonScale = value * 0.01f, + opts -> Math.round(AngelicaConfig.shadowGraphHorizonScale * 100.0f)) + .build(); + shadowGraphHorizonScale.iris$dynamicallyEnable( + () -> ShadowRenderer.shadowGraphGateIsNotBypassed() && shadowGraphAngleDelta.getValue() != 0); + + groups.add(OptionGroup.createBuilder() + .add(shadowGraphAngleDelta) + .add(shadowGraphHorizonScale) + .build()); + } + groups.add(OptionGroup.createBuilder() .add(OptionImpl.createBuilder(boolean.class, angelicaOpts) .setName(I18n.format("options.angelica.hudCaching")) diff --git a/src/main/java/me/jellysquid/mods/sodium/client/gui/options/control/ControlValueFormatter.java b/src/main/java/me/jellysquid/mods/sodium/client/gui/options/control/ControlValueFormatter.java index ec211998a..c670d3889 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/gui/options/control/ControlValueFormatter.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/gui/options/control/ControlValueFormatter.java @@ -2,6 +2,8 @@ package me.jellysquid.mods.sodium.client.gui.options.control; import net.minecraft.client.resources.I18n; +import java.util.Locale; + public interface ControlValueFormatter { static ControlValueFormatter guiScale() { return (v) -> (v == 0) ? I18n.format("options.guiScale.auto") : I18n.format(v + "x"); @@ -47,4 +49,14 @@ public interface ControlValueFormatter { static ControlValueFormatter number() { return String::valueOf; } + + static ControlValueFormatter shadowGraphAngleDelta() { + return (v) -> v == 0 + ? I18n.format("options.angelica.shadowGraphAngleDelta.always") + : String.format(Locale.ROOT, "%.3f°", v * 0.001f); + } + + static ControlValueFormatter shadowGraphHorizonScale() { + return (v) -> v == 100 ? I18n.format("options.angelica.shadowGraphHorizonScale.off") : v + "%"; + } } diff --git a/src/main/java/net/coderbot/batchedentityrendering/impl/AngelicaBufferSource.java b/src/main/java/net/coderbot/batchedentityrendering/impl/AngelicaBufferSource.java index 5c3bf80ea..9be6304c6 100644 --- a/src/main/java/net/coderbot/batchedentityrendering/impl/AngelicaBufferSource.java +++ b/src/main/java/net/coderbot/batchedentityrendering/impl/AngelicaBufferSource.java @@ -6,6 +6,8 @@ import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.coderbot.batchedentityrendering.impl.ordering.SimpleRenderOrderManager; import net.coderbot.iris.Iris; +import net.coderbot.iris.layer.GbufferPrograms; +import net.coderbot.iris.pipeline.WorldRenderingPhase; import net.coderbot.iris.pipeline.WorldRenderingPipeline; import net.coderbot.iris.uniforms.CapturedRenderingState; import org.lwjgl.opengl.GL11; @@ -159,15 +161,17 @@ public class AngelicaBufferSource implements Groupable { private void clearCurrentId() { if (!anyIdSet) return; - if (idKind == GroupIdKind.ENTITY) { - CapturedRenderingState.INSTANCE.setCurrentEntity(-1); - CapturedRenderingState.INSTANCE.setCurrentEntityColor(0f, 0f, 0f, 0f); - } else { - CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0); - } + // A pass can set either kind: nested entities inside a TESR write the entity id during a block entity pass + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(-1, 0); + CapturedRenderingState.INSTANCE.setCurrentEntityColor(0f, 0f, 0f, 0f); + CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0); anyIdSet = false; } + private GroupIdKind effectiveIdKind() { + return GbufferPrograms.getCurrentPhase() == WorldRenderingPhase.ENTITIES ? GroupIdKind.ENTITY : idKind; + } + private void drawLayer(RenderLayer layer, LayerDrawHook hook) { final List segments = typeToSegment.get(layer); final boolean hasDynamic = segments != null && !segments.isEmpty(); @@ -184,7 +188,7 @@ public class AngelicaBufferSource implements Groupable { GLStateManager.glPushMatrix(); GLStateManager.glLoadIdentity(); SegmentedBufferBuilder.LayerBuffer bound = null; - final boolean entityKind = idKind == GroupIdKind.ENTITY; + final boolean entityKind = effectiveIdKind() == GroupIdKind.ENTITY; int currentId = Integer.MIN_VALUE; int currentColor = 0; boolean colorSet = false; @@ -271,8 +275,8 @@ public class AngelicaBufferSource implements Groupable { public void applyIdAndRebind(int id) { anyIdSet = true; - if (idKind == GroupIdKind.ENTITY) { - CapturedRenderingState.INSTANCE.setCurrentEntity(id); + if (effectiveIdKind() == GroupIdKind.ENTITY) { + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(id, 0); rebindPass(); } else { setBlockEntityAndRebind(id); @@ -281,8 +285,8 @@ public class AngelicaBufferSource implements Groupable { public void applyIdNoRebind(int id) { anyIdSet = true; - if (idKind == GroupIdKind.ENTITY) { - CapturedRenderingState.INSTANCE.setCurrentEntity(id); + if (effectiveIdKind() == GroupIdKind.ENTITY) { + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(id, 0); } else { CapturedRenderingState.INSTANCE.setCurrentBlockEntity(id); } diff --git a/src/main/java/net/coderbot/iris/Iris.java b/src/main/java/net/coderbot/iris/Iris.java index 4a28ac733..70e8be86f 100644 --- a/src/main/java/net/coderbot/iris/Iris.java +++ b/src/main/java/net/coderbot/iris/Iris.java @@ -54,6 +54,7 @@ import net.minecraft.launchwrapper.Launch; import net.minecraft.util.ChatComponentText; import net.minecraft.util.EnumChatFormatting; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.lwjgl.input.Keyboard; import java.io.IOException; @@ -919,6 +920,11 @@ public class Iris { return pipelineManager; } + @Nullable + public static PipelineManager getPipelineManagerNullable() { + return pipelineManager; + } + @NotNull public static Optional getCurrentPack() { return Optional.ofNullable(currentPack); diff --git a/src/main/java/net/coderbot/iris/celeritas/CeleritasTerrainPipeline.java b/src/main/java/net/coderbot/iris/celeritas/CeleritasTerrainPipeline.java index 763ee6b04..ee53fbee0 100644 --- a/src/main/java/net/coderbot/iris/celeritas/CeleritasTerrainPipeline.java +++ b/src/main/java/net/coderbot/iris/celeritas/CeleritasTerrainPipeline.java @@ -69,13 +69,17 @@ public class CeleritasTerrainPipeline { IntFunction createTerrainImages, IntFunction createShadowImages, CustomUniforms customUniforms, - Optional terrainSource, + Optional terrainSolidSource, + Optional terrainCutoutSource, Optional translucentSource, - Optional shadowSource, + Optional shadowSolidSource, + Optional shadowCutoutSource, Optional shadowTranslucentSource, - CompletableFuture> terrainFuture, + CompletableFuture> terrainSolidFuture, + CompletableFuture> terrainCutoutFuture, CompletableFuture> translucentFuture, - CompletableFuture> shadowFuture, + CompletableFuture> shadowSolidFuture, + CompletableFuture> shadowCutoutFuture, CompletableFuture> shadowTranslucentFuture, RenderTargets renderTargets, ImmutableSet flippedAfterPrepare, @@ -90,11 +94,12 @@ public class CeleritasTerrainPipeline { // Build program source map (local - only needed during construction) final EnumMap> gbufferProgramSource = new EnumMap<>(IrisTerrainPass.class); - gbufferProgramSource.put(IrisTerrainPass.GBUFFER_SOLID, terrainSource); - gbufferProgramSource.put(IrisTerrainPass.GBUFFER_CUTOUT, terrainSource); - gbufferProgramSource.put(IrisTerrainPass.GBUFFER_TRANSLUCENT, translucentSource.isPresent() ? translucentSource : terrainSource); - gbufferProgramSource.put(IrisTerrainPass.SHADOW, shadowSource); - gbufferProgramSource.put(IrisTerrainPass.SHADOW_TRANSLUCENT, shadowTranslucentSource.isPresent() ? shadowTranslucentSource : shadowSource); + gbufferProgramSource.put(IrisTerrainPass.GBUFFER_SOLID, terrainSolidSource); + gbufferProgramSource.put(IrisTerrainPass.GBUFFER_CUTOUT, terrainCutoutSource); + gbufferProgramSource.put(IrisTerrainPass.GBUFFER_TRANSLUCENT, translucentSource); + gbufferProgramSource.put(IrisTerrainPass.SHADOW, shadowSolidSource); + gbufferProgramSource.put(IrisTerrainPass.SHADOW_CUTOUT, shadowCutoutSource); + gbufferProgramSource.put(IrisTerrainPass.SHADOW_TRANSLUCENT, shadowTranslucentSource); // Initialize PassInfo, framebuffers, blend modes, and alpha in single pass for (IrisTerrainPass pass : IrisTerrainPass.VALUES) { @@ -103,10 +108,12 @@ public class CeleritasTerrainPipeline { // Set up framebuffer, blend mode, and buffer blend overrides final ProgramId programId = switch (pass) { + case GBUFFER_SOLID -> ProgramId.TerrainSolid; + case GBUFFER_CUTOUT -> ProgramId.TerrainCutout; case GBUFFER_TRANSLUCENT -> ProgramId.Water; case SHADOW_TRANSLUCENT -> ProgramId.ShadowWater; - case SHADOW, SHADOW_CUTOUT -> ProgramId.Shadow; - default -> ProgramId.Terrain; + case SHADOW -> ProgramId.ShadowSolid; + case SHADOW_CUTOUT -> ProgramId.ShadowCutout; }; if (pass.isShadow()) { @@ -146,10 +153,12 @@ public class CeleritasTerrainPipeline { } // Process and apply shader sources - processShaderFuture(terrainFuture, terrainSource, passInfoMap.get(IrisTerrainPass.GBUFFER_SOLID), passInfoMap.get(IrisTerrainPass.GBUFFER_CUTOUT)); + processShaderFuture(terrainSolidFuture, terrainSolidSource, passInfoMap.get(IrisTerrainPass.GBUFFER_SOLID)); + processShaderFuture(terrainCutoutFuture, terrainCutoutSource, passInfoMap.get(IrisTerrainPass.GBUFFER_CUTOUT)); processShaderFuture(translucentFuture, translucentSource, passInfoMap.get(IrisTerrainPass.GBUFFER_TRANSLUCENT)); - processShaderFuture(shadowFuture, shadowSource, passInfoMap.get(IrisTerrainPass.SHADOW), passInfoMap.get(IrisTerrainPass.SHADOW_CUTOUT)); - processShaderFuture(shadowTranslucentFuture, shadowTranslucentSource.isPresent() ? shadowTranslucentSource : shadowSource, passInfoMap.get(IrisTerrainPass.SHADOW_TRANSLUCENT)); + processShaderFuture(shadowSolidFuture, shadowSolidSource, passInfoMap.get(IrisTerrainPass.SHADOW)); + processShaderFuture(shadowCutoutFuture, shadowCutoutSource, passInfoMap.get(IrisTerrainPass.SHADOW_CUTOUT)); + processShaderFuture(shadowTranslucentFuture, shadowTranslucentSource, passInfoMap.get(IrisTerrainPass.SHADOW_TRANSLUCENT)); } private void processShaderFuture(@Nullable CompletableFuture> future, Optional source, PassInfo... targets) { diff --git a/src/main/java/net/coderbot/iris/fantastic/IrisParticleRenderTypes.java b/src/main/java/net/coderbot/iris/fantastic/IrisParticleRenderTypes.java deleted file mode 100644 index 1d1a146ce..000000000 --- a/src/main/java/net/coderbot/iris/fantastic/IrisParticleRenderTypes.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.coderbot.iris.fantastic; - -// TODO: RenderType -//public class IrisParticleRenderTypes { -// public static final ParticleRenderType OPAQUE_TERRAIN = new ParticleRenderType() { -// public void begin(BufferBuilder bufferBuilder, TextureManager textureManager) { -// GL11.glDisable(GL11.GL_BLEND); -// GL11.glDepthMask(true); -// GL11.glAlphaFunc(GL11.GL_GREATER, 0.1F); -// textureManager.bind(TextureAtlas.LOCATION_BLOCKS); -// bufferBuilder.begin(7, DefaultVertexFormat.PARTICLE); -// } -// -// public void end(Tesselator tesselator) { -// tesselator.end(); -// } -// -// public String toString() { -// return "OPAQUE_TERRAIN_SHEET"; -// } -// }; -//} diff --git a/src/main/java/net/coderbot/iris/gbuffer_overrides/matching/RenderCondition.java b/src/main/java/net/coderbot/iris/gbuffer_overrides/matching/RenderCondition.java index 813c8c4e7..bec8342fe 100644 --- a/src/main/java/net/coderbot/iris/gbuffer_overrides/matching/RenderCondition.java +++ b/src/main/java/net/coderbot/iris/gbuffer_overrides/matching/RenderCondition.java @@ -12,6 +12,8 @@ public enum RenderCondition { BEACON_BEAM, ENTITIES, ENTITIES_TRANSLUCENT, + PARTICLES, + PARTICLES_TRANSLUCENT, GLINT, ENTITY_EYES, HAND_OPAQUE, diff --git a/src/main/java/net/coderbot/iris/gbuffer_overrides/matching/TranslucentBlendMatcher.java b/src/main/java/net/coderbot/iris/gbuffer_overrides/matching/TranslucentBlendMatcher.java new file mode 100644 index 000000000..c07b1bd54 --- /dev/null +++ b/src/main/java/net/coderbot/iris/gbuffer_overrides/matching/TranslucentBlendMatcher.java @@ -0,0 +1,24 @@ +package net.coderbot.iris.gbuffer_overrides.matching; + +import com.gtnewhorizons.angelica.glsm.GLStateManager; +import com.gtnewhorizons.angelica.glsm.states.BlendState; +import org.lwjgl.opengl.GL11; + +/** + * Decides whether a draw is blending. + */ +public final class TranslucentBlendMatcher { + + /** Render thread only. */ + private static final BlendState scratch = new BlendState(); + + private TranslucentBlendMatcher() {} + + public static boolean matchesCurrentState() { + if (!GLStateManager.isEffectiveBlendEnabled()) { + return false; + } + GLStateManager.getEffectiveBlendState(scratch); + return scratch.getSrcRgb() == GL11.GL_SRC_ALPHA && scratch.getDstRgb() == GL11.GL_ONE_MINUS_SRC_ALPHA; + } +} diff --git a/src/main/java/net/coderbot/iris/gl/blending/AlphaTestStorage.java b/src/main/java/net/coderbot/iris/gl/blending/AlphaTestStorage.java index cac45750d..19961d826 100644 --- a/src/main/java/net/coderbot/iris/gl/blending/AlphaTestStorage.java +++ b/src/main/java/net/coderbot/iris/gl/blending/AlphaTestStorage.java @@ -3,57 +3,114 @@ package net.coderbot.iris.gl.blending; import com.gtnewhorizons.angelica.glsm.states.AlphaTestFunction; import com.gtnewhorizons.angelica.glsm.GLStateManager; +import com.gtnewhorizons.angelica.glsm.hooks.VanillaBooleanLayer; +import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer; import com.gtnewhorizons.angelica.glsm.states.AlphaState; import lombok.Getter; +import org.lwjgl.opengl.GL11; public class AlphaTestStorage { - private static boolean originalAlphaTestEnable; - private static AlphaTest originalAlphaTest; - @Getter + private static boolean vanillaEnabled; + private static int vanillaFunction = GL11.GL_ALWAYS; + private static float vanillaReference; + + @Getter private static boolean alphaTestLocked; + private static boolean overrideHeld; + public static void overrideAlphaTest(AlphaTest override) { - if (!alphaTestLocked) { + if (!overrideHeld) { final AlphaState alphaState = GLStateManager.getAlphaState(); - // Only save the previous state if the alpha test wasn't already locked - originalAlphaTestEnable = GLStateManager.getAlphaTest().isEnabled(); - originalAlphaTest = new AlphaTest(AlphaTestFunction.fromGlId(alphaState.getFunction()).orElse(AlphaTestFunction.ALWAYS), alphaState.getReference()); - } + // Only save the previous state if the alpha test wasn't already overridden + vanillaEnabled = GLStateManager.getAlphaTest().isEnabled(); + vanillaFunction = alphaState.getFunction(); + vanillaReference = alphaState.getReference(); + } - alphaTestLocked = false; + overrideHeld = true; - if (override == null) { - GLStateManager.disableAlphaTest(); - } else { + if (override == null) { + apply(false, GL11.GL_ALWAYS, 0.0f); + } else { + apply(true, override.getFunction().getGlId(), override.getReference()); + } + } + + private static void apply(boolean enabled, int function, float reference) { + alphaTestLocked = false; + try { + if (enabled) { + GLStateManager.enableAlphaTest(); + GLStateManager.glAlphaFunc(function, reference); + } else { + GLStateManager.disableAlphaTest(); + } + } finally { + alphaTestLocked = true; + } + } + + public static void deferAlphaTestToggle(boolean enabled) { + vanillaEnabled = enabled; + } + + public static void deferAlphaFunc(int function, float reference) { + vanillaFunction = function; + vanillaReference = reference; + } + + public static void restoreAlphaTest() { + if (!overrideHeld) { + return; + } + + alphaTestLocked = false; + + if (vanillaEnabled) { GLStateManager.enableAlphaTest(); - GLStateManager.glAlphaFunc(override.getFunction().getGlId(), override.getReference()); - } - - alphaTestLocked = true; - } - - public static void deferAlphaTestToggle(boolean enabled) { - originalAlphaTestEnable = enabled; - } - - public static void deferAlphaFunc(int function, float reference) { - originalAlphaTest = new AlphaTest(AlphaTestFunction.fromGlId(function).get(), reference); - } - - public static void restoreAlphaTest() { - if (!alphaTestLocked) { - return; - } - - alphaTestLocked = false; - - if (originalAlphaTestEnable) { - GLStateManager.enableAlphaTest(); - } else { + } else { GLStateManager.disableAlphaTest(); - } + } - GLStateManager.glAlphaFunc(originalAlphaTest.getFunction().getGlId(), originalAlphaTest.getReference()); - } + GLStateManager.glAlphaFunc(vanillaFunction, vanillaReference); + overrideHeld = false; + } + + public static final VanillaBooleanLayer ENABLE_LAYER = new VanillaBooleanLayer() { + @Override + public boolean isOverrideHeld() { + return overrideHeld; + } + + @Override + public boolean getVanilla() { + return vanillaEnabled; + } + + @Override + public void setVanilla(boolean enabled) { + vanillaEnabled = enabled; + } + }; + + public static final VanillaStateLayer FUNC_LAYER = new VanillaStateLayer<>() { + @Override + public boolean isOverrideHeld() { + return overrideHeld; + } + + @Override + public void readVanilla(AlphaState into) { + into.setFunction(vanillaFunction); + into.setReference(vanillaReference); + } + + @Override + public void writeVanilla(AlphaState from) { + vanillaFunction = from.getFunction(); + vanillaReference = from.getReference(); + } + }; } diff --git a/src/main/java/net/coderbot/iris/gl/blending/BlendModeStorage.java b/src/main/java/net/coderbot/iris/gl/blending/BlendModeStorage.java index 3b79ade14..39dd45b3f 100644 --- a/src/main/java/net/coderbot/iris/gl/blending/BlendModeStorage.java +++ b/src/main/java/net/coderbot/iris/gl/blending/BlendModeStorage.java @@ -2,126 +2,187 @@ package net.coderbot.iris.gl.blending; import com.gtnewhorizons.angelica.glsm.GLStateManager; import com.gtnewhorizons.angelica.glsm.RenderSystem; +import com.gtnewhorizons.angelica.glsm.hooks.VanillaBooleanLayer; +import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer; import com.gtnewhorizons.angelica.glsm.states.BlendState; import lombok.Getter; public class BlendModeStorage { - private static final int MAX_DRAW_BUFFERS = 8; + private static final int MAX_DRAW_BUFFERS = 8; - private static boolean originalBlendEnable; - private static final BlendState originalBlend = new BlendState(); - @Getter private static boolean blendLocked; - @Getter private static boolean hasDeferredChanges; - private static final int[] bufferOverrideIndices = new int[MAX_DRAW_BUFFERS]; - private static final boolean[] bufferOverrideIsDisable = new boolean[MAX_DRAW_BUFFERS]; - private static final BlendState[] bufferOverrideStates = new BlendState[MAX_DRAW_BUFFERS]; - private static int bufferOverrideCount; + private static boolean vanillaBlendEnable; + private static final BlendState vanillaBlend = new BlendState(); - static { - for (int i = 0; i < MAX_DRAW_BUFFERS; i++) { - bufferOverrideStates[i] = new BlendState(); - } - } + @Getter + private static boolean blendLocked; + @Getter + private static boolean overrideHeld; + + private static boolean hasGlobalOverride; + + private static boolean hasDeferredChanges; + private static final int[] bufferOverrideIndices = new int[MAX_DRAW_BUFFERS]; + private static final boolean[] bufferOverrideIsDisable = new boolean[MAX_DRAW_BUFFERS]; + private static final BlendState[] bufferOverrideStates = new BlendState[MAX_DRAW_BUFFERS]; + private static int bufferOverrideCount; + private static boolean bufferOverridesDirty; + + static { + for (int i = 0; i < MAX_DRAW_BUFFERS; i++) { + bufferOverrideStates[i] = new BlendState(); + } + } public static void overrideBlend(BlendState override) { - if (!blendLocked) { - // Only save the previous state if the blend mode wasn't already locked - originalBlendEnable = GLStateManager.getBlendMode().isEnabled(); - originalBlend.set(GLStateManager.getBlendState()); - } + captureVanilla(); - bufferOverrideCount = 0; - hasDeferredChanges = false; - blendLocked = false; + bufferOverrideCount = 0; + bufferOverridesDirty = false; + hasDeferredChanges = false; + overrideHeld = true; + hasGlobalOverride = true; - if (override == null) { - GLStateManager.disableBlend(); - } else { - GLStateManager.enableBlend(); - GLStateManager.tryBlendFuncSeparate(override.getSrcRgb(), override.getDstRgb(), override.getSrcAlpha(), override.getDstAlpha()); - } + blendLocked = false; + try { + if (override == null) { + GLStateManager.disableBlend(); + } else { + GLStateManager.enableBlend(); + GLStateManager.tryBlendFuncSeparate(override.getSrcRgb(), override.getDstRgb(), override.getSrcAlpha(), override.getDstAlpha()); + } + } finally { + blendLocked = true; + } + } - blendLocked = true; - } + public static void overrideBufferBlend(int index, BlendState override) { + captureVanilla(); - public static void overrideBufferBlend(int index, BlendState override) { - if (!blendLocked) { - // Only save the previous state if the blend mode wasn't already locked - originalBlendEnable = GLStateManager.getBlendMode().isEnabled(); - originalBlend.set(GLStateManager.getBlendState()); - } + if (override == null) { + RenderSystem.disableBufferBlend(index); + } else { + RenderSystem.enableBufferBlend(index); + RenderSystem.blendFuncSeparatei(index, override.getSrcRgb(), override.getDstRgb(), override.getSrcAlpha(), override.getDstAlpha()); + } - if (override == null) { - RenderSystem.disableBufferBlend(index); - } else { - RenderSystem.enableBufferBlend(index); - RenderSystem.blendFuncSeparatei(index, override.getSrcRgb(), override.getDstRgb(), override.getSrcAlpha(), override.getDstAlpha()); - } + if (bufferOverrideCount < MAX_DRAW_BUFFERS) { + final int slot = bufferOverrideCount++; + bufferOverrideIndices[slot] = index; + bufferOverrideIsDisable[slot] = (override == null); + if (override != null) { + bufferOverrideStates[slot].setAll(override.getSrcRgb(), override.getDstRgb(), override.getSrcAlpha(), override.getDstAlpha()); + } + } + overrideHeld = true; + blendLocked = true; + } - if (bufferOverrideCount < MAX_DRAW_BUFFERS) { - final int slot = bufferOverrideCount++; - bufferOverrideIndices[slot] = index; - bufferOverrideIsDisable[slot] = (override == null); - if (override != null) { - bufferOverrideStates[slot].setAll(override.getSrcRgb(), override.getDstRgb(), override.getSrcAlpha(), override.getDstAlpha()); - } - } - blendLocked = true; - } + private static void captureVanilla() { + if (!overrideHeld) { + vanillaBlendEnable = GLStateManager.getBlendMode().isEnabled(); + vanillaBlend.set(GLStateManager.getBlendState()); + } + } - public static void deferBlendModeToggle(boolean enabled) { - originalBlendEnable = enabled; - hasDeferredChanges = true; - } + public static void deferBlendModeToggle(boolean enabled) { + vanillaBlendEnable = enabled; + hasDeferredChanges = true; + } - public static void deferBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) { - originalBlend.setAll(srcRgb, dstRgb, srcAlpha, dstAlpha); - hasDeferredChanges = true; - } + public static void deferBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) { + vanillaBlend.setAll(srcRgb, dstRgb, srcAlpha, dstAlpha); + hasDeferredChanges = true; + } - public static void flushDeferredBlend() { - if (!blendLocked || !hasDeferredChanges) return; - hasDeferredChanges = false; - blendLocked = false; + public static void flushDeferredBlend() { + if (!overrideHeld) return; - if (originalBlendEnable) { - GLStateManager.enableBlend(); - } else { - GLStateManager.disableBlend(); - } - GLStateManager.tryBlendFuncSeparate( - originalBlend.getSrcRgb(), originalBlend.getDstRgb(), - originalBlend.getSrcAlpha(), originalBlend.getDstAlpha()); + if (hasDeferredChanges && !hasGlobalOverride) { + hasDeferredChanges = false; + applyVanilla(); + bufferOverridesDirty = true; + } - blendLocked = true; + if (bufferOverridesDirty) { + bufferOverridesDirty = false; + reassertBufferOverrides(); + } + } - for (int i = 0; i < bufferOverrideCount; i++) { - final int idx = bufferOverrideIndices[i]; - if (bufferOverrideIsDisable[i]) { - RenderSystem.disableBufferBlend(idx); - } else { - final BlendState s = bufferOverrideStates[i]; - RenderSystem.enableBufferBlend(idx); - RenderSystem.blendFuncSeparatei(idx, s.getSrcRgb(), s.getDstRgb(), s.getSrcAlpha(), s.getDstAlpha()); - } - } - } + private static void reassertBufferOverrides() { + for (int i = 0; i < bufferOverrideCount; i++) { + final int idx = bufferOverrideIndices[i]; + if (bufferOverrideIsDisable[i]) { + RenderSystem.disableBufferBlend(idx); + } else { + final BlendState s = bufferOverrideStates[i]; + RenderSystem.enableBufferBlend(idx); + RenderSystem.blendFuncSeparatei(idx, s.getSrcRgb(), s.getDstRgb(), s.getSrcAlpha(), s.getDstAlpha()); + } + } + } - public static void restoreBlend() { - if (!blendLocked) { - return; - } + public static void restoreBlend() { + if (!overrideHeld) { + return; + } - blendLocked = false; - hasDeferredChanges = false; - bufferOverrideCount = 0; + hasGlobalOverride = false; + hasDeferredChanges = false; + bufferOverrideCount = 0; + bufferOverridesDirty = false; + blendLocked = false; - if (originalBlendEnable) { - GLStateManager.enableBlend(); - } else { - GLStateManager.disableBlend(); - } + applyVanilla(); + overrideHeld = false; + } - GLStateManager.tryBlendFuncSeparate(originalBlend.getSrcRgb(), originalBlend.getDstRgb(), originalBlend.getSrcAlpha(), originalBlend.getDstAlpha()); - } + private static void applyVanilla() { + final boolean wasLocked = blendLocked; + blendLocked = false; + try { + if (vanillaBlendEnable) { + GLStateManager.enableBlend(); + } else { + GLStateManager.disableBlend(); + } + GLStateManager.tryBlendFuncSeparate(vanillaBlend.getSrcRgb(), vanillaBlend.getDstRgb(), vanillaBlend.getSrcAlpha(), vanillaBlend.getDstAlpha()); + } finally { + blendLocked = wasLocked; + } + } + + public static final VanillaBooleanLayer ENABLE_LAYER = new VanillaBooleanLayer() { + @Override + public boolean isOverrideHeld() { + return overrideHeld; + } + + @Override + public boolean getVanilla() { + return vanillaBlendEnable; + } + + @Override + public void setVanilla(boolean enabled) { + deferBlendModeToggle(enabled); + } + }; + + public static final VanillaStateLayer FUNC_LAYER = new VanillaStateLayer<>() { + @Override + public boolean isOverrideHeld() { + return overrideHeld; + } + + @Override + public void readVanilla(BlendState into) { + into.setAll(vanillaBlend.getSrcRgb(), vanillaBlend.getDstRgb(), vanillaBlend.getSrcAlpha(), vanillaBlend.getDstAlpha()); + } + + @Override + public void writeVanilla(BlendState from) { + deferBlendFunc(from.getSrcRgb(), from.getDstRgb(), from.getSrcAlpha(), from.getDstAlpha()); + } + }; } diff --git a/src/main/java/net/coderbot/iris/gl/blending/DepthColorStorage.java b/src/main/java/net/coderbot/iris/gl/blending/DepthColorStorage.java index c4abfa591..73a391099 100644 --- a/src/main/java/net/coderbot/iris/gl/blending/DepthColorStorage.java +++ b/src/main/java/net/coderbot/iris/gl/blending/DepthColorStorage.java @@ -3,64 +3,105 @@ package net.coderbot.iris.gl.blending; import com.gtnewhorizons.angelica.glsm.states.DepthState; import com.gtnewhorizons.angelica.glsm.states.ColorMask; import com.gtnewhorizons.angelica.glsm.GLStateManager; +import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import lombok.Getter; public class DepthColorStorage { - private static boolean originalDepthEnable; - private static net.coderbot.iris.gl.blending.ColorMask originalColor; - @Getter + private static boolean vanillaDepthEnable; + private static final ColorMask vanillaColor = new ColorMask(); + + @Getter private static boolean depthColorLocked; - private static final IntOpenHashSet ownedPrograms = new IntOpenHashSet(); + @Getter + private static boolean overrideHeld; - public static void registerOwnedProgram(int programId) { - ownedPrograms.add(programId); - } + private static final IntOpenHashSet ownedPrograms = new IntOpenHashSet(); - public static void unregisterOwnedProgram(int programId) { - ownedPrograms.remove(programId); - } + public static void registerOwnedProgram(int programId) { + ownedPrograms.add(programId); + } - public static boolean isOwnedProgram(int programId) { - return ownedPrograms.contains(programId); - } + public static void unregisterOwnedProgram(int programId) { + ownedPrograms.remove(programId); + } + + public static boolean isOwnedProgram(int programId) { + return ownedPrograms.contains(programId); + } public static void disableDepthColor() { - if (!depthColorLocked) { - // Only save the previous state if the depth and color mask wasn't already locked - final ColorMask colorMask = GLStateManager.getColorMask(); - final DepthState depthState = GLStateManager.getDepthState(); + if (!overrideHeld) { + final ColorMask colorMask = GLStateManager.getColorMask(); + final DepthState depthState = GLStateManager.getDepthState(); - originalDepthEnable = depthState.isEnabled(); - originalColor = new net.coderbot.iris.gl.blending.ColorMask(colorMask.red, colorMask.green, colorMask.blue, colorMask.alpha); - } + vanillaDepthEnable = depthState.isEnabled(); + vanillaColor.set(colorMask); + } - depthColorLocked = false; + overrideHeld = true; - GLStateManager.glDepthMask(false); - GLStateManager.glColorMask(false, false, false, false); + depthColorLocked = false; + try { + GLStateManager.glDepthMask(false); + GLStateManager.glColorMask(false, false, false, false); + } finally { + depthColorLocked = true; + } + } - depthColorLocked = true; - } + public static void deferDepthEnable(boolean enabled) { + vanillaDepthEnable = enabled; + } - public static void deferDepthEnable(boolean enabled) { - originalDepthEnable = enabled; - } + public static void deferColorMask(boolean red, boolean green, boolean blue, boolean alpha) { + vanillaColor.setAll(red, green, blue, alpha); + } - public static void deferColorMask(boolean red, boolean green, boolean blue, boolean alpha) { - originalColor = new net.coderbot.iris.gl.blending.ColorMask(red, green, blue, alpha); - } + public static void unlockDepthColor() { + if (!overrideHeld) { + return; + } - public static void unlockDepthColor() { - if (!depthColorLocked) { - return; - } + depthColorLocked = false; - depthColorLocked = false; + GLStateManager.glDepthMask(vanillaDepthEnable); + GLStateManager.glColorMask(vanillaColor.red, vanillaColor.green, vanillaColor.blue, vanillaColor.alpha); + overrideHeld = false; + } - GLStateManager.glDepthMask(originalDepthEnable); + public static final VanillaStateLayer DEPTH_LAYER = new VanillaStateLayer<>() { + @Override + public boolean isOverrideHeld() { + return overrideHeld; + } - GLStateManager.glColorMask(originalColor.isRedMasked(), originalColor.isGreenMasked(), originalColor.isBlueMasked(), originalColor.isAlphaMasked()); - } + @Override + public void readVanilla(DepthState into) { + into.setEnabled(vanillaDepthEnable); + } + + @Override + public void writeVanilla(DepthState from) { + vanillaDepthEnable = from.isEnabled(); + } + }; + + public static final VanillaStateLayer COLOR_LAYER = new VanillaStateLayer<>() { + @Override + public boolean isOverrideHeld() { + return overrideHeld; + } + + @Override + public void readVanilla(ColorMask into) { + into.set(vanillaColor); + } + + @Override + public void writeVanilla(ColorMask from) { + vanillaColor.set(from); + } + }; } diff --git a/src/main/java/net/coderbot/iris/gui/element/ShaderPackSelectionList.java b/src/main/java/net/coderbot/iris/gui/element/ShaderPackSelectionList.java index 34b8e57cd..0de5adf27 100644 --- a/src/main/java/net/coderbot/iris/gui/element/ShaderPackSelectionList.java +++ b/src/main/java/net/coderbot/iris/gui/element/ShaderPackSelectionList.java @@ -36,7 +36,6 @@ public class ShaderPackSelectionList extends IrisGuiSlot { @Getter private ShaderPackEntry applied = null; - @Setter @Getter private ShaderPackEntry selected = null; @@ -203,6 +202,11 @@ public class ShaderPackSelectionList extends IrisGuiSlot { } } + public void setSelected(ShaderPackEntry selected) { + this.selected = selected; + this.screen.refreshScreenSwitchButton(); + } + public void select(String name) { for (BaseEntry entry : this.entries) { if (entry instanceof ShaderPackEntry shaderPackEntry && name.equals(shaderPackEntry.getPackName())) { diff --git a/src/main/java/net/coderbot/iris/gui/screen/ShaderPackScreen.java b/src/main/java/net/coderbot/iris/gui/screen/ShaderPackScreen.java index 88e23ece2..233c541b0 100644 --- a/src/main/java/net/coderbot/iris/gui/screen/ShaderPackScreen.java +++ b/src/main/java/net/coderbot/iris/gui/screen/ShaderPackScreen.java @@ -39,8 +39,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Properties; import java.util.Set; @@ -85,6 +87,8 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable { private boolean guiHidden = false; private boolean dirty = false; + private final Map packOptionAvailability = new HashMap<>(); + public ShaderPackScreen(GuiScreen parent) { this.title = I18n.format("options.iris.shaderPackSelection.title"); @@ -250,19 +254,7 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable { I18n.format("options.iris.openShaderPackFolder"), button -> this.openShaderPackFolder())); this.screenSwitchButton = new IrisButton(topCenter + 78, this.height - 51, 152, 20, - I18n.format("options.iris.shaderPackList"), button -> { - this.optionMenuOpen = !this.optionMenuOpen; - - // UX: Apply changes before switching screens to avoid unintuitive behavior - // - // Not doing this leads to unintuitive behavior, since selecting a pack in the - // list (but not applying) would open the settings for the previous pack, rather - // than opening the settings for the selected (but not applied) pack. - this.applyChanges(); - - initGui(); - } - ); + I18n.format("options.iris.shaderPackList"), button -> this.toggleOptionMenu()); this.buttonList.add(this.screenSwitchButton); refreshScreenSwitchButton(); @@ -299,6 +291,17 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable { } + @Override + public void updateScreen() { + super.updateScreen(); + + if (this.notificationDialogTimer > 0) { + this.notificationDialogTimer--; + } else if (this.notificationDialog != null) { + this.notificationDialog = null; + } + } + /** * Called when the mouse is clicked. */ @@ -346,10 +349,52 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable { } } + /** + * Switches between the pack list and the pack settings, applying the pending pack first. + *

+ * UX: Apply changes before switching screens to avoid unintuitive behavior. Not doing this leads to + * unintuitive behavior, since selecting a pack in the list (but not applying) would open the settings + * for the previous pack, rather than opening the settings for the selected (but not applied) pack. + */ + private void toggleOptionMenu() { + final boolean opening = !this.optionMenuOpen; + this.optionMenuOpen = opening; + + this.applyChanges(); + + // The pack is only parsed by applyChanges, so this is the first point where an option-less + // pack can be recognized. Stay on the list instead of opening an empty settings screen. + if (opening && !currentPackHasOptions()) { + this.optionMenuOpen = false; + displayNotification(I18n.format("options.iris.shaderPackOptions.noOptions", Iris.getCurrentPackName())); + } + + initGui(); + } + + private boolean canOpenOptionMenu() { + if (!this.shaderPackList.getTopButtonRow().shadersEnabled) { + return false; + } + + final ShaderPackEntry highlighted = this.shaderPackList.getSelected(); + final String name = highlighted != null ? highlighted.getPackName() : Iris.getCurrentPackName(); + + return name == null || this.packOptionAvailability.getOrDefault(name, Boolean.TRUE); + } + + private boolean currentPackHasOptions() { + return Iris.getCurrentPack().map(p -> p.getMenuContainer().hasMenuElements()).orElse(false); + } + public void refreshForChangedPack() { if (Iris.getCurrentPack().isPresent()) { final ShaderPack currentPack = Iris.getCurrentPack().get(); + if (Iris.getCurrentPackName() != null) { + this.packOptionAvailability.put(Iris.getCurrentPackName(), currentPack.getMenuContainer().hasMenuElements()); + } + this.navigation = new NavigationController(); if (this.shaderOptionList != null) { @@ -364,11 +409,10 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable { } public void refreshScreenSwitchButton() { - if (this.screenSwitchButton != null) { + if (this.screenSwitchButton != null && this.shaderPackList != null) { this.screenSwitchButton.displayString = optionMenuOpen ? I18n.format("options.iris.shaderPackList") : I18n.format("options.iris.shaderPackSettings"); - // Disable the settings switch when shaders are off or the selected pack exposes no options - this.screenSwitchButton.enabled = optionMenuOpen || (shaderPackList.getTopButtonRow().shadersEnabled - && Iris.getCurrentPack().map(p -> !p.getMenuContainer().mainScreen.elements.isEmpty()).orElse(true)); + // Disable the settings switch when shaders are off or the highlighted pack exposes no options + this.screenSwitchButton.enabled = optionMenuOpen || canOpenOptionMenu(); } } @@ -435,12 +479,8 @@ public class ShaderPackScreen extends GuiScreen implements HudHideable { return; } if (keyCode == Keyboard.KEY_TAB) { - final boolean canOpenOptions = shaderPackList.getTopButtonRow().shadersEnabled - && Iris.getCurrentPack().map(p -> !p.getMenuContainer().mainScreen.elements.isEmpty()).orElse(false); - if (this.optionMenuOpen || canOpenOptions) { - this.optionMenuOpen = !this.optionMenuOpen; - this.applyChanges(); - this.initGui(); + if (this.optionMenuOpen || canOpenOptionMenu()) { + toggleOptionMenu(); } return; } diff --git a/src/main/java/net/coderbot/iris/layer/GbufferPrograms.java b/src/main/java/net/coderbot/iris/layer/GbufferPrograms.java index c323388c6..cf71a6a4f 100644 --- a/src/main/java/net/coderbot/iris/layer/GbufferPrograms.java +++ b/src/main/java/net/coderbot/iris/layer/GbufferPrograms.java @@ -1,24 +1,32 @@ package net.coderbot.iris.layer; +import lombok.Getter; import net.coderbot.iris.Iris; import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition; import com.gtnewhorizons.angelica.glsm.GLStateManager; +import com.gtnewhorizons.angelica.glsm.states.AlphaState; +import com.gtnewhorizons.angelica.glsm.states.BlendState; import net.coderbot.iris.gl.shader.ProgramCreator; import net.coderbot.iris.gl.state.StateUpdateNotifiers; import net.coderbot.iris.pipeline.WorldRenderingPhase; import net.coderbot.iris.pipeline.WorldRenderingPipeline; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL13; + +import java.util.Arrays; public class GbufferPrograms { private static boolean entities; private static boolean blockEntities; + private static boolean particles; private static boolean outline; private static boolean entityLoop; private static Runnable phaseChangeListener; - + static { StateUpdateNotifiers.phaseChangeNotifier = listener -> phaseChangeListener = listener; } - + private static void checkReentrancy() { if (entities || blockEntities || outline) { throw new IllegalStateException("GbufferPrograms in weird state, tried to call begin function when entities = " @@ -61,8 +69,18 @@ public class GbufferPrograms { throw new IllegalStateException("GbufferPrograms in weird state, tried to call endEntities when entities = false"); } - setPhase(WorldRenderingPhase.NONE); entities = false; + endPhase(); + } + + private static void endPhase() { + if (particles) { + setPhase(WorldRenderingPhase.PARTICLES); + } else if (entityLoop) { + setPhase(WorldRenderingPhase.ENTITIES); + } else { + setPhase(WorldRenderingPhase.NONE); + } } public static void beginOutline() { @@ -76,10 +94,41 @@ public class GbufferPrograms { throw new IllegalStateException("GbufferPrograms in weird state, tried to call endOutline when outline = false"); } - setPhase(WorldRenderingPhase.NONE); outline = false; + endPhase(); } + public static int beginParticles() { + if (particleDepth == particlePhaseSaves.length) { + particlePhaseSaves = Arrays.copyOf(particlePhaseSaves, particleDepth * 2); + particleTranslucencySaves = Arrays.copyOf(particleTranslucencySaves, particleDepth * 2); + } + + particlePhaseSaves[particleDepth] = getCurrentPhase(); + particleTranslucencySaves[particleDepth] = beginTranslucencyDeclaration(Boolean.FALSE); + + setPhase(WorldRenderingPhase.PARTICLES); + particles = true; + + return particleDepth++; + } + + public static void endParticles(int depth) { + particleDepth = depth; + + endTranslucencyDeclaration(particleTranslucencySaves[depth]); + particleTranslucencySaves[depth] = null; + + setPhase(particlePhaseSaves[depth]); + particlePhaseSaves[depth] = null; + + particles = particleDepth > 0; + } + + private static WorldRenderingPhase[] particlePhaseSaves = new WorldRenderingPhase[4]; + private static Boolean[] particleTranslucencySaves = new Boolean[4]; + private static int particleDepth; + public static void beginBlockEntities() { checkReentrancy(); setPhase(WorldRenderingPhase.BLOCK_ENTITIES); @@ -92,8 +141,101 @@ public class GbufferPrograms { throw new IllegalStateException("GbufferPrograms in weird state, tried to call endBlockEntities when blockEntities = false"); } - setPhase(WorldRenderingPhase.NONE); blockEntities = false; + endPhase(); + } + + public static void setCutoutDefaults() { + GLStateManager.enableAlphaTest(); + GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F); + + final int previousUnit = GLStateManager.getActiveTextureUnitForServerState(); + GLStateManager.glActiveTexture(GL13.GL_TEXTURE1); + GLStateManager.enableTexture(); + GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + previousUnit); + } + + public static long pushCutoutDefaults() { + final long saved = packCutoutState(); + setCutoutDefaults(); + return saved; + } + + public static void popCutoutDefaults(long saved) { + if ((saved & ALPHA_ENABLED_BIT) != 0) { + GLStateManager.enableAlphaTest(); + } else { + GLStateManager.disableAlphaTest(); + } + GLStateManager.glAlphaFunc((int) ((saved >>> 32) & 0xFFFF), Float.intBitsToFloat((int) saved)); + + if ((saved & LIGHTMAP_ENABLED_BIT) == 0) { + final int previousUnit = GLStateManager.getActiveTextureUnitForServerState(); + GLStateManager.glActiveTexture(GL13.GL_TEXTURE1); + GLStateManager.disableTexture(); + GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + previousUnit); + } + } + + public static int pushBlendState() { + if (blendDepth == blendSaves.length) { + blendSaves = Arrays.copyOf(blendSaves, blendDepth * 2); + blendEnabledSaves = Arrays.copyOf(blendEnabledSaves, blendDepth * 2); + } + + BlendState saved = blendSaves[blendDepth]; + if (saved == null) { + saved = new BlendState(); + blendSaves[blendDepth] = saved; + } + + GLStateManager.getEffectiveBlendState(saved); + blendEnabledSaves[blendDepth] = GLStateManager.isEffectiveBlendEnabled(); + + return blendDepth++; + } + public static void popBlendState(int depth) { + blendDepth = depth; + + final boolean enabled = blendEnabledSaves[depth]; + if (enabled != GLStateManager.isEffectiveBlendEnabled()) { + if (enabled) { + GLStateManager.enableBlend(); + } else { + GLStateManager.disableBlend(); + } + } + + final BlendState saved = blendSaves[depth]; + GLStateManager.getEffectiveBlendState(blendScratch); + if (blendScratch.getSrcRgb() != saved.getSrcRgb() || blendScratch.getDstRgb() != saved.getDstRgb() + || blendScratch.getSrcAlpha() != saved.getSrcAlpha() || blendScratch.getDstAlpha() != saved.getDstAlpha()) { + GLStateManager.tryBlendFuncSeparate(saved.getSrcRgb(), saved.getDstRgb(), saved.getSrcAlpha(), saved.getDstAlpha()); + } + } + + public static void popBlendStateTop() { + if (blendDepth > 0) { + popBlendState(blendDepth - 1); + } + } + + private static BlendState[] blendSaves = new BlendState[4]; + private static boolean[] blendEnabledSaves = new boolean[4]; + private static int blendDepth; + private static final BlendState blendScratch = new BlendState(); + + private static final long ALPHA_ENABLED_BIT = 1L << 48; + private static final long LIGHTMAP_ENABLED_BIT = 1L << 49; + private static final AlphaState alphaScratch = new AlphaState(); + + private static long packCutoutState() { + GLStateManager.getEffectiveAlphaState(alphaScratch); + + return (Float.floatToRawIntBits(alphaScratch.getReference()) & 0xFFFFFFFFL) + | ((long) (alphaScratch.getFunction() & 0xFFFF) << 32) + | (GLStateManager.isEffectiveAlphaTestEnabled() ? ALPHA_ENABLED_BIT : 0L) + | (GLStateManager.getTextures().getTextureUnitStates(1).isEnabled() ? LIGHTMAP_ENABLED_BIT : 0L); } public static void setBlockEntityDefaults() { @@ -121,7 +263,23 @@ public class GbufferPrograms { } } + public static boolean beginNestedEntityPhase() { + if (getCurrentPhase() != WorldRenderingPhase.BLOCK_ENTITIES) { + return false; + } + setOverridePhase(WorldRenderingPhase.ENTITIES); + return true; + } + + public static void endNestedEntityPhase(boolean pushed) { + if (pushed) { + setOverridePhase(null); + } + } + public static void setOverridePhase(WorldRenderingPhase phase) { + overridePhase = phase; + final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable(); if (pipeline != null) { @@ -129,20 +287,62 @@ public class GbufferPrograms { } } + @Getter + private static WorldRenderingPhase overridePhase; + + public static Boolean beginTranslucencyDeclaration(Boolean translucent) { + final Boolean previous = declaredTranslucent; + declaredTranslucent = translucent; + applyTranslucencyDeclaration(); + return previous; + } + + public static void endTranslucencyDeclaration(Boolean previous) { + declaredTranslucent = previous; + applyTranslucencyDeclaration(); + } + + public static void setTranslucencyDeclaration(Boolean translucent) { + declaredTranslucent = translucent; + applyTranslucencyDeclaration(); + } + + private static Boolean declaredTranslucent; + + private static void applyTranslucencyDeclaration() { + final WorldRenderingPipeline pipeline = Iris.getPipelineManager().getPipelineNullable(); + + if (pipeline != null) { + pipeline.setDeclaredTranslucency(declaredTranslucent); + } + } + + private static SpecialCondition currentSpecial; + + public static SpecialCondition getSpecialCondition() { + return currentSpecial; + } + + public static Boolean getDeclaredTranslucency() { + return declaredTranslucent; + } + public static void setupSpecialRenderCondition(SpecialCondition override) { + currentSpecial = override; Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setSpecialCondition(override)); } public static void teardownSpecialRenderCondition() { + currentSpecial = null; Iris.getPipelineManager().getPipeline().ifPresent(p -> p.setSpecialCondition(null)); } - + public static void runPhaseChangeNotifier() { if (phaseChangeListener != null) { phaseChangeListener.run(); } } - + public static void init() { // Empty initializer to run static } diff --git a/src/main/java/net/coderbot/iris/layer/PassOverride.java b/src/main/java/net/coderbot/iris/layer/PassOverride.java new file mode 100644 index 000000000..7f40909f7 --- /dev/null +++ b/src/main/java/net/coderbot/iris/layer/PassOverride.java @@ -0,0 +1,48 @@ +package net.coderbot.iris.layer; + +import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition; +import net.coderbot.iris.pipeline.WorldRenderingPhase; + +import java.util.Locale; + +public record PassOverride(SpecialCondition special, Boolean translucent, WorldRenderingPhase phase) { + + public static final PassOverride NONE = new PassOverride(null, null, null); + + public PassOverride(SpecialCondition special, Boolean translucent) { + this(special, translucent, null); + } + + public static PassOverride capture() { + final SpecialCondition special = GbufferPrograms.getSpecialCondition(); + final Boolean translucent = GbufferPrograms.getDeclaredTranslucency(); + final WorldRenderingPhase phase = GbufferPrograms.getOverridePhase(); + return special == null && translucent == null && phase == null + ? NONE : new PassOverride(special, translucent, phase); + } + + public void apply() { + GbufferPrograms.setupSpecialRenderCondition(special); + GbufferPrograms.setTranslucencyDeclaration(translucent); + GbufferPrograms.setOverridePhase(phase); + } + + public void clear() { + GbufferPrograms.setupSpecialRenderCondition(null); + GbufferPrograms.setTranslucencyDeclaration(null); + GbufferPrograms.setOverridePhase(null); + } + + public boolean isEntityPhase() { + return phase == WorldRenderingPhase.ENTITIES; + } + + public String nameSuffix() { + if (special == null && translucent == null && phase == null) { + return ""; + } + return "_" + (special == null ? "plain" : special.name().toLowerCase(Locale.ROOT)) + + (translucent == null ? "" : (translucent ? "_translucent" : "_opaque")) + + (phase == null ? "" : "_" + phase.name().toLowerCase(Locale.ROOT)); + } +} diff --git a/src/main/java/net/coderbot/iris/pipeline/DeferredWorldRenderingPipeline.java b/src/main/java/net/coderbot/iris/pipeline/DeferredWorldRenderingPipeline.java index d4003752b..212f38346 100644 --- a/src/main/java/net/coderbot/iris/pipeline/DeferredWorldRenderingPipeline.java +++ b/src/main/java/net/coderbot/iris/pipeline/DeferredWorldRenderingPipeline.java @@ -26,6 +26,7 @@ import net.coderbot.iris.features.FeatureFlags; import net.coderbot.iris.gbuffer_overrides.matching.InputAvailability; import net.coderbot.iris.gbuffer_overrides.matching.ProgramTable; import net.coderbot.iris.gbuffer_overrides.matching.RenderCondition; +import net.coderbot.iris.gbuffer_overrides.matching.TranslucentBlendMatcher; import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition; import net.coderbot.iris.gbuffer_overrides.state.RenderTargetStateListener; import net.coderbot.iris.gl.blending.AlphaTestOverride; @@ -127,9 +128,6 @@ import java.util.function.Supplier; * Encapsulates the compiled shader program objects for the currently loaded shaderpack. */ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, RenderTargetStateListener { - private final static int SRC_ALPHA = 770; - private final static int ONE_MINUS_SRC_ALPHA = 771; - private final static int ONE = 1; private final RenderTargets renderTargets; @Nullable @@ -260,14 +258,31 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R final Map, CompletableFuture>> instancedTransformFutures = submitAttributeTransforms(resolver, INSTANCED_PROGRAM_IDS, true); final Optional terrainSource = first(programs.getGbuffersTerrain(), programs.getGbuffersTexturedLit(), programs.getGbuffersTextured(), programs.getGbuffersBasic()); + final Optional terrainSolidOverride = programs.getGbuffersTerrainSolid(); + final Optional terrainCutoutOverride = programs.getGbuffersTerrainCutout(); + final Optional terrainSolidSource = first(terrainSolidOverride, terrainSource); + final Optional terrainCutoutSource = first(terrainCutoutOverride, terrainSource); + final Optional translucentSource = first(programs.getGbuffersWater(), terrainSource); final Optional shadowSource = programs.getShadow(); + final Optional shadowSolidOverride = programs.getShadowSolid(); + final Optional shadowCutoutOverride = programs.getShadowCutout(); + final Optional shadowSolidSource = first(shadowSolidOverride, shadowSource); + final Optional shadowCutoutSource = first(shadowCutoutOverride, shadowSource); final Optional shadowTranslucentSource = first(programs.getShadowWater(), shadowSource); // Celeritas terrain transform futures final CompletableFuture> celeritasTerrainFuture = terrainSource.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(null); + final CompletableFuture> celeritasTerrainSolidFuture = terrainSolidOverride + .map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(celeritasTerrainFuture); + final CompletableFuture> celeritasTerrainCutoutFuture = terrainCutoutOverride + .map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(celeritasTerrainFuture); final CompletableFuture> celeritasTranslucentFuture = translucentSource.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(null); final CompletableFuture> celeritasShadowFuture = shadowSource.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(null); + final CompletableFuture> celeritasShadowSolidFuture = shadowSolidOverride + .map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(celeritasShadowFuture); + final CompletableFuture> celeritasShadowCutoutFuture = shadowCutoutOverride + .map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(celeritasShadowFuture); final CompletableFuture> celeritasShadowTranslucentFuture = shadowTranslucentSource.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(null); final Int2ObjectArrayMap> setupComputeFutures = submitSetupComputeTransforms(programs.getSetup(), programs.getPackDirectives().getTextureMap()); @@ -461,6 +476,8 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R ProgramId.BeaconBeam, ProgramId.BeaconBeam, ProgramId.BeaconBeam, ProgramId.Entities, ProgramId.Entities, ProgramId.Entities, ProgramId.EntitiesTrans, ProgramId.EntitiesTrans, ProgramId.EntitiesTrans, + ProgramId.Particles, ProgramId.Particles, ProgramId.Particles, + ProgramId.ParticlesTrans, ProgramId.ParticlesTrans, ProgramId.ParticlesTrans, null, ProgramId.ArmorGlint, ProgramId.ArmorGlint, null, ProgramId.SpiderEyes, ProgramId.SpiderEyes, ProgramId.Hand, ProgramId.Hand, ProgramId.Hand, @@ -697,11 +714,14 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R this.celeritasTerrainPipeline = new CeleritasTerrainPipeline(createTerrainSamplers, shadowRenderer == null ? null : createShadowTerrainSamplers, createTerrainImages, shadowRenderer == null ? null : createShadowTerrainImages, this.customUniforms, - terrainSource, + terrainSolidSource, + terrainCutoutSource, translucentSource, - shadowSource, + shadowSolidSource, + shadowCutoutSource, shadowTranslucentSource, - celeritasTerrainFuture, celeritasTranslucentFuture, celeritasShadowFuture, celeritasShadowTranslucentFuture, + celeritasTerrainSolidFuture, celeritasTerrainCutoutFuture, celeritasTranslucentFuture, + celeritasShadowSolidFuture, celeritasShadowCutoutFuture, celeritasShadowTranslucentFuture, renderTargets, flippedAfterPrepare, flippedAfterTranslucent, celeritasShadowFb); @@ -843,28 +863,18 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R } switch (phase) { - case NONE, OUTLINE, DEBUG, PARTICLES: + case NONE, OUTLINE, DEBUG: return RenderCondition.DEFAULT; + case PARTICLES: + return isTranslucentDraw() ? RenderCondition.PARTICLES_TRANSLUCENT : RenderCondition.PARTICLES; case SKY, SUNSET, CUSTOM_SKY, SUN, MOON, STARS, VOID: return RenderCondition.SKY; case TERRAIN_SOLID, TERRAIN_CUTOUT, TERRAIN_CUTOUT_MIPPED: return RenderCondition.TERRAIN_OPAQUE; case ENTITIES: - if (GLStateManager.getBlendState().getSrcRgb() == SRC_ALPHA && - GLStateManager.getBlendState().getSrcAlpha() == ONE_MINUS_SRC_ALPHA && - GLStateManager.getBlendState().getDstRgb() == ONE && - GLStateManager.getBlendState().getDstAlpha() == ONE_MINUS_SRC_ALPHA) - { - return RenderCondition.ENTITIES_TRANSLUCENT; - } else { - return RenderCondition.ENTITIES; - } + return isTranslucentDraw() ? RenderCondition.ENTITIES_TRANSLUCENT : RenderCondition.ENTITIES; case BLOCK_ENTITIES: - if (GLStateManager.getBlendState().getSrcRgb() == SRC_ALPHA && - GLStateManager.getBlendState().getDstRgb() == ONE_MINUS_SRC_ALPHA) { - return RenderCondition.BLOCK_ENTITIES_TRANSLUCENT; - } - return RenderCondition.BLOCK_ENTITIES; + return isTranslucentDraw() ? RenderCondition.BLOCK_ENTITIES_TRANSLUCENT : RenderCondition.BLOCK_ENTITIES; case DESTROY: return RenderCondition.DESTROY; case HAND_SOLID: @@ -884,6 +894,10 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R } } + private boolean isTranslucentDraw() { + return declaredTranslucent != null ? declaredTranslucent : TranslucentBlendMatcher.matchesCurrentState(); + } + public boolean shouldOverrideShaders() { return isRenderingLevelGeometry() && isMainBound; } @@ -906,10 +920,60 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R * Called when a mod overrides the GL program away from the active Iris pass */ public void onModProgramOverride() { + if (current != null) { + modProgramOverrode = true; + programBeforeModOverride = getActivePassProgramId(); + } current = null; currentCondition = null; } + public void restorePassAfterModProgram(int newProgram) { + if (refreshingPass || drivingProgram || !modProgramOverrode) { + return; + } + if (newProgram != 0 && newProgram != programBeforeModOverride) { + return; + } + refreshingPass = true; + try { + current = null; + matchPass(); + } finally { + modProgramOverrode = false; + programBeforeModOverride = -1; + refreshingPass = false; + } + } + + private boolean refreshingPass; + private boolean modProgramOverrode; + private int programBeforeModOverride = -1; + + public void onVanillaBlendChanged() { + if (matchingBlend || drivingProgram || refreshingPass) { + return; + } + + if (declaredTranslucent != null) { + return; + } + + final WorldRenderingPhase phase = getPhase(); + if (phase != WorldRenderingPhase.ENTITIES && phase != WorldRenderingPhase.BLOCK_ENTITIES) { + return; + } + + matchingBlend = true; + try { + matchPass(); + } finally { + matchingBlend = false; + } + } + + private boolean matchingBlend; + private void matchPass() { if (!shouldOverrideShaders()) { return; @@ -946,13 +1010,20 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R current = pass; - if (pass != null) { - pass.use(); - } else { - Program.unbind(); + drivingProgram = true; + try { + if (pass != null) { + pass.use(); + } else { + Program.unbind(); + } + } finally { + drivingProgram = false; } } + private boolean drivingProgram; + @Override public void rebindCurrentPass() { final Pass pass = this.current; @@ -1631,6 +1702,9 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R @Nullable public ComputeProgram getShadowVoxelizationCompute() { return shadowVoxelizationCompute; } + @Nullable + public ShadowRenderer getShadowRenderer() { return shadowRenderer; } + private GlUniformMatrix4f voxelizationModelView; private GlUniformMatrix3f voxelizationNormalMatrix; private final Matrix3f voxelizationNormalScratch = new Matrix3f(); @@ -1772,6 +1846,14 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R @Override public void beginHand() { + isRenderingFullScreenPass = true; + beginPass(null); + currentCondition = null; + profiler.startSection("iris_center_depth"); + centerDepthSampler.sampleCenterDepth(); + profiler.endSection(); + isRenderingFullScreenPass = false; + if (!usesSampler(IrisSamplers.USAGE_DEPTHTEX2)) { return; } @@ -2005,10 +2087,6 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R isRenderingFullScreenPass = true; - profiler.startSection("iris_center_depth"); - centerDepthSampler.sampleCenterDepth(); - profiler.endSection(); - profiler.startSection("iris_composites"); GLDebug.pushGroup("composite"); try { @@ -2110,6 +2188,15 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R matchPass(); } + @Override + public void setDeclaredTranslucency(@Nullable Boolean translucent) { + this.declaredTranslucent = translucent; + matchPass(); + } + + @Nullable + private Boolean declaredTranslucent; + @Override public RenderTargetStateListener getRenderTargetStateListener() { return this; diff --git a/src/main/java/net/coderbot/iris/pipeline/FixedFunctionWorldRenderingPipeline.java b/src/main/java/net/coderbot/iris/pipeline/FixedFunctionWorldRenderingPipeline.java index 57da5a3cd..9d249c452 100644 --- a/src/main/java/net/coderbot/iris/pipeline/FixedFunctionWorldRenderingPipeline.java +++ b/src/main/java/net/coderbot/iris/pipeline/FixedFunctionWorldRenderingPipeline.java @@ -19,6 +19,7 @@ import net.coderbot.iris.uniforms.FrameUpdateNotifier; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.EntityRenderer; import net.minecraft.client.shader.Framebuffer; +import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.OptionalInt; @@ -83,6 +84,11 @@ public class FixedFunctionWorldRenderingPipeline implements WorldRenderingPipeli } + @Override + public void setDeclaredTranslucency(@Nullable Boolean translucent) { + + } + @Override public RenderTargetStateListener getRenderTargetStateListener() { return RenderTargetStateListener.NOP; diff --git a/src/main/java/net/coderbot/iris/pipeline/HandRenderer.java b/src/main/java/net/coderbot/iris/pipeline/HandRenderer.java index e20469d85..586d0234a 100644 --- a/src/main/java/net/coderbot/iris/pipeline/HandRenderer.java +++ b/src/main/java/net/coderbot/iris/pipeline/HandRenderer.java @@ -1,10 +1,12 @@ package net.coderbot.iris.pipeline; +import com.gtnewhorizons.angelica.compat.ModStatus; import com.gtnewhorizons.angelica.compat.mojang.Camera; import com.gtnewhorizons.angelica.compat.mojang.GameModeUtil; import com.gtnewhorizons.angelica.compat.mojang.InteractionHand; import com.gtnewhorizons.angelica.event.RenderHandEvent; import com.gtnewhorizons.angelica.glsm.GLStateManager; +import com.gtnewhorizons.angelica.mixins.interfaces.ItemRendererAccessor; import com.gtnewhorizons.angelica.rendering.RenderingState; import com.gtnewhorizons.angelica.rendering.celeritas.BlockRenderLayer; import lombok.Getter; @@ -83,96 +85,133 @@ public class HandRenderer { !RenderHandEvent.post(); } - public boolean isHandTranslucent(InteractionHand hand) { - ItemStack heldItem = hand.getItemInHand(Minecraft.getMinecraft().thePlayer); - + public boolean isItemTranslucent(ItemStack heldItem) { if (heldItem == null) return false; final Item item = heldItem.getItem(); if (item instanceof ItemBlock itemBlock) { + final Block block = itemBlock.field_150939_a; + if (block == null) return false; + final Map blockTypeIds = BlockRenderingSettings.INSTANCE.getBlockTypeIds(); - return blockTypeIds != null && blockTypeIds.get(itemBlock.field_150939_a) == BlockRenderLayer.TRANSLUCENT; + final BlockRenderLayer override = blockTypeIds != null ? blockTypeIds.get(block) : null; + final BlockRenderLayer layer = override != null ? override : BlockRenderLayer.fromVanillaPass(block.getRenderBlockPass()); + return layer == BlockRenderLayer.TRANSLUCENT; } return false; } + public boolean isHandTranslucent(InteractionHand hand) { + return isItemTranslucent(hand.getItemInHand(Minecraft.getMinecraft().thePlayer)); + } + + private ItemStack mainHandItem() { + return ((ItemRendererAccessor) Minecraft.getMinecraft().entityRenderer.itemRenderer).angelica$getItemToRender(); + } + public boolean isAnyHandTranslucent() { - return isHandTranslucent(InteractionHand.MAIN_HAND) || isHandTranslucent(InteractionHand.OFF_HAND); + return isItemTranslucent(mainHandItem()) + || (ModStatus.isBackhandLoaded && isHandTranslucent(InteractionHand.OFF_HAND)); + } + + public boolean isAnyHandSolid() { + return !(isItemTranslucent(mainHandItem()) + && (!ModStatus.isBackhandLoaded || isHandTranslucent(InteractionHand.OFF_HAND))); } public void renderSolid(float tickDelta, Camera camera, RenderGlobal gameRenderer, WorldRenderingPipeline pipeline) { - if (!canRender(camera, gameRenderer) || !IrisApi.getInstance().isShaderPackInUse()) { + if (!IrisApi.getInstance().isShaderPackInUse() || !canRender(camera, gameRenderer) || !isAnyHandSolid()) { return; } Minecraft mc = Minecraft.getMinecraft(); ACTIVE = true; + final int blendSave = GbufferPrograms.pushBlendState(); + final long cutoutSave = GbufferPrograms.pushCutoutDefaults(); + final boolean depthMaskBefore = GLStateManager.isEffectiveDepthMaskEnabled(); + + GLStateManager.disableBlend(); + GLStateManager.defaultBlendFunc(); + pipeline.setPhase(WorldRenderingPhase.HAND_SOLID); GLStateManager.glPushMatrix(); GLStateManager.glDepthMask(true); // actually write to the depth buffer, it's normally disabled at this point mc.mcProfiler.startSection("iris_hand"); + try { + setupGlState(gameRenderer, camera, tickDelta); - setupGlState(gameRenderer, camera, tickDelta); + GbufferPrograms.setBlockEntityDefaults(); - GbufferPrograms.setBlockEntityDefaults(); + renderingSolid = true; - renderingSolid = true; + mc.entityRenderer.enableLightmap(tickDelta); + mc.entityRenderer.itemRenderer.renderItemInFirstPerson(tickDelta); + mc.entityRenderer.disableLightmap(tickDelta); + } finally { + renderingSolid = false; - mc.entityRenderer.enableLightmap(tickDelta); - mc.entityRenderer.itemRenderer.renderItemInFirstPerson(tickDelta); - mc.entityRenderer.disableLightmap(tickDelta); + mc.mcProfiler.endSection(); - GLStateManager.defaultBlendFunc(); - GLStateManager.glDepthMask(false); - GLStateManager.glPopMatrix(); + GLStateManager.glPopMatrix(); + resetProjectionMatrix(); - mc.mcProfiler.endSection(); + GLStateManager.glDepthMask(depthMaskBefore); + GbufferPrograms.popCutoutDefaults(cutoutSave); + GbufferPrograms.popBlendState(blendSave); - resetProjectionMatrix(); + pipeline.setPhase(WorldRenderingPhase.NONE); - renderingSolid = false; - - pipeline.setPhase(WorldRenderingPhase.NONE); - - ACTIVE = false; + ACTIVE = false; + } } - // TODO: RenderType public void renderTranslucent(float tickDelta, Camera camera, RenderGlobal gameRenderer, WorldRenderingPipeline pipeline) { - if (!canRender(camera, gameRenderer) || !isAnyHandTranslucent() || !IrisApi.getInstance().isShaderPackInUse()) { + if (!IrisApi.getInstance().isShaderPackInUse() || !canRender(camera, gameRenderer) || !isAnyHandTranslucent()) { return; } Minecraft mc = Minecraft.getMinecraft(); ACTIVE = true; + final int blendSave = GbufferPrograms.pushBlendState(); + final long cutoutSave = GbufferPrograms.pushCutoutDefaults(); + final boolean depthMaskBefore = GLStateManager.isEffectiveDepthMaskEnabled(); + + GLStateManager.enableBlend(); + GLStateManager.defaultBlendFunc(); + pipeline.setPhase(WorldRenderingPhase.HAND_TRANSLUCENT); GLStateManager.glPushMatrix(); + GLStateManager.glDepthMask(false); mc.mcProfiler.startSection("iris_hand_translucent"); + try { + setupGlState(gameRenderer, camera, tickDelta); - setupGlState(gameRenderer, camera, tickDelta); + GbufferPrograms.setBlockEntityDefaults(); - GbufferPrograms.setBlockEntityDefaults(); + mc.entityRenderer.enableLightmap(tickDelta); + mc.entityRenderer.itemRenderer.renderItemInFirstPerson(tickDelta); + mc.entityRenderer.disableLightmap(tickDelta); + } finally { + mc.mcProfiler.endSection(); - mc.entityRenderer.enableLightmap(tickDelta); - mc.entityRenderer.itemRenderer.renderItemInFirstPerson(tickDelta); - mc.entityRenderer.disableLightmap(tickDelta); + GLStateManager.glPopMatrix(); + resetProjectionMatrix(); - GLStateManager.glPopMatrix(); + GLStateManager.glDepthMask(depthMaskBefore); + GbufferPrograms.popCutoutDefaults(cutoutSave); + GbufferPrograms.popBlendState(blendSave); - resetProjectionMatrix(); + pipeline.setPhase(WorldRenderingPhase.NONE); - Minecraft.getMinecraft().mcProfiler.endSection(); - - pipeline.setPhase(WorldRenderingPhase.NONE); - - ACTIVE = false; + ACTIVE = false; + } } private void resetProjectionMatrix() { diff --git a/src/main/java/net/coderbot/iris/pipeline/ShadowRenderer.java b/src/main/java/net/coderbot/iris/pipeline/ShadowRenderer.java index 7efaed9df..06613498e 100644 --- a/src/main/java/net/coderbot/iris/pipeline/ShadowRenderer.java +++ b/src/main/java/net/coderbot/iris/pipeline/ShadowRenderer.java @@ -17,6 +17,7 @@ import com.gtnewhorizons.angelica.rendering.RenderingState; import com.gtnewhorizons.angelica.rendering.celeritas.AngelicaRenderSectionManager; import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import lombok.Setter; import net.coderbot.iris.Iris; import net.coderbot.iris.compat.dh.DHCompat; import net.coderbot.iris.gui.option.IrisVideoSettings; @@ -106,7 +107,8 @@ public class ShadowRenderer { private final ShadowCullState packCullingState; private final ShadowCompositeRenderer compositeRenderer; private boolean packHasVoxelization; - private boolean playerReflectionCaptureEnabled = true; + @Setter + private boolean playerReflectionCaptureEnabled = true; private final boolean shouldRenderTerrain; private final boolean shouldRenderTranslucent; private final boolean shouldRenderEntities; @@ -148,10 +150,19 @@ public class ShadowRenderer { private boolean preSubmitActive; private float preSubmittedShadowAngle = Float.NaN; + private float shadowAngleDelta() { + final Vector4f light = celestialUniforms.getShadowLightPositionInWorldSpace(); + final float x = light.x(), y = light.y(), z = light.z(); + final float length = (float) Math.sqrt(x * x + y * y + z * z); + return ShadowGraphGate.deltaForElevation(length > 0.0f ? y / length : 1.0f, + ShadowGraphGate.degreesToRotation(AngelicaConfig.shadowGraphAngleDelta), + AngelicaConfig.shadowGraphHorizonScale); + } + public void preSubmitGraphUpdate(int frame, boolean spectator) { final AngelicaRenderSectionManager rsm = CeleritasWorldRenderer.getInstance().getRenderSectionManager(); final float currentShadowAngle = getShadowAngle(); - if (ShadowGraphGate.shouldMarkDirty(lastGraphShadowAngle, currentShadowAngle)) { + if (ShadowGraphGate.shouldMarkDirty(lastGraphShadowAngle, currentShadowAngle, shadowAngleDelta())) { rsm.markShadowGraphDirty(); lastGraphShadowAngle = currentShadowAngle; } @@ -235,13 +246,21 @@ public class ShadowRenderer { public void setUsesImages(boolean usesImages) { this.packHasVoxelization = packHasVoxelization || usesImages; + this.shadowWritesImages = usesImages; } - public void setPlayerReflectionCaptureEnabled(boolean enabled) { - this.playerReflectionCaptureEnabled = enabled; + private boolean shadowWritesImages; + + public boolean shadowTerrainAlwaysRelays() { + return !shouldRenderTerrain || voxelizationActive() || shadowWritesImages; } - public static MatrixStack createShadowModelView(float sunPathRotation, float intervalSize) { + public static boolean shadowGraphGateIsNotBypassed() { + return !(Iris.getPipelineManager().getPipelineNullable() instanceof DeferredWorldRenderingPipeline deferred) + || deferred.getShadowRenderer() == null || !deferred.getShadowRenderer().shadowTerrainAlwaysRelays(); + } + + public static MatrixStack createShadowModelView(float sunPathRotation, float intervalSize) { // Use entity position for shadow matrix final Vector3d entityPos = Camera.INSTANCE.getEntityPos(); @@ -833,7 +852,7 @@ public class ShadowRenderer { globalTileEntities.clear(); final float currentShadowAngle = getShadowAngle(); - if (ShadowGraphGate.shouldMarkDirty(lastGraphShadowAngle, currentShadowAngle)) { + if (ShadowGraphGate.shouldMarkDirty(lastGraphShadowAngle, currentShadowAngle, shadowAngleDelta())) { CeleritasWorldRenderer.getInstance().getRenderSectionManager().markShadowGraphDirty(); lastGraphShadowAngle = currentShadowAngle; } @@ -843,8 +862,7 @@ public class ShadowRenderer { final boolean consumePreSubmit = preSubmitActive; preSubmitActive = false; - final boolean relayTerrain = !deferActive - || voxelizationActive() + final boolean relayTerrain = shadowTerrainAlwaysRelays() || !targets.isTerrainSnapshotValid() || consumePreSubmit || CeleritasWorldRenderer.getInstance().getRenderSectionManager().isShadowGraphDirty() diff --git a/src/main/java/net/coderbot/iris/pipeline/WorldRenderingPipeline.java b/src/main/java/net/coderbot/iris/pipeline/WorldRenderingPipeline.java index a1940e01c..19c55945f 100644 --- a/src/main/java/net/coderbot/iris/pipeline/WorldRenderingPipeline.java +++ b/src/main/java/net/coderbot/iris/pipeline/WorldRenderingPipeline.java @@ -14,6 +14,7 @@ import net.coderbot.iris.shaderpack.CloudSetting; import net.coderbot.iris.shaderpack.texture.TextureStage; import net.coderbot.iris.uniforms.FrameUpdateNotifier; import net.minecraft.client.renderer.EntityRenderer; +import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.OptionalInt; @@ -31,6 +32,7 @@ public interface WorldRenderingPipeline { void setPhase(WorldRenderingPhase phase); void setInputs(InputAvailability availability); void setSpecialCondition(SpecialCondition special); + void setDeclaredTranslucency(@Nullable Boolean translucent); RenderTargetStateListener getRenderTargetStateListener(); int getCurrentNormalTexture(); diff --git a/src/main/java/net/coderbot/iris/shaderpack/ProgramSet.java b/src/main/java/net/coderbot/iris/shaderpack/ProgramSet.java index 8348892ea..0258c7f36 100644 --- a/src/main/java/net/coderbot/iris/shaderpack/ProgramSet.java +++ b/src/main/java/net/coderbot/iris/shaderpack/ProgramSet.java @@ -17,6 +17,8 @@ public class ProgramSet { private final PackDirectives packDirectives; private final ProgramSource shadow; + private final ProgramSource shadowSolid; + private final ProgramSource shadowCutout; private final ProgramSource shadowWater; private final ComputeSource[] shadowCompute; @@ -34,6 +36,8 @@ public class ProgramSet { private final ProgramSource gbuffersTextured; private final ProgramSource gbuffersTexturedLit; private final ProgramSource gbuffersTerrain; + private final ProgramSource gbuffersTerrainSolid; + private final ProgramSource gbuffersTerrainCutout; private ProgramSource gbuffersDamagedBlock; private final ProgramSource gbuffersSkyBasic; private final ProgramSource gbuffersSkyTextured; @@ -43,6 +47,8 @@ public class ProgramSet { private final ProgramSource gbuffersEntitiesTrans; private final ProgramSource gbuffersEntitiesGlowing; private final ProgramSource gbuffersLightning; + private final ProgramSource gbuffersParticles; + private final ProgramSource gbuffersParticlesTrans; private final ProgramSource gbuffersGlint; private final ProgramSource gbuffersEntityEyes; private final ProgramSource gbuffersBlock; @@ -83,6 +89,10 @@ public class ProgramSet { // - https://github.com/IrisShaders/Iris/issues/987 this.shadow = readProgramSource(directory, sourceProvider, "shadow", this, shaderProperties, BlendModeOverride.OFF); + this.shadowSolid = readProgramSource(directory, sourceProvider, "shadow_solid", this, shaderProperties, + BlendModeOverride.OFF); + this.shadowCutout = readProgramSource(directory, sourceProvider, "shadow_cutout", this, shaderProperties, + BlendModeOverride.OFF); this.shadowWater = readProgramSource(directory, sourceProvider, "shadow_water", this, shaderProperties, BlendModeOverride.OFF); this.shadowCompute = readComputeArray(directory, sourceProvider, "shadow"); @@ -114,6 +124,8 @@ public class ProgramSet { this.gbuffersTextured = readProgramSource(directory, sourceProvider, "gbuffers_textured", this, shaderProperties); this.gbuffersTexturedLit = readProgramSource(directory, sourceProvider, "gbuffers_textured_lit", this, shaderProperties); this.gbuffersTerrain = readProgramSource(directory, sourceProvider, "gbuffers_terrain", this, shaderProperties); + this.gbuffersTerrainSolid = readProgramSource(directory, sourceProvider, "gbuffers_terrain_solid", this, shaderProperties); + this.gbuffersTerrainCutout = readProgramSource(directory, sourceProvider, "gbuffers_terrain_cutout", this, shaderProperties); this.gbuffersDamagedBlock = readProgramSource(directory, sourceProvider, "gbuffers_damagedblock", this, shaderProperties); this.gbuffersSkyBasic = readProgramSource(directory, sourceProvider, "gbuffers_skybasic", this, shaderProperties); this.gbuffersSkyTextured = readProgramSource(directory, sourceProvider, "gbuffers_skytextured", this, shaderProperties); @@ -123,6 +135,8 @@ public class ProgramSet { this.gbuffersEntitiesTrans = readProgramSource(directory, sourceProvider, "gbuffers_entities_translucent", this, shaderProperties); this.gbuffersEntitiesGlowing = readProgramSource(directory, sourceProvider, "gbuffers_entities_glowing", this, shaderProperties); this.gbuffersLightning = readProgramSource(directory, sourceProvider, "gbuffers_lightning", this, shaderProperties); + this.gbuffersParticles = readProgramSource(directory, sourceProvider, "gbuffers_particles", this, shaderProperties); + this.gbuffersParticlesTrans = readProgramSource(directory, sourceProvider, "gbuffers_particles_translucent", this, shaderProperties); this.gbuffersGlint = readProgramSource(directory, sourceProvider, "gbuffers_armor_glint", this, shaderProperties); this.gbuffersEntityEyes = readProgramSource(directory, sourceProvider, "gbuffers_spidereyes", this, shaderProperties); this.gbuffersBlock = readProgramSource(directory, sourceProvider, "gbuffers_block", this, shaderProperties); @@ -224,6 +238,8 @@ public class ProgramSet { List computes = new ArrayList<>(); programs.add(shadow); + programs.add(shadowSolid); + programs.add(shadowCutout); programs.add(shadowWater); programs.addAll(Arrays.asList(shadowcomp)); programs.addAll(Arrays.asList(begin)); @@ -231,8 +247,9 @@ public class ProgramSet { programs.addAll (Arrays.asList( gbuffersBasic, gbuffersBeaconBeam, gbuffersTextured, gbuffersTexturedLit, gbuffersTerrain, + gbuffersTerrainSolid, gbuffersTerrainCutout, gbuffersDamagedBlock, gbuffersSkyBasic, gbuffersSkyTextured, gbuffersClouds, gbuffersWeather, - gbuffersEntities, gbuffersEntitiesTrans, gbuffersEntitiesGlowing, gbuffersLightning, gbuffersGlint, gbuffersEntityEyes, gbuffersBlock, gbuffersBlockTrans, + gbuffersEntities, gbuffersEntitiesTrans, gbuffersEntitiesGlowing, gbuffersLightning, gbuffersParticles, gbuffersParticlesTrans, gbuffersGlint, gbuffersEntityEyes, gbuffersBlock, gbuffersBlockTrans, gbuffersHand, dhShadow, dhTerrain, dhGeneric, dhWater )); @@ -309,6 +326,14 @@ public class ProgramSet { return shadow.requireValid(); } + public Optional getShadowSolid() { + return shadowSolid.requireValid(); + } + + public Optional getShadowCutout() { + return shadowCutout.requireValid(); + } + public Optional getShadowWater() { return shadowWater.requireValid(); } @@ -353,6 +378,14 @@ public class ProgramSet { return gbuffersTerrain.requireValid(); } + public Optional getGbuffersTerrainSolid() { + return gbuffersTerrainSolid.requireValid(); + } + + public Optional getGbuffersTerrainCutout() { + return gbuffersTerrainCutout.requireValid(); + } + public Optional getGbuffersDamagedBlock() { return gbuffersDamagedBlock.requireValid(); } @@ -389,6 +422,14 @@ public class ProgramSet { return gbuffersLightning.requireValid(); } + public Optional getGbuffersParticles() { + return gbuffersParticles.requireValid(); + } + + public Optional getGbuffersParticlesTrans() { + return gbuffersParticlesTrans.requireValid(); + } + public Optional getGbuffersGlint() { return gbuffersGlint.requireValid(); } @@ -428,6 +469,8 @@ public class ProgramSet { public Optional get(ProgramId programId) { return switch (programId) { case Shadow -> getShadow(); + case ShadowSolid -> getShadowSolid(); + case ShadowCutout -> getShadowCutout(); case ShadowWater -> first(getShadowWater(), getShadow()); case Basic -> getGbuffersBasic(); case Line -> gbuffersLine.requireValid(); @@ -437,6 +480,8 @@ public class ProgramSet { case SkyTextured -> getGbuffersSkyTextured(); case Clouds -> getGbuffersClouds(); case Terrain -> getGbuffersTerrain(); + case TerrainSolid -> getGbuffersTerrainSolid(); + case TerrainCutout -> getGbuffersTerrainCutout(); case DamagedBlock -> getGbuffersDamagedBlock(); case Block -> getGbuffersBlock(); case BlockTrans -> getGbuffersBlockTrans(); @@ -445,6 +490,8 @@ public class ProgramSet { case EntitiesTrans -> getGbuffersEntitiesTrans(); case EntitiesGlowing -> getGbuffersEntitiesGlowing(); case Lightning -> getGbuffersLightning(); + case Particles -> getGbuffersParticles(); + case ParticlesTrans -> getGbuffersParticlesTrans(); case ArmorGlint -> getGbuffersGlint(); case SpiderEyes -> getGbuffersEntityEyes(); case Hand -> getGbuffersHand(); diff --git a/src/main/java/net/coderbot/iris/shaderpack/loading/ProgramId.java b/src/main/java/net/coderbot/iris/shaderpack/loading/ProgramId.java index 4632dc32d..cd238d6d8 100644 --- a/src/main/java/net/coderbot/iris/shaderpack/loading/ProgramId.java +++ b/src/main/java/net/coderbot/iris/shaderpack/loading/ProgramId.java @@ -27,7 +27,9 @@ public enum ProgramId { Terrain(ProgramGroup.Gbuffers, "terrain", TexturedLit), TerrainSolid(ProgramGroup.Gbuffers, "terrain_solid", Terrain), - TerrainCutoutMip(ProgramGroup.Gbuffers, "terrain_cutout_mip", Terrain), + // terrain_cutout_mip program was removed in modern versions of Minecraft. As the goal is to be a faithful backport of Iris + // we likely should just group cutout and cutout mipped into the same program. + // CUTOUT_MIPPED essentially became CUTOUT and now everything uses mipmaps in modern. We don't as of writing. TerrainCutout(ProgramGroup.Gbuffers, "terrain_cutout", Terrain), DamagedBlock(ProgramGroup.Gbuffers, "damagedblock", Terrain), @@ -40,6 +42,8 @@ public enum ProgramId { EntitiesTrans(ProgramGroup.Gbuffers, "entities_translucent", Entities), EntitiesGlowing(ProgramGroup.Gbuffers, "entities_glowing", Entities), Lightning(ProgramGroup.Gbuffers, "lightning", Entities), + Particles(ProgramGroup.Gbuffers, "particles", TexturedLit), + ParticlesTrans(ProgramGroup.Gbuffers, "particles_translucent", Particles), ArmorGlint(ProgramGroup.Gbuffers, "armor_glint", Textured), SpiderEyes(ProgramGroup.Gbuffers, "spidereyes", Textured, new BlendModeOverride(new BlendState(GL11.GL_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE)), diff --git a/src/main/java/net/coderbot/iris/shaderpack/option/menu/OptionMenuContainer.java b/src/main/java/net/coderbot/iris/shaderpack/option/menu/OptionMenuContainer.java index ae26d6884..8f21c0c6c 100644 --- a/src/main/java/net/coderbot/iris/shaderpack/option/menu/OptionMenuContainer.java +++ b/src/main/java/net/coderbot/iris/shaderpack/option/menu/OptionMenuContainer.java @@ -82,6 +82,10 @@ public class OptionMenuContainer { // Screens will call this when they contain a "*" element, so that the list of // unused options can be added after all other screens have been resolved + public boolean hasMenuElements() { + return !this.originalMainElements.isEmpty(); + } + public void queueForUnusedOptionDump(int index, List elementList) { this.unusedOptionDumpQueue.put(elementList, index); } diff --git a/src/main/java/net/coderbot/iris/shadows/ShadowGraphGate.java b/src/main/java/net/coderbot/iris/shadows/ShadowGraphGate.java index 41938e4a9..8f6cba81e 100644 --- a/src/main/java/net/coderbot/iris/shadows/ShadowGraphGate.java +++ b/src/main/java/net/coderbot/iris/shadows/ShadowGraphGate.java @@ -2,14 +2,24 @@ package net.coderbot.iris.shadows; public final class ShadowGraphGate { - public static final float SHADOW_ANGLE_GRAPH_DELTA = 0.0005f; + public static final float DEFAULT_ANGLE_DELTA_DEGREES = 0.03f; + + public static final float DEFAULT_HORIZON_SCALE = 0.5f; private ShadowGraphGate() {} - public static boolean shouldMarkDirty(float last, float current) { - float delta = current - last; - if (delta > 0.5f) delta -= 1.0f; - if (delta < -0.5f) delta += 1.0f; - return Float.isNaN(last) || Math.abs(delta) > SHADOW_ANGLE_GRAPH_DELTA; + public static float degreesToRotation(float degrees) { + return degrees / 360.0f; + } + + public static boolean shouldMarkDirty(float last, float current, float delta) { + float d = current - last; + if (d > 0.5f) d -= 1.0f; + if (d < -0.5f) d += 1.0f; + return Float.isNaN(last) || Math.abs(d) > delta; + } + + public static float deltaForElevation(float sinElevation, float baseDelta, float minElevationScale) { + return baseDelta * Math.max(minElevationScale, sinElevation * sinElevation); } } diff --git a/src/main/java/net/coderbot/iris/uniforms/CapturedRenderingState.java b/src/main/java/net/coderbot/iris/uniforms/CapturedRenderingState.java index 7772352b1..74308c3ed 100644 --- a/src/main/java/net/coderbot/iris/uniforms/CapturedRenderingState.java +++ b/src/main/java/net/coderbot/iris/uniforms/CapturedRenderingState.java @@ -54,13 +54,20 @@ public class CapturedRenderingState { } } - public void setCurrentEntity(int entity) { + private void setCurrentEntity(int entity) { this.currentRenderedEntity = entity; if (this.entityIdListener != null) { this.entityIdListener.run(); } - setCurrentRenderedItem(0); + } + + /** + * I should have done this a while ago, better solution to not forget these exist in tandem. + */ + public void setCurrentEntityAndItem(int entity, int item) { + setCurrentEntity(entity); + setCurrentRenderedItem(item); } public void setCurrentEntityColor(float r, float g, float b, float a) { diff --git a/src/main/java/net/coderbot/iris/uniforms/IdMapUniforms.java b/src/main/java/net/coderbot/iris/uniforms/IdMapUniforms.java index 2b5196b1e..87844a37c 100644 --- a/src/main/java/net/coderbot/iris/uniforms/IdMapUniforms.java +++ b/src/main/java/net/coderbot/iris/uniforms/IdMapUniforms.java @@ -16,6 +16,7 @@ import net.minecraft.util.ResourceLocation; import org.jetbrains.annotations.NotNull; import static net.coderbot.iris.gl.uniform.UniformUpdateFrequency.PER_FRAME; +import static org.joml.Math.clamp; public final class IdMapUniforms { @@ -71,6 +72,13 @@ public final class IdMapUniforms { lightValue = 0; } + /** + * Damn you chisel beacon + */ + private static int clampEmission(int emission) { + return clamp(0, 15, emission); + } + public void update() { EntityPlayer player = Minecraft.getMinecraft().thePlayer; @@ -98,7 +106,7 @@ public final class IdMapUniforms { intID = itemIdMap.applyAsInt(new NamespacedId(heldItemId.getResourceDomain(), heldItemId.getResourcePath())); IrisItemLightProvider lightProvider = (IrisItemLightProvider) heldItem; - lightValue = lightProvider.getLightEmission(Minecraft.getMinecraft().thePlayer, heldStack); + lightValue = clampEmission(lightProvider.getLightEmission(Minecraft.getMinecraft().thePlayer, heldStack)); if (applyOldHandLight) { lightProvider = applyOldHandLighting(player, lightProvider); @@ -119,7 +127,7 @@ public final class IdMapUniforms { } IrisItemLightProvider lightProvider = (IrisItemLightProvider) offHandItem; - int newEmission = lightProvider.getLightEmission(Minecraft.getMinecraft().thePlayer, offHandStack); + int newEmission = clampEmission(lightProvider.getLightEmission(Minecraft.getMinecraft().thePlayer, offHandStack)); if (lightValue < newEmission) { lightValue = newEmission; diff --git a/src/main/java/net/coderbot/iris/uniforms/ItemIdManager.java b/src/main/java/net/coderbot/iris/uniforms/ItemIdManager.java index ad5b1769a..9ab341979 100644 --- a/src/main/java/net/coderbot/iris/uniforms/ItemIdManager.java +++ b/src/main/java/net/coderbot/iris/uniforms/ItemIdManager.java @@ -31,7 +31,7 @@ public class ItemIdManager { * Set the item ID for a Block rendered outside of terrain. */ public static void setBlockId(Block block, int metadata) { - CapturedRenderingState.INSTANCE.setCurrentRenderedItem(Math.max(0, ItemMaterialHelper.getMaterialId(block, metadata))); + CapturedRenderingState.INSTANCE.setCurrentRenderedItem(ItemMaterialHelper.getMaterialId(block, metadata)); } /** diff --git a/src/main/java/net/coderbot/iris/uniforms/ItemMaterialHelper.java b/src/main/java/net/coderbot/iris/uniforms/ItemMaterialHelper.java index 38d0c393d..72ac654c0 100644 --- a/src/main/java/net/coderbot/iris/uniforms/ItemMaterialHelper.java +++ b/src/main/java/net/coderbot/iris/uniforms/ItemMaterialHelper.java @@ -27,15 +27,13 @@ public class ItemMaterialHelper { /** * Get the material ID for an ItemStack. - * For ItemBlock: checks block.properties first, then item.properties. - * For other items: checks item.properties only. + * 0 if unmapped. * * @param itemStack The item stack to get material ID for - * @return The material ID, or -1 if not found */ public static int getMaterialId(ItemStack itemStack) { if (itemStack == null || itemStack.getItem() == null) { - return -1; + return 0; } // Check item NBT conditions first @@ -56,7 +54,7 @@ public class ItemMaterialHelper { public static int getMaterialId(Item item, int metadata) { if (item == null) { - return -1; + return 0; } // Check cache first @@ -84,11 +82,11 @@ public class ItemMaterialHelper { /** * Get the material ID for a Block rendered outside of terrain. * - * @return The material ID, or -1 if not found + * @return The material ID, or 0 if not found */ public static int getMaterialId(Block block, int metadata) { if (block == null) { - return -1; + return 0; } Int2IntMap metadataCache = BLOCK_MATERIAL_CACHE.get(block); @@ -112,60 +110,44 @@ public class ItemMaterialHelper { } private static int lookupBlockMaterialId(Block block, int metadata) { - Reference2ObjectMap blockMetaMatches = BlockRenderingSettings.INSTANCE.getBlockMetaMatches(); - if (blockMetaMatches != null) { - Int2IntMap metaMap = blockMetaMatches.get(block); - if (metaMap != null) { - int id = BlockMaterialMapping.resolveId(metaMap, metadata); - if (id != -1) { - return id; - } - } - } - - final Item item = Item.getItemFromBlock(block); - return item != null ? lookupItemPropertiesId(item) : -1; + return lookupBlockPropertiesId(block, metadata); } /** * Perform the actual material ID lookup without caching. - * - * For block items (ItemBlock): use block.properties first, then fall back to item.properties. - * For non-block items: use item.properties only. - * - * This matches modern Iris behavior where block items use block IDs and non-block items use item IDs. */ private static int lookupMaterialId(Item item, int metadata) { - // For ItemBlock: check block.properties first if (item instanceof ItemBlock itemBlock) { final Block block = itemBlock.field_150939_a; // The block this item places - - if (block != null) { - Reference2ObjectMap blockMetaMatches = BlockRenderingSettings.INSTANCE.getBlockMetaMatches(); - if (blockMetaMatches != null) { - Int2IntMap metaMap = blockMetaMatches.get(block); - if (metaMap != null) { - int id = BlockMaterialMapping.resolveId(metaMap, itemBlock.getMetadata(metadata)); - if (id != -1) { - return id; - } - } - } - } + return block == null ? 0 : lookupBlockPropertiesId(block, itemBlock.getMetadata(metadata)); } - // Fall back to item.properties return lookupItemPropertiesId(item); } + private static int lookupBlockPropertiesId(Block block, int metadata) { + final Reference2ObjectMap blockMetaMatches = BlockRenderingSettings.INSTANCE.getBlockMetaMatches(); + if (blockMetaMatches == null) { + return 0; + } + + final Int2IntMap metaMap = blockMetaMatches.get(block); + if (metaMap == null) { + return 0; + } + + final int id = BlockMaterialMapping.resolveId(metaMap, metadata); + return id == -1 ? 0 : id; + } + private static int lookupItemPropertiesId(Item item) { final Object2IntFunction itemIds = BlockRenderingSettings.INSTANCE.getItemIds(); if (itemIds == null) { - return -1; + return 0; } final NamespacedId name = getCachedItemName(item); - return name == null ? -1 : itemIds.applyAsInt(name); + return name == null ? 0 : itemIds.applyAsInt(name); } /** diff --git a/src/main/resources/assets/angelica/lang/en_US.lang b/src/main/resources/assets/angelica/lang/en_US.lang index b98d32483..004f61055 100644 --- a/src/main/resources/assets/angelica/lang/en_US.lang +++ b/src/main/resources/assets/angelica/lang/en_US.lang @@ -130,6 +130,7 @@ options.iris.shaderPackSelection.failedAdd=Files were not valid Shader Packs options.iris.shaderPackSelection.failedAddSingle="%s" is not a valid Shader Pack options.iris.shaderPackSelection.copyError=Could not add Shader Pack "%s" options.iris.shaderPackSelection.copyErrorAlreadyExists="%s" is already in your Shader Packs folder! +options.iris.shaderPackOptions.noOptions="%s" has no configurable settings options.iris.shaderPackOptions.tooManyFiles=Cannot import multiple Shader Settings files! options.iris.shaderPackOptions.failedImport=Failed to import Shader Settings file "%s" options.iris.shaderPackOptions.importedSettings=Imported Shader Settings from "%s" @@ -224,6 +225,12 @@ options.angelica.droppedItemLimit=Dropped Item Render Limit options.angelica.droppedItemLimit.tooltip=The maximum number of dropped items that will be rendered. Lower values can improve performance. options.angelica.mobSpawnerRenderDistance=Mob spawner render distance options.angelica.mobSpawnerRenderDistance.tooltip=Render distance for the spinning mob inside mod spawners +options.angelica.shadowGraphAngleDelta=Shadow Graph Rebuild Threshold +options.angelica.shadowGraphAngleDelta.tooltip=How much a celestial body can move before the shadow map can update. Note this option is disabled when a shader pack utilizes voxelization features. +options.angelica.shadowGraphAngleDelta.always=Always +options.angelica.shadowGraphHorizonScale=Shadow Graph Horizon Tightening +options.angelica.shadowGraphHorizonScale.tooltip=Adjusts the rate the shadow map rebuilds when when a celestial body is near the horizon. Rate is sinusoidal. +options.angelica.shadowGraphHorizonScale.off=OFF options.angelica.itemdisplaylistcount=Item Renderer Cache Limit options.angelica.itemdisplaylistcount.tooltip=The upper limit of VBO's to cache in the optimized item renderer. Higher values will significantly increase performance, but may result in increased VRAM usage. options.angelica.threadedChunkBuilding=Threaded Chunk Building diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEffectRenderer_Particles.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEffectRenderer_Particles.java new file mode 100644 index 000000000..cf07cfcb2 --- /dev/null +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEffectRenderer_Particles.java @@ -0,0 +1,35 @@ +package com.gtnewhorizons.angelica.mixins.early.shaders; + +import com.gtnewhorizons.angelica.rendering.ParticleRunSplitter; +import net.minecraft.client.particle.EffectRenderer; +import net.minecraft.client.particle.EntityFX; +import net.minecraft.client.renderer.Tessellator; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +/** + * Splits the particle batch between {@code gbuffers_particles} and {@code gbuffers_particles_translucent}. + */ +@Mixin(EffectRenderer.class) +public class MixinEffectRenderer_Particles { + + @Redirect( + method = "renderParticles", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;startDrawingQuads()V") + ) + private void iris$beginRun(Tessellator tessellator) { + ParticleRunSplitter.beginRun(); + tessellator.startDrawingQuads(); + } + + @Redirect( + method = "renderParticles", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/EntityFX;getBrightnessForRender(F)I"), + require = 1 + ) + private int iris$splitRun(EntityFX particle, float partialTicks) { + ParticleRunSplitter.splitIfNeeded(particle, Tessellator.instance, partialTicks); + return particle.getBrightnessForRender(partialTicks); + } +} diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityPickupFX.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityPickupFX.java index ec6bde197..e40686506 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityPickupFX.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityPickupFX.java @@ -3,9 +3,11 @@ package com.gtnewhorizons.angelica.mixins.early.shaders; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.coderbot.iris.layer.GbufferPrograms; +import net.coderbot.iris.pipeline.HandRenderer; import net.minecraft.client.particle.EntityPickupFX; import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.entity.Entity; +import net.minecraft.entity.item.EntityItem; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -23,11 +25,17 @@ public class MixinEntityPickupFX { at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/RenderManager;renderEntityWithPosYaw(Lnet/minecraft/entity/Entity;DDDFF)Z") ) private boolean iris$wrapPickupRender(RenderManager renderManager, Entity entity, double x, double y, double z, float yaw, float partialTicks, Operation original) { + final Boolean translucent = entity instanceof EntityItem item + ? HandRenderer.INSTANCE.isItemTranslucent(item.getEntityItem()) + : null; + + final Boolean previous = GbufferPrograms.beginTranslucencyDeclaration(translucent); GbufferPrograms.beginEntities(); try { return original.call(renderManager, entity, x, y, z, yaw, partialTicks); } finally { GbufferPrograms.endEntities(); + GbufferPrograms.endTranslucencyDeclaration(previous); } } } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityRenderer.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityRenderer.java index 1066486d9..98adb91fb 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityRenderer.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinEntityRenderer.java @@ -11,6 +11,7 @@ import net.coderbot.iris.Iris; import net.coderbot.iris.shaderpack.CloudSetting; import net.coderbot.iris.compat.dh.DHCompat; import net.coderbot.iris.gl.program.Program; +import net.coderbot.iris.layer.GbufferPrograms; import net.coderbot.iris.pipeline.HandRenderer; import net.coderbot.iris.pipeline.WorldRenderingPhase; import net.coderbot.iris.pipeline.WorldRenderingPipeline; @@ -18,12 +19,14 @@ import net.coderbot.iris.uniforms.CapturedRenderingState; import net.coderbot.iris.uniforms.SystemTimeUniforms; import net.irisshaders.iris.api.v0.IrisApi; import net.minecraft.client.Minecraft; +import net.minecraft.client.particle.EffectRenderer; import net.minecraft.client.renderer.EntityRenderer; import net.minecraft.client.renderer.ItemRenderer; import net.minecraft.client.renderer.RenderGlobal; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.resources.IResourceManagerReloadListener; import net.minecraft.client.settings.GameSettings; +import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -142,4 +145,24 @@ public abstract class MixinEntityRenderer implements IResourceManagerReloadListe original.call(instance, tessellator, entity, partialTicks); pipeline.get().setPhase(WorldRenderingPhase.NONE); } + + @WrapOperation(method = "renderWorld(FJ)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/EffectRenderer;renderLitParticles(Lnet/minecraft/entity/Entity;F)V")) + private void iris$litParticlePhase(EffectRenderer instance, Entity viewer, float partialTicks, Operation original) { + final int depth = GbufferPrograms.beginParticles(); + try { + original.call(instance, viewer, partialTicks); + } finally { + GbufferPrograms.endParticles(depth); + } + } + + @WrapOperation(method = "renderWorld(FJ)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/EffectRenderer;renderParticles(Lnet/minecraft/entity/Entity;F)V")) + private void iris$particlePhase(EffectRenderer instance, Entity viewer, float partialTicks, Operation original) { + final int depth = GbufferPrograms.beginParticles(); + try { + original.call(instance, viewer, partialTicks); + } finally { + GbufferPrograms.endParticles(depth); + } + } } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinItemRenderer.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinItemRenderer.java index 9c81ad318..7ac6b594c 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinItemRenderer.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinItemRenderer.java @@ -1,16 +1,10 @@ package com.gtnewhorizons.angelica.mixins.early.shaders; -import com.gtnewhorizons.angelica.compat.mojang.InteractionHand; -import com.gtnewhorizons.angelica.shadercompat.ShaderGlint; -import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition; -import net.coderbot.iris.layer.GbufferPrograms; import net.coderbot.iris.pipeline.HandRenderer; import net.coderbot.iris.uniforms.ItemIdManager; import net.irisshaders.iris.api.v0.IrisApi; import net.minecraft.client.renderer.ItemRenderer; -import net.minecraft.entity.EntityLivingBase; import net.minecraft.item.ItemStack; -import net.minecraftforge.client.IItemRenderer; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -31,14 +25,10 @@ public class MixinItemRenderer { */ @Inject(method = "renderItemInFirstPerson", at = @At("HEAD"), cancellable = true) private void iris$setFirstPersonItemId(float partialTicks, CallbackInfo ci) { - if (IrisApi.getInstance().isShaderPackInUse()) { - boolean isHandTranslucent = HandRenderer.INSTANCE.isHandTranslucent(InteractionHand.MAIN_HAND); - boolean isRenderingSolid = HandRenderer.INSTANCE.isRenderingSolid(); - - if (isRenderingSolid == isHandTranslucent) { - ci.cancel(); - return; // Don't set item ID since we're cancelling - } + if (IrisApi.getInstance().isShaderPackInUse() + && HandRenderer.INSTANCE.isRenderingSolid() == HandRenderer.INSTANCE.isItemTranslucent(this.itemToRender)) { + ci.cancel(); + return; // Don't set item ID since we're cancelling } // Wait for hand to lower before setting ID @@ -56,54 +46,4 @@ public class MixinItemRenderer { ItemIdManager.resetItemId(); } - /** - * Set the item ID for any item rendered on an entity. - */ - @Inject( - method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V", - at = @At("HEAD"), - remap = false - ) - private void iris$setEntityItemId(EntityLivingBase entity, ItemStack itemStack, int renderPass, IItemRenderer.ItemRenderType type, CallbackInfo ci) { - ItemIdManager.setItemId(itemStack); - } - - /** - * Clear the ID once the item is drawn. - */ - @Inject( - method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V", - at = @At("RETURN"), - remap = false - ) - private void iris$clearEntityItemId(EntityLivingBase entity, ItemStack itemStack, int renderPass, IItemRenderer.ItemRenderType type, CallbackInfo ci) { - ItemIdManager.resetItemId(); - } - - /** - * Activate GLINT shader before rendering enchantment glint on held items (third person). - */ - @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 - ) - private void iris$glintStart(CallbackInfo ci) { - ItemIdManager.resetItemId(); - GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.GLINT); - ShaderGlint.beginGlint(); - } - - /** - * Deactivate GLINT shader after rendering enchantment glint on held items (third person). - */ - @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 = 1, shift = At.Shift.AFTER), - remap = false - ) - private void iris$glintEnd(CallbackInfo ci) { - GbufferPrograms.teardownSpecialRenderCondition(); - ShaderGlint.endGlint(); - } } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinItemRendererBackhand.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinItemRendererBackhand.java index 05b61437f..997889799 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinItemRendererBackhand.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinItemRendererBackhand.java @@ -4,39 +4,43 @@ import com.gtnewhorizons.angelica.compat.ModStatus; import com.gtnewhorizons.angelica.compat.backhand.BackhandReflectionCompat; import com.gtnewhorizons.angelica.compat.mojang.InteractionHand; import net.coderbot.iris.pipeline.HandRenderer; +import net.coderbot.iris.uniforms.ItemIdManager; import net.irisshaders.iris.api.v0.IrisApi; import net.minecraft.client.renderer.ItemRenderer; +import net.minecraft.item.ItemStack; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(ItemRenderer.class) public class MixinItemRendererBackhand { + + @Shadow private ItemStack itemToRender; + @Inject(method = "renderItemInFirstPerson", at = @At("HEAD"), cancellable = true) private void iris$skipTranslucentHands(float partialTicks, CallbackInfo ci) { - if (IrisApi.getInstance().isShaderPackInUse()) { - final boolean isHandTranslucent = HandRenderer.INSTANCE.isHandTranslucent(InteractionHand.MAIN_HAND); - if (HandRenderer.INSTANCE.isRenderingSolid() && isHandTranslucent) { - ci.cancel(); - BackhandReflectionCompat.renderOffhand(partialTicks); - } else if (!HandRenderer.INSTANCE.isRenderingSolid() && !isHandTranslucent) { - ci.cancel(); - BackhandReflectionCompat.renderOffhand(partialTicks); - } + if (IrisApi.getInstance().isShaderPackInUse() + && HandRenderer.INSTANCE.isRenderingSolid() == HandRenderer.INSTANCE.isItemTranslucent(this.itemToRender)) { + ci.cancel(); + BackhandReflectionCompat.renderOffhand(partialTicks); + return; } + ItemIdManager.setItemId(this.itemToRender); + } + + @Inject(method = "renderItemInFirstPerson", at = @At("RETURN"), order = 800) + private void iris$resetFirstPersonItemId(float partialTicks, CallbackInfo ci) { + ItemIdManager.resetItemId(); } // change injection to before Backhand's inject @Inject(method = "renderItemInFirstPerson", at = @At("RETURN"), cancellable = true, order = 900) private void iris$skipTranslucentHandsBackhand(float partialTicks, CallbackInfo ci) { - if (ModStatus.isBackhandLoaded && IrisApi.getInstance().isShaderPackInUse()) { - final boolean isHandTranslucent = HandRenderer.INSTANCE.isHandTranslucent(InteractionHand.OFF_HAND); - if (HandRenderer.INSTANCE.isRenderingSolid() && isHandTranslucent) { - ci.cancel(); - } else if (!HandRenderer.INSTANCE.isRenderingSolid() && !isHandTranslucent) { - ci.cancel(); - } + if (ModStatus.isBackhandLoaded && IrisApi.getInstance().isShaderPackInUse() + && HandRenderer.INSTANCE.isRenderingSolid() == HandRenderer.INSTANCE.isHandTranslucent(InteractionHand.OFF_HAND)) { + ci.cancel(); } } } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinItemRenderer_ItemId.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinItemRenderer_ItemId.java new file mode 100644 index 000000000..bcea0943f --- /dev/null +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinItemRenderer_ItemId.java @@ -0,0 +1,75 @@ +package com.gtnewhorizons.angelica.mixins.early.shaders; + +import com.gtnewhorizons.angelica.mixins.interfaces.ItemRendererAccessor; +import com.gtnewhorizons.angelica.shadercompat.ShaderGlint; +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition; +import net.coderbot.iris.layer.GbufferPrograms; +import net.coderbot.iris.pipeline.HandRenderer; +import net.coderbot.iris.uniforms.ItemIdManager; +import net.minecraft.client.renderer.ItemRenderer; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.item.ItemStack; +import net.minecraftforge.client.IItemRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * Item ID and glint handling for items rendered on entities. + */ +@Mixin(ItemRenderer.class) +public class MixinItemRenderer_ItemId implements ItemRendererAccessor { + + @Shadow private ItemStack itemToRender; + + @Override + public ItemStack angelica$getItemToRender() { + return itemToRender; + } + + @WrapMethod( + method = "renderItem(Lnet/minecraft/entity/EntityLivingBase;Lnet/minecraft/item/ItemStack;ILnet/minecraftforge/client/IItemRenderer$ItemRenderType;)V", + remap = false + ) + private void iris$entityItemId(EntityLivingBase entity, ItemStack itemStack, int renderPass, IItemRenderer.ItemRenderType type, Operation original) { + final int prevItemId = ItemIdManager.getItemId(); + final long prevCutout = GbufferPrograms.pushCutoutDefaults(); + + final Boolean prevTranslucency = GbufferPrograms.beginTranslucencyDeclaration( + HandRenderer.INSTANCE.isItemTranslucent(itemStack)); + + ItemIdManager.setItemId(itemStack); + try { + original.call(entity, itemStack, renderPass, type); + } finally { + GbufferPrograms.endTranslucencyDeclaration(prevTranslucency); + ItemIdManager.setItemIdRaw(prevItemId); + GbufferPrograms.popCutoutDefaults(prevCutout); + } + } + + @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 + ) + private void iris$glintStart(CallbackInfo ci) { + ItemIdManager.resetItemId(); + GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.GLINT); + ShaderGlint.beginGlint(); + } + + @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 = 1, shift = At.Shift.AFTER), + remap = false + ) + private void iris$glintEnd(CallbackInfo ci) { + GbufferPrograms.teardownSpecialRenderCondition(); + ShaderGlint.endGlint(); + } +} diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderDragon.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderDragon.java index 239ea0f13..fcb4ee3e0 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderDragon.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderDragon.java @@ -32,10 +32,19 @@ public abstract class MixinRenderDragon { private static final NamespacedId DRAGON_DEATH_RAY = new NamespacedId("minecraft", "dragon_death_rays"); @Unique - private int angelica$previousEntityId = -1; + private static final int NOTHING_SAVED = Integer.MIN_VALUE; @Unique - private int angelica$previousDeathRayEntityId = -1; + private int angelica$previousEntityId = NOTHING_SAVED; + + @Unique + private int angelica$previousDeathRayEntityId = NOTHING_SAVED; + + @Unique + private int angelica$previousItemId = 0; + + @Unique + private int angelica$previousDeathRayItemId = 0; @Unique private boolean angelica$deathBeamsActive = false; @@ -57,9 +66,10 @@ public abstract class MixinRenderDragon { if (entityIdMap != null) { // Save the current entity ID angelica$previousEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(); + angelica$previousItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem(); int beamId = entityIdMap.applyAsInt(END_CRYSTAL_BEAM); - CapturedRenderingState.INSTANCE.setCurrentEntity(beamId); + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(beamId, 0); } } } @@ -69,9 +79,10 @@ public abstract class MixinRenderDragon { at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glPopMatrix()V", ordinal = 0, shift = At.Shift.BEFORE, remap = false) ) private void iris$restoreEntityId(EntityDragon dragon, double x, double y, double z, float entityYaw, float partialTicks, CallbackInfo ci) { - if (angelica$previousEntityId != -1) { - CapturedRenderingState.INSTANCE.setCurrentEntity(angelica$previousEntityId); - angelica$previousEntityId = -1; + if (angelica$previousEntityId != NOTHING_SAVED) { + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(angelica$previousEntityId, angelica$previousItemId); + angelica$previousEntityId = NOTHING_SAVED; + angelica$previousItemId = 0; } } @@ -92,8 +103,9 @@ public abstract class MixinRenderDragon { Object2IntFunction entityIdMap = BlockRenderingSettings.INSTANCE.getEntityIds(); if (entityIdMap != null) { angelica$previousDeathRayEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(); + angelica$previousDeathRayItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem(); int deathRayId = entityIdMap.applyAsInt(DRAGON_DEATH_RAY); - CapturedRenderingState.INSTANCE.setCurrentEntity(deathRayId); + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(deathRayId, 0); } angelica$depthPassReplay++; @@ -131,9 +143,10 @@ public abstract class MixinRenderDragon { private void angelica$endDeathBeamsLighting(EntityDragon dragon, float partialTicks, CallbackInfo ci) { if (angelica$depthPassReplay > 0) return; if (angelica$deathBeamsActive) { - if (angelica$previousDeathRayEntityId != -1) { - CapturedRenderingState.INSTANCE.setCurrentEntity(angelica$previousDeathRayEntityId); - angelica$previousDeathRayEntityId = -1; + if (angelica$previousDeathRayEntityId != NOTHING_SAVED) { + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(angelica$previousDeathRayEntityId, angelica$previousDeathRayItemId); + angelica$previousDeathRayEntityId = NOTHING_SAVED; + angelica$previousDeathRayItemId = 0; } GbufferPrograms.teardownSpecialRenderCondition(); angelica$deathBeamsActive = false; diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderEndPortal.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderEndPortal.java index a5d42631c..481d00751 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderEndPortal.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderEndPortal.java @@ -1,11 +1,12 @@ package com.gtnewhorizons.angelica.mixins.early.shaders; import com.gtnewhorizons.angelica.glsm.GLStateManager; +import net.coderbot.iris.Iris; import net.coderbot.iris.uniforms.SystemTimeUniforms; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.tileentity.RenderEndPortal; -import net.minecraft.tileentity.TileEntityEndPortal; +import net.minecraft.tileentity.TileEntity; import net.minecraft.util.ResourceLocation; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; @@ -25,9 +26,9 @@ public class MixinRenderEndPortal { @Unique private static final float iris$GREEN = 0.15f; @Unique private static final float iris$BLUE = 0.2f; - @Inject(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntityEndPortal;DDDF)V", at = @At("HEAD"), cancellable = true) - private void iris$renderShaderPortal(TileEntityEndPortal te, double x, double y, double z, float partialTicks, CallbackInfo ci) { - // Always use shader-based portal rendering — eliminates glTexGen dependency for core profile + @Inject(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDF)V", at = @At("HEAD"), cancellable = true) + private void iris$renderShaderPortal(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) { + if (Iris.getCurrentPack().isEmpty()) return; ci.cancel(); float progress = (SystemTimeUniforms.TIMER.getFrameTimeCounter() * 0.01f) % 1.0f; diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderEntityFlame.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderEntityFlame.java index 9cf55bb59..e7dd331ce 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderEntityFlame.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderEntityFlame.java @@ -25,13 +25,13 @@ public class MixinRenderEntityFlame { private void iris$setFlame(Entity entity, double x, double y, double z, float partialTicks, CallbackInfo ci) { Object2IntFunction entityIdMap = BlockRenderingSettings.INSTANCE.getEntityIds(); if (entityIdMap != null) { - CapturedRenderingState.INSTANCE.setCurrentEntity(entityIdMap.applyAsInt(flameId)); + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(entityIdMap.applyAsInt(flameId), 0); } } @Inject(method = "renderEntityOnFire", at = @At("RETURN")) private void iris$resetFlame(Entity entity, double x, double y, double z, float partialTicks, CallbackInfo ci) { - CapturedRenderingState.INSTANCE.setCurrentEntity(0); + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(0, 0); } } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderItem.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderItem.java index 52009917d..fc561c816 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderItem.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderItem.java @@ -1,12 +1,13 @@ package com.gtnewhorizons.angelica.mixins.early.shaders; import com.gtnewhorizons.angelica.shadercompat.ShaderGlint; +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition; import net.coderbot.iris.layer.GbufferPrograms; import net.coderbot.iris.uniforms.ItemIdManager; import net.minecraft.client.renderer.entity.RenderItem; import net.minecraft.entity.item.EntityItem; -import net.minecraft.item.ItemStack; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -19,16 +20,20 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; public class MixinRenderItem { /** - * Set the item ID before rendering dropped items. - * Checks both item.properties and block.properties. + * Item ID and cutout draw state for dropped items. */ - @Inject( - method = "doRender(Lnet/minecraft/entity/item/EntityItem;DDDFF)V", - at = @At("HEAD") - ) - private void iris$setItemIdBeforeRender(EntityItem entity, double x, double y, double z, float entityYaw, float partialTicks, CallbackInfo ci) { - ItemStack itemStack = entity.getEntityItem(); - ItemIdManager.setItemId(itemStack); + @WrapMethod(method = "doRender(Lnet/minecraft/entity/item/EntityItem;DDDFF)V") + private void iris$droppedItemRender(EntityItem entity, double x, double y, double z, float entityYaw, float partialTicks, Operation original) { + final int prevItemId = ItemIdManager.getItemId(); + final long prevCutout = GbufferPrograms.pushCutoutDefaults(); + + ItemIdManager.setItemId(entity.getEntityItem()); + try { + original.call(entity, x, y, z, entityYaw, partialTicks); + } finally { + ItemIdManager.setItemIdRaw(prevItemId); + GbufferPrograms.popCutoutDefaults(prevCutout); + } } /** diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderManager.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderManager.java index f57329db3..db15563d8 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderManager.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderManager.java @@ -12,6 +12,7 @@ import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.entity.Entity; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; /** * Mixin to handle entity ID and item ID for entities rendered through renderEntityWithPosYaw and renderEntityStatic. @@ -21,23 +22,46 @@ import org.spongepowered.asm.mixin.injection.At; @Mixin(RenderManager.class) public class MixinRenderManager { + @Redirect( + method = "func_147939_a", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/Render;doRenderShadowAndFire(Lnet/minecraft/entity/Entity;DDDFF)V") + ) + private void iris$wrapShadowAndFire(Render render, Entity entity, double x, double y, double z, float entityYaw, float partialTicks) { + final Boolean previous = GbufferPrograms.beginTranslucencyDeclaration(null); + + try { + render.doRenderShadowAndFire(entity, x, y, z, entityYaw, partialTicks); + } finally { + GbufferPrograms.endTranslucencyDeclaration(previous); + } + } + @WrapOperation( method = "func_147939_a", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/Render;doRender(Lnet/minecraft/entity/Entity;DDDFF)V") ) private void iris$wrapDoRender(Render render, Entity entity, double x, double y, double z, float entityYaw, float partialTicks, Operation original) { - int entityId = EntityIdHelper.getEntityId(entity); - CapturedRenderingState.INSTANCE.setCurrentEntity(entityId); + final int prevEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(); + final int prevItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem(); + final Class prevRenderable = TesrAttribution.currentRenderable; + + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(EntityIdHelper.getEntityId(entity), 0); TesrAttribution.currentRenderable = entity != null ? entity.getClass() : null; final boolean lightning = EntityIdHelper.isLightningBolt(entity); if (lightning) { GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.LIGHTNING); } - original.call(render, entity, x, y, z, entityYaw, partialTicks); - if (lightning) { - GbufferPrograms.teardownSpecialRenderCondition(); + final boolean nestedInBlockEntity = GbufferPrograms.beginNestedEntityPhase(); + + try { + original.call(render, entity, x, y, z, entityYaw, partialTicks); + } finally { + GbufferPrograms.endNestedEntityPhase(nestedInBlockEntity); + if (lightning) { + GbufferPrograms.teardownSpecialRenderCondition(); + } + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(prevEntityId, prevItemId); + TesrAttribution.currentRenderable = prevRenderable; } - CapturedRenderingState.INSTANCE.setCurrentEntity(-1); - TesrAttribution.currentRenderable = null; } } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderManagerDAPI.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderManagerDAPI.java index 343bcff93..5f9848942 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderManagerDAPI.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderManagerDAPI.java @@ -1,5 +1,6 @@ package com.gtnewhorizons.angelica.mixins.early.shaders; +import com.gtnewhorizons.angelica.rendering.tesr.TesrAttribution; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition; @@ -23,16 +24,27 @@ public class MixinRenderManagerDAPI { require = 1 // Require this if DAPI is present, which should be the case when this mixin is applied. ) private void iris$wrapDoRenderDragonAPI(Render render, Entity entity, double x, double y, double z, float entityYaw, float partialTicks, Operation original) { - int entityId = EntityIdHelper.getEntityId(entity); - CapturedRenderingState.INSTANCE.setCurrentEntity(entityId); + final int prevEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(); + final int prevItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem(); + final Class prevRenderable = TesrAttribution.currentRenderable; + + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(EntityIdHelper.getEntityId(entity), 0); + TesrAttribution.currentRenderable = entity != null ? entity.getClass() : null; final boolean lightning = EntityIdHelper.isLightningBolt(entity); if (lightning) { GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.LIGHTNING); } - original.call(render, entity, x, y, z, entityYaw, partialTicks); - if (lightning) { - GbufferPrograms.teardownSpecialRenderCondition(); + final boolean nestedInBlockEntity = GbufferPrograms.beginNestedEntityPhase(); + + try { + original.call(render, entity, x, y, z, entityYaw, partialTicks); + } finally { + GbufferPrograms.endNestedEntityPhase(nestedInBlockEntity); + if (lightning) { + GbufferPrograms.teardownSpecialRenderCondition(); + } + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(prevEntityId, prevItemId); + TesrAttribution.currentRenderable = prevRenderable; } - CapturedRenderingState.INSTANCE.setCurrentEntity(-1); } } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderNameTag.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderNameTag.java index 64d66bfcb..e05c0ad92 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderNameTag.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderNameTag.java @@ -23,7 +23,13 @@ public class MixinRenderNameTag { private static final NamespacedId NAME_TAG_ID = new NamespacedId("minecraft", "name_tag"); @Unique - private int angelica$previousEntityId = -1; + private static final int NOTHING_SAVED = Integer.MIN_VALUE; + + @Unique + private int angelica$previousEntityId = NOTHING_SAVED; + + @Unique + private int angelica$previousItemId = 0; /** * Inject at the HEAD of func_147906_a to set the special name_tag entity ID before rendering. @@ -37,10 +43,11 @@ public class MixinRenderNameTag { Object2IntFunction entityIdMap = BlockRenderingSettings.INSTANCE.getEntityIds(); if (entityIdMap != null) { angelica$previousEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(); + angelica$previousItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem(); // Set the special name_tag entity ID int nameTagId = entityIdMap.applyAsInt(NAME_TAG_ID); - CapturedRenderingState.INSTANCE.setCurrentEntity(nameTagId); + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(nameTagId, 0); } } @@ -52,9 +59,10 @@ public class MixinRenderNameTag { at = @At("RETURN") ) private void iris$restoreEntityId(Entity entity, String name, double x, double y, double z, int maxDistance, CallbackInfo ci) { - if (angelica$previousEntityId != -1) { - CapturedRenderingState.INSTANCE.setCurrentEntity(angelica$previousEntityId); - angelica$previousEntityId = -1; + if (angelica$previousEntityId != NOTHING_SAVED) { + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(angelica$previousEntityId, angelica$previousItemId); + angelica$previousEntityId = NOTHING_SAVED; + angelica$previousItemId = 0; } } } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderPlayerArmor.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderPlayerArmor.java index 2b059f04b..14778bd4c 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderPlayerArmor.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRenderPlayerArmor.java @@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.mixins.early.shaders; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.coderbot.iris.block_rendering.BlockRenderingSettings; +import net.coderbot.iris.layer.GbufferPrograms; import net.coderbot.iris.shaderpack.materialmap.NamespacedId; import net.coderbot.iris.uniforms.CapturedRenderingState; import net.coderbot.iris.uniforms.ItemIdManager; @@ -49,7 +50,7 @@ public class MixinRenderPlayerArmor { } /** - * Set "minecraft:player_cape" when rendering the player's cape. + * Set "minecraft:player_cape" when rendering the player's cape, and declare the cape opaque. */ @WrapOperation( method = "renderEquippedItems(Lnet/minecraft/client/entity/AbstractClientPlayer;F)V", @@ -62,7 +63,12 @@ public class MixinRenderPlayerArmor { CapturedRenderingState.INSTANCE.setCurrentRenderedItem(capeId); } - original.call(modelBiped, scale); + final Boolean previous = GbufferPrograms.beginTranslucencyDeclaration(Boolean.FALSE); + try { + original.call(modelBiped, scale); + } finally { + GbufferPrograms.endTranslucencyDeclaration(previous); + } ItemIdManager.resetItemId(); } } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRendererLivingEntity.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRendererLivingEntity.java index a96045c5c..a3b3eb8fd 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRendererLivingEntity.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinRendererLivingEntity.java @@ -10,18 +10,28 @@ import net.coderbot.iris.uniforms.ItemIdManager; import net.minecraft.client.model.ModelBase; import net.minecraft.client.renderer.entity.RenderDragon; import net.minecraft.client.renderer.entity.RenderEnderman; +import net.minecraft.client.renderer.entity.RenderPlayer; import net.minecraft.client.renderer.entity.RenderSpider; import net.minecraft.client.renderer.entity.RendererLivingEntity; import net.minecraft.entity.Entity; import net.minecraft.entity.EntityLivingBase; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; 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.Slice; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(RendererLivingEntity.class) -public class MixinRendererLivingEntity { +public abstract class MixinRendererLivingEntity { + + @Shadow + protected abstract void renderModel(EntityLivingBase entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, float scale); + + @Shadow + protected abstract int getColorMultiplier(EntityLivingBase entity, float brightness, float partialTicks); + /** * Reset ID before damage overlay renders, otherwise you get random shiny zombies */ @@ -33,15 +43,33 @@ public class MixinRendererLivingEntity { ItemIdManager.resetItemId(); } + @Redirect( + method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;renderModel(Lnet/minecraft/entity/EntityLivingBase;FFFFFF)V") + ) + private void iris$declarePlayerModelTranslucent(RendererLivingEntity instance, EntityLivingBase entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch, float scale) { + if (!(instance instanceof RenderPlayer)) { + renderModel(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch, scale); + return; + } + + final Boolean previous = GbufferPrograms.beginTranslucencyDeclaration(Boolean.TRUE); + try { + renderModel(entity, limbSwing, limbSwingAmount, ageInTicks, netHeadYaw, headPitch, scale); + } finally { + GbufferPrograms.endTranslucencyDeclaration(previous); + } + } + /** * Capture entity color for shader access. */ - @WrapOperation( + @Redirect( method = "doRender", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/RendererLivingEntity;getColorMultiplier(Lnet/minecraft/entity/EntityLivingBase;FF)I") ) - private int iris$setEntityColor(RendererLivingEntity instance, EntityLivingBase elb, float f0, float f1, Operation original) { - final int j = original.call(instance, elb, f0, f1); + private int iris$setEntityColor(RendererLivingEntity instance, EntityLivingBase elb, float f0, float f1) { + final int j = getColorMultiplier(elb, f0, f1); final float a = (j >> 24 & 255) / 255.0F; final float r = (j >> 16 & 255) / 255.0F; final float g = (j >> 8 & 255) / 255.0F; diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinTileEntityRendererDispatcher.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinTileEntityRendererDispatcher.java index 0c7e7e9b4..57fea3976 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinTileEntityRendererDispatcher.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/early/shaders/MixinTileEntityRendererDispatcher.java @@ -4,10 +4,12 @@ import com.gtnewhorizons.angelica.rendering.tesr.PassRebindGate; import com.gtnewhorizons.angelica.rendering.tesr.TesrAttribution; import com.gtnewhorizons.angelica.rendering.tesr.TesrProviderDispatch; import com.prupe.mcpatcher.ctm.CTMUtils; +import net.coderbot.iris.layer.GbufferPrograms; import net.coderbot.iris.uniforms.CapturedRenderingState; import net.minecraft.client.renderer.tileentity.TileEntityRendererDispatcher; import net.minecraft.tileentity.TileEntity; 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; @@ -17,6 +19,7 @@ public class MixinTileEntityRendererDispatcher { @Inject(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDF)V", at = @At("HEAD")) private void iris$setBlockEntityId(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) { + GbufferPrograms.pushBlendState(); CTMUtils.clearCurrentCompact(); CapturedRenderingState.INSTANCE.setCurrentBlockEntity(iris$resolveBlockEntityId(te)); TesrAttribution.currentRenderable = te != null ? te.getClass() : null; @@ -25,6 +28,7 @@ public class MixinTileEntityRendererDispatcher { PassRebindGate.rebindIfDirty(); } + @Unique private static int iris$resolveBlockEntityId(TileEntity te) { return TesrProviderDispatch.resolveBlockEntityId(te); } @@ -34,5 +38,6 @@ public class MixinTileEntityRendererDispatcher { CTMUtils.clearCurrentCompact(); CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0); TesrAttribution.currentRenderable = null; + GbufferPrograms.popBlendStateTop(); } } diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/late/chisel/MixinRenderCarvableBeacon.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/late/chisel/MixinRenderCarvableBeacon.java index efc7cc0d1..586fefec9 100644 --- a/src/mixin/java/com/gtnewhorizons/angelica/mixins/late/chisel/MixinRenderCarvableBeacon.java +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/late/chisel/MixinRenderCarvableBeacon.java @@ -1,9 +1,11 @@ package com.gtnewhorizons.angelica.mixins.late.chisel; +import com.gtnewhorizons.angelica.glsm.GLStateManager; import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition; import net.coderbot.iris.layer.GbufferPrograms; import net.minecraft.client.renderer.Tessellator; import net.minecraft.world.World; +import org.lwjgl.opengl.GL11; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.Unique; @@ -22,12 +24,15 @@ public class MixinRenderCarvableBeacon { @Inject(method = "renderBeam(FLnet/minecraft/world/World;DDDIF)V", at = @At("HEAD")) private void angelica$beginBeaconBeam(float f1, World world, double x, double y, double z, int meta, float partialTicks, CallbackInfo ci) { + // Chisel's beam leaves GL_CULL_FACE disabled behind it + GLStateManager.pushState(GL11.GL_ENABLE_BIT); GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.BEACON_BEAM); } @Inject(method = "renderBeam(FLnet/minecraft/world/World;DDDIF)V", at = @At("RETURN")) private void angelica$endBeaconBeam(float f1, World world, double x, double y, double z, int meta, float partialTicks, CallbackInfo ci) { GbufferPrograms.teardownSpecialRenderCondition(); + GLStateManager.popState(); } @Redirect( diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/late/client/dragonapi/MixinThrottleableEffectRenderer.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/late/client/dragonapi/MixinThrottleableEffectRenderer.java new file mode 100644 index 000000000..098e0e517 --- /dev/null +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/late/client/dragonapi/MixinThrottleableEffectRenderer.java @@ -0,0 +1,33 @@ +package com.gtnewhorizons.angelica.mixins.late.client.dragonapi; + +import com.gtnewhorizons.angelica.rendering.ParticleRunSplitter; +import net.minecraft.client.particle.EntityFX; +import net.minecraft.client.renderer.Tessellator; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Pseudo +@Mixin(targets = { "Reika/DragonAPI/Extras/ThrottleableEffectRenderer" }, remap = false) +public class MixinThrottleableEffectRenderer { + + @Redirect( + method = "doRenderParticles", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/Tessellator;startDrawingQuads()V", remap = true) + ) + private void angelica$beginRun(Tessellator tessellator) { + ParticleRunSplitter.beginRun(); + tessellator.startDrawingQuads(); + } + + @Redirect( + method = "doRenderParticles", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/particle/EntityFX;getBrightnessForRender(F)I", remap = true), + require = 1 + ) + private int angelica$splitRun(EntityFX particle, float partialTicks) { + ParticleRunSplitter.splitIfNeeded(particle, Tessellator.instance, partialTicks); + return particle.getBrightnessForRender(partialTicks); + } +} diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/late/client/etfuturum/MixinTileEntityNewBeaconRenderer.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/late/client/etfuturum/MixinTileEntityNewBeaconRenderer.java new file mode 100644 index 000000000..2598de1e1 --- /dev/null +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/late/client/etfuturum/MixinTileEntityNewBeaconRenderer.java @@ -0,0 +1,27 @@ +package com.gtnewhorizons.angelica.mixins.late.client.etfuturum; + +import ganymedes01.etfuturum.client.renderer.tileentity.TileEntityNewBeaconRenderer; +import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition; +import net.coderbot.iris.layer.GbufferPrograms; +import net.minecraft.tileentity.TileEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +/** + * Et Futurum's replaces the regular vanilla beacon with its own. + */ +@Mixin(value = TileEntityNewBeaconRenderer.class, remap = false) +public class MixinTileEntityNewBeaconRenderer { + + @Inject(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDF)V", at = @At("HEAD"), remap = true) + private void angelica$beginBeaconBeam(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) { + GbufferPrograms.setupSpecialRenderCondition(SpecialCondition.BEACON_BEAM); + } + + @Inject(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDF)V", at = @At("RETURN"), remap = true) + private void angelica$endBeaconBeam(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) { + GbufferPrograms.teardownSpecialRenderCondition(); + } +} diff --git a/src/mixin/java/com/gtnewhorizons/angelica/mixins/late/client/openblocks/MixinTileEntityTrophyRenderer.java b/src/mixin/java/com/gtnewhorizons/angelica/mixins/late/client/openblocks/MixinTileEntityTrophyRenderer.java new file mode 100644 index 000000000..0e82dd36d --- /dev/null +++ b/src/mixin/java/com/gtnewhorizons/angelica/mixins/late/client/openblocks/MixinTileEntityTrophyRenderer.java @@ -0,0 +1,38 @@ +package com.gtnewhorizons.angelica.mixins.late.client.openblocks; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.coderbot.iris.layer.GbufferPrograms; +import net.coderbot.iris.uniforms.CapturedRenderingState; +import net.coderbot.iris.uniforms.EntityIdHelper; +import net.minecraft.client.renderer.entity.Render; +import net.minecraft.entity.Entity; +import openblocks.client.renderer.tileentity.TileEntityTrophyRenderer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +/** + * Trophies render a mob through {@link Render#doRender}, force entity phase here. + */ +@Mixin(value = TileEntityTrophyRenderer.class, remap = false) +public class MixinTileEntityTrophyRenderer { + + @WrapOperation( + method = "renderTrophy", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/Render;doRender(Lnet/minecraft/entity/Entity;DDDFF)V", remap = true)) + private static void angelica$renderTrophyAsEntity(Render render, Entity entity, double x, double y, double z, float entityYaw, float partialTicks, Operation original) { + final int prevEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(); + final int prevItemId = CapturedRenderingState.INSTANCE.getCurrentRenderedItem(); + + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(EntityIdHelper.getEntityId(entity), 0); + + final boolean nestedInBlockEntity = GbufferPrograms.beginNestedEntityPhase(); + + try { + original.call(render, entity, x, y, z, entityYaw, partialTicks); + } finally { + GbufferPrograms.endNestedEntityPhase(nestedInBlockEntity); + CapturedRenderingState.INSTANCE.setCurrentEntityAndItem(prevEntityId, prevItemId); + } + } +} diff --git a/src/test/java/com/gtnewhorizons/angelica/rendering/tesr/TesrLayerKeyTest.java b/src/test/java/com/gtnewhorizons/angelica/rendering/tesr/TesrLayerKeyTest.java index 263ef4304..4c2006a0e 100644 --- a/src/test/java/com/gtnewhorizons/angelica/rendering/tesr/TesrLayerKeyTest.java +++ b/src/test/java/com/gtnewhorizons/angelica/rendering/tesr/TesrLayerKeyTest.java @@ -3,6 +3,8 @@ package com.gtnewhorizons.angelica.rendering.tesr; import com.gtnewhorizons.angelica.api.tesr.TesrMaterial; import com.gtnewhorizons.angelica.api.tesr.TesrShader; import com.gtnewhorizons.angelica.api.tesr.TesrShaders; +import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition; +import net.coderbot.iris.layer.PassOverride; import net.minecraft.util.ResourceLocation; import org.junit.jupiter.api.Test; @@ -15,44 +17,72 @@ class TesrLayerKeyTest { private static final ResourceLocation TEX = new ResourceLocation("angelica", "textures/test.png"); private static final TesrShader SHADER = TesrShaders.register("test:layer_key", () -> {}, () -> {}); private static final TesrMaterial.SpecialRender NONE = TesrMaterial.SpecialRender.NONE; + private static final PassOverride NO_PASS = PassOverride.NONE; + private static final PassOverride EYES = new PassOverride(SpecialCondition.ENTITY_EYES, null); + private static final PassOverride TRANSLUCENT = new PassOverride(null, Boolean.TRUE); @Test void scratchProbeMatchesCopiedKey() { - final TesrBatchRenderer.LayerKey scratch = new TesrBatchRenderer.LayerKey().set(TEX, TesrMaterial.Transparency.OPAQUE, true, false, false, false, 0.1f, false, NONE, SHADER); + final TesrBatchRenderer.LayerKey scratch = new TesrBatchRenderer.LayerKey().set(TEX, TesrMaterial.Transparency.OPAQUE, true, false, false, false, 0.1f, false, NONE, SHADER, false, NO_PASS, 0.0f, 0.0f); final TesrBatchRenderer.LayerKey stored = scratch.copy(); assertNotSame(scratch, stored); assertEquals(scratch, stored); assertEquals(scratch.hashCode(), stored.hashCode()); - scratch.set(TEX, TesrMaterial.Transparency.OPAQUE, true, false, false, false, 0.1f, false, NONE, SHADER); + scratch.set(TEX, TesrMaterial.Transparency.OPAQUE, true, false, false, false, 0.1f, false, NONE, SHADER, false, NO_PASS, 0.0f, 0.0f); assertEquals(stored, scratch, "re-set scratch still matches stored copy"); } @Test void fieldChangesBreakEquality() { - final TesrBatchRenderer.LayerKey base = new TesrBatchRenderer.LayerKey().set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, NONE, null); + final TesrBatchRenderer.LayerKey base = new TesrBatchRenderer.LayerKey().set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, NONE, null, false, NO_PASS, 0.0f, 0.0f); final TesrBatchRenderer.LayerKey other = base.copy(); - other.set(null, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, NONE, null); + other.set(null, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, NONE, null, false, NO_PASS, 0.0f, 0.0f); assertNotEquals(base, other); - other.set(TEX, TesrMaterial.Transparency.TRANSLUCENT, false, false, false, false, 0f, false, NONE, null); + other.set(TEX, TesrMaterial.Transparency.TRANSLUCENT, false, false, false, false, 0f, false, NONE, null, false, NO_PASS, 0.0f, 0.0f); assertNotEquals(base, other); - other.set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0.5f, false, NONE, null); + other.set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0.5f, false, NONE, null, false, NO_PASS, 0.0f, 0.0f); assertNotEquals(base, other); - other.set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, true, 0f, false, NONE, null); + other.set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, true, 0f, false, NONE, null, false, NO_PASS, 0.0f, 0.0f); assertNotEquals(base, other); - other.set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, TesrMaterial.SpecialRender.GLINT, null); + other.set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, TesrMaterial.SpecialRender.GLINT, null, false, NO_PASS, 0.0f, 0.0f); assertNotEquals(base, other); - other.set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, NONE, SHADER); + other.set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, NONE, SHADER, false, NO_PASS, 0.0f, 0.0f); + assertNotEquals(base, other); + other.set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, NONE, null, true, NO_PASS, 0.0f, 0.0f); assertNotEquals(base, other); - other.set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, NONE, null); + other.set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, NONE, null, false, NO_PASS, 0.0f, 0.0f); assertEquals(base, other); } + @Test + void passSeparatesOtherwiseIdenticalKeys() { + final TesrBatchRenderer.LayerKey plain = new TesrBatchRenderer.LayerKey().set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, NONE, null, false, NO_PASS, 0.0f, 0.0f); + final TesrBatchRenderer.LayerKey eyes = plain.copy(); + eyes.set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, NONE, null, false, EYES, 0.0f, 0.0f); + assertNotEquals(plain, eyes); + + final TesrBatchRenderer.LayerKey translucent = plain.copy(); + translucent.set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, NONE, null, false, TRANSLUCENT, 0.0f, 0.0f); + assertNotEquals(plain, translucent); + assertNotEquals(eyes, translucent); + + assertEquals(eyes, eyes.copy()); + assertEquals(eyes.hashCode(), eyes.copy().hashCode()); + + // Polygon offset is the other thing a batch outlives -- the coplanar eye overlay needs it at emit time. + final TesrBatchRenderer.LayerKey offset = plain.copy(); + offset.set(TEX, TesrMaterial.Transparency.OPAQUE, false, false, false, false, 0f, false, NONE, null, false, NO_PASS, -1.0f, -1.0f); + assertNotEquals(plain, offset); + assertEquals(offset, offset.copy()); + assertEquals(offset.hashCode(), offset.copy().hashCode()); + } + @Test void nullTextureAndShaderHash() { - final TesrBatchRenderer.LayerKey key = new TesrBatchRenderer.LayerKey().set(null, null, false, false, false, false, 0f, false, null, null); + final TesrBatchRenderer.LayerKey key = new TesrBatchRenderer.LayerKey().set(null, null, false, false, false, false, 0f, false, null, null, false, NO_PASS, 0.0f, 0.0f); assertEquals(key, key.copy()); assertEquals(key.hashCode(), key.copy().hashCode()); } diff --git a/src/test/java/net/coderbot/iris/pipeline/ShadowGraphGateTest.java b/src/test/java/net/coderbot/iris/pipeline/ShadowGraphGateTest.java index 5ca61c52e..ecb86a885 100644 --- a/src/test/java/net/coderbot/iris/pipeline/ShadowGraphGateTest.java +++ b/src/test/java/net/coderbot/iris/pipeline/ShadowGraphGateTest.java @@ -3,32 +3,84 @@ package net.coderbot.iris.pipeline; import net.coderbot.iris.shadows.ShadowGraphGate; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; class ShadowGraphGateTest { + private static final float OVERHEAD = ShadowGraphGate.degreesToRotation(ShadowGraphGate.DEFAULT_ANGLE_DELTA_DEGREES); + + private static final float HORIZON_SCALE = ShadowGraphGate.DEFAULT_HORIZON_SCALE; + + private static float deltaForElevation(float sinElevation) { + return ShadowGraphGate.deltaForElevation(sinElevation, OVERHEAD, HORIZON_SCALE); + } + @Test void nanAlwaysDirty() { - assertTrue(ShadowGraphGate.shouldMarkDirty(Float.NaN, 0.25f)); + assertTrue(ShadowGraphGate.shouldMarkDirty(Float.NaN, 0.25f, OVERHEAD)); } @Test void smallDeltaClean() { - assertFalse(ShadowGraphGate.shouldMarkDirty(0.25f, 0.25f + 0.0004f)); - assertFalse(ShadowGraphGate.shouldMarkDirty(0.25f, 0.25f - 0.0004f)); + assertFalse(ShadowGraphGate.shouldMarkDirty(0.25f, 0.25f + 0.00006f, OVERHEAD)); + assertFalse(ShadowGraphGate.shouldMarkDirty(0.25f, 0.25f - 0.00006f, OVERHEAD)); } @Test void thresholdDeltaDirty() { - assertTrue(ShadowGraphGate.shouldMarkDirty(0.25f, 0.25f + 0.001f)); - assertTrue(ShadowGraphGate.shouldMarkDirty(0.25f, 0.25f - 0.001f)); + assertTrue(ShadowGraphGate.shouldMarkDirty(0.25f, 0.25f + 0.0002f, OVERHEAD)); + assertTrue(ShadowGraphGate.shouldMarkDirty(0.25f, 0.25f - 0.0002f, OVERHEAD)); + } + + @Test + void aFullRotationIsThreeSixtyDegrees() { + assertEquals(1.0f, ShadowGraphGate.degreesToRotation(360.0f), 1e-9f); + assertEquals(0.25f, ShadowGraphGate.degreesToRotation(90.0f), 1e-9f); } @Test void wrapAroundIsNotADiscontinuity() { - assertFalse(ShadowGraphGate.shouldMarkDirty(0.9999f, 0.0001f), "0.0002 across the wrap is below threshold"); - assertTrue(ShadowGraphGate.shouldMarkDirty(0.999f, 0.0006f), "0.0016 across the wrap is above threshold"); - assertFalse(ShadowGraphGate.shouldMarkDirty(0.0001f, 0.9999f), "wrap works in both directions"); + assertFalse(ShadowGraphGate.shouldMarkDirty(0.99997f, 0.00001f, OVERHEAD), "0.00004 across the wrap is below threshold"); + assertTrue(ShadowGraphGate.shouldMarkDirty(0.999f, 0.0006f, OVERHEAD), "0.0016 across the wrap is above threshold"); + assertFalse(ShadowGraphGate.shouldMarkDirty(0.00001f, 0.99997f, OVERHEAD), "wrap works in both directions"); + } + + @Test + void overheadLightUsesTheFullTolerance() { + assertEquals(OVERHEAD, deltaForElevation(1.0f), 1e-9f); + } + + @Test + void toleranceTightensTowardsTheHorizon() { + // The default floor is 0.5, so tightening only bites above sin^2 0.5 + final float high = deltaForElevation(0.8f); + assertEquals(OVERHEAD * 0.64f, high, 1e-9f); + assertTrue(high < deltaForElevation(1.0f)); + assertTrue(deltaForElevation(0.75f) < high); + } + + @Test + void toleranceIsFlooredAtTheHorizon() { + final float floor = OVERHEAD * HORIZON_SCALE; + assertEquals(floor, deltaForElevation(0.0f), 1e-9f); + assertEquals(floor, deltaForElevation(0.1f), 1e-9f, "sin^2 0.1 is below the floor"); + assertEquals(floor, deltaForElevation(0.7f), 1e-9f, "sin^2 0.7 is still below the floor"); + } + + @Test + void elevationBelowTheHorizonMirrorsAbove() { + // The shadow light points down at night; sin^2 keeps the sign out of it + assertEquals(deltaForElevation(1.0f), deltaForElevation(-1.0f), 1e-9f); + assertEquals(deltaForElevation(0.8f), deltaForElevation(-0.8f), 1e-9f); + } + + @Test + void nearTheHorizonASmallStepIsDirty() { + final float horizon = deltaForElevation(0.0f); + assertFalse(ShadowGraphGate.shouldMarkDirty(0.25f, 0.25f + 0.00006f, OVERHEAD)); + assertTrue(ShadowGraphGate.shouldMarkDirty(0.25f, 0.25f + 0.00006f, horizon), + "0.00006 clears the tightened horizon threshold even though it is clean overhead"); } }