Many bug fixes (#2017)

Co-authored-by: Martin Robertz <dream-master@gmx.net>
This commit is contained in:
Eclipse-SolandMartin Robertz authored and GitHub committed 2026-08-19 08:55:43 -07:00
1 parent b2f597a73a
commit 22c9479946
89 files changed
+2820 -735

No files matched your search

+2
View File
@@ -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")
@@ -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;
@@ -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<IStateStack<?>> 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;
}
@@ -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();
@@ -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);
}
@@ -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;
@@ -25,6 +25,8 @@ public final class GLSMHooks {
public static final EventBus<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
public static final EventBus<AlphaStateChangeEvent> ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.class);
public static final EventBus<ShaderColorChangeEvent> SHADER_COLOR_CHANGE = EventBus.create(ShaderColorChangeEvent.class);
public static final EventBus<ForeignDrawEndEvent> FOREIGN_DRAW_END = EventBus.create(ForeignDrawEndEvent.class);
public static final EventBus<VanillaBlendChangeEvent> VANILLA_BLEND_CHANGE = EventBus.create(VanillaBlendChangeEvent.class);
public static final EventBus<LoadingCheckpointEvent> 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() {}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface VanillaBooleanLayer {
boolean isOverrideHeld();
boolean getVanilla();
void setVanilla(boolean enabled);
}
@@ -0,0 +1,22 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface VanillaStateLayer<T> {
boolean isOverrideHeld();
void readVanilla(T into);
void writeVanilla(T from);
static boolean isHeld(VanillaStateLayer<?> layer) {
return layer != null && layer.isOverrideHeld();
}
static <T> void capture(VanillaStateLayer<T> layer, T slot) {
if (isHeld(layer)) layer.readVanilla(slot);
}
static <T> boolean restore(VanillaStateLayer<T> layer, T saved) {
if (!isHeld(layer)) return false;
layer.writeVanilla(saved);
return true;
}
}
@@ -0,0 +1,6 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class ForeignDrawEndEvent extends MutableEvent {
}
@@ -0,0 +1,6 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class VanillaBlendChangeEvent extends MutableEvent {
}
@@ -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<AlphaState> {
@@ -10,6 +12,8 @@ public class AlphaStateStack extends AlphaState implements IStateStack<AlphaStat
protected int pointer;
@Setter private VanillaStateLayer<AlphaState> 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<AlphaStat
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
}
@@ -31,10 +35,19 @@ public class AlphaStateStack extends AlphaState implements IStateStack<AlphaStat
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
final AlphaState saved = stack[--pointer];
if (!VanillaStateLayer.restore(vanillaLayer, saved)) {
set(saved);
}
return this;
}
public AlphaState readEffective(AlphaState out) {
out.set(this);
VanillaStateLayer.capture(vanillaLayer, out);
return out;
}
public boolean isEmpty() {
return pointer == 0;
}
@@ -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.BlendState;
import lombok.Setter;
public class BlendStateStack extends BlendState implements IStateStack<BlendStateStack> {
@@ -10,6 +12,8 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
protected int pointer;
@Setter private VanillaStateLayer<BlendState> 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<BlendStat
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
}
@@ -31,10 +35,21 @@ public class BlendStateStack extends BlendState implements IStateStack<BlendStat
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
final BlendState saved = stack[--pointer];
if (VanillaStateLayer.restore(vanillaLayer, saved)) {
setExceptFunc(saved);
} else {
set(saved);
}
return this;
}
public BlendState readEffective(BlendState out) {
out.set(this);
VanillaStateLayer.capture(vanillaLayer, out);
return out;
}
public boolean isEmpty() {
return pointer == 0;
}
@@ -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.VanillaBooleanLayer;
import com.gtnewhorizons.angelica.glsm.states.BooleanState;
import lombok.Setter;
/**
* A stack for boolean GL state with lazy copy-on-write optimization.
@@ -14,6 +16,7 @@ import com.gtnewhorizons.angelica.glsm.states.BooleanState;
public class BooleanStateStack extends BooleanState implements IStateStack<BooleanStateStack> {
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<Boole
if (savedDepth >= 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<Boole
if (savedDepth == 0) {
throw new IllegalStateException("Stack underflow");
}
final boolean oldValue = stack[--savedDepth];
// Call setEnabledDirect to avoid triggering beforeModify during restore
setEnabledDirect(oldValue);
restore(stack[--savedDepth]);
return this;
}
public boolean isEffectivelyEnabled() {
return (vanillaLayer != null && vanillaLayer.isOverrideHeld()) ? vanillaLayer.getVanilla() : enabled;
}
private boolean vanillaValue() {
return isEffectivelyEnabled();
}
private void restore(boolean value) {
if (vanillaLayer != null && vanillaLayer.isOverrideHeld()) {
vanillaLayer.setVanilla(value);
} else {
setEnabledDirect(value);
}
}
@Override
public boolean isEmpty() {
return savedDepth == 0;
@@ -92,8 +109,7 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
public BooleanStateStack popDepth() {
// We're only called if we were modified, so savedDepth should match
if (savedDepth > 0) {
final boolean oldValue = stack[--savedDepth];
setEnabledDirect(oldValue);
restore(stack[--savedDepth]);
}
return this;
}
@@ -106,7 +122,7 @@ public class BooleanStateStack extends BooleanState implements IStateStack<Boole
public void beforeModify() {
final int globalDepth = GLStateManager.getAttribDepth();
if (savedDepth < globalDepth) {
stack[savedDepth++] = enabled;
stack[savedDepth++] = vanillaValue();
GLStateManager.registerModifiedState(this);
}
}
@@ -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.ColorMask;
import lombok.Setter;
public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskStack> {
@@ -10,6 +12,8 @@ public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskSt
protected int pointer;
@Setter private VanillaStateLayer<ColorMask> 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<ColorMaskSt
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
}
@@ -31,7 +35,10 @@ public class ColorMaskStack extends ColorMask implements IStateStack<ColorMaskSt
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
final ColorMask saved = stack[--pointer];
if (!VanillaStateLayer.restore(vanillaLayer, saved)) {
set(saved);
}
return this;
}
@@ -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.DepthState;
import lombok.Setter;
public class DepthStateStack extends DepthState implements IStateStack<DepthStateStack> {
@@ -10,6 +12,10 @@ public class DepthStateStack extends DepthState implements IStateStack<DepthStat
protected int pointer;
@Setter private VanillaStateLayer<DepthState> 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<DepthStat
throw new IllegalStateException("Stack overflow size " + (pointer + 1) + " reached");
}
stack[pointer++].set(this);
VanillaStateLayer.capture(vanillaLayer, stack[pointer++].set(this));
return this;
}
@@ -31,10 +37,21 @@ public class DepthStateStack extends DepthState implements IStateStack<DepthStat
throw new IllegalStateException("Stack underflow");
}
set(stack[--pointer]);
final DepthState saved = stack[--pointer];
if (VanillaStateLayer.restore(vanillaLayer, saved)) {
setExceptMask(saved);
} else {
set(saved);
}
return this;
}
public boolean isEffectiveMaskEnabled() {
if (!VanillaStateLayer.isHeld(vanillaLayer)) return isEnabled();
vanillaLayer.readVanilla(effective);
return effective.isEnabled();
}
public boolean isEmpty() {
return pointer == 0;
}
@@ -55,6 +55,15 @@ public class BlendState implements ISettableState<BlendState> {
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;
@@ -18,6 +18,12 @@ public class DepthState implements ISettableState<DepthState> {
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;
@@ -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;
}
@@ -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<TextSegment> deferredSegments = ObjectArrayList.wrap(new TextSegment[16], 0);
private static final ObjectArrayList<TextSegment> 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;
}
@@ -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;
@@ -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<Long, Layering> 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;
@@ -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();
@@ -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;
@@ -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 <T> VanillaStateLayer<T> gated(VanillaStateLayer<T> 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;
@@ -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)
@@ -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"),
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.mixins.interfaces;
import net.minecraft.item.ItemStack;
public interface ItemRendererAccessor {
ItemStack angelica$getItemToRender();
}
@@ -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);
@@ -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();
@@ -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;
}
}
@@ -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);
@@ -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;
@@ -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;
}
@@ -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
@@ -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);
@@ -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;
}
@@ -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();
@@ -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<AngelicaConfig, Integer> 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<AngelicaConfig, Integer> 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"))
@@ -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 + "%";
}
}
@@ -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<BufferSegment> 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);
}
@@ -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<ShaderPack> getCurrentPack() {
return Optional.ofNullable(currentPack);
@@ -69,13 +69,17 @@ public class CeleritasTerrainPipeline {
IntFunction<ProgramImages> createTerrainImages,
IntFunction<ProgramImages> createShadowImages,
CustomUniforms customUniforms,
Optional<ProgramSource> terrainSource,
Optional<ProgramSource> terrainSolidSource,
Optional<ProgramSource> terrainCutoutSource,
Optional<ProgramSource> translucentSource,
Optional<ProgramSource> shadowSource,
Optional<ProgramSource> shadowSolidSource,
Optional<ProgramSource> shadowCutoutSource,
Optional<ProgramSource> shadowTranslucentSource,
CompletableFuture<Map<PatchShaderType, String>> terrainFuture,
CompletableFuture<Map<PatchShaderType, String>> terrainSolidFuture,
CompletableFuture<Map<PatchShaderType, String>> terrainCutoutFuture,
CompletableFuture<Map<PatchShaderType, String>> translucentFuture,
CompletableFuture<Map<PatchShaderType, String>> shadowFuture,
CompletableFuture<Map<PatchShaderType, String>> shadowSolidFuture,
CompletableFuture<Map<PatchShaderType, String>> shadowCutoutFuture,
CompletableFuture<Map<PatchShaderType, String>> shadowTranslucentFuture,
RenderTargets renderTargets,
ImmutableSet<Integer> flippedAfterPrepare,
@@ -90,11 +94,12 @@ public class CeleritasTerrainPipeline {
// Build program source map (local - only needed during construction)
final EnumMap<IrisTerrainPass, Optional<ProgramSource>> 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<Map<PatchShaderType, String>> future, Optional<ProgramSource> source, PassInfo... targets) {
@@ -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";
// }
// };
//}
@@ -12,6 +12,8 @@ public enum RenderCondition {
BEACON_BEAM,
ENTITIES,
ENTITIES_TRANSLUCENT,
PARTICLES,
PARTICLES_TRANSLUCENT,
GLINT,
ENTITY_EYES,
HAND_OPAQUE,
@@ -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;
}
}
@@ -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<AlphaState> 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();
}
};
}
@@ -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<BlendState> 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());
}
};
}
@@ -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<DepthState> 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<ColorMask> 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);
}
};
}
@@ -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())) {
@@ -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<String, Boolean> 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.
* <p>
* 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;
}
@@ -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
}
@@ -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));
}
}
@@ -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<Pair<String, InputAvailability>, CompletableFuture<Map<PatchShaderType, String>>> instancedTransformFutures = submitAttributeTransforms(resolver, INSTANCED_PROGRAM_IDS, true);
final Optional<ProgramSource> terrainSource = first(programs.getGbuffersTerrain(), programs.getGbuffersTexturedLit(), programs.getGbuffersTextured(), programs.getGbuffersBasic());
final Optional<ProgramSource> terrainSolidOverride = programs.getGbuffersTerrainSolid();
final Optional<ProgramSource> terrainCutoutOverride = programs.getGbuffersTerrainCutout();
final Optional<ProgramSource> terrainSolidSource = first(terrainSolidOverride, terrainSource);
final Optional<ProgramSource> terrainCutoutSource = first(terrainCutoutOverride, terrainSource);
final Optional<ProgramSource> translucentSource = first(programs.getGbuffersWater(), terrainSource);
final Optional<ProgramSource> shadowSource = programs.getShadow();
final Optional<ProgramSource> shadowSolidOverride = programs.getShadowSolid();
final Optional<ProgramSource> shadowCutoutOverride = programs.getShadowCutout();
final Optional<ProgramSource> shadowSolidSource = first(shadowSolidOverride, shadowSource);
final Optional<ProgramSource> shadowCutoutSource = first(shadowCutoutOverride, shadowSource);
final Optional<ProgramSource> shadowTranslucentSource = first(programs.getShadowWater(), shadowSource);
// Celeritas terrain transform futures
final CompletableFuture<Map<PatchShaderType, String>> celeritasTerrainFuture = terrainSource.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(null);
final CompletableFuture<Map<PatchShaderType, String>> celeritasTerrainSolidFuture = terrainSolidOverride
.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(celeritasTerrainFuture);
final CompletableFuture<Map<PatchShaderType, String>> celeritasTerrainCutoutFuture = terrainCutoutOverride
.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(celeritasTerrainFuture);
final CompletableFuture<Map<PatchShaderType, String>> celeritasTranslucentFuture = translucentSource.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(null);
final CompletableFuture<Map<PatchShaderType, String>> celeritasShadowFuture = shadowSource.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(null);
final CompletableFuture<Map<PatchShaderType, String>> celeritasShadowSolidFuture = shadowSolidOverride
.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(celeritasShadowFuture);
final CompletableFuture<Map<PatchShaderType, String>> celeritasShadowCutoutFuture = shadowCutoutOverride
.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(celeritasShadowFuture);
final CompletableFuture<Map<PatchShaderType, String>> celeritasShadowTranslucentFuture = shadowTranslucentSource.map(DeferredWorldRenderingPipeline::submitCeleritasTerrainTransform).orElse(null);
final Int2ObjectArrayMap<CompletableFuture<String>> 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;
@@ -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;
@@ -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<Block, BlockRenderLayer> 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() {
@@ -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()
@@ -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();
@@ -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<ComputeSource> 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<ProgramSource> getShadowSolid() {
return shadowSolid.requireValid();
}
public Optional<ProgramSource> getShadowCutout() {
return shadowCutout.requireValid();
}
public Optional<ProgramSource> getShadowWater() {
return shadowWater.requireValid();
}
@@ -353,6 +378,14 @@ public class ProgramSet {
return gbuffersTerrain.requireValid();
}
public Optional<ProgramSource> getGbuffersTerrainSolid() {
return gbuffersTerrainSolid.requireValid();
}
public Optional<ProgramSource> getGbuffersTerrainCutout() {
return gbuffersTerrainCutout.requireValid();
}
public Optional<ProgramSource> getGbuffersDamagedBlock() {
return gbuffersDamagedBlock.requireValid();
}
@@ -389,6 +422,14 @@ public class ProgramSet {
return gbuffersLightning.requireValid();
}
public Optional<ProgramSource> getGbuffersParticles() {
return gbuffersParticles.requireValid();
}
public Optional<ProgramSource> getGbuffersParticlesTrans() {
return gbuffersParticlesTrans.requireValid();
}
public Optional<ProgramSource> getGbuffersGlint() {
return gbuffersGlint.requireValid();
}
@@ -428,6 +469,8 @@ public class ProgramSet {
public Optional<ProgramSource> 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();
@@ -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)),
@@ -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<OptionMenuElement> elementList) {
this.unusedOptionDumpQueue.put(elementList, index);
}
@@ -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);
}
}
@@ -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) {
@@ -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;
@@ -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));
}
/**
@@ -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<Block, Int2IntMap> 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<Block, Int2IntMap> 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<Block, Int2IntMap> 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<NamespacedId> 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);
}
/**
@@ -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
@@ -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);
}
}
@@ -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<Boolean> 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);
}
}
}
@@ -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<Void> 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<Void> original) {
final int depth = GbufferPrograms.beginParticles();
try {
original.call(instance, viewer, partialTicks);
} finally {
GbufferPrograms.endParticles(depth);
}
}
}
@@ -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();
}
}
@@ -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();
}
}
}
@@ -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<Void> 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();
}
}
@@ -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<NamespacedId> 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;
@@ -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;
@@ -25,13 +25,13 @@ public class MixinRenderEntityFlame {
private void iris$setFlame(Entity entity, double x, double y, double z, float partialTicks, CallbackInfo ci) {
Object2IntFunction<NamespacedId> 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);
}
}
@@ -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<Void> 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);
}
}
/**
@@ -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<Void> 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;
}
}
@@ -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<Void> 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);
}
}
@@ -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<NamespacedId> 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;
}
}
}
@@ -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();
}
}
@@ -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<Integer> 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;
@@ -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();
}
}
@@ -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(
@@ -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);
}
}
@@ -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();
}
}
@@ -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<Void> 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);
}
}
}
@@ -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());
}
@@ -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");
}
}