Compare commits

..
2 Commits
Author SHA1 Message Date
Ranzu b095c2f648 Exclude the shader pack screen 2026-10-30 10:25:07 +01:00
Ranzu f959648090 feat: add built-in menu blur based on Blur by tterrag 2026-10-23 12:00:11 +02:00
62 changed files with 562 additions and 5875 deletions

No files matched your search

+3 -1
View File
@@ -17,7 +17,7 @@
* HUD caching
* Custom fonts and text effects
* MCPatcher - connected textures, custom item textures, custom colors, random mobs, better skies
* Bundled NotFine, zoom, FPS reducer
* Bundled NotFine, zoom, FPS reducer, menu blur
## Status
* OpenGL + Celeritas terrain - Default
@@ -129,5 +129,7 @@ Targets GL 3.3+ core profile
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
* Fog parameter optimization inspired by st_nir_lower_fog.c (Copyright 2023 Google LLC) and prog_statevars.c (Copyright 1999-2007 Brian Paul)
* Blur
* tterrag for [Blur](https://github.com/tterrag1098/Blur) - Menu blur based on Blur under the MIT license
* RGSS Terrain Texture Filtering
* Indiesoft for [defold-sharp-sprite](https://github.com/indiesoftby/defold-sharp-sprite), used as the reference implementation under the MIT license
@@ -1,45 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
import com.gtnewhorizons.angelica.glsm.states.BlendState;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import com.gtnewhorizons.angelica.glsm.states.ColorMask;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
public final class ExitState {
public static final int POLYGON_OFFSET_ENABLE_WRITTEN = 1;
public static final int POLYGON_OFFSET_VALUES_WRITTEN = 2;
final Values entry = new Values();
final Values value = new Values();
final MaterialState frontMaterial = new MaterialState(GL11.GL_FRONT);
final MaterialState backMaterial = new MaterialState(GL11.GL_BACK);
long writes, dependencies;
boolean valid;
int textureDeleteCount;
public void clear() {
valid = false;
writes = dependencies = 0;
}
static final class Values {
final Color4 color = new Color4();
final Vector3f normal = new Vector3f();
final Vector4f texCoord = new Vector4f();
final Vector4f shaderColor = new Vector4f();
final Vector4f overlayColor = new Vector4f();
final BlendState blend = new BlendState();
final AlphaState alpha = new AlphaState();
final ColorMask colorMask = new ColorMask();
final Matrix4f textureMatrix = new Matrix4f();
long enabled;
int activeUnit, clientActiveUnit, matrixMode, texture, depthFunction, cullFace, frontFace, shadeModel;
int materialFace, materialParameter;
boolean depthMask;
float lightmapX, lightmapY, offsetFactor, offsetUnits, offsetClamp;
}
}
@@ -1,597 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredAlphaHandler;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredDepthColorHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.CowStateStack;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap;
import org.joml.Matrix4f;
import org.joml.Matrix4fStack;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Records the GL state a block of rendering code leaves behind, so a later run of the same block can be skipped and
* its exit state applied instead.
*/
final class ExitStateCapture {
// Fields 0..12 are the capabilities, in CAPS order. FRONT_* and BACK_* are four contiguous fields each, ordered like
// vector(): apply() and finish() address them as 15L << FRONT_AMBIENT and (field - FRONT_AMBIENT) & 3.
static final int BLEND = 0, ALPHA = 1, DEPTH = 2, CULL = 3, LIGHTING = 4, NORMALIZE = 5, RESCALE = 6, LIGHT0 = 7, LIGHT1 = 8, TEXTURE0 = 9, TEXTURE1 = 10, OFFSET = 11, COLOR_MATERIAL = 12, COLOR = 13, NORMAL = 14, TEXCOORD = 15,
LIGHTMAP = 16, SHADER_COLOR = 17, BLEND_FUNC = 18, ALPHA_FUNC = 19, DEPTH_FUNC = 20, DEPTH_MASK = 21, COLOR_MASK = 22, CULL_FACE = 23, FRONT_FACE = 24, SHADE_MODEL = 25, ACTIVE_UNIT = 26, TEXTURE_BINDING = 27, MATRIX_MODE = 28,
TEXTURE_MATRIX = 29, OFFSET_VALUES = 30, COLOR_MATERIAL_SETTINGS = 31, FRONT_AMBIENT = 32, FRONT_DIFFUSE = 33, FRONT_SPECULAR = 34, FRONT_EMISSION = 35, BACK_AMBIENT = 36, BACK_DIFFUSE = 37, BACK_SPECULAR = 38,
BACK_EMISSION = 39, OVERLAY_COLOR = 40, CLIENT_ACTIVE_UNIT = 41;
private static final int FIELD_COUNT = 42;
private static final long IMPLICIT_INPUTS = bits(ACTIVE_UNIT, MATRIX_MODE);
private static final long MATERIAL_BAKE_INPUTS = bits(COLOR, COLOR_MATERIAL_SETTINGS);
private static final long DRAW_INPUTS = bits(CULL, TEXTURE0, TEXTURE1, COLOR, LIGHTMAP, OVERLAY_COLOR, CULL_FACE, FRONT_FACE, TEXTURE_BINDING);
private static final long VERTEX_INPUTS = bits(NORMAL, TEXCOORD);
private static final long MATERIAL_INPUTS = bits(BLEND, ALPHA, DEPTH, BLEND_FUNC, ALPHA_FUNC, DEPTH_FUNC, DEPTH_MASK, COLOR_MASK);
private static final int[] CAPS = {GL11.GL_BLEND, GL11.GL_ALPHA_TEST, GL11.GL_DEPTH_TEST, GL11.GL_CULL_FACE, GL11.GL_LIGHTING, GL11.GL_NORMALIZE, GL12.GL_RESCALE_NORMAL, GL11.GL_LIGHT0, GL11.GL_LIGHT1, GL11.GL_TEXTURE_2D,
GL11.GL_TEXTURE_2D, GL11.GL_POLYGON_OFFSET_FILL, GL11.GL_COLOR_MATERIAL};
private static final AtomicInteger openCaptures = new AtomicInteger();
private final GLContextState context;
private final Matrix4f modelView = new Matrix4f();
private final long[] attribWrites = new long[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
private final long[] matrixWrites = new long[GLStateManager.MAX_TEXTURE_STACK_DEPTH];
private final boolean[] matrixInherited = new boolean[GLStateManager.MAX_TEXTURE_STACK_DEPTH];
private final ExitState.Values current = new ExitState.Values();
private final Reference2LongOpenHashMap<StateSet> restoredFieldsBySet = new Reference2LongOpenHashMap<>();
private ExitState result;
private int suspended, attribDepth, modelViewDepth, textureDepth, entryOverrides;
private int filterTexture, filterMin, filterMag;
private float filterAniso;
private boolean filterOperation;
private boolean textureInherited;
ExitStateCapture(GLContextState context) {
this.context = context;
}
static boolean anyOpen() {
return openCaptures.get() != 0;
}
private static boolean blendWritesReachVanilla() {
final DeferredBlendHandler handler = GLSMHooks.blendHandler;
return handler == null || !handler.isOverrideHeld() || handler.isBlendLocked();
}
private static boolean alphaWritesReachVanilla() {
final DeferredAlphaHandler handler = GLSMHooks.alphaHandler;
return handler == null || !handler.isOverrideHeld() || handler.isAlphaTestLocked();
}
private static boolean depthColorWritesReachVanilla() {
final DeferredDepthColorHandler handler = GLSMHooks.depthColorHandler;
return handler == null || !handler.isOverrideHeld() || handler.isDepthColorLocked();
}
private static int overridesInControl() {
return (blendWritesReachVanilla() ? 0 : 1) | (alphaWritesReachVanilla() ? 0 : 2) | (depthColorWritesReachVanilla() ? 0 : 4);
}
private static boolean same(int field, ExitState.Values a, ExitState.Values b) {
return switch (field) {
case COLOR -> a.color.sameAs(b.color);
case NORMAL -> a.normal.equals(b.normal);
case TEXCOORD -> a.texCoord.equals(b.texCoord);
case LIGHTMAP -> a.lightmapX == b.lightmapX && a.lightmapY == b.lightmapY;
case OVERLAY_COLOR -> a.overlayColor.equals(b.overlayColor);
case BLEND_FUNC -> a.blend.sameAs(b.blend);
case ALPHA_FUNC -> a.alpha.sameAs(b.alpha);
case DEPTH_FUNC -> a.depthFunction == b.depthFunction;
case DEPTH_MASK -> a.depthMask == b.depthMask;
case COLOR_MASK -> a.colorMask.sameAs(b.colorMask);
case CULL_FACE -> a.cullFace == b.cullFace;
case FRONT_FACE -> a.frontFace == b.frontFace;
case ACTIVE_UNIT -> a.activeUnit == b.activeUnit;
case TEXTURE_BINDING -> a.texture == b.texture;
case MATRIX_MODE -> a.matrixMode == b.matrixMode;
case TEXTURE_MATRIX -> a.textureMatrix.equals(b.textureMatrix);
case COLOR_MATERIAL_SETTINGS -> a.materialFace == b.materialFace && a.materialParameter == b.materialParameter;
default -> throw new IllegalArgumentException("Unsupported renderer state dependency " + field);
};
}
private static Vector4f vector(MaterialState state, int component) {
return switch (component) {
case 0 -> state.ambient;
case 1 -> state.diffuse;
case 2 -> state.specular;
default -> state.emission;
};
}
private static boolean has(ExitState state, int field) {
return has(state.writes, field);
}
private static boolean has(long fields, int field) {
return (fields & (1L << field)) != 0;
}
private static long bits(int... fields) {
long bits = 0;
for (int field : fields) bits |= 1L << field;
return bits;
}
boolean active() {
return result != null && result.valid && suspended == 0 && !context.poppingAttributes && !BatchStateGuard.isSuspended();
}
void suspend() {
suspended++;
}
void resume() {
if (suspended == 0) throw new IllegalStateException("Exit capture suspension underflow");
suspended--;
}
void begin(ExitState out) {
out.clear();
if (result != null) {
result.valid = false;
return;
}
result = out;
openCaptures.incrementAndGet();
filterTexture = 0;
filterOperation = false;
out.valid = suspended == 0 && !BatchStateGuard.isSuspended() && GLStateManager.isCachingEnabled() && !DisplayListManager.isRecording() && context.savedFilterTextureId == 0;
if (!out.valid) return;
out.dependencies = IMPLICIT_INPUTS;
capture(out.entry, IMPLICIT_INPUTS | DRAW_INPUTS | MATERIAL_INPUTS | MATERIAL_BAKE_INPUTS | VERTEX_INPUTS | (1L << TEXTURE_MATRIX));
textureInherited = true;
modelView.set(context.modelViewMatrix);
attribDepth = context.attribDepth;
modelViewDepth = GLStateManager.getMatrixStackDepth(context.modelViewMatrix);
textureDepth = GLStateManager.getMatrixStackDepth(context.textures.getTextureUnitMatrix(0));
entryOverrides = overridesInControl();
}
boolean finish(ExitState out) {
if (result != out) {
out.clear();
return false;
}
if (textureInherited && has(out, TEXTURE_MATRIX)) out.dependencies |= 1L << TEXTURE_MATRIX;
result = null;
openCaptures.decrementAndGet();
if (out.valid) out.valid = suspended == 0 && filterTexture == 0 && stacksMatchEntry() && overridesInControl() == entryOverrides;
if (out.valid) {
capture(out.value, out.writes);
if ((out.writes & (15L << FRONT_AMBIENT)) != 0) out.frontMaterial.set(context.frontMaterial);
if ((out.writes & (15L << BACK_AMBIENT)) != 0) out.backMaterial.set(context.backMaterial);
out.textureDeleteCount = TextureInfoCache.INSTANCE.deleteCount();
}
filterTexture = 0;
return out.valid;
}
private boolean stacksMatchEntry() {
return context.attribDepth == attribDepth && GLStateManager.getMatrixStackDepth(context.modelViewMatrix) == modelViewDepth
&& GLStateManager.getMatrixStackDepth(context.textures.getTextureUnitMatrix(0)) == textureDepth && modelView.equals(context.modelViewMatrix);
}
void cancel() {
if (result != null) {
result.clear();
openCaptures.decrementAndGet();
}
result = null;
filterTexture = 0;
filterOperation = false;
}
void reject() {
if (active()) result.valid = false;
}
// Callers record before their cache's unchanged-value early return
void write(int field) {
if (active()) record(field);
}
void writeBlend() {
if (active() && blendWritesReachVanilla()) record(ExitStateCapture.BLEND_FUNC);
}
void writeAlpha() {
if (active() && alphaWritesReachVanilla()) record(ExitStateCapture.ALPHA_FUNC);
}
void writeDepthColor(int field) {
if (active() && depthColorWritesReachVanilla()) record(field);
}
private void record(int field) {
result.writes |= 1L << field;
}
void requireDrawState(boolean materialFromState) {
if (!active()) return;
long fields = (materialFromState ? DRAW_INPUTS | MATERIAL_INPUTS : DRAW_INPUTS) & ~(result.writes | result.dependencies);
if ((fields & (1L << BLEND_FUNC)) != 0 && !context.blendMode.isEffectivelyEnabled()) fields &= ~(1L << BLEND_FUNC);
if ((fields & (1L << ALPHA_FUNC)) != 0 && !context.alphaTest.isEffectivelyEnabled()) fields &= ~(1L << ALPHA_FUNC);
result.dependencies |= fields;
if (textureInherited) result.dependencies |= 1L << TEXTURE_MATRIX;
}
void requireMaterialBakeInputs() {
if (active()) result.dependencies |= MATERIAL_BAKE_INPUTS & ~result.writes;
}
void requireVertexDefaults(boolean normal, boolean texCoord, boolean color, boolean lightmap) {
if (!active()) return;
final long fields = (normal ? 1L << NORMAL : 0) | (texCoord ? 1L << TEXCOORD : 0) | (color ? 1L << COLOR : 0) | (lightmap ? 1L << LIGHTMAP : 0);
result.dependencies |= fields & ~result.writes;
}
boolean beginTextureFilter(boolean nearest) {
if (filterOperationBlocked()) return false;
if (!nearest || filterTexture != 0 || context.savedFilterTextureId <= 0 || context.activeTextureUnit.getValue() != 0 || context.savedFilterMin < 0 || context.savedFilterMag < 0 || !(context.savedFilterAniso >= 1)) {
reject();
return false;
}
filterTexture = context.savedFilterTextureId;
filterMin = context.savedFilterMin;
filterMag = context.savedFilterMag;
filterAniso = context.savedFilterAniso;
startFilterOperation();
return true;
}
void finishTextureFilterSave() {
endFilterOperation();
if (active() && (savedFilterDiffers() || context.activeTextureUnit.getValue() != 0 || context.textures.getTextureUnitBindings(0).getBinding() != filterTexture || filterDiffersFrom(GL11.GL_NEAREST, GL11.GL_NEAREST, 1))) reject();
}
boolean beginTextureFilterRestore() {
if (filterOperationBlocked()) return false;
if (savedFilterDiffers()) {
reject();
return false;
}
startFilterOperation();
return true;
}
void finishTextureFilterRestore() {
endFilterOperation();
if (active() && (savedFilterDiffers() || filterDiffersFrom(filterMin, filterMag, filterAniso))) reject();
filterTexture = 0;
}
private boolean filterOperationBlocked() {
if (filterOperation) {
failTextureFilter();
return true;
}
return !active();
}
private void startFilterOperation() {
filterOperation = true;
suspend();
}
private void endFilterOperation() {
resume();
filterOperation = false;
}
void failTextureFilter() {
if (result != null) result.valid = false;
}
void requireTextureFilter(int texture, boolean nearest) {
if (filterTexture != 0 && texture == filterTexture && !nearest && active()) reject();
}
private boolean savedFilterDiffers() {
return filterTexture == 0 || context.savedFilterTextureId != filterTexture || context.savedFilterMin != filterMin || context.savedFilterMag != filterMag || context.savedFilterAniso != filterAniso;
}
private boolean filterDiffersFrom(int min, int mag, float aniso) {
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(filterTexture);
return info == null || info.getMinFilter() != min || info.getMagFilter() != mag || info.getMaxAnisotropy() != aniso;
}
boolean lightingWritten() {
return active() && has(result, LIGHTING);
}
int polygonOffsetWrites() {
if (!active()) return 0;
return (has(result, OFFSET) ? ExitState.POLYGON_OFFSET_ENABLE_WRITTEN : 0) | (has(result, OFFSET_VALUES) ? ExitState.POLYGON_OFFSET_VALUES_WRITTEN : 0);
}
void capability(int cap, int unit) {
if (!active()) return;
int field = -1;
for (int i = 0; i < CAPS.length; i++) {
if (CAPS[i] == cap) {
field = i;
break;
}
}
if (field == TEXTURE0) field = unit == 0 ? TEXTURE0 : unit == 1 ? TEXTURE1 : -1;
if (field < 0) result.valid = false;
else if (field == BLEND ? blendWritesReachVanilla() : field != ALPHA || alphaWritesReachVanilla()) record(field);
}
void material(int face, int parameter) {
if (!active()) return;
int first = switch (parameter) {
case GL11.GL_AMBIENT, GL11.GL_AMBIENT_AND_DIFFUSE -> 0;
case GL11.GL_DIFFUSE -> 1;
case GL11.GL_SPECULAR -> 2;
case GL11.GL_EMISSION -> 3;
default -> -1;
};
if (first < 0) {
result.valid = false;
return;
}
if (face != GL11.GL_BACK) {
record(FRONT_AMBIENT + first);
if (parameter == GL11.GL_AMBIENT_AND_DIFFUSE) record(FRONT_DIFFUSE);
}
if (face != GL11.GL_FRONT) {
record(BACK_AMBIENT + first);
if (parameter == GL11.GL_AMBIENT_AND_DIFFUSE) record(BACK_DIFFUSE);
}
}
// Pushes and pops check result rather than active()
void pushAttrib() {
if (result == null) return;
if (context.attribDepth < attribWrites.length) attribWrites[context.attribDepth] = result.writes;
else result.valid = false;
}
void popAttrib(StateSet set) {
if (result == null) return;
if (context.attribDepth <= attribDepth) {
result.valid = false;
return;
}
final long restored = restoredFields(set);
result.writes = (result.writes & ~restored) | (attribWrites[context.attribDepth - 1] & restored);
}
private long restoredFields(StateSet set) {
long restored = restoredFieldsBySet.getOrDefault(set, -1L);
if (restored >= 0) return restored;
restored = 0;
for (int field = 0; field < FIELD_COUNT; field++) {
final CowStateStack<?> stack = stack(field);
if (stack != null && set.contains(stack.stackId())) restored |= 1L << field;
}
restoredFieldsBySet.put(set, restored);
return restored;
}
private CowStateStack<?> stack(int field) {
return switch (field) {
case BLEND -> context.blendMode;
case ALPHA -> context.alphaTest;
case DEPTH -> context.depthTest;
case CULL -> context.cullState;
case LIGHTING -> context.lightingState;
case NORMALIZE -> context.normalizeState;
case RESCALE -> context.rescaleNormalState;
case LIGHT0, LIGHT1 -> context.lightStates[field - LIGHT0];
case TEXTURE0, TEXTURE1 -> context.textures.getTextureUnitStates(field - TEXTURE0);
case OFFSET -> context.polygonOffsetFillState;
case COLOR_MATERIAL -> context.colorMaterial;
case COLOR -> context.color;
case NORMAL -> context.ffp.normalStack;
case TEXCOORD -> context.ffp.texCoordStack;
case BLEND_FUNC -> context.blendState;
case ALPHA_FUNC -> context.alphaState;
case DEPTH_FUNC, DEPTH_MASK -> context.depthState;
case COLOR_MASK -> context.colorMask;
case CULL_FACE, FRONT_FACE, OFFSET_VALUES -> context.polygonState;
case SHADE_MODEL -> context.shadeModelState;
case ACTIVE_UNIT -> context.activeTextureUnit;
case TEXTURE_BINDING -> context.textures.getTextureUnitBindings(0);
case MATRIX_MODE -> context.matrixMode;
// Face and parameter are restored together (GL_LIGHTING_BIT and the batch set hold both).
case COLOR_MATERIAL_SETTINGS -> context.colorMaterialFace;
case FRONT_AMBIENT, FRONT_DIFFUSE, FRONT_SPECULAR, FRONT_EMISSION -> context.frontMaterial;
case BACK_AMBIENT, BACK_DIFFUSE, BACK_SPECULAR, BACK_EMISSION -> context.backMaterial;
default -> null;
};
}
void matrixChange(boolean absolute) {
if (result == null) return;
final int mode = context.matrixMode.getMode();
if ((absolute || mode != GL11.GL_MODELVIEW) && active()) recordMatrixChange(mode, context.activeTextureUnit.getValue(), absolute);
}
void matrixChange(int mode, int unit, boolean absolute) {
if (active()) recordMatrixChange(mode, unit, absolute);
}
private void recordMatrixChange(int mode, int unit, boolean absolute) {
if (mode == GL11.GL_MODELVIEW) {
if (absolute) result.valid = false;
return;
}
if (mode != GL11.GL_TEXTURE || unit != 0) {
result.valid = false;
return;
}
record(TEXTURE_MATRIX);
if (absolute) textureInherited = false;
}
void pushMatrix() {
if (result == null) return;
final int mode = context.matrixMode.getMode();
if (mode == GL11.GL_TEXTURE && context.activeTextureUnit.getValue() == 0) {
final int depth = GLStateManager.getMatrixStackDepth(context.textures.getTextureUnitMatrix(0));
if (depth < matrixWrites.length) {
matrixWrites[depth] = result.writes;
matrixInherited[depth] = textureInherited;
} else {
result.valid = false;
}
} else if (mode != GL11.GL_MODELVIEW) {
reject();
}
}
void popMatrix() {
if (result == null) return;
final int mode = context.matrixMode.getMode();
final boolean texture = mode == GL11.GL_TEXTURE && context.activeTextureUnit.getValue() == 0;
final Matrix4fStack matrix;
final int entryDepth;
if (mode == GL11.GL_MODELVIEW) {
matrix = context.modelViewMatrix;
entryDepth = modelViewDepth;
} else if (texture) {
matrix = context.textures.getTextureUnitMatrix(0);
entryDepth = textureDepth;
} else {
reject();
return;
}
final int depth = GLStateManager.getMatrixStackDepth(matrix);
if (depth <= entryDepth) {
result.valid = false;
return;
}
if (texture) {
final long bit = 1L << TEXTURE_MATRIX;
result.writes = (result.writes & ~bit) | (matrixWrites[depth - 1] & bit);
textureInherited = matrixInherited[depth - 1];
}
}
private void capture(ExitState.Values out, long fields) {
out.enabled = 0;
for (int field = 0; field < CAPS.length; field++) {
if (has(fields, field) && ((BooleanStateStack) Objects.requireNonNull(stack(field))).isEffectivelyEnabled()) out.enabled |= 1L << field;
}
if (has(fields, COLOR)) out.color.set(context.color);
if (has(fields, NORMAL)) out.normal.set(context.ffp.currentNormal);
if (has(fields, TEXCOORD)) out.texCoord.set(context.ffp.currentTexCoords[0]);
if (has(fields, LIGHTMAP)) {
out.lightmapX = context.lastBrightnessX;
out.lightmapY = context.lastBrightnessY;
}
if (has(fields, SHADER_COLOR)) out.shaderColor.set(context.shaderColorR, context.shaderColorG, context.shaderColorB, context.shaderColorA);
if (has(fields, OVERLAY_COLOR)) out.overlayColor.set(context.overlayR, context.overlayG, context.overlayB, context.overlayA);
if (has(fields, BLEND_FUNC)) context.blendState.readEffective(out.blend);
if (has(fields, ALPHA_FUNC)) context.alphaState.readEffective(out.alpha);
if (has(fields, COLOR_MASK)) context.colorMask.readEffective(out.colorMask);
if (has(fields, DEPTH_FUNC)) out.depthFunction = context.depthState.getFunc();
if (has(fields, DEPTH_MASK)) out.depthMask = context.depthState.isEffectiveMaskEnabled();
if (has(fields, CULL_FACE)) out.cullFace = context.polygonState.getCullFaceMode();
if (has(fields, FRONT_FACE)) out.frontFace = context.polygonState.getFrontFace();
if (has(fields, OFFSET_VALUES)) {
out.offsetFactor = context.polygonState.getOffsetFactor();
out.offsetUnits = context.polygonState.getOffsetUnits();
out.offsetClamp = context.polygonState.getOffsetClamp();
}
if (has(fields, SHADE_MODEL)) out.shadeModel = context.shadeModelState.getValue();
if (has(fields, ACTIVE_UNIT)) out.activeUnit = context.activeTextureUnit.getValue();
if (has(fields, CLIENT_ACTIVE_UNIT)) out.clientActiveUnit = context.clientActiveTextureUnit;
if (has(fields, MATRIX_MODE)) out.matrixMode = context.matrixMode.getMode();
if (has(fields, TEXTURE_BINDING)) out.texture = context.textures.getTextureUnitBindings(0).getBinding();
if (has(fields, TEXTURE_MATRIX)) out.textureMatrix.set(context.textures.getTextureUnitMatrix(0));
if (has(fields, COLOR_MATERIAL_SETTINGS)) {
out.materialFace = context.colorMaterialFace.getValue();
out.materialParameter = context.colorMaterialParameter.getValue();
}
}
boolean canApply(ExitState in) {
if (!in.valid || result != null || !GLStateManager.isCachingEnabled() || DisplayListManager.isRecording() || context.savedFilterTextureId != 0) return false;
if (in.textureDeleteCount != TextureInfoCache.INSTANCE.deleteCount()) return false;
capture(current, in.dependencies);
if (((current.enabled ^ in.entry.enabled) & in.dependencies & ((1L << CAPS.length) - 1)) != 0) return false;
long remaining = in.dependencies & -(1L << CAPS.length);
while (remaining != 0) {
final int field = Long.numberOfTrailingZeros(remaining);
if (!same(field, in.entry, current)) return false;
remaining &= remaining - 1;
}
return true;
}
void apply(ExitState state) {
if (!state.valid) throw new IllegalStateException("Invalid renderer exit state");
if (result != null) throw new IllegalStateException("Exit state applied while a capture is open");
final ExitState.Values in = state.value;
final long fields = state.writes;
final int liveUnit = context.activeTextureUnit.getValue();
// Capabilities go first so the glColor4f below bakes into the material only if color material is on at exit.
for (int field = 0; field < CAPS.length; field++) {
if (!has(fields, field)) continue;
if (field == TEXTURE0 || field == TEXTURE1) GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + field - TEXTURE0);
// glEnable would bake the live color, not the exit color, into the material.
if (field == COLOR_MATERIAL) {
context.colorMaterial.setEnabled((in.enabled & (1L << field)) != 0);
} else if ((in.enabled & (1L << field)) != 0) GLStateManager.glEnable(CAPS[field]);
else GLStateManager.glDisable(CAPS[field]);
}
if (has(fields, COLOR)) GLStateManager.glColor4f(in.color.getRed(), in.color.getGreen(), in.color.getBlue(), in.color.getAlpha());
if (has(fields, NORMAL)) GLStateManager.glNormal3f(in.normal.x, in.normal.y, in.normal.z);
if (has(fields, TEXCOORD)) GLStateManager.glTexCoord4f(in.texCoord.x, in.texCoord.y, in.texCoord.z, in.texCoord.w);
if (has(fields, LIGHTMAP)) GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, in.lightmapX, in.lightmapY);
if (has(fields, SHADER_COLOR)) GLStateManager.setShaderColor(in.shaderColor.x, in.shaderColor.y, in.shaderColor.z, in.shaderColor.w);
if (has(fields, OVERLAY_COLOR)) GLStateManager.setOverlayColor(in.overlayColor.x, in.overlayColor.y, in.overlayColor.z, in.overlayColor.w);
if (has(fields, BLEND_FUNC)) GLStateManager.tryBlendFuncSeparate(in.blend.getSrcRgb(), in.blend.getDstRgb(), in.blend.getSrcAlpha(), in.blend.getDstAlpha());
if (has(fields, ALPHA_FUNC)) GLStateManager.glAlphaFunc(in.alpha.getFunction(), in.alpha.getReference());
if (has(fields, DEPTH_FUNC)) GLStateManager.glDepthFunc(in.depthFunction);
if (has(fields, DEPTH_MASK)) GLStateManager.glDepthMask(in.depthMask);
if (has(fields, COLOR_MASK)) GLStateManager.glColorMask(in.colorMask.red, in.colorMask.green, in.colorMask.blue, in.colorMask.alpha);
if (has(fields, CULL_FACE)) GLStateManager.glCullFace(in.cullFace);
if (has(fields, FRONT_FACE)) GLStateManager.glFrontFace(in.frontFace);
if (has(fields, SHADE_MODEL)) GLStateManager.glShadeModel(in.shadeModel);
if (has(fields, OFFSET_VALUES)) GLStateManager.glPolygonOffsetClamp(in.offsetFactor, in.offsetUnits, in.offsetClamp);
if (has(fields, TEXTURE_BINDING)) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, in.texture);
}
if (has(fields, TEXTURE_MATRIX)) GLStateManager.setTextureMatrix(0, in.textureMatrix);
if (has(fields, COLOR_MATERIAL_SETTINGS)) GLStateManager.glColorMaterial(in.materialFace, in.materialParameter);
boolean materialChanged = false;
for (int field = FRONT_AMBIENT; field <= BACK_EMISSION; field++) {
if (!has(fields, field)) continue;
final boolean front = field < BACK_AMBIENT;
final int component = (field - FRONT_AMBIENT) & 3;
final Vector4f source = vector(front ? state.frontMaterial : state.backMaterial, component);
final var target = front ? context.frontMaterial : context.backMaterial;
if (vector(target, component).equals(source)) continue;
target.beforeModify();
vector(target, component).set(source);
materialChanged = true;
}
if (materialChanged) context.lightingGeneration++;
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + (has(fields, ACTIVE_UNIT) ? in.activeUnit : liveUnit));
if (has(fields, CLIENT_ACTIVE_UNIT)) GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0 + in.clientActiveUnit);
if (has(fields, MATRIX_MODE)) GLStateManager.glMatrixMode(in.matrixMode);
}
}
@@ -59,7 +59,6 @@ import java.util.ArrayList;
import java.util.List;
public class GLContextState {
final ExitStateCapture exitCapture = new ExitStateCapture(this);
private final List<IStateStack<?>> allStacksBuilder = new ArrayList<>();
public IStateStack<?>[] allStacks;
final List<BooleanStateStack> allBooleanStates = new ArrayList<>();
File diff suppressed because it is too large. Load diff
@@ -198,17 +198,14 @@ public class RenderSystem {
}
public static void generateMipmaps(int texture, int mipmapTarget) {
GLStateManager.rejectExitCapture();
dsaState.generateMipmaps(texture, mipmapTarget);
}
public static void bindAttributeLocation(int program, int index, CharSequence name) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.bindAttribLocation(program, index, name);
}
public static void texImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
GLStateManager.rejectExitCapture();
final GLStateManager.GLESTexImageRemap remap = GLStateManager.remapTexImageForGLES(internalformat, format, type);
internalformat = remap.internalFormat();
format = remap.format();
@@ -223,7 +220,6 @@ public class RenderSystem {
}
public static void uniformMatrix4fv(int location, boolean transpose, FloatBuffer matrix) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.uniformMatrix4(location, transpose, matrix);
}
@@ -232,78 +228,61 @@ public class RenderSystem {
}
public static void uniform1f(int location, float v0) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.uniform1f(location, v0);
}
public static void uniform1i(int location, int v0) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.uniform1i(location, v0);
}
public static void uniform2f(int location, float v0, float v1) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.uniform2f(location, v0, v1);
}
public static void uniform2i(int location, int v0, int v1) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.uniform2i(location, v0, v1);
}
public static void uniform3f(int location, float v0, float v1, float v2) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.uniform3f(location, v0, v1, v2);
}
public static void uniform3i(int location, int v0, int v1, int v2) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.uniform3i(location, v0, v1, v2);
}
public static void uniform3i(int location, int v0, int v1, int v2) {RENDER_BACKEND.uniform3i(location, v0, v1, v2);}
public static void uniform4f(int location, float v0, float v1, float v2, float v3) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.uniform4f(location, v0, v1, v2, v3);
}
public static void uniform4i(int location, int v0, int v1, int v2, int v3) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.uniform4i(location, v0, v1, v2, v3);
}
public static int getAttribLocation(int programId, String name) {
GLStateManager.rejectExitCapture();
return RENDER_BACKEND.getAttribLocation(programId, name);
}
public static int getUniformLocation(int programId, String name) {
GLStateManager.rejectExitCapture();
return RENDER_BACKEND.getUniformLocation(programId, name);
}
public static void texParameteriv(int texture, int target, int pname, IntBuffer params) {
GLStateManager.rejectExitCapture();
dsaState.texParameteriv(texture, target, pname, params);
}
public static void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
GLStateManager.rejectExitCapture();
dsaState.copyTexSubImage2D(destTexture, target, i, i1, i2, i3, i4, width, height);
if (i == 0) TextureInfoCache.INSTANCE.onContentsChanged(destTexture);
}
public static void texParameteri(int texture, int target, int pname, int param) {
GLStateManager.rejectExitCapture();
dsaState.texParameteri(texture, target, pname, param);
}
public static void texParameterf(int texture, int target, int pname, float param) {
GLStateManager.rejectExitCapture();
dsaState.texParameterf(texture, target, pname, param);
}
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
GLStateManager.rejectExitCapture();
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
@@ -311,7 +290,6 @@ public class RenderSystem {
}
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
GLStateManager.rejectExitCapture();
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
@@ -319,7 +297,6 @@ public class RenderSystem {
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
GLStateManager.rejectExitCapture();
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
@@ -327,7 +304,6 @@ public class RenderSystem {
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
GLStateManager.rejectExitCapture();
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
@@ -335,7 +311,6 @@ public class RenderSystem {
}
public static void texImage1D(int texture, int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
GLStateManager.rejectExitCapture();
GLStateManager.glBindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage1D(target, level, internalformat, width, border, format, type, pixels);
@@ -343,7 +318,6 @@ public class RenderSystem {
}
public static void texImage3D(int texture, int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
GLStateManager.rejectExitCapture();
GLStateManager.glBindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
@@ -351,32 +325,26 @@ public class RenderSystem {
}
public static String getProgramInfoLog(int program) {
GLStateManager.rejectExitCapture();
return RENDER_BACKEND.getProgramInfoLog(program, RENDER_BACKEND.getProgrami(program, GL20.GL_INFO_LOG_LENGTH));
}
public static String getShaderInfoLog(int shader) {
GLStateManager.rejectExitCapture();
return RENDER_BACKEND.getShaderInfoLog(shader, RENDER_BACKEND.getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
}
public static void drawBuffers(int framebuffer, IntBuffer buffers) {
GLStateManager.rejectExitCapture();
dsaState.drawBuffers(framebuffer, buffers);
}
public static void readBuffer(int framebuffer, int buffer) {
GLStateManager.rejectExitCapture();
dsaState.readBuffer(framebuffer, buffer);
}
public static String getActiveUniform(int program, int index, int maxLength, IntBuffer sizeType) {
GLStateManager.rejectExitCapture();
return RENDER_BACKEND.getActiveUniform(program, index, maxLength, sizeType);
}
public static void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
GLStateManager.rejectExitCapture();
GLStateManager.suspendPixelPackBuffer();
RENDER_BACKEND.readPixels(x, y, width, height, format, type, pixels);
GLStateManager.restorePixelPackBuffer();
@@ -387,37 +355,30 @@ public class RenderSystem {
}
public static int bufferStorage(int target, FloatBuffer data, int usage) {
GLStateManager.rejectExitCapture();
return dsaState.bufferStorage(target, data, usage);
}
public static void vertexAttrib4f(int index, float v0, float v1, float v2, float v3) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.vertexAttrib4f(index, v0, v1, v2, v3);
}
public static void detachShader(int program, int shader) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.detachShader(program, shader);
}
public static void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
GLStateManager.rejectExitCapture();
dsaState.framebufferTexture2D(fb, fbtarget, attachment, target, texture, levels);
}
public static int getTexParameteri(int texture, int target, int pname) {
GLStateManager.rejectExitCapture();
return dsaState.getTexParameteri(texture, target, pname);
}
public static float getTexParameterf(int texture, int target, int pname) {
GLStateManager.rejectExitCapture();
return dsaState.getTexParameterf(texture, target, pname);
}
public static int getTexLevelParameteri(int texture, int level, int pname) {
GLStateManager.rejectExitCapture();
return dsaState.getTexLevelParameteri(texture, level, pname);
}
@@ -514,21 +475,18 @@ public class RenderSystem {
}
public static void getProgramiv(int program, int value, IntBuffer storage) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.getProgramiv(program, value, storage);
}
public static void dispatchCompute(int workX, int workY, int workZ) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.dispatchCompute(workX, workY, workZ);
}
public static void dispatchCompute(Vector3i workGroups) {
dispatchCompute(workGroups.x, workGroups.y, workGroups.z);
RENDER_BACKEND.dispatchCompute(workGroups.x, workGroups.y, workGroups.z);
}
public static void dispatchComputeIndirect(long offset) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.dispatchComputeIndirect(offset);
}
@@ -537,7 +495,6 @@ public class RenderSystem {
}
public static void memoryBarrier(int barriers) {
GLStateManager.rejectExitCapture();
if (supportsCompute) {
RENDER_BACKEND.memoryBarrier(barriers);
}
@@ -560,14 +517,12 @@ public class RenderSystem {
}
public static void bindTextureToUnit(int unit, int texture) {
if (unit == 0) GLStateManager.recordExitTextureBind(GL11.GL_TEXTURE_2D, 0);
else if (GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
dsaState.bindTextureToUnit(unit, texture);
}
public static void bindTextureToUnit(int target, int unit, int texture) {
if (unit == 0) GLStateManager.recordExitTextureBind(target, 0);
else if (GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
dsaState.bindTextureToUnit(target, unit, texture);
}
@@ -587,7 +542,6 @@ public class RenderSystem {
}
public static void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2, int bufferChoice, int filter) {
GLStateManager.rejectExitCapture();
final boolean scissor = GLStateManager.glIsEnabled(GL11.GL_SCISSOR_TEST);
if (scissor) GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
try {
@@ -598,12 +552,10 @@ public class RenderSystem {
}
public static int createFramebuffer() {
GLStateManager.rejectExitCapture();
return dsaState.createFramebuffer();
}
public static int createTexture(int target) {
GLStateManager.rejectExitCapture();
return dsaState.createTexture(target);
}
@@ -621,7 +573,6 @@ public class RenderSystem {
}
public static int getIndexedBufferBinding(int target, int index) {
GLStateManager.rejectExitCapture();
return GLStateManager.getIndexedBufferBinding(target, index);
}
@@ -653,7 +604,6 @@ public class RenderSystem {
}
public static void clearTexImage(int texture, int target, int level, int format, int type) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.clearTexImage(texture, level, format, type);
}
@@ -199,21 +199,18 @@ public final class ShaderManager {
}
public void setNormal(float x, float y, float z) {
GLStateManager.recordExitNormal();
normalStack.beforeModify();
currentNormal.set(x, y, z);
normalGeneration++;
}
public void setTexCoord(float s, float t, float r, float q) {
GLStateManager.recordExitTexCoord(0);
texCoordStack.beforeModify();
currentTexCoords[0].set(s, t, r, q);
texCoordGeneration++;
}
public void setTexCoord(int unit, float s, float t, float r, float q) {
GLStateManager.recordExitTexCoord(unit);
if (unit == 0) texCoordStack.beforeModify();
currentTexCoords[unit].set(s, t, r, q);
texCoordGeneration++;
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.glsm.hooks;
public interface DeferredAlphaHandler {
boolean isAlphaTestLocked();
boolean isOverrideHeld();
boolean deferAlphaTestToggle(boolean enabled);
boolean deferAlphaFunc(int function, float reference);
}
@@ -47,31 +47,6 @@ public final class TextureUtilHooks {
GLStateManager.ctx().savedFilterTextureId = 0;
}
public static boolean beginFilterSave(boolean blurred, boolean mipmap, float aniso) {
captureBoundFilterTexture();
return GLStateManager.beginExitTextureFilter(!blurred && !mipmap && aniso == 1);
}
public static void endFilterSave(boolean captured) {
if (captured) GLStateManager.finishExitTextureFilterSave();
}
public static boolean beginFilterRestore() {
return GLStateManager.beginExitTextureFilterRestore();
}
public static void endFilterRestore(boolean captured) {
try {
if (captured) GLStateManager.finishExitTextureFilterRestore();
} finally {
clearSavedFilterTexture();
}
}
public static void filterFailed(boolean captured) {
if (captured) GLStateManager.failExitTextureFilter();
}
public static void restoreFilter(int min, int mag) {
final int id = GLStateManager.ctx().savedFilterTextureId;
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
@@ -96,7 +96,6 @@ public class BooleanStateStack extends BooleanState implements CowStateStack<Boo
@Override
public void setEnabled(boolean enabled) {
final GLContextState o = cow.owner;
GLStateManager.recordExitCapability(o, glCap, -1);
if (o != null && o.attribDepth > 0
&& (stateUnknown || !GLStateManager.isCachingEnabled() || enabled != vanillaValue())) {
beforeModify();
@@ -24,7 +24,6 @@ public final class TextureUnitBooleanStateStack extends BooleanStateStack {
@Override
public void setEnabled(boolean enabled) {
GLStateManager.recordExitCapability(cowDepths().owner, glCap, unitIndex);
if (!GLStateManager.isCachingEnabled() || enabled != this.enabled) {
GLStateManager.beforeUncapturedTextureUnitChange(glCap, unitIndex);
beforeModify();
@@ -197,7 +197,6 @@ public class TessellatorStreamingDrawer {
final boolean faceNormals = !tess.hasNormals && normalHandler != null
&& ImmediateExtendedAttribHandler.extPrimVerts(tess.drawMode, vertexCount) == 4 && normalHandler.wantsFaceNormals();
final boolean fillNormal = !tess.hasNormals && !faceNormals;
GLStateManager.requireExitVertexDefaults(fillNormal, !tess.hasTexture, !tess.hasColor, !tess.hasBrightness);
final int defaultNormal;
if (fillNormal) {
@@ -1,151 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
import org.junit.jupiter.api.Test;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import static org.junit.jupiter.api.Assertions.assertTrue;
class GLSM_ExitCaptureCoverage_UnitTest {
private static final String PACKAGE = "com/gtnewhorizons/angelica/glsm/";
private static final String MANAGER = PACKAGE + "GLStateManager";
private static final String RENDER_SYSTEM = PACKAGE + "RenderSystem";
private static final String CAPTURE = PACKAGE + "ExitStateCapture";
private static final Set<String> RECORDING = Set.of(
"reject", "write", "writeBlend", "writeAlpha", "writeDepthColor", "capability", "material",
"matrixChange", "pushAttrib", "popAttrib", "pushMatrix", "popMatrix",
"requireDrawState", "requireMaterialBakeInputs", "requireVertexDefaults", "requireTextureFilter",
"beginTextureFilter", "finishTextureFilterSave", "beginTextureFilterRestore", "finishTextureFilterRestore",
"failTextureFilter");
private static final Set<String> IGNORED = Set.of(
// glBegin and glEnd reject
"glVertex2d", "glVertex2f", "glVertex2i", "glVertex3d", "glVertex3f", "glVertex3i",
// No render state
"glDebugMessageCallback", "glDebugMessageControl", "glDebugMessageInsert", "glObjectLabel",
"glPushDebugGroup", "glPopDebugGroup",
// Others
"glInitNames", "glPushName", "glPopName", "glLoadName", "isFramebufferEnabled", "gluUnProject", "gluErrorString");
private final Map<String, ClassNode> classes = new HashMap<>();
private final Set<String> reaching = new HashSet<>();
private final Set<String> notReaching = new HashSet<>();
private Set<String> counted = RECORDING;
@Test
void everyEntryPointRejectsOrRecordsTheExitCapture() {
final Set<String> entryPoints = new HashSet<>(GLSMRedirector.getGLPrefixMethodRedirects().values());
GLSMRedirector.getNamedClassMethodRedirects().values().forEach(redirects -> entryPoints.addAll(redirects.values()));
final List<String> missing = missing(MANAGER, name -> name.startsWith("gl") || entryPoints.contains(name), method -> reachesCapture(MANAGER, method));
assertTrue(missing.isEmpty(), "Entry points that neither reject nor record an exit capture:\n " + String.join("\n ", missing));
}
// None of RenderSystem's writes are modeled. A method that drives the backend or DSA itself must reject itself, since
// a GLSM call it also makes (glBindTexture) may only record.
@Test
void everyRenderSystemCallRejectsTheExitCapture() {
counted = Set.of("reject");
final List<String> missing = missing(RENDER_SYSTEM, name -> !name.equals("initRenderer") && !name.equals("getVRAM")
&& !name.matches("(supports|has|is|getMax|getGl|parse|probe).*"), method -> reachesCapture(RENDER_SYSTEM, method)
&& (!calls(method, owner -> owner.startsWith(PACKAGE + "backend/") || owner.startsWith(PACKAGE + "dsa/"))
|| calls(method, owner -> owner.equals(MANAGER), "rejectExitCapture", "beforeUncapturedStateChange")));
assertTrue(missing.isEmpty(), "RenderSystem methods that do not reject an exit capture:\n " + String.join("\n ", missing));
}
private List<String> missing(String owner, Predicate<String> checked, Predicate<MethodNode> covered) {
final List<String> missing = new ArrayList<>();
for (MethodNode method : load(owner).methods) {
if ((method.access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC)) != (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC)) continue;
if ((method.access & Opcodes.ACC_SYNTHETIC) != 0) continue;
if (!checked.test(method.name) || IGNORED.contains(method.name)) continue;
if (!covered.test(method)) missing.add(method.name + method.desc);
}
missing.sort(null);
return missing;
}
private static boolean calls(MethodNode method, Predicate<String> owner, String... names) {
final Set<String> wanted = Set.of(names);
for (AbstractInsnNode insn : method.instructions) {
if (insn instanceof MethodInsnNode call && owner.test(call.owner) && (wanted.isEmpty() || wanted.contains(call.name))) return true;
}
return false;
}
private boolean reachesCapture(String owner, MethodNode root) {
final String key = owner + '.' + root.name + root.desc;
if (reaching.contains(key)) return true;
if (notReaching.contains(key)) return false;
final Set<String> visited = new HashSet<>();
if (search(owner, root, visited)) {
reaching.add(key);
return true;
}
notReaching.addAll(visited);
return false;
}
private boolean search(String owner, MethodNode method, Set<String> visited) {
final String key = owner + '.' + method.name + method.desc;
if (reaching.contains(key)) return true;
if (notReaching.contains(key) || !visited.add(key)) return false;
for (AbstractInsnNode insn : method.instructions) {
if (!(insn instanceof MethodInsnNode call)) continue;
if (call.owner.equals(CAPTURE)) {
if (counted.contains(call.name)) return true;
continue;
}
if (!call.owner.startsWith(PACKAGE)) continue;
final String[] declaring = new String[1];
final MethodNode target = resolve(call.owner, call.name, call.desc, declaring);
if (target != null && search(declaring[0], target, visited)) return true;
}
return false;
}
private MethodNode resolve(String owner, String name, String desc, String[] declaring) {
for (String type = owner; type != null && type.startsWith(PACKAGE); ) {
final ClassNode node = load(type);
for (MethodNode method : node.methods) {
if (method.name.equals(name) && method.desc.equals(desc)) {
if (method.instructions.size() == 0) return null;
declaring[0] = type;
return method;
}
}
type = node.superName;
}
return null;
}
private ClassNode load(String internalName) {
return classes.computeIfAbsent(internalName, name -> {
try (InputStream in = GLSM_ExitCaptureCoverage_UnitTest.class.getClassLoader().getResourceAsStream(name + ".class")) {
if (in == null) throw new IllegalStateException("Missing class file for " + name);
final ClassNode node = new ClassNode();
new ClassReader(in).accept(node, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
return node;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
}
@@ -1,561 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaBooleanLayer;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
import com.gtnewhorizons.angelica.glsm.states.BlendState;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import net.minecraft.client.renderer.Tessellator;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@GLCoreTest
class GLSM_ExitState_GLTest {
private final Matrix4f modelView = new Matrix4f();
private final Matrix4f projection = new Matrix4f();
private final Matrix4f texture = new Matrix4f();
private final Vector4f overlay = new Vector4f();
private final Vector4f shaderColor = new Vector4f();
private int outer;
private int clientActiveUnit;
private float lightmapX, lightmapY;
private ImmediateExtendedAttribHandler extendedHandler;
private DeferredBlendHandler blendHandler;
@BeforeEach
void setUp() {
final GLContextState context = GLStateManager.ctx();
modelView.set(context.modelViewMatrix);
projection.set(context.projectionMatrix);
texture.set(context.textures.getTextureUnitMatrix(0));
overlay.set(context.overlayR, context.overlayG, context.overlayB, context.overlayA);
shaderColor.set(context.shaderColorR, context.shaderColorG, context.shaderColorB, context.shaderColorA);
lightmapX = context.lastBrightnessX;
lightmapY = context.lastBrightnessY;
extendedHandler = GLSMHooks.immediateExtendedHandler;
blendHandler = GLSMHooks.blendHandler;
clientActiveUnit = context.clientActiveTextureUnit;
outer = GLStateManager.pushState(StateSet.forMask(GL11.GL_ALL_ATTRIB_BITS));
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.setTextureMatrix(0, new Matrix4f());
GLStateManager.glColor4f(1, 1, 1, 1);
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
GLStateManager.disableColorMaterial();
GLStateManager.enableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
GLStateManager.glPolygonOffset(2, 3);
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
}
@AfterEach
void tearDown() {
GLStateManager.cancelExitCapture();
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0 + clientActiveUnit);
GLSMHooks.blendHandler = blendHandler;
GLSMHooks.immediateExtendedHandler = extendedHandler;
GLStateManager.getBlendMode().setVanillaLayer(null);
GLStateManager.getBlendState().setVanillaLayer(null);
GLStateManager.setModelViewMatrix(modelView);
GLStateManager.setProjectionMatrix(projection);
GLStateManager.setTextureMatrix(0, texture);
GLStateManager.setOverlayColor(overlay.x, overlay.y, overlay.z, overlay.w);
GLStateManager.setShaderColor(shaderColor.x, shaderColor.y, shaderColor.z, shaderColor.w);
GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, lightmapX, lightmapY);
GLStateManager.popStateTo(outer);
}
@Test
void unchangedWritesStillApplyAndLeaveOtherPolygonStateAlone() {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
GLStateManager.enableCull();
GLStateManager.glCullFace(GL11.GL_BACK);
assertTrue(GLStateManager.finishExitCapture(state));
GLStateManager.disableCull();
GLStateManager.glCullFace(GL11.GL_FRONT);
GLStateManager.glEnable(GL11.GL_POLYGON_OFFSET_FILL);
GLStateManager.glPolygonOffset(7, 11);
assertTrue(GLStateManager.canApplyExitState(state));
GLStateManager.applyExitState(state);
assertTrue(GLStateManager.getCullState().isEnabled());
assertEquals(GL11.GL_BACK, GLStateManager.getPolygonState().getCullFaceMode());
assertTrue(GLStateManager.getPolygonOffsetFillState().isEnabled());
assertEquals(7, GLStateManager.getPolygonState().getOffsetFactor());
assertEquals(11, GLStateManager.getPolygonState().getOffsetUnits());
}
@Test
void attribPopUndoesTheWritesItRestores() {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
GLStateManager.glEnable(GL11.GL_POLYGON_OFFSET_FILL);
assertEquals(ExitState.POLYGON_OFFSET_ENABLE_WRITTEN, GLStateManager.getExitPolygonOffsetWrites());
GLStateManager.glPushAttrib(GL11.GL_POLYGON_BIT | GL11.GL_CURRENT_BIT);
GLStateManager.glPolygonOffset(5, 6);
GLStateManager.disableCull();
GLStateManager.glColor4f(0, 0, 0, 1);
assertEquals(3, GLStateManager.getExitPolygonOffsetWrites());
GLStateManager.glPopAttrib();
assertEquals(ExitState.POLYGON_OFFSET_ENABLE_WRITTEN, GLStateManager.getExitPolygonOffsetWrites());
assertTrue(GLStateManager.finishExitCapture(state));
GLStateManager.disableCull();
GLStateManager.glColor4f(0.25F, 0.5F, 0.75F, 1);
GLStateManager.glPolygonOffset(8, 9);
GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
GLStateManager.applyExitState(state);
assertFalse(GLStateManager.getCullState().isEnabled());
assertEquals(0.25F, GLStateManager.getColor().getRed());
assertEquals(8, GLStateManager.getPolygonState().getOffsetFactor());
assertTrue(GLStateManager.getPolygonOffsetFillState().isEnabled());
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void drawsDependOnTheAlphaFunctionOnlyWhenTheyReadItFromState(boolean materialFromState) {
GLStateManager.enableAlphaTest();
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
GLStateManager.glColor4f(1, 1, 1, 1);
GLStateManager.requireExitDrawState(materialFromState);
assertTrue(GLStateManager.finishExitCapture(state));
GLStateManager.glColor4f(0, 0, 0, 1);
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.6F);
assertEquals(!materialFromState, GLStateManager.canApplyExitState(state));
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
assertTrue(GLStateManager.canApplyExitState(state));
GLStateManager.disableCull();
assertFalse(GLStateManager.canApplyExitState(state));
}
@ParameterizedTest
@ValueSource(ints = {0, 1, 2})
void blendAndAlphaFunctionsMatterOnlyToDrawsWithThemEnabled(int mode) {
GLStateManager.disableBlend();
GLStateManager.disableAlphaTest();
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
GLStateManager.requireExitDrawState(true);
if (mode != 0) {
if (mode == 2) {
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
}
GLStateManager.enableBlend();
GLStateManager.enableAlphaTest();
GLStateManager.requireExitDrawState(true);
}
assertTrue(GLStateManager.finishExitCapture(state));
GLStateManager.disableBlend();
GLStateManager.disableAlphaTest();
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
assertEquals(mode != 1, GLStateManager.canApplyExitState(state));
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.5F);
assertEquals(mode != 1, GLStateManager.canApplyExitState(state));
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
assertTrue(GLStateManager.canApplyExitState(state));
}
@Test
void settingTheClientTextureUnitToItsCurrentValueIsStillRecorded() {
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
GLStateManager.glPushAttrib(GL11.GL_TEXTURE_BIT);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glPopAttrib();
assertTrue(GLStateManager.finishExitCapture(state));
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
assertTrue(GLStateManager.canApplyExitState(state));
GLStateManager.applyExitState(state);
assertEquals(0, GLStateManager.getClientActiveTextureUnit());
GLStateManager.beginExitCapture(state);
assertTrue(GLStateManager.finishExitCapture(state));
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.applyExitState(state);
assertEquals(1, GLStateManager.getClientActiveTextureUnit());
}
@Test
void drawAfterAnAttribPopDependsOnTheOriginalCullState() {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.disableCull();
GLStateManager.requireExitDrawState(false);
GLStateManager.glPopAttrib();
GLStateManager.requireExitDrawState(false);
assertTrue(GLStateManager.finishExitCapture(state));
GLStateManager.disableCull();
assertFalse(GLStateManager.canApplyExitState(state));
}
@Test
void lightingAndShaderStateAreNotDrawDependencies() {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
assertFalse(GLStateManager.isExitLightingWritten());
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
GLStateManager.enableLighting();
assertTrue(GLStateManager.isExitLightingWritten());
GLStateManager.glPopAttrib();
assertFalse(GLStateManager.isExitLightingWritten());
GLStateManager.requireExitDrawState(false);
assertTrue(GLStateManager.finishExitCapture(state));
GLStateManager.getLightingState().setEnabled(!GLStateManager.getLightingState().isEnabled());
GLStateManager.getNormalizeState().setEnabled(!GLStateManager.getNormalizeState().isEnabled());
GLStateManager.getRescaleNormalState().setEnabled(!GLStateManager.getRescaleNormalState().isEnabled());
GLStateManager.getLightStates()[0].setEnabled(!GLStateManager.getLightStates()[0].isEnabled());
GLStateManager.getLightStates()[1].setEnabled(!GLStateManager.getLightStates()[1].isEnabled());
GLStateManager.glShadeModel(GLStateManager.getShadeModelState().getValue() == GL11.GL_FLAT ? GL11.GL_SMOOTH : GL11.GL_FLAT);
GLStateManager.setShaderColor(0.1F, 0.2F, 0.3F, 0.4F);
GLStateManager.glNormal3f(0.5F, 0.25F, 0.75F);
GLStateManager.glTexCoord2f(0.75F, 0.25F);
assertTrue(GLStateManager.canApplyExitState(state));
}
@ParameterizedTest
@ValueSource(ints = {0, 1, 2, 3})
void tessellatorDrawsDependOnlyOnTheCurrentValuesTheyFillIn(int mode) {
final Tessellator tess = new Tessellator();
tess.rawBuffer = new int[32];
tess.vertexCount = 4;
tess.drawMode = GL11.GL_QUADS;
tess.hasNormals = mode != 1 && mode != 3;
tess.hasTexture = mode != 2;
tess.hasColor = tess.hasBrightness = true;
GLSMHooks.immediateExtendedHandler = mode == 3 ? new FaceNormals() : null;
GLStateManager.glNormal3f(0, 1, 0);
GLStateManager.glTexCoord2f(0.25F, 0.5F);
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
TessellatorStreamingDrawer.fillMissingAttributes(tess);
assertTrue(GLStateManager.finishExitCapture(state));
if (mode == 1) assertEquals(0x7F00, tess.rawBuffer[6]);
if (mode == 2) assertEquals(Float.floatToRawIntBits(0.25F), tess.rawBuffer[3]);
if (mode == 3) assertEquals(0x7F, tess.rawBuffer[6]);
GLStateManager.glNormal3f(1, 0, 0);
GLStateManager.glTexCoord2f(0.75F, 0.5F);
assertEquals(mode == 0 || mode == 3, GLStateManager.canApplyExitState(state));
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void deletingTheBoundTextureBlocksApplyingEvenIfTheCaptureUnboundIt(boolean explicitlyUnbound) {
final int id = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
if (explicitlyUnbound) GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
assertTrue(GLStateManager.finishExitCapture(state));
assertTrue(GLStateManager.canApplyExitState(state));
GLStateManager.glDeleteTextures(id);
assertFalse(GLStateManager.canApplyExitState(state));
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void textureMatrixMustMatchOnlyWhenTheCaptureMultipliedOntoIt(boolean absolute) {
GLStateManager.setTextureMatrix(0, new Matrix4f().translation(1, 2, 0));
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glPushMatrix();
if (absolute) GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(3, 4, 0);
GLStateManager.requireExitDrawState(false);
GLStateManager.glPopMatrix();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
assertTrue(GLStateManager.finishExitCapture(state));
final Matrix4f mainTexture = new Matrix4f().translation(8, 9, 0);
GLStateManager.setTextureMatrix(0, mainTexture);
assertEquals(absolute, GLStateManager.canApplyExitState(state));
if (absolute) {
GLStateManager.applyExitState(state);
assertEquals(mainTexture, GLStateManager.getTextures().getTextureUnitMatrix(0));
}
}
@Test
void changedOrUnbalancedViewMatricesReject() {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
GLStateManager.glTranslatef(1, 0, 0);
assertFalse(GLStateManager.finishExitCapture(state));
GLStateManager.setModelViewMatrix(modelView);
GLStateManager.beginExitCapture(state);
GLStateManager.glPushMatrix();
assertFalse(GLStateManager.finishExitCapture(state));
GLStateManager.glPopMatrix();
GLStateManager.beginExitCapture(state);
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(2, 3, 4);
GLStateManager.glPopMatrix();
assertTrue(GLStateManager.finishExitCapture(state));
GLStateManager.beginExitCapture(state);
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glPopMatrix();
assertFalse(GLStateManager.finishExitCapture(state));
}
@ParameterizedTest
@ValueSource(ints = {0, 1, 2, 3, 4, 5})
void untrackedCallsAndStateQueriesReject(int operation) {
final ExitState state = new ExitState();
final int program = GLStateManager.getActiveProgram();
GLStateManager.beginExitCapture(state);
switch (operation) {
case 0 -> GLStateManager.glIsEnabled(GL11.GL_CULL_FACE);
case 1 -> GLStateManager.glGetInteger(GL11.GL_DEPTH_FUNC);
case 2 -> GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
case 3 -> GLStateManager.glUseProgram(program);
case 4 -> GLStateManager.glBlendEquation(GL14.GL_FUNC_ADD);
case 5 -> GLStateManager.fogColor((float) GLStateManager.getFogState().getFogColor().x,
(float) GLStateManager.getFogState().getFogColor().y, (float) GLStateManager.getFogState().getFogColor().z,
GLStateManager.getFogState().getFogAlpha());
default -> throw new AssertionError();
}
assertFalse(GLStateManager.finishExitCapture(state));
}
@Test
void suspendedCallsAreNeitherRecordedNorRejected() {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
final boolean suspended = GLStateManager.suspendExitCapture();
try {
assertFalse(GLStateManager.isExitCaptureActive());
GLStateManager.glIsEnabled(GL11.GL_CULL_FACE);
GLStateManager.glColor4f(0, 0, 0, 1);
BatchStateGuard.suspend();
try {
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
} finally {
BatchStateGuard.resume();
}
} finally {
GLStateManager.resumeExitCapture(suspended);
}
BatchStateGuard.suspend();
try {
assertFalse(GLStateManager.isExitCaptureActive());
GLStateManager.glGetInteger(GL11.GL_DEPTH_FUNC);
} finally {
BatchStateGuard.resume();
}
assertTrue(GLStateManager.isExitCaptureActive());
assertTrue(GLStateManager.finishExitCapture(state));
GLStateManager.glColor4f(0.5F, 1, 1, 1);
GLStateManager.applyExitState(state);
assertEquals(0.5F, GLStateManager.getColor().getRed());
}
@Test
void attribPushedWhileSuspendedRestoresThatCapturesWrites() {
final ExitState earlier = new ExitState();
GLStateManager.beginExitCapture(earlier);
GLStateManager.glColor4f(0.5F, 1, 1, 1);
GLStateManager.glPushAttrib(GL11.GL_CURRENT_BIT);
GLStateManager.glPopAttrib();
assertTrue(GLStateManager.finishExitCapture(earlier));
GLStateManager.glColor4f(0.25F, 1, 1, 1);
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
final boolean suspended = GLStateManager.suspendExitCapture();
try {
GLStateManager.glPushAttrib(GL11.GL_CURRENT_BIT);
} finally {
GLStateManager.resumeExitCapture(suspended);
}
GLStateManager.glColor4f(0, 0, 0, 1);
GLStateManager.glPopAttrib();
assertTrue(GLStateManager.finishExitCapture(state));
GLStateManager.glColor4f(0.75F, 1, 1, 1);
GLStateManager.applyExitState(state);
assertEquals(0.75F, GLStateManager.getColor().getRed(), "the pop restored color, so the capture leaves it alone");
}
@Test
void attribPoppedWhileSuspendedUndoesTheCapturesWrites() {
GLStateManager.glColor4f(0.25F, 1, 1, 1);
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
GLStateManager.glPushAttrib(GL11.GL_CURRENT_BIT);
GLStateManager.glColor4f(0, 0, 0, 1);
final boolean suspended = GLStateManager.suspendExitCapture();
try {
GLStateManager.glPopAttrib();
} finally {
GLStateManager.resumeExitCapture(suspended);
}
assertTrue(GLStateManager.finishExitCapture(state));
GLStateManager.glColor4f(0.75F, 1, 1, 1);
GLStateManager.applyExitState(state);
assertEquals(0.75F, GLStateManager.getColor().getRed(), "the pop restored color, so the capture leaves it alone");
}
@Test
void separateAndReusedCapturesKeepOnlyTheirOwnWrites() {
final ExitState first = new ExitState();
final ExitState second = new ExitState();
GLStateManager.beginExitCapture(first);
GLStateManager.glColor4f(0.25F, 1, 1, 1);
GLStateManager.setOverlayColor(0.1F, 0.2F, 0.3F, 0.4F);
assertTrue(GLStateManager.finishExitCapture(first));
GLStateManager.beginExitCapture(second);
GLStateManager.glColor4f(0.75F, 1, 1, 1);
assertTrue(GLStateManager.finishExitCapture(second));
GLStateManager.applyExitState(first);
assertEquals(0.25F, GLStateManager.getColor().getRed());
assertEquals(0.4F, GLStateManager.getOverlayA());
GLStateManager.applyExitState(second);
assertEquals(0.75F, GLStateManager.getColor().getRed());
first.clear();
assertFalse(GLStateManager.canApplyExitState(first));
GLStateManager.beginExitCapture(first);
assertTrue(GLStateManager.finishExitCapture(first));
GLStateManager.glColor4f(0.5F, 1, 1, 1);
GLStateManager.applyExitState(first);
assertEquals(0.5F, GLStateManager.getColor().getRed());
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void toggledColorMaterialKeepsItsMaterialWritesUnlessAnAttribPopUndoesThem(boolean scoped) {
GLStateManager.enableColorMaterial();
GLStateManager.disableColorMaterial();
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
GLStateManager.glColor4f(1, 1, 1, 1);
if (scoped) GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
GLStateManager.enableColorMaterial();
GLStateManager.disableColorMaterial();
if (scoped) GLStateManager.glPopAttrib();
assertTrue(GLStateManager.finishExitCapture(state));
final var changed = BufferUtils.createFloatBuffer(4);
changed.put(new float[]{0.2F, 0.3F, 0.4F, 1}).flip();
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE, changed);
assertTrue(GLStateManager.canApplyExitState(state));
GLStateManager.applyExitState(state);
assertEquals(scoped ? 0.2F : 1, GLStateManager.getFrontMaterial().ambient.x);
assertEquals(scoped ? 0.2F : 1, GLStateManager.getBackMaterial().diffuse.x);
assertFalse(GLStateManager.getColorMaterial().isEnabled());
}
@Test
void colorMaterialSettingsTheCaptureNeverTouchedAreLeftAlone() {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
GLStateManager.glColor4f(1, 1, 1, 1);
assertTrue(GLStateManager.finishExitCapture(state));
GLStateManager.glColorMaterial(GL11.GL_BACK, GL11.GL_EMISSION);
GLStateManager.enableColorMaterial();
assertTrue(GLStateManager.canApplyExitState(state));
GLStateManager.applyExitState(state);
assertTrue(GLStateManager.getColorMaterial().isEnabled());
assertEquals(GL11.GL_BACK, GLStateManager.getColorMaterialFace().getValue());
assertEquals(GL11.GL_EMISSION, GLStateManager.getColorMaterialParameter().getValue());
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void colorMaterialFaceMustMatchOnlyWhenTheCaptureDidNotSetIt(boolean establishesContext) {
GLStateManager.glColorMaterial(GL11.GL_FRONT, GL11.GL_DIFFUSE);
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
GLStateManager.glColor4f(1, 1, 1, 1);
if (establishesContext) GLStateManager.glColorMaterial(GL11.GL_FRONT, GL11.GL_DIFFUSE);
GLStateManager.enableColorMaterial();
GLStateManager.disableColorMaterial();
assertTrue(GLStateManager.finishExitCapture(state));
GLStateManager.glColorMaterial(GL11.GL_BACK, GL11.GL_EMISSION);
assertEquals(establishesContext, GLStateManager.canApplyExitState(state));
}
@Test
void writesUnderALockedIrisOverrideRecordAndApplyTheVanillaValues() {
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.getBlendMode().beforeModify();
GLStateManager.getBlendState().beforeModify();
final LockedBlend lock = new LockedBlend();
GLSMHooks.blendHandler = lock;
GLStateManager.getBlendMode().setVanillaLayer(lock);
GLStateManager.getBlendState().setVanillaLayer(lock);
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
assertTrue(GLStateManager.finishExitCapture(state));
lock.enabled = false;
lock.blend.setAll(GL11.GL_DST_COLOR, GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE);
assertTrue(GLStateManager.canApplyExitState(state));
GLStateManager.applyExitState(state);
assertTrue(lock.enabled);
assertEquals(GL11.GL_SRC_ALPHA, lock.blend.getSrcRgb());
assertFalse(GLStateManager.getBlendMode().isEnabled());
assertEquals(GL11.GL_ONE, GLStateManager.getBlendState().getSrcRgb());
assertEquals(GL11.GL_ZERO, GLStateManager.getBlendState().getDstRgb());
}
private static final class LockedBlend implements DeferredBlendHandler, VanillaBooleanLayer, VanillaStateLayer<BlendState> {
boolean enabled = true;
final BlendState blend = new BlendState(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
@Override public boolean isBlendLocked() { return true; }
@Override public boolean isOverrideHeld() { return true; }
@Override public void deferBlendModeToggle(boolean value) { enabled = value; }
@Override public void deferBlendFunc(int sr, int dr, int sa, int da) { blend.setAll(sr, dr, sa, da); }
@Override public void flushDeferredBlend() {}
@Override public boolean getVanilla() { return enabled; }
@Override public void setVanilla(boolean value) { enabled = value; }
@Override public void readVanilla(BlendState into) { into.set(blend); }
@Override public void writeVanilla(BlendState from) { blend.set(from); }
}
private static final class FaceNormals implements ImmediateExtendedAttribHandler {
@Override public boolean wantsExtended() { return false; }
@Override public boolean wantsTangent() { return false; }
@Override public boolean wantsEntityIds() { return false; }
@Override public boolean wantsExtendedCapture() { return false; }
@Override public boolean wantsFaceNormals() { return true; }
@Override public void writeFaceNormals(int[] data, int count) {
for (int i = 0; i < count; i++) data[i * 8 + 6] = 0x7F;
}
@Override public void build(int[] data, int count, int primitive, int normal, long destination, int stride) {}
@Override public void buildPacked(long source, int stride, int position, int texture, int normal,
int count, int primitive, long destination, int destinationStride) {}
}
}
@@ -1,208 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.hooks.TextureUtilHooks;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@GLCoreTest
class GLSM_ExitTextureFilter_GLTest {
private int outer, texture, otherTexture, savedMin, savedMag;
private float savedAniso;
private boolean failOperation;
@BeforeEach
void setUp() {
outer = GLStateManager.pushState(StateSet.forMask(GL11.GL_TEXTURE_BIT));
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
texture = GLStateManager.glGenTextures();
otherTexture = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, otherTexture);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR_MIPMAP_LINEAR);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
}
@AfterEach
void tearDown() {
GLStateManager.cancelExitCapture();
TextureUtilHooks.clearSavedFilterTexture();
GLStateManager.glDeleteTextures(texture);
GLStateManager.glDeleteTextures(otherTexture);
GLStateManager.popStateTo(outer);
}
@Test
void nearestFilterSaveAndRestoreIsAllowedAndApplyLeavesFiltersAlone() {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
assertTrue(save(false, false, 1));
assertTrue(GLStateManager.isExitCaptureActive());
GLStateManager.requireExitTextureFilter(otherTexture, false);
GLStateManager.requireExitTextureFilter(texture, true);
restore();
assertTrue(GLStateManager.finishExitCapture(state));
assertFilters(GL11.GL_LINEAR_MIPMAP_LINEAR, GL11.GL_LINEAR);
assertEquals(0, GLStateManager.ctx().savedFilterTextureId);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
assertTrue(GLStateManager.canApplyExitState(state));
GLStateManager.applyExitState(state);
assertFilters(GL11.GL_LINEAR, GL11.GL_NEAREST);
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void drawingTheTemporarilyFilteredTextureRequiresNearestFiltering(boolean nearest) {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
save(false, false, 1);
GLStateManager.requireExitTextureFilter(texture, nearest);
restore();
assertEquals(nearest, GLStateManager.finishExitCapture(state));
}
@ParameterizedTest
@ValueSource(ints = {0, 1, 2})
void savingAnythingButNearestFilteringRejects(int request) {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
assertFalse(save(request == 0, request == 1, request == 2 ? 2 : 1));
restore();
assertFalse(GLStateManager.finishExitCapture(state));
}
@ParameterizedTest
@ValueSource(ints = {0, 1, 2})
void textureParameterReadsAndWritesStillReject(int operation) {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
save(false, false, 1);
switch (operation) {
case 0 -> GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
case 1 -> GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
case 2 -> GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
default -> throw new AssertionError();
}
restore();
assertFalse(GLStateManager.finishExitCapture(state));
}
@Test
void missingRestoreRejectsAndDoesNotAffectTheNextCapture() {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
save(false, false, 1);
assertFalse(GLStateManager.finishExitCapture(state));
restore();
GLStateManager.beginExitCapture(state);
assertTrue(GLStateManager.finishExitCapture(state));
}
@Test
void secondSaveBeforeRestoreRejects() {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
assertTrue(save(false, false, 1));
assertFalse(save(false, false, 1));
restore();
assertFalse(GLStateManager.finishExitCapture(state));
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void restoreMustUseTheSameTextureAndOriginalParameters(boolean wrongTexture) {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
save(false, false, 1);
if (wrongTexture) GLStateManager.ctx().savedFilterTextureId = otherTexture;
else savedMin = GL11.GL_NEAREST;
restore();
assertFalse(GLStateManager.finishExitCapture(state));
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void failedSaveOrRestoreRejectsAndTheNextCaptureStillWorks(boolean restoring) {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
if (restoring) save(false, false, 1);
failOperation = true;
assertThrows(IllegalStateException.class, () -> {
if (restoring) restore();
else save(false, false, 1);
});
assertFalse(GLStateManager.finishExitCapture(state));
TextureUtilHooks.clearSavedFilterTexture();
GLStateManager.beginExitCapture(state);
assertTrue(GLStateManager.isExitCaptureActive());
assertTrue(GLStateManager.finishExitCapture(state));
}
@Test
void openFilterSaveBlocksCapturingAndApplying() {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
assertTrue(GLStateManager.finishExitCapture(state));
save(false, false, 1);
assertFalse(GLStateManager.canApplyExitState(state));
final ExitState nested = new ExitState();
GLStateManager.beginExitCapture(nested);
assertFalse(GLStateManager.finishExitCapture(nested));
restore();
assertTrue(GLStateManager.canApplyExitState(state));
}
private boolean save(boolean blurred, boolean mipmap, float aniso) {
final boolean captured = TextureUtilHooks.beginFilterSave(blurred, mipmap, aniso);
try {
savedMin = GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
savedMag = GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER);
savedAniso = GLStateManager.glGetTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
blurred ? (mipmap ? GL11.GL_LINEAR_MIPMAP_LINEAR : GL11.GL_LINEAR)
: (mipmap ? GL11.GL_NEAREST_MIPMAP_LINEAR : GL11.GL_NEAREST));
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, blurred ? GL11.GL_LINEAR : GL11.GL_NEAREST);
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
if (failOperation) throw new IllegalStateException("filter save failed");
} catch (RuntimeException | Error failure) {
TextureUtilHooks.filterFailed(captured);
throw failure;
} finally {
TextureUtilHooks.endFilterSave(captured);
}
return captured;
}
private void restore() {
final boolean captured = TextureUtilHooks.beginFilterRestore();
try {
TextureUtilHooks.restoreFilter(savedMin, savedMag);
TextureUtilHooks.restoreAniso(savedAniso);
if (failOperation) throw new IllegalStateException("filter restore failed");
} catch (RuntimeException | Error failure) {
TextureUtilHooks.filterFailed(captured);
throw failure;
} finally {
TextureUtilHooks.endFilterRestore(captured);
}
}
private void assertFilters(int min, int mag) {
final var info = TextureInfoCache.INSTANCE.getInfo(texture);
assertEquals(min, info.getMinFilter());
assertEquals(mag, info.getMagFilter());
}
}
@@ -179,10 +179,6 @@ public final class FfpFixture {
}
public static void assertPixelParity(String subject, int[] reference, int[] candidate, int size, int background, int minCoverageDivisor, int channelTolerance, IntFunction<String> describe) {
assertPixelParity(subject, reference, candidate, size, background, minCoverageDivisor, channelTolerance, 0, describe);
}
public static void assertPixelParity(String subject, int[] reference, int[] candidate, int size, int background, int minCoverageDivisor, int channelTolerance, int allowedMismatches, IntFunction<String> describe) {
int covered = 0;
for (int pixel : reference) {
if (pixel != background) covered++;
@@ -200,7 +196,7 @@ public final class FfpFixture {
}
final int index = firstMismatch;
final int differing = mismatches;
assertTrue(mismatches <= allowedMismatches, () -> subject + " differ from baked quads in " + differing + " of " + reference.length + " pixels, first at (" + (index % size) + "," + (index / size) + "): reference " + describe.apply(reference[index]) + " instanced " + describe.apply(candidate[index]));
assertEquals(0, mismatches, () -> subject + " differ from baked quads in " + differing + " of " + reference.length + " pixels, first at (" + (index % size) + "," + (index / size) + "): reference " + describe.apply(reference[index]) + " instanced " + describe.apply(candidate[index]));
}
private static boolean withinTolerance(int a, int b, int tolerance) {
@@ -0,0 +1,25 @@
The menu blur in this package is based on [Blur](https://github.com/tterrag1098/Blur), used under the following license:
```
MIT License
Copyright (c) 2017 tterrag
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
@@ -0,0 +1,195 @@
package com.gtnewhorizons.angelica.blur;
import com.gtnewhorizons.angelica.client.rendering.GlUniformFloat2v;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.render.PanoramaRenderer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.shader.Framebuffer;
import org.apache.commons.lang3.ArrayUtils;
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
import org.embeddedt.embeddium.impl.gl.shader.ShaderBindingContext;
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
/**
* Blurs the world behind open GUIs and fades in a configurable background gradient.
* Based on Blur by tterrag (https://github.com/tterrag1098/Blur) under the MIT license, see LICENSE-Blur.md.
* Reimplemented on top of Angelica's own blur shader instead of a vanilla post-processing shader.
*/
public class MenuBlur {
private static final Minecraft mc = Minecraft.getMinecraft();
private static GlProgram<BlurUniforms> blurProgram;
private static StateSet allState;
private static int emptyVao;
// The blur runs on half resolution copies of the screen, which is much cheaper and looks the same once blurred
private static int horizontalFbo, horizontalTexture, verticalFbo, verticalTexture;
private static int sourceWidth, sourceHeight, halfWidth, halfHeight;
private static boolean blurring;
private static long fadeStartMillis;
/** Called right after the world has been drawn, before the HUD and the open GUI. */
public static void renderBlur() {
final boolean shouldBlur = shouldBlur();
if (shouldBlur && !blurring) {
fadeStartMillis = System.currentTimeMillis();
}
blurring = shouldBlur;
if (!blurring) return;
// The radius grows with the fade, so the blur gets stronger over the first moments
final float radius = (float) Math.floor(AngelicaConfig.menuBlurRadius * getProgress());
if (radius < 1.0F) return;
final Framebuffer mainFramebuffer = mc.getFramebuffer();
if (blurProgram == null) init();
final int previousProgram = GLStateManager.getActiveProgram();
final int previousVao = GLStateManager.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING);
final int previousFilter = mainFramebuffer.framebufferFilter;
final int stateDepth = GLStateManager.pushState(allState);
try {
ensureBlurTargets(mainFramebuffer.framebufferWidth, mainFramebuffer.framebufferHeight);
GLStateManager.disableBlend();
GLStateManager.disableDepthTest();
GLStateManager.glDepthMask(false);
GLStateManager.glColorMask(true, true, true, true);
GLStateManager.disableCull();
GLStateManager.disableScissorTest();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
blurProgram.bind();
GLStateManager.glBindVertexArray(emptyVao);
final BlurUniforms uniforms = blurProgram.getInterface();
// Each half resolution pixel covers two screen pixels, so half the radius gives the same strength
uniforms.radius.setFloat(radius * 0.5F);
// Horizontal pass, shrinking the screen to half size. Linear filtering makes every
// half size pixel average the four screen pixels it covers instead of picking one.
mainFramebuffer.setFramebufferFilter(GL11.GL_LINEAR);
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, horizontalFbo);
GLStateManager.glViewport(0, 0, halfWidth, halfHeight);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, mainFramebuffer.framebufferTexture);
uniforms.direction.set(1.0F / halfWidth, 0.0F);
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
// Vertical pass, still at half size
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, verticalFbo);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, horizontalTexture);
uniforms.direction.set(0.0F, 1.0F / halfHeight);
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
// Stretch the result back over the screen. A radius of 0 makes the blur shader a plain copy.
mainFramebuffer.bindFramebuffer(true);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, verticalTexture);
uniforms.radius.setFloat(0.0F);
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
} finally {
mainFramebuffer.setFramebufferFilter(previousFilter);
GLStateManager.glBindVertexArray(previousVao);
GLStateManager.glUseProgram(previousProgram);
GLStateManager.popStateTo(stateDepth);
mainFramebuffer.bindFramebuffer(true);
}
}
/** Replaces one of the two vanilla GUI background gradient colors while the blur is showing. */
public static int getBackgroundColor(int vanillaColor, String configuredColor) {
if (!blurring) return vanillaColor;
final int color;
try {
color = Integer.parseUnsignedInt(configuredColor, 16);
} catch (NumberFormatException e) {
return vanillaColor;
}
final float progress = getProgress();
final int alpha = (int) ((color >>> 24) * progress);
final int red = (int) ((color >> 16 & 0xFF) * progress);
final int green = (int) ((color >> 8 & 0xFF) * progress);
final int blue = (int) ((color & 0xFF) * progress);
return alpha << 24 | red << 16 | green << 8 | blue;
}
private static boolean shouldBlur() {
return AngelicaConfig.enableMenuBlur
&& mc.theWorld != null
&& mc.currentScreen != null
&& !ArrayUtils.contains(AngelicaConfig.menuBlurExcludedGuis, mc.currentScreen.getClass().getName());
}
private static float getProgress() {
if (AngelicaConfig.menuBlurFadeTime <= 0) return 1.0F;
return Math.min((System.currentTimeMillis() - fadeStartMillis) / (float) AngelicaConfig.menuBlurFadeTime, 1.0F);
}
private static void init() {
blurProgram = PanoramaRenderer.loadProgram("angelica:menu_blur", "angelica:panorama_blur", "angelica:panorama_blur", BlurUniforms::new);
allState = StateSet.forMask(GL11.GL_ALL_ATTRIB_BITS);
emptyVao = GLStateManager.glGenVertexArrays();
}
private static void ensureBlurTargets(int width, int height) {
if (horizontalFbo != 0 && width == sourceWidth && height == sourceHeight) return;
if (horizontalFbo != 0) {
GLStateManager.glDeleteFramebuffers(horizontalFbo);
GLStateManager.glDeleteFramebuffers(verticalFbo);
GLStateManager.glDeleteTextures(horizontalTexture);
GLStateManager.glDeleteTextures(verticalTexture);
}
sourceWidth = width;
sourceHeight = height;
halfWidth = Math.max((width + 1) / 2, 1);
halfHeight = Math.max((height + 1) / 2, 1);
horizontalTexture = createTexture(halfWidth, halfHeight);
horizontalFbo = createFramebuffer(horizontalTexture);
verticalTexture = createTexture(halfWidth, halfHeight);
verticalFbo = createFramebuffer(verticalTexture);
}
private static int createTexture(int width, int height) {
final int texture = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
return texture;
}
private static int createFramebuffer(int colorTexture) {
final int fbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, colorTexture, 0);
final int status = GLStateManager.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER);
if (status != GL30.GL_FRAMEBUFFER_COMPLETE) {
throw new RuntimeException("Menu blur FBO incomplete: 0x" + Integer.toHexString(status));
}
return fbo;
}
private static class BlurUniforms {
final GlUniformFloat2v direction;
final GlUniformFloat radius;
BlurUniforms(ShaderBindingContext ctx) {
direction = ctx.bindUniform("u_Direction", GlUniformFloat2v::new);
radius = ctx.bindUniform("u_Radius", GlUniformFloat::new);
}
}
}
@@ -15,7 +15,6 @@ import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
import com.gtnewhorizons.angelica.rendering.tesr.BatchDrawDefaults;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
@@ -1252,7 +1251,6 @@ public class BatchingFontRenderer {
if (string == null || string.length() == 0) {
return anchorX + (enableShadow ? 1.0f : 0.0f);
}
EntityShadowReplay.rejectForeignDraw();
final int color = buttonSectionActive
? DarkModeUtils.recolorButtonText(inputColor, buttonEnabledColor, buttonHoveredColor, buttonDisabledColor)
: inputColor;
@@ -167,20 +167,6 @@ public class AngelicaConfig {
@Config.DefaultBoolean(true)
public static boolean enableEntityTextureAtlas;
@Config.Comment("Replay entity draws recorded in the shader shadow pass in the main view instead of running each entity renderer again (experimental, requires entity batching)")
@Config.DefaultBoolean(false)
@Config.RequiresMcRestart
public static boolean enableEntityShadowReplay;
@Config.Comment("Entity classes that run their renderer again in the main view instead of replaying shadow pass draws. Players are always excluded.")
@Config.DefaultStringList({
"chylex.hee.entity.boss.EntityBossDragon",
"twilightforest.entity.passive.EntityTFMobileFirefly",
"gregtech.common.entity.EntityDrone"
})
@Config.RequiresMcRestart
public static String[] entityShadowReplayExcludedClasses;
@Config.Comment("Skip the end-of-frame shader buffer copy by ping-ponging buffers. Disable if a shader pack misrenders.")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
@@ -290,6 +276,32 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean enableZoom;
@Config.Comment("Blur the world behind open GUIs.")
@Config.DefaultBoolean(false)
public static boolean enableMenuBlur;
@Config.Comment("How strong the menu blur is, in pixels.")
@Config.DefaultInt(12)
@Config.RangeInt(min = 1, max = 100)
public static int menuBlurRadius;
@Config.Comment("How long the menu blur takes to fade in, in milliseconds.")
@Config.DefaultInt(200)
@Config.RangeInt(min = 0, max = 2000)
public static int menuBlurFadeTime;
@Config.Comment("GUI classes that never get the menu blur.")
@Config.DefaultStringList({"net.minecraft.client.gui.GuiChat", "net.coderbot.iris.gui.screen.ShaderPackScreen"})
public static String[] menuBlurExcludedGuis;
@Config.Comment("Top color of the GUI background gradient while the menu blur is showing, as ARGB hex.")
@Config.DefaultString("75000000")
public static String menuBlurGradientTopColor;
@Config.Comment("Bottom color of the GUI background gradient while the menu blur is showing, as ARGB hex.")
@Config.DefaultString("75000000")
public static String menuBlurGradientBottomColor;
@Config.Comment("Optimizes in-world item rendering")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
@@ -562,10 +574,6 @@ public class AngelicaConfig {
return enableEntityBatching && enableCubeInstancing;
}
public static boolean entityShadowReplayEnabled() {
return enableEntityBatching && enableEntityShadowReplay;
}
public static boolean sdlGpuConfigured() {
return renderBackend == RenderBackendChoice.SDL_GPU;
}
@@ -67,15 +67,8 @@ public class IrisGLSMBridge {
public static void installImmediateExtendedHandler() {
GLSMHooks.immediateExtendedHandler = new ImmediateExtendedAttribs();
final CapturedRenderingState state = CapturedRenderingState.INSTANCE;
state.setEntityIdsListener(() -> {
final boolean suspended = GLStateManager.suspendExitCapture();
try {
GLStateManager.setEntityIds(
state.getCurrentRenderedEntity(), state.getCurrentRenderedBlockEntity(), state.getCurrentRenderedItem());
} finally {
GLStateManager.resumeExitCapture(suspended);
}
});
state.setEntityIdsListener(() -> GLStateManager.setEntityIds(
state.getCurrentRenderedEntity(), state.getCurrentRenderedBlockEntity(), state.getCurrentRenderedItem()));
}
public static void installPostTransformHook() {
@@ -168,11 +161,6 @@ public class IrisGLSMBridge {
return Iris.enabled && AlphaTestStorage.isAlphaTestLocked();
}
@Override
public boolean isOverrideHeld() {
return Iris.enabled && AlphaTestStorage.isOverrideHeld();
}
@Override
public boolean deferAlphaTestToggle(boolean enabled) {
return AlphaTestStorage.deferAlphaTestToggle(enabled);
@@ -207,15 +207,6 @@ public enum Mixins implements IMixins {
)
),
ANGELICA_ENTITY_SHADOW_REPLAY(new MixinBuilder("Replay entity draws recorded in the shader shadow pass in the main view instead of running each entity renderer again")
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enableIris && AngelicaConfig.entityShadowReplayEnabled())
.addClientMixins(
"angelica.entity.MixinBossStatus_EntityReplay",
"angelica.entity.MixinRenderGlobal_EntityReplay"
)
),
ANGELICA_CUBE_INSTANCING(new MixinBuilder("Capture cuboid parameters so model cubes can be drawn from a shared unit cube")
.setPhase(Phase.EARLY)
.setApplyIf(AngelicaConfig::cubeInstancingEnabled)
@@ -626,6 +617,14 @@ public enum Mixins implements IMixins {
"angelica.zoom.MixinMouseFilter"
)),
ANGELICA_MENU_BLUR(new MixinBuilder("Blur the world behind open GUIs")
.setPhase(Phase.EARLY)
.addExcludedMod(TargetedMod.BLUR)
.addClientMixins(
"angelica.blur.MixinEntityRenderer_MenuBlur",
"angelica.blur.MixinGuiScreen_MenuBlur"
)),
ANGELICA_FPS_REDUCER(new MixinBuilder("Reduce frame rate and volume while unfocused, minimized, or idle")
.setPhase(Phase.EARLY)
.addClientMixins(
@@ -9,6 +9,7 @@ public enum TargetedMod implements ITargetMod {
ARCHAICFIX("org.embeddedt.archaicfix.ArchaicCore", "archaicfix"),
BACKHAND("xonin.backhand.coremod.BackhandLoadingPlugin", "backhand"),
BLUR("com.tterrag.blur.BlurPlugin", "blur"),
CAMPFIRE_BACKPORT(null, "campfirebackport"),
CHISEL(null, "chisel"),
COFHCORE( "cofh.asm.LoadingPlugin", "CoFHCore"),
@@ -1,7 +1,6 @@
package com.gtnewhorizons.angelica.rendering;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
import lombok.Getter;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.uniforms.CapturedRenderingState;
@@ -9,8 +8,6 @@ import net.coderbot.iris.uniforms.EntityIdHelper;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.entity.RendererLivingEntity;
import net.minecraft.entity.EntityLivingBase;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
@@ -40,7 +37,6 @@ public class DeferredEntityOverlay {
private static final List<DeferredEntry> pool = new ArrayList<>(INITIAL_CAPACITY);
private static final FloatBuffer MATRIX_BUF = BufferUtils.createFloatBuffer(16);
private static final Matrix4f replayMatrix = new Matrix4f();
// Set by the shouldRenderPass inject, consumed by the doRender WrapOperation.
@Getter
@@ -72,9 +68,6 @@ public class DeferredEntityOverlay {
public static void clearStaleOverlayFlag() {
overlayPassActive = false;
pendingShouldRenderPass = null;
pendingRenderer = null;
pendingEntity = null;
}
/**
@@ -83,23 +76,21 @@ public class DeferredEntityOverlay {
*/
public static void deferRender(float limbSwing, float limbSwingAmount, float ageInTicks,
float headYaw, float headPitch, float scale) {
overlayPassActive = false;
DeferredEntry entry = acquireEntry();
entry.set(pendingShouldRenderPass, pendingRenderer, pendingEntity, pendingPartialTick,
limbSwing, limbSwingAmount, ageInTicks, headYaw, headPitch, scale);
GLStateManager.getModelViewMatrix().get(entry.matrix);
MATRIX_BUF.clear();
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, MATRIX_BUF);
MATRIX_BUF.get(entry.matrix);
deferred.add(entry);
EntityShadowReplay.recordDeferredOverlay(entry);
clearStaleOverlayFlag();
}
public static void queueReplay(DeferredEntry captured, Matrix4fc mainFromShadow) {
final DeferredEntry entry = acquireEntry();
entry.set(captured);
replayMatrix.set(captured.matrix).mulLocal(mainFromShadow).get(entry.matrix);
deferred.add(entry);
pendingShouldRenderPass = null;
pendingRenderer = null;
pendingEntity = null;
}
private static DeferredEntry acquireEntry() {
@@ -112,7 +103,9 @@ public class DeferredEntityOverlay {
private static void recycle() {
for (DeferredEntry entry : deferred) {
entry.clear();
entry.shouldRenderPass = null;
entry.renderer = null;
entry.entity = null;
pool.add(entry);
}
deferred.clear();
@@ -120,7 +113,7 @@ public class DeferredEntityOverlay {
public static void clear() {
recycle();
clearStaleOverlayFlag();
overlayPassActive = false;
replaying = false;
}
@@ -145,7 +138,7 @@ public class DeferredEntityOverlay {
// Restore the entity's lightmap coords
int brightness = entity.getBrightnessForRender(entry.partialTick);
GLStateManager.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit,
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit,
brightness % 65536, (float) brightness / 65536);
// Restore the saved modelview matrix
@@ -190,7 +183,7 @@ public class DeferredEntityOverlay {
}
}
public static final class DeferredEntry {
private static class DeferredEntry {
final float[] matrix = new float[16];
ShouldRenderPassFn shouldRenderPass;
RendererLivingEntity renderer;
@@ -203,22 +196,6 @@ public class DeferredEntityOverlay {
float headPitch;
float scale;
public void copyFrom(DeferredEntry source) {
set(source);
System.arraycopy(source.matrix, 0, matrix, 0, matrix.length);
}
public void clear() {
shouldRenderPass = null;
renderer = null;
entity = null;
}
private void set(DeferredEntry source) {
set(source.shouldRenderPass, source.renderer, source.entity, source.partialTick,
source.limbSwing, source.limbSwingAmount, source.ageInTicks, source.headYaw, source.headPitch, source.scale);
}
void set(ShouldRenderPassFn shouldRenderPass, RendererLivingEntity renderer,
EntityLivingBase entity, float partialTick,
float limbSwing, float limbSwingAmount, float ageInTicks,
@@ -32,23 +32,22 @@ public final class SkippedGlintBlock {
}
private static void applyExitState(float red, float green, float blue, float alpha, int srcRgb, int dstRgb, int srcAlpha, int dstAlpha, boolean resetTextureMatrix, boolean restoreDepthMask) {
final boolean recording = GLStateManager.isExitCaptureActive();
final Color4 color = GLStateManager.getColor();
if (recording || color.getRed() != red || color.getGreen() != green || color.getBlue() != blue || color.getAlpha() != alpha) {
if (color.getRed() != red || color.getGreen() != green || color.getBlue() != blue || color.getAlpha() != alpha) {
GLStateManager.glColor4f(red, green, blue, alpha);
}
if (resetTextureMatrix && (recording || !MatrixHelper.isIdentity(GLStateManager.getTextures().getTextureUnitMatrix(0)))) {
if (resetTextureMatrix && !MatrixHelper.isIdentity(GLStateManager.getTextures().getTextureUnitMatrix(0))) {
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
}
if (recording || GLStateManager.getMatrixMode().getMode() != GL11.GL_MODELVIEW) GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
if (restoreDepthMask && (recording || !GLStateManager.isEffectiveDepthMaskEnabled())) GLStateManager.glDepthMask(true);
if (recording || !GLStateManager.getLightingState().isEnabled()) GLStateManager.glEnable(GL11.GL_LIGHTING);
if (GLStateManager.getMatrixMode().getMode() != GL11.GL_MODELVIEW) GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
if (restoreDepthMask && !GLStateManager.isEffectiveDepthMaskEnabled()) GLStateManager.glDepthMask(true);
if (!GLStateManager.getLightingState().isEnabled()) GLStateManager.glEnable(GL11.GL_LIGHTING);
GLStateManager.getEffectiveBlendState(blend);
if (recording || GLStateManager.getBlendState().isFuncUnknown() || blend.getSrcRgb() != srcRgb || blend.getDstRgb() != dstRgb || blend.getSrcAlpha() != srcAlpha || blend.getDstAlpha() != dstAlpha) {
if (GLStateManager.getBlendState().isFuncUnknown() || blend.getSrcRgb() != srcRgb || blend.getDstRgb() != dstRgb || blend.getSrcAlpha() != srcAlpha || blend.getDstAlpha() != dstAlpha) {
GLStateManager.tryBlendFuncSeparate(srcRgb, dstRgb, srcAlpha, dstAlpha);
}
if (recording || GLStateManager.isEffectiveBlendEnabled()) GLStateManager.glDisable(GL11.GL_BLEND);
if (recording || GLStateManager.getDepthState().getFunc() != GL11.GL_LEQUAL) GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
if (GLStateManager.isEffectiveBlendEnabled()) GLStateManager.glDisable(GL11.GL_BLEND);
if (GLStateManager.getDepthState().getFunc() != GL11.GL_LEQUAL) GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
}
}
@@ -13,7 +13,6 @@ import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
import com.gtnewhorizons.angelica.rendering.tesr.EntityMaterials;
import com.gtnewhorizons.angelica.rendering.tesr.GlintCapture;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
import com.gtnewhorizons.angelica.rendering.tesr.TemplateBuffer;
import com.gtnewhorizons.angelica.utils.AnimationsRenderUtils;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
@@ -150,7 +149,6 @@ public final class DroppedItemInstancer {
}
final boolean allowed = BatchEligibility.batchingAllowed();
if (allowed && ModelPartBatcher.INSTANCE.isShadowPass()) {
EntityShadowReplay.rejectShadowOnly();
BatchEligibility.onPartQueued();
return SKIP;
}
@@ -115,7 +115,6 @@ public final class BatchEligibility {
static boolean insideRenderer() { return depth != 0; }
static void onUncapturedState() {
EntityShadowReplay.rejectUncapturedState();
if (depth == 0) return;
uncapturedState = true;
allowed = false;
@@ -126,7 +125,6 @@ public final class BatchEligibility {
}
static void onBail(ModelPartBatcher.BailReason reason) {
EntityShadowReplay.rejectBatcherBail();
if (depth == 0 || !allowed) return;
if (reason == ModelPartBatcher.BailReason.INACTIVE) {
allowed = false;
@@ -140,7 +138,6 @@ public final class BatchEligibility {
}
public static void onPartFallback(long drawsBefore, long drawsNow) {
EntityShadowReplay.rejectPartFallback();
parts++;
if (!allowed) {
expectedDraws += drawsNow - drawsBefore;
@@ -44,7 +44,6 @@ public final class EntityMaterials {
}
static TesrMaterial fromCurrentState(boolean texIdentity) {
GLStateManager.requireExitDrawState(true);
final boolean textured = GLStateManager.getTextures().getTextureUnitStates(0).isEnabled();
final AlphaState alpha = GLStateManager.getEffectiveAlphaState(effectiveAlpha);
final ColorMask colorMask = GLStateManager.getEffectiveColorMask(effectiveColorMask);
@@ -59,7 +58,6 @@ public final class EntityMaterials {
}
public static TesrMaterial itemFromCurrentState(boolean unfilteredAtlas) {
GLStateManager.requireExitDrawState(true);
final BlendState blend = GLStateManager.getEffectiveBlendState(effectiveBlend);
final AlphaState alpha = GLStateManager.getEffectiveAlphaState(effectiveAlpha);
return itemFromState(unfilteredAtlas, GLStateManager.isEffectiveBlendEnabled(), blend.getSrcRgb(), blend.getDstRgb(),
@@ -1,387 +0,0 @@
package com.gtnewhorizons.angelica.rendering.tesr;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil;
import com.gtnewhorizons.angelica.api.tesr.TesrMaterial;
import com.gtnewhorizons.angelica.compat.mojang.RenderLayer;
import com.gtnewhorizons.angelica.glsm.ffp.CubeParams;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.rendering.DeferredEntityOverlay;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay.Rejection;
import com.gtnewhorizons.angelica.rendering.tesr.RetainedTesrGroups.InstanceColumns;
import com.gtnewhorizons.angelica.rendering.tesr.RetainedTesrGroups.InstanceRange;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import net.coderbot.iris.layer.PassOverride;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.minecraft.util.ResourceLocation;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.joml.Vector4fc;
import java.util.Objects;
import static com.gtnewhorizons.angelica.rendering.items.ItemGlint.canResetStencil;
/**
* Ordered, pooled submissions captured before the main entity pass.
*/
final class EntityRenderCommands {
private final ObjectArrayList<Command> commands = new ObjectArrayList<>();
private final Pool<Model> models = new Pool<>(Model::new, Model::clear);
private final Pool<ModelGlint> modelGlints = new Pool<>(ModelGlint::new, ModelGlint::clear);
private final Pool<ItemGlint> itemGlints = new Pool<>(ItemGlint::new, ItemGlint::clear);
private final Pool<Stream> streams = new Pool<>(Stream::new, Stream::clear);
private final Pool<Overlay> overlays = new Pool<>(Overlay::new, Overlay::clear);
private final Pool<InstanceRange> ranges = new Pool<>(InstanceRange::new, InstanceRange::clear);
private final Matrix4f glintPose = new Matrix4f();
long sharedParts, sharedRanges, streamEntries;
private int mergeStart;
int size() {
return commands.size();
}
int split() {
mergeStart = size();
return mergeStart;
}
Model model(int recordStart, InstanceColumns source, RenderLayer layer, DrawInputs inputs, int entityId, boolean cubesInsideTexture, Matrix4fc modelView, Matrix4fc texMatrix) {
if (source != null && size() > recordStart && size() > mergeStart && commands.get(size() - 1) instanceof Model previous) {
if (previous.source == source && previous.sourceVersion == source.version && previous.end == source.size - 1 && previous.shadowLayer == layer && previous.inputs.matches(inputs) && previous.entityId == entityId
&& previous.cubesInsideTexture == cubesInsideTexture && previous.glint == null && (texMatrix == null ? !previous.hasTextureMatrix : previous.hasTextureMatrix && previous.textureMatrix.equals(texMatrix))) {
previous.end = source.size;
return null;
}
}
final Model model = models.acquire();
commands.add(model);
model.inputs.copyFrom(inputs);
model.entityId = entityId;
model.cubesInsideTexture = cubesInsideTexture;
model.hasTextureMatrix = texMatrix != null;
if (texMatrix != null) model.textureMatrix.set(texMatrix);
model.source = source;
model.shadowLayer = layer;
if (source == null) {
model.modelView.set(modelView);
} else {
model.sourceVersion = source.version;
model.start = source.size - 1;
model.end = source.size;
}
return model;
}
ModelGlint modelGlint(int start, int end, DrawInputs inputs) {
for (int i = start; i < end; i++) {
if (!(commands.get(i) instanceof Model model) || model.cannotAddGlint()) return null;
}
final ModelGlint glint = modelGlints.acquire();
commands.add(glint);
glint.inputs.copyFrom(inputs);
glint.base = (Model) commands.get(start);
Model previous = null;
for (int i = start; i < end; i++) {
final Model model = (Model) commands.get(i);
addGlint(model, glint, true);
if (previous != null) previous.nextGlintBase = model;
previous = model;
}
return glint;
}
ItemGlint itemGlint(Matrix4fc modelView, DrawInputs inputs) {
if (!(commands.get(size() - 1) instanceof Model base) || base.cannotAddGlint() || base.drawsCubes() || base.sharedRangeChanged()) return null;
final Matrix4fc pose = base.source != null ? base.source.pose(base.end - 1, glintPose) : base.modelView;
if (!pose.equals(modelView)) return null;
final ItemGlint glint = itemGlints.acquire();
commands.add(glint);
glint.inputs.copyFrom(inputs);
glint.base = base;
addGlint(base, glint, false);
return glint;
}
private void addGlint(Model base, Glint glint, boolean reuseUpload) {
base.glint = glint;
base.replayed = ranges.acquire();
base.replayed.reuseUpload = reuseUpload;
}
void stream(Matrix4fc modelView, int[] vertices, int vertexCount, int entityId, DrawInputs inputs) {
final Stream stream = streams.acquire();
commands.add(stream);
stream.inputs.copyFrom(inputs);
stream.modelView.set(modelView);
stream.entityId = entityId;
stream.capture(vertices, vertexCount);
}
void overlay(DeferredEntityOverlay.DeferredEntry source) {
final Overlay overlay = overlays.acquire();
commands.add(overlay);
// The shadow overlay queue is recycled before main-view entity
overlay.captured.copyFrom(source);
}
Rejection validate(int start, int end) {
for (int i = start; i < end; i++) {
final Rejection reason = commands.get(i).validate();
if (reason != null) return reason;
}
return null;
}
void replay(int start, int end) {
for (int i = start; i < end; i++) {
final Command command = commands.get(i);
command.replay();
if (Tracy.ENABLED) {
if (command instanceof Model model && model.source != null) {
sharedParts += model.end - model.start;
sharedRanges++;
} else if (command instanceof Stream) {
streamEntries++;
}
}
}
}
void clear() {
commands.clear();
models.clear();
modelGlints.clear();
itemGlints.clear();
streams.clear();
overlays.clear();
ranges.clear();
mergeStart = 0;
}
void release() {
clear();
models.release();
modelGlints.release();
itemGlints.release();
streams.release();
overlays.release();
ranges.release();
}
abstract static class Command {
Rejection validate() {
return null;
}
abstract void replay();
}
static final class DrawInputs {
ResourceLocation texture;
TesrMaterial material;
PassOverride pass;
int textureId, glintSlot, cull;
float offsetFactor, offsetUnits;
boolean offsetEnabled, lit, lightmap, afterGlint;
boolean inheritedOffsetEnable, inheritedOffsetValues, inheritedLighting;
DrawInputs set(ResourceLocation texture, int textureId, TesrMaterial material, PassOverride pass, boolean offsetEnabled, float offsetFactor, float offsetUnits, int glintSlot, int cull, boolean lit, boolean lightmap, boolean afterGlint) {
this.texture = texture;
this.textureId = textureId;
this.material = material;
this.pass = pass;
this.offsetEnabled = offsetEnabled;
this.offsetFactor = offsetFactor;
this.offsetUnits = offsetUnits;
this.glintSlot = glintSlot;
this.cull = cull;
this.lit = lit;
this.lightmap = lightmap;
this.afterGlint = afterGlint;
return this;
}
private boolean offsetValuesUsed() {
return !inheritedOffsetValues && (offsetEnabled || inheritedOffsetEnable);
}
boolean matches(DrawInputs other) {
return textureId == other.textureId && Objects.equals(texture, other.texture) && material == other.material && pass == other.pass && (!offsetValuesUsed() || Float.floatToIntBits(offsetFactor)
== Float.floatToIntBits(other.offsetFactor) && Float.floatToIntBits(offsetUnits) == Float.floatToIntBits(other.offsetUnits)) && offsetEnabled == other.offsetEnabled && glintSlot == other.glintSlot && cull == other.cull
&& lit == other.lit && lightmap == other.lightmap && afterGlint == other.afterGlint && inheritedOffsetEnable == other.inheritedOffsetEnable && inheritedOffsetValues == other.inheritedOffsetValues && inheritedLighting
== other.inheritedLighting;
}
void copyFrom(DrawInputs other) {
set(other.texture, other.textureId, other.material, other.pass, other.offsetEnabled, other.offsetFactor, other.offsetUnits, other.glintSlot, other.cull, other.lit, other.lightmap, other.afterGlint);
inheritedOffsetEnable = other.inheritedOffsetEnable;
inheritedOffsetValues = other.inheritedOffsetValues;
inheritedLighting = other.inheritedLighting;
}
void clear() {
texture = null;
material = null;
pass = null;
}
}
static final class Instance {
TemplateBuffer template;
int light, color, overlay;
long entityInfo;
void set(TemplateBuffer template, int light, int color, int overlay, long entityInfo) {
this.template = template;
this.light = light;
this.color = color;
this.overlay = overlay;
this.entityInfo = entityInfo;
}
}
abstract static class Draw extends Command {
final DrawInputs inputs = new DrawInputs();
int entityId;
void clear() {
inputs.clear();
}
}
static final class Model extends Draw {
final Matrix4f modelView = new Matrix4f();
final Matrix4f textureMatrix = new Matrix4f();
final Instance instance = new Instance();
InstanceColumns source;
long sourceVersion;
int start, end;
RenderLayer shadowLayer;
CubeParams[] cubes;
boolean cubesInsideTexture, hasTextureMatrix;
float scale;
Glint glint;
Model nextGlintBase;
InstanceRange replayed;
boolean cannotAddGlint() {
final TesrMaterial material = inputs.material;
return material == null || !material.isStream() || material.shader() != null || glint != null;
}
boolean drawsCubes() {
return source != null ? !source.cubes.isEmpty() : cubes != null;
}
boolean sharedRangeChanged() {
return source != null && (source.version != sourceVersion || end > source.size);
}
@Override
Rejection validate() {
if (sharedRangeChanged()) return Rejection.SHARED_RANGE_CHANGED;
if (glint != null && !ModelPartBatcher.INSTANCE.canInstanceBaseAndGlint(this, glint.inputs.lightmap)) return Rejection.NOT_READY;
return null;
}
@Override
void replay() {
ModelPartBatcher.INSTANCE.replay(this);
}
@Override
void clear() {
super.clear();
source = null;
shadowLayer = null;
instance.template = null;
cubes = null;
glint = null;
nextGlintBase = null;
replayed = null;
}
}
abstract static class Glint extends Draw {
Model base;
@Override
Rejection validate() {
if (!ModelPartBatcher.INSTANCE.canReplayGlint(inputs.material)) return Rejection.NOT_READY;
return inputs.material == EntityMaterials.ITEM_GLINT && !canResetStencil() ? Rejection.UNCAPTURED_STATE : null;
}
@Override
void clear() {
super.clear();
base = null;
}
}
static final class ModelGlint extends Glint {
@Override
void replay() {
ModelPartBatcher.INSTANCE.replay(this);
}
}
static final class ItemGlint extends Glint {
final Instance instance = new Instance();
@Override
void replay() {
ModelPartBatcher.INSTANCE.replay(this);
}
@Override
void clear() {
super.clear();
instance.template = null;
}
}
static final class Stream extends Draw {
final Matrix4f modelView = new Matrix4f();
int[] vertices;
int vertexCount, entity, blockEntity, item;
float red, green, blue, alpha;
void capture(int[] vertices, int vertexCount) {
final int length = vertexCount * ModelQuadUtil.VERTEX_SIZE;
if (this.vertices == null) {
this.vertices = new int[length];
} else if (this.vertices.length < length) {
this.vertices = new int[Math.max(length, this.vertices.length << 1)];
}
System.arraycopy(vertices, 0, this.vertices, 0, length);
this.vertexCount = vertexCount;
final CapturedRenderingState state = CapturedRenderingState.INSTANCE;
entity = state.getCurrentRenderedEntity();
blockEntity = state.getCurrentRenderedBlockEntity();
item = state.getCurrentRenderedItem();
final Vector4fc color = state.getCurrentEntityColor();
red = color.x();
green = color.y();
blue = color.z();
alpha = color.w();
}
@Override
void replay() {
ModelPartBatcher.INSTANCE.replay(this);
}
}
private static final class Overlay extends Command {
final DeferredEntityOverlay.DeferredEntry captured = new DeferredEntityOverlay.DeferredEntry();
@Override
void replay() {
DeferredEntityOverlay.queueReplay(captured, ModelPartBatcher.INSTANCE.replayTransform);
}
void clear() {
captured.clear();
}
}
}
@@ -1,409 +0,0 @@
package com.gtnewhorizons.angelica.rendering.tesr;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.compat.mojang.RenderLayer;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.ExitState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.rendering.DeferredEntityOverlay;
import com.gtnewhorizons.angelica.rendering.tesr.EntityRenderCommands.DrawInputs;
import com.gtnewhorizons.angelica.rendering.tesr.RetainedTesrGroups.InstanceColumns;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import lombok.Setter;
import net.coderbot.iris.pipeline.ShadowRenderer;
import net.minecraft.client.renderer.culling.Frustrum;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.Entity;
import net.minecraft.entity.boss.BossStatus;
import net.minecraft.entity.boss.IBossDisplayData;
import net.minecraft.entity.player.EntityPlayer;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import java.util.Arrays;
import java.util.Objects;
/**
* Records a shadow-pass entity call, then submits its models, fire, and deferred overlays in the main pass.
*/
public final class EntityShadowReplay {
private static final Tracy.ZoneId Z_REPLAY = Tracy.zoneId("entityReplay", Tracy.COLOR_CLIENT);
private static final long P_REPLAYED = Tracy.plotHandle("entityReplay.replayed");
private static final long P_SHARED_PARTS = Tracy.plotHandle("entityReplay.sharedParts");
private static final long P_SHARED_RANGES = Tracy.plotHandle("entityReplay.sharedRanges");
private static final long P_STREAM_ENTRIES = Tracy.plotHandle("entityReplay.streamEntries");
private static final long P_RECORDED = Tracy.plotHandle("entityReplay.recorded");
private static final long P_OUTSIDE_MAIN_VIEW = Tracy.plotHandle("entityReplay.outsideMainView");
private static final Rejection[] REJECTIONS = Rejection.values();
private static final long[] rejected = new long[REJECTIONS.length];
private static final Reference2ObjectOpenHashMap<Entity, Record> records = new Reference2ObjectOpenHashMap<>();
private static final Reference2ObjectOpenHashMap<Class<?>, Boolean> excluded = new Reference2ObjectOpenHashMap<>();
private static final Reference2ObjectOpenHashMap<Class<?>, Reference2IntOpenHashMap<Class<?>>> loggedRejections = new Reference2ObjectOpenHashMap<>();
private static final Pool<Record> recordPool = new Pool<>(Record::new, Record::clear);
private static final EntityRenderCommands commands = new EntityRenderCommands();
private static final Matrix4f shadowInverse = new Matrix4f();
private static final Matrix4f mainFromShadow = new Matrix4f();
private static Record recording;
@Setter private static Frustrum mainView;
private static Class<?> lastType;
private static boolean lastExcluded;
private static long drawsAtStart, replayed, outsideMainView;
private static boolean shadowViewValid, shadowAvailable, mainReady, logRejections;
private static double shadowX, shadowY, shadowZ;
private static float shadowYaw, shadowPitch;
private static float capturedBossHealthScale;
private static int capturedBossBarTime;
private static String capturedBossName;
private static boolean capturedBossColorModifier;
private EntityShadowReplay() {
}
public static void beginFrame() {
if (Tracy.ENABLED) {
Tracy.plotInt(P_REPLAYED, replayed);
Tracy.plotInt(P_SHARED_PARTS, commands.sharedParts);
Tracy.plotInt(P_SHARED_RANGES, commands.sharedRanges);
Tracy.plotInt(P_STREAM_ENTRIES, commands.streamEntries);
Tracy.plotInt(P_RECORDED, recordPool.size());
Tracy.plotInt(P_OUTSIDE_MAIN_VIEW, outsideMainView);
for (Rejection reason : REJECTIONS) Tracy.plotInt(reason.plot, rejected[reason.ordinal()]);
}
replayed = outsideMainView = 0;
commands.sharedParts = commands.sharedRanges = commands.streamEntries = 0;
Arrays.fill(rejected, 0);
resetRecords();
mainView = null;
logRejections = SystemProperties.debugTooling();
}
private static void resetRecords() {
if (recording != null && recording.capturingState) GLStateManager.cancelExitCapture();
records.clear();
commands.clear();
recordPool.clear();
recording = null;
capturedBossName = null;
shadowViewValid = shadowAvailable = mainReady = false;
}
static void beginPass(boolean shadow, int renderPass) {
if (shadow) {
resetRecords();
shadowInverse.set(GLStateManager.getModelViewMatrix()).invert();
shadowViewValid = shadowInverse.isFinite();
shadowX = RenderManager.renderPosX;
shadowY = RenderManager.renderPosY;
shadowZ = RenderManager.renderPosZ;
shadowYaw = RenderManager.instance.playerViewY;
shadowPitch = RenderManager.instance.playerViewX;
} else {
mainReady = shadowAvailable && renderPass == 0;
if (mainReady) {
mainFromShadow.set(GLStateManager.getModelViewMatrix()).translate(
(float) (shadowX - RenderManager.renderPosX), (float) (shadowY - RenderManager.renderPosY),
(float) (shadowZ - RenderManager.renderPosZ)).mul(shadowInverse);
}
}
}
public static boolean beginEntity(Entity entity, float partialTicks) {
if (recording != null) {
reject(Rejection.FOREIGN_DRAW);
return false;
}
if (!shadowViewValid || !ShadowRenderer.ACTIVE || !ModelPartBatcher.INSTANCE.entitiesActive()) return false;
shadowAvailable = true;
// replay() rejects these without a record
if (isExcluded(entity.getClass()) || entity.shouldRenderInPass(1)) return false;
if (!inMainView(entity)) {
outsideMainView++;
return false;
}
final Record record = recordPool.acquire();
record.start = record.end = commands.size();
record.entity = entity;
record.partialTicks = partialTicks;
record.hasBossStatus = false;
record.bossColorModifier = false;
record.rejection = null;
records.put(entity, record);
recording = record;
record.capturingState = true;
GLStateManager.beginExitCapture(record.exitState);
drawsAtStart = GLStateManager.drawCalls;
return true;
}
public static void endEntity(boolean completed) {
if (recording == null) return;
if (recording.capturingState) {
recording.capturingState = false;
if (!GLStateManager.finishExitCapture(recording.exitState)) reject(Rejection.EXIT_STATE_UNSUPPORTED);
}
if (GLStateManager.drawCalls != drawsAtStart) reject(Rejection.FOREIGN_DRAW);
if (recording.renderer == null) reject(Rejection.UNSAFE_RENDERER);
if (!completed) reject(Rejection.RENDER_FAILED);
if (recording.entity instanceof IBossDisplayData && (!recording.hasBossStatus || bossStatusChanged())) {
reject(Rejection.UNCAPTURED_STATE);
}
recording.end = commands.size();
if (recording.start == recording.end) reject(Rejection.EMPTY_RECORD);
if (recording.rejection == null) recording.texture.capture();
recording = null;
capturedBossName = null;
}
public static boolean beginRenderer(Render renderer, Entity entity, float partialTicks, byte batchState) {
if (recording == null) return false;
if (recording.renderer != null || recording.entity != entity) {
reject(Rejection.FOREIGN_DRAW);
return false;
}
recording.renderer = renderer;
if (recording.partialTicks != partialTicks) reject(Rejection.PARTIAL_TICKS_CHANGED);
if (batchState != BatchEligibility.SAFE) reject(Rejection.UNSAFE_RENDERER);
return true;
}
public static void endRenderer(boolean completed, byte batchState) {
if (!completed) reject(Rejection.RENDER_FAILED);
if (batchState != BatchEligibility.SAFE) reject(Rejection.UNSAFE_RENDERER);
}
private static boolean inMainView(Entity entity) {
return mainView == null || entity.isInRangeToRender3d(mainView.xPosition, mainView.yPosition, mainView.zPosition)
&& (entity.ignoreFrustumCheck || mainView.isBoundingBoxInFrustum(entity.boundingBox));
}
static boolean isExcluded(Class<?> type) {
if (type == lastType) return lastExcluded;
Boolean result = excluded.get(type);
if (result == null) {
result = EntityPlayer.class.isAssignableFrom(type);
for (Class<?> parent = type; !result && parent != null; parent = parent.getSuperclass()) {
final String[] names = AngelicaConfig.entityShadowReplayExcludedClasses;
if (names != null) for (String name : names) {
if (parent.getName().equals(name)) {
result = true;
break;
}
}
}
excluded.put(type, result);
}
lastType = type;
lastExcluded = result;
return result;
}
public static boolean replay(RenderManager manager, Entity entity, float partialTicks, int renderPass) {
if (!shadowAvailable || ShadowRenderer.ACTIVE) return false;
final Record record = records.get(entity);
if (record == null && !Tracy.ENABLED && !logRejections) return false;
Rejection reason = renderPass != 0 ? Rejection.RENDER_PASS
: record != null ? record.rejection
: isExcluded(entity.getClass()) ? Rejection.EXCLUDED
: entity.shouldRenderInPass(1) ? Rejection.RENDER_PASS : Rejection.NOT_IN_SHADOW_PASS;
if (reason == null) {
if (!mainReady || !ModelPartBatcher.INSTANCE.canReplayEntity()) reason = Rejection.NOT_READY;
else if (record.renderer != manager.getEntityRenderObject(entity)) reason = Rejection.RENDERER_CHANGED;
else if (record.partialTicks != partialTicks) reason = Rejection.PARTIAL_TICKS_CHANGED;
else if (shadowYaw != RenderManager.instance.playerViewY || shadowPitch != RenderManager.instance.playerViewX) reason = Rejection.VIEW_CHANGED;
else if (!GLStateManager.canApplyExitState(record.exitState)) reason = Rejection.ENTRY_STATE_CHANGED;
else if (GLStateManager.getPolygonState().getOffsetClamp() != 0) reason = Rejection.UNCAPTURED_STATE;
}
if (reason == null) {
reason = commands.validate(record.start, record.end);
}
if (reason != null) {
rejected[reason.ordinal()]++;
logRejection(entity.getClass(), record == null || record.renderer == null ? null : record.renderer.getClass(), reason);
return false;
}
replayed++;
if (Tracy.FINE_ZONES) Tracy.beginZone(Z_REPLAY);
try {
if (record.hasBossStatus) BossStatus.setBossStatus((IBossDisplayData) record.entity, record.bossColorModifier);
ModelPartBatcher.INSTANCE.beginReplay(mainFromShadow);
commands.replay(record.start, record.end);
GLStateManager.applyExitState(record.exitState);
record.texture.apply();
} finally {
if (Tracy.FINE_ZONES) Tracy.endZone();
}
return true;
}
private static void logRejection(Class<?> entity, Class<?> renderer, Rejection reason) {
if (!logRejections) return;
Reference2IntOpenHashMap<Class<?>> reasons = loggedRejections.get(entity);
if (reasons == null) {
reasons = new Reference2IntOpenHashMap<>();
loggedRejections.put(entity, reasons);
}
final int bit = 1 << reason.ordinal();
final int logged = reasons.getInt(renderer);
if ((logged & bit) != 0) return;
reasons.put(renderer, logged | bit);
AngelicaMod.LOGGER.info("Entity shadow replay fallback: entity={} capturedRenderer={} reason={}: {}",
entity.getName(), renderer == null ? "<not captured>" : renderer.getName(), reason.label, reason.description);
}
static EntityRenderCommands.Model recordModel(InstanceColumns source, RenderLayer layer, DrawInputs inputs, int entityId,
boolean cubesInsideTexture, Matrix4fc modelView, Matrix4fc texMatrix) {
return captureInheritedState(inputs)
? commands.model(recording.start, source, layer, inputs, entityId, cubesInsideTexture, modelView, texMatrix) : null;
}
static void recordStream(Matrix4fc modelView, int[] vertices, int vertexCount, int groupId, DrawInputs inputs) {
if (captureInheritedState(inputs)) commands.stream(modelView, vertices, vertexCount, groupId, inputs);
}
static EntityRenderCommands.ModelGlint recordArmorGlint(int start, int end, DrawInputs inputs) {
return captureInheritedState(inputs) && start >= recording.start && end > start && end <= commands.size()
? commands.modelGlint(start, end, inputs) : null;
}
static EntityRenderCommands.ItemGlint recordItemGlint(Matrix4fc modelView, DrawInputs inputs) {
return captureInheritedState(inputs) && commands.size() > recording.start ? commands.itemGlint(modelView, inputs) : null;
}
private static boolean captureInheritedState(DrawInputs inputs) {
if (GLStateManager.getPolygonState().getOffsetClamp() != 0) reject(Rejection.UNCAPTURED_STATE);
if (!isRecording()) return false;
final int offsetWrites = GLStateManager.getExitPolygonOffsetWrites();
inputs.inheritedOffsetEnable = (offsetWrites & ExitState.POLYGON_OFFSET_ENABLE_WRITTEN) == 0;
inputs.inheritedOffsetValues = (offsetWrites & ExitState.POLYGON_OFFSET_VALUES_WRITTEN) == 0;
inputs.inheritedLighting = !GLStateManager.isExitLightingWritten();
return true;
}
static boolean isRecording() {
return recording != null && recording.rejection == null;
}
static int splitCommands() {
return isRecording() ? commands.split() : -1;
}
public static void recordDeferredOverlay(DeferredEntityOverlay.DeferredEntry overlay) {
if (isRecording()) commands.overlay(overlay);
}
public static void recordBossStatus(IBossDisplayData boss, boolean colorModifier) {
if (!isRecording()) return;
if (boss != recording.entity || recording.hasBossStatus) {
reject(Rejection.UNCAPTURED_STATE);
return;
}
recording.hasBossStatus = true;
recording.bossColorModifier = colorModifier;
capturedBossHealthScale = BossStatus.healthScale;
capturedBossBarTime = BossStatus.statusBarTime;
capturedBossName = BossStatus.bossName;
capturedBossColorModifier = BossStatus.hasColorModifier;
}
private static boolean bossStatusChanged() {
return Float.floatToIntBits(capturedBossHealthScale) != Float.floatToIntBits(BossStatus.healthScale)
|| capturedBossBarTime != BossStatus.statusBarTime
|| !Objects.equals(capturedBossName, BossStatus.bossName)
|| capturedBossColorModifier != BossStatus.hasColorModifier;
}
private static void reject(Rejection reason) {
if (recording != null && recording.rejection == null) {
recording.rejection = reason;
if (recording.capturingState) {
GLStateManager.cancelExitCapture();
recording.capturingState = false;
}
}
}
public static void rejectForeignDraw() {
reject(Rejection.FOREIGN_DRAW);
}
public static void rejectRenderFailed() {
reject(Rejection.RENDER_FAILED);
}
public static void rejectShadowOnly() {
reject(Rejection.SHADOW_ONLY_SKIP);
}
static void rejectUncapturedState() {
reject(Rejection.UNCAPTURED_STATE);
}
static void rejectPartFallback() {
reject(Rejection.PART_FALLBACK);
}
static void rejectBatcherBail() {
reject(Rejection.BATCHER_BAIL);
}
public static void clear() {
resetRecords();
mainView = null;
commands.release();
recordPool.release();
excluded.clear();
lastType = null;
loggedRejections.clear();
}
enum Rejection {
EXCLUDED( "excluded", "Player or configured excluded class."),
FOREIGN_DRAW( "foreignDraw", "Uncaptured draw or nested entity rendering."),
SHADOW_ONLY_SKIP( "shadowOnlySkip", "Shadow rendering skipped work needed in the main view."),
RENDER_PASS( "renderPass", "Entity requires a render pass outside the supported pass 0."),
NOT_IN_SHADOW_PASS( "notInShadowPass", "No shadow record this frame; shadow range or culling can cause this!"),
UNSAFE_RENDERER( "unsafeRenderer", "Renderer is not marked safe for batching, or no renderer ran."),
EMPTY_RECORD( "emptyRecord", "No replayable rendering commands were captured."),
UNCAPTURED_STATE( "uncapturedState", "Unsupported render state or boss HUD side effects."),
PART_FALLBACK( "partFallback", "A model part used the live rendering path."),
BATCHER_BAIL( "batcherBail", "A draw could not use the model batcher."),
RENDER_FAILED( "renderFailed", "The captured renderer call did not complete successfully."),
NOT_READY( "notReady", "Main-pass batching is inactive or a required instanced shader variant is missing."),
RENDERER_CHANGED( "rendererChanged", "The main renderer differs from the captured renderer."),
PARTIAL_TICKS_CHANGED( "partialTicksChanged", "Animation time differs from the one the shadow pass recorded."),
VIEW_CHANGED( "viewChanged", "Camera angles changed between shadow and main views."),
EXIT_STATE_UNSUPPORTED( "exitStateUnsupported", "Renderer state includes an unsupported operation, state query, or unbalanced stack."),
ENTRY_STATE_CHANGED( "entryStateChanged", "Main renderer entry state differs from a captured dependency, or a texture was deleted."),
SHARED_RANGE_CHANGED( "sharedRangeChanged", "Captured instance storage was rebuilt or shortened.");
final long plot;
final String label, description;
Rejection(String name, String description) {
plot = Tracy.plotHandle("entityReplay." + name);
label = name;
this.description = description;
}
}
static final class Record {
final ExitState exitState = new ExitState();
final ModelPartBatcher.BoundTexture texture = new ModelPartBatcher.BoundTexture();
int start, end;
Render renderer;
Entity entity;
float partialTicks;
boolean hasBossStatus, bossColorModifier, capturingState;
Rejection rejection;
void clear() {
renderer = null;
entity = null;
texture.location = null;
}
}
}
@@ -247,10 +247,9 @@ public final class EntityTextureAtlas {
private Page newPage() {
if (pages.size() == MAX_PAGES) return null;
final int previous = GLStateManager.getBoundTextureForServerState();
final int texture;
final int texture = GLStateManager.glGenTextures();
BatchStateGuard.suspend();
try {
texture = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
@@ -270,15 +269,15 @@ public final class EntityTextureAtlas {
}
private void copy(int texture, Slot slot) {
if (readFbo == 0) {
readFbo = GLStateManager.glGenFramebuffers();
drawFbo = GLStateManager.glGenFramebuffers();
}
final int previousRead = GLStateManager.getReadFramebuffer();
final int previousDraw = GLStateManager.getDrawFramebuffer();
final boolean scissor = GLStateManager.getScissorTest().isEnabled();
final boolean scissor = GLStateManager.glIsEnabled(GL11.GL_SCISSOR_TEST);
BatchStateGuard.suspend();
try {
if (readFbo == 0) {
readFbo = GLStateManager.glGenFramebuffers();
drawFbo = GLStateManager.glGenFramebuffers();
}
if (scissor) GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, readFbo);
GLStateManager.attachTextureForReading(GL30.GL_COLOR_ATTACHMENT0, texture);
@@ -36,7 +36,6 @@ public final class GlintCapture {
private boolean poseRestored;
private int restoredGeneration;
private boolean uniformLight;
private int replayStart = -1, replayEnd = -1;
public void reset() {
if (owner != null && owner.glintCapture == this) owner.glintCapture = null;
@@ -52,7 +51,6 @@ public final class GlintCapture {
base = false;
trusted = false;
poseRestored = false;
replayStart = replayEnd = -1;
}
boolean begin(RetainedTesrGroups groups) {
@@ -81,10 +79,6 @@ public final class GlintCapture {
return true;
}
void beginShadowReplay() {
replayStart = EntityShadowReplay.splitCommands();
}
/** For records queued without running model or mod code; the caller replays before any GL state changes. */
boolean beginTrusted(RetainedTesrGroups groups) {
if (!open(groups)) return false;
@@ -107,7 +101,6 @@ public final class GlintCapture {
void invalidate() { valid = false; }
void allowBase() { base = true; }
boolean capturesBase() { return base && valid; }
boolean keepsBaseUpload() { return capturesBase() && replayStart < 0; }
void record(RetainedTesrGroups.Group group, RetainedTesrGroups.InstanceColumns columns, RetainedTesrGroups.TexRun source, int start, int end, int instances) {
if (!valid) return;
@@ -151,7 +144,6 @@ public final class GlintCapture {
valid &= GLStateManager.drawCalls == draws && used > 0;
this.poseRestored = poseRestored;
restoredGeneration = GLStateManager.getMvGeneration();
if (replayStart >= 0) replayEnd = EntityShadowReplay.splitCommands();
}
private boolean sameParentPose() {
@@ -173,7 +165,7 @@ public final class GlintCapture {
}
for (int i = 0; i < used; i++) {
final Range range = ranges.get(i);
groups.replayRange(range.group, range.columns, range.source, range.start, range.end, range.instances, textureMatrix);
range.group.replayRange(range.columns, range.source, range.start, range.end, range.instances, textureMatrix);
}
valid = false;
return true;
@@ -192,23 +184,12 @@ public final class GlintCapture {
boolean queueBothLayers(ModelPartBatcher batcher, RetainedTesrGroups groups, ResourceLocation glintTexture) {
if (!baseStillValid(groups) || !sameLightAndOverlay(batcher.currentOverlay()) || !batcher.prepareSkippedArmorGlint(glintTexture)) return false;
queueLayers(batcher);
return true;
}
boolean recordBothLayers(ModelPartBatcher batcher, RetainedTesrGroups groups, ResourceLocation glintTexture) {
if (!baseStillValid(groups) || !sameLightAndOverlay(batcher.currentOverlay())
|| !batcher.recordArmorGlint(replayStart, replayEnd, glintTexture)) return false;
valid = false;
return true;
}
void queueLayers(ModelPartBatcher batcher) {
for (int i = 0; i < used; i++) {
final Range range = ranges.get(i);
batcher.queueArmorGlintLayers(range.columns, range.source, range.start, range.end);
}
valid = false;
return true;
}
private boolean baseStillValid(RetainedTesrGroups groups) {
@@ -35,8 +35,6 @@ import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource;
import net.coderbot.batchedentityrendering.impl.TransparencyType;
import net.coderbot.iris.Iris;
import net.coderbot.iris.gbuffer_overrides.matching.InputAvailability;
import net.coderbot.iris.gbuffer_overrides.matching.RenderCondition;
import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.layer.PassOverride;
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
@@ -52,14 +50,12 @@ import net.minecraft.util.ResourceLocation;
import net.minecraft.world.World;
import net.minecraftforge.client.MinecraftForgeClient;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.ToLongFunction;
public final class ModelPartBatcher {
@@ -95,8 +91,6 @@ public final class ModelPartBatcher {
private final RetainedTesrGroups groups = new RetainedTesrGroups(bufferSource);
private final RetainedTesrGroups entityGroups = new RetainedTesrGroups(entitySource);
private final RetainedTesrGroups shadowGroups = new RetainedTesrGroups(bufferSource);
private final RetainedTesrGroups shadowEntityGroups = new RetainedTesrGroups(bufferSource);
private final RetainedTesrGroups[] allGroups = {groups, entityGroups, shadowGroups, shadowEntityGroups};
private RetainedTesrGroups activeGroups = groups;
private AngelicaBufferSource activeSource = bufferSource;
private boolean entityTranslucentsHeld;
@@ -105,12 +99,6 @@ public final class ModelPartBatcher {
private final Matrix4f identity = new Matrix4f();
private final Matrix4f modelView = new Matrix4f();
private final Matrix4f texMatrixScratch = new Matrix4f();
private final Matrix4f replayView = new Matrix4f();
private final EntityRenderCommands.DrawInputs drawInputs = new EntityRenderCommands.DrawInputs();
Matrix4fc replayTransform;
private boolean replayOffsetEnabled;
private float replayOffsetFactor, replayOffsetUnits;
private boolean loggedDroppedGlint;
private int lastMvGeneration;
private boolean mvCaptured;
private int texGeneration;
@@ -139,17 +127,11 @@ public final class ModelPartBatcher {
public long statArmorLoopSkips() { return armorLoopSkips; }
public long statArmorSectionSkips() { return armorSectionSkips; }
public long statLiveFallbacks() { return liveFallbacks; }
public long statInstancedDraws() { return sumGroups(g -> g.instancedDraws); }
public long statInstancedInstances() { return sumGroups(g -> g.instancedInstances); }
public long statCubeInstances() { return sumGroups(g -> g.cubeInstances); }
public long statRedrawnInstances() { return sumGroups(g -> g.redrawnInstances); }
public long statTexMatrixRuns() { return sumGroups(g -> g.texMatrixRuns); }
private long sumGroups(ToLongFunction<RetainedTesrGroups> stat) {
long sum = 0;
for (RetainedTesrGroups group : allGroups) sum += stat.applyAsLong(group);
return sum;
}
public long statInstancedDraws() { return groups.instancedDraws + entityGroups.instancedDraws + shadowGroups.instancedDraws; }
public long statInstancedInstances() { return groups.instancedInstances + entityGroups.instancedInstances + shadowGroups.instancedInstances; }
public long statCubeInstances() { return groups.cubeInstances + entityGroups.cubeInstances + shadowGroups.cubeInstances; }
public long statRedrawnInstances() { return groups.redrawnInstances + entityGroups.redrawnInstances + shadowGroups.redrawnInstances; }
public long statTexMatrixRuns() { return groups.texMatrixRuns + entityGroups.texMatrixRuns + shadowGroups.texMatrixRuns; }
public long statBail(BailReason reason) { return bails[reason.ordinal()]; }
public long statShadowQuads() { return shadows.statQuads(); }
public long statShadowDraws() { return shadows.statDraws(); }
@@ -238,24 +220,6 @@ public final class ModelPartBatcher {
private static int lastBoundGlId = -1;
private static int lastBoundGlintSlot = ShaderGlint.NO_TINT;
static final class BoundTexture {
ResourceLocation location;
int id, glintSlot;
void capture() {
id = GLStateManager.getBoundTextureForServerState(0);
location = id == lastBoundGlId ? lastBoundLocation : null;
glintSlot = id == lastBoundGlId ? lastBoundGlintSlot : ShaderGlint.NO_TINT;
}
void apply() {
if (GLStateManager.getBoundTextureForServerState(0) != id) return;
lastBoundLocation = location;
lastBoundGlId = id;
lastBoundGlintSlot = glintSlot;
}
}
private ModelPartBatcher() {}
public static void onTextureSwap(int glId, int glintSlot) {
@@ -294,17 +258,17 @@ public final class ModelPartBatcher {
final long nowMs = System.currentTimeMillis();
mvCaptured = false;
texCaptured = false;
final boolean replayEntities = mode == Mode.ENTITIES && AngelicaConfig.entityShadowReplayEnabled();
activeGroups = shadow ? (replayEntities ? shadowEntityGroups : shadowGroups) : entityPass ? entityGroups : groups;
activeGroups = shadow ? shadowGroups : entityPass ? entityGroups : groups;
activeSource = entityPass ? entitySource : bufferSource;
activeSource.setIdKind(mode == Mode.ENTITIES ? AngelicaBufferSource.GroupIdKind.ENTITY : AngelicaBufferSource.GroupIdKind.BLOCK_ENTITY);
activeSource.capturePassLighting();
BatchDrawDefaults.capturePassFramebuffer();
activeGroups.beginPass(identity, 0, 0, 0, instancedThisCycle ? TesrBatchRenderer.INSTANCE.instancedRenderer : null, shaderPipeline);
if (replayEntities && shaderPipeline != null) EntityShadowReplay.beginPass(shadow, MinecraftForgeClient.getRenderPass());
if (nowMs - lastSweepMs >= SWEEP_INTERVAL_MS) {
lastSweepMs = nowMs;
for (RetainedTesrGroups group : allGroups) group.sweep(nowMs);
groups.sweep(nowMs);
entityGroups.sweep(nowMs);
shadowGroups.sweep(nowMs);
sweepTemplates(nowMs);
}
}
@@ -438,19 +402,8 @@ public final class ModelPartBatcher {
/** Skipped glint blocks queue each layer as its own instanced draw, so a shader pack only needs its pass program current. */
public boolean canQueueGlint() {
return !shadow && canBatchGlint();
}
private boolean cannotQueueOrRecordGlint() {
return shadow && (mode != Mode.ENTITIES || !EntityShadowReplay.isRecording()) || !canBatchGlint();
}
private boolean canBatchGlint() {
return active && instancedThisCycle && BatchEligibility.batchingAllowed() && !GLStateManager.isRecordingDisplayList() && passProgramBound();
}
private boolean passProgramBound() {
return GLStateManager.getActiveProgram() == (shaderPipeline != null ? shaderPipeline.getActivePassProgramId() : 0);
return active && instancedThisCycle && !shadow && BatchEligibility.batchingAllowed() && !GLStateManager.isRecordingDisplayList()
&& GLStateManager.getActiveProgram() == (shaderPipeline != null ? shaderPipeline.getActivePassProgramId() : 0);
}
public boolean replayGlint(GlintCapture capture) {
@@ -473,25 +426,7 @@ public final class ModelPartBatcher {
}
public boolean beginArmorCapture(GlintCapture capture) {
if (cannotQueueOrRecordGlint() || !capture.beginBase(activeGroups, currentOverlay())) return false;
if (shadow) capture.beginShadowReplay();
return true;
}
boolean canReplayGlint(TesrMaterial material) {
return entityPassActive() && instancedThisCycle && activeGroups.glintCapture == null
&& activeGroups.canCopyInstances(material);
}
boolean canInstanceBaseAndGlint(EntityRenderCommands.Model base, boolean glintLightmap) {
final EntityRenderCommands.DrawInputs inputs = base.inputs;
if (!activeGroups.canCopyInstances(inputs.material)) return false;
if (shaderPipeline == null) return true;
final RenderCondition condition = inputs.pass.entityCondition(inputs.material.transparency() == TesrMaterial.Transparency.TRANSLUCENT);
if (condition == null) return false;
final Instancing kind = base.drawsCubes() ? Instancing.CUBE : Instancing.TEMPLATE;
return shaderPipeline.hasInstancedVariant(condition, InputAvailability.of(inputs.texture != null, inputs.lightmap), kind)
&& shaderPipeline.hasInstancedVariant(RenderCondition.GLINT, InputAvailability.of(true, glintLightmap), kind);
return canQueueGlint() && capture.beginBase(activeGroups, currentOverlay());
}
public boolean reuseArmorBase(GlintCapture base, GlintCapture glint) {
@@ -511,29 +446,20 @@ public final class ModelPartBatcher {
}
public boolean queueSkippedArmorGlint(GlintCapture base, ResourceLocation glintTexture) {
if (cannotQueueOrRecordGlint() || GLStateManager.getActiveTextureUnit() != 0 || !GLStateManager.getTextures().getTextureUnitStates(0).isEnabled()) return false;
if (!(shadow ? base.recordBothLayers(this, activeGroups, glintTexture) : base.queueBothLayers(this, activeGroups, glintTexture))) return false;
if (!shadow) {
armorBaseReuses++;
glintPassReuses++;
armorSectionSkips++;
}
if (!canQueueGlint() || GLStateManager.getActiveTextureUnit() != 0 || !GLStateManager.getTextures().getTextureUnitStates(0).isEnabled() || !base.queueBothLayers(this, activeGroups, glintTexture)) return false;
armorBaseReuses++;
glintPassReuses++;
armorSectionSkips++;
BatchEligibility.onPartQueued();
return true;
}
public boolean queueSkippedItemGlint(TemplateBuffer template, TesrMaterial material) {
if (cannotQueueOrRecordGlint() || GLStateManager.getActiveTextureUnit() != 0 || !activeGroups.canCopyInstances(material) || !MatrixHelper.isIdentity(GLStateManager.getTextures().getTextureUnitMatrix(0))) return false;
final EntityRenderCommands.DrawInputs inputs = glintInputs(ItemGlint.texture(), material, ShaderGlint.NO_TINT, false);
if (shadow) {
if (material == EntityMaterials.ITEM_GLINT && !ItemGlint.canResetStencil()) return false;
final EntityRenderCommands.ItemGlint entry = EntityShadowReplay.recordItemGlint(currentModelView(), inputs);
if (entry == null) return false;
GLStateManager.requireExitDrawState(false);
entry.instance.set(template, GLSMConfig.packedLastBrightness(), VANILLA_GLINT_COLOR, currentOverlay(), currentEntityInfo(0));
return true;
}
final RenderLayer layer = layerFor(ItemGlint.texture(), inputs);
if (!canQueueGlint() || GLStateManager.getActiveTextureUnit() != 0 || !activeGroups.canCopyInstances(material) || !MatrixHelper.isIdentity(GLStateManager.getTextures().getTextureUnitMatrix(0))) return false;
final boolean offset = GLStateManager.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL);
final PolygonState polygon = GLStateManager.getPolygonState();
final int cullCode = DrawState.liveCull(material);
final RenderLayer layer = layerFor(ItemGlint.texture(), material, PassOverride.NONE, offset ? polygon.getOffsetFactor() : 0, offset ? polygon.getOffsetUnits() : 0, ShaderGlint.NO_TINT, cullCode, false, DrawState.liveLightmap());
activeGroups.queueGlintLayers(template, layer, material, currentModelView(), GLSMConfig.packedLastBrightness(), VANILLA_GLINT_COLOR, currentOverlay(), currentEntityInfo(0),
ItemGlint.firstMatrix(), ItemGlint.secondMatrix());
parts++;
@@ -541,23 +467,6 @@ public final class ModelPartBatcher {
return true;
}
boolean recordArmorGlint(int start, int end, ResourceLocation texture) {
if (EntityShadowReplay.recordArmorGlint(start, end, glintInputs(texture, EntityMaterials.GLINT, ShaderGlint.NO_TINT, false)) == null) return false;
GLStateManager.requireExitDrawState(false);
return true;
}
private EntityRenderCommands.DrawInputs glintInputs(ResourceLocation texture, TesrMaterial material, int glintSlot, boolean lit) {
return liveInputs(texture, 0, material, PassOverride.NONE, glintSlot, DrawState.liveCull(material), lit, DrawState.liveLightmap(), false);
}
private EntityRenderCommands.DrawInputs liveInputs(ResourceLocation texture, int textureId, TesrMaterial material, PassOverride pass,
int glintSlot, int cull, boolean lit, boolean lightmap, boolean afterGlint) {
final PolygonState polygon = GLStateManager.getPolygonState();
return drawInputs.set(texture, textureId, material, pass, GLStateManager.getPolygonOffsetFillState().isEnabled(),
polygon.getOffsetFactor(), polygon.getOffsetUnits(), glintSlot, cull, lit, lightmap, afterGlint);
}
private Matrix4f currentModelView() {
final int mvGen = GLStateManager.getMvGeneration();
if (!mvCaptured || mvGen != lastMvGeneration) {
@@ -591,7 +500,10 @@ public final class ModelPartBatcher {
}
private void setArmorGlint(ResourceLocation texture, int glintSlot, boolean lit, Matrix4f textureMatrix, int color) {
armorGlintLayer = layerFor(texture, glintInputs(texture, EntityMaterials.GLINT, glintSlot, lit));
final boolean offset = GLStateManager.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL);
final PolygonState polygon = GLStateManager.getPolygonState();
final int cullCode = DrawState.liveCull(EntityMaterials.GLINT);
armorGlintLayer = layerFor(texture, EntityMaterials.GLINT, PassOverride.NONE, offset ? polygon.getOffsetFactor() : 0, offset ? polygon.getOffsetUnits() : 0, glintSlot, cullCode, lit, DrawState.liveLightmap());
armorGlintTexture.set(textureMatrix);
armorGlintColor = color;
armorGlintLight = GLSMConfig.packedLastBrightness();
@@ -616,10 +528,6 @@ public final class ModelPartBatcher {
return entitiesActive() && !shadow;
}
boolean canReplayEntity() {
return entityPassActive() && passProgramBound();
}
public boolean entitiesActive() {
return active && mode == Mode.ENTITIES;
}
@@ -628,10 +536,6 @@ public final class ModelPartBatcher {
return active && shadow;
}
boolean recordsEntitySubmissions(RetainedTesrGroups groups) {
return active && shadow && mode == Mode.ENTITIES && activeGroups == groups;
}
public static boolean recordShadow(World world, Entity entity, double x, double y, double z, float shadowAlpha, float partialTicks, float shadowSize) {
return INSTANCE.shadows.record(world, entity, x, y, z, shadowAlpha, partialTicks, shadowSize);
}
@@ -664,7 +568,6 @@ public final class ModelPartBatcher {
return true;
}
if (shadow && isDepthEqualDecalState()) {
EntityShadowReplay.rejectShadowOnly();
return true;
}
return queueTemplate(entry.template, entry.cubes, entry.cubesInsideTexture, scale, null, false);
@@ -716,14 +619,17 @@ public final class ModelPartBatcher {
return false;
}
GLStateManager.requireExitDrawState(false);
final Matrix4f texMatrix = texture != null && !unitTexIdentity() ? texMatrixScratch : null;
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 int glintSlot = material.special().isGlint() ? lastBoundGlintSlot : ShaderGlint.NO_TINT;
final EntityRenderCommands.DrawInputs inputs = liveInputs(texture, lastBoundGlId, material, pass, glintSlot,
DrawState.liveCull(material), DrawState.liveLit(material), DrawState.liveLightmap(), afterGlint);
final int cullCode = DrawState.liveCull(material);
final boolean lit = DrawState.liveLit(material);
final EntityTextureAtlas.Slot atlasSlot = texMatrix == null && cubesInsideTexture ? atlasSlotFor(material) : null;
final RenderLayer layer = layerFor(atlasSlot != null ? Objects.requireNonNull(atlasSlot.page).location : texture, inputs);
final RenderLayer layer = layerFor(atlasSlot != null ? Objects.requireNonNull(atlasSlot.page).location : texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit, DrawState.liveLightmap(), afterGlint);
// Entities nested inside a TESR (mob spawner, OpenBlocks trophy) run in the block entity pass but carry an entity id
final int entityId = groupId(shaderPipeline != null, mode == Mode.ENTITIES || pass.isEntityPhase(), CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity());
final Color4 color = GLStateManager.getColor();
@@ -731,110 +637,18 @@ public final class ModelPartBatcher {
final int overlayABGR = currentOverlay();
final long entityInfo = currentEntityInfo(CapturedRenderingState.INSTANCE.getCurrentRenderedItem());
final int packedLight = GLSMConfig.packedLastBrightness();
final Matrix4f modelView = currentModelView();
activeGroups.queueAndRecord(template, cubes, scale, layer, modelView, packedLight, colorABGR, overlayABGR, entityInfo,
entityId, texMatrix, atlasSlot, cubesInsideTexture, inputs);
activeGroups.queue(0L, template, cubes, scale, layer, material, currentModelView(), packedLight, colorABGR, overlayABGR, entityInfo, entityId, texMatrix, atlasSlot);
parts++;
return true;
}
private EntityTextureAtlas.Slot atlasSlotFor(TesrMaterial material) {
return atlasSlotFor(material, lastBoundGlId);
}
private EntityTextureAtlas.Slot atlasSlotFor(TesrMaterial material, int textureId) {
if (!AngelicaConfig.enableEntityTextureAtlas) return null;
// Glint, overlay and custom shaders sample their own textures
if (material.special() != TesrMaterial.SpecialRender.NONE || material.shader() != null || material.isUnfilteredAtlas() || material == EntityMaterials.OVERLAY) return null;
if (!activeGroups.instancesCubes(material)) return null;
if (shaderPipeline != null && !shaderPipeline.hasInstancedVariant(Instancing.CUBE)) return null;
return EntityTextureAtlas.INSTANCE.slotFor(textureId, shaderPipeline != null);
}
void beginReplay(Matrix4fc mainFromShadow) {
replayTransform = mainFromShadow;
final PolygonState polygon = GLStateManager.getPolygonState();
replayOffsetEnabled = GLStateManager.getPolygonOffsetFillState().isEnabled();
replayOffsetFactor = polygon.getOffsetFactor();
replayOffsetUnits = polygon.getOffsetUnits();
}
private RenderLayer replayLayer(EntityRenderCommands.Draw entry, EntityTextureAtlas.Slot slot) {
final EntityRenderCommands.DrawInputs inputs = entry.inputs;
final boolean enabled = inputs.inheritedOffsetEnable ? replayOffsetEnabled : inputs.offsetEnabled;
final float factor = inputs.inheritedOffsetValues ? replayOffsetFactor : inputs.offsetFactor;
final float units = inputs.inheritedOffsetValues ? replayOffsetUnits : inputs.offsetUnits;
final boolean lit = inputs.inheritedLighting ? DrawState.liveLit(inputs.material) : inputs.lit;
return layerFor(slot != null ? Objects.requireNonNull(slot.page).location : inputs.texture,
inputs.material, inputs.pass, enabled, factor, units, inputs.glintSlot, inputs.cull, lit, inputs.lightmap, inputs.afterGlint);
}
void replay(EntityRenderCommands.Model entry) {
final RetainedTesrGroups.InstanceRange replayed = entry.glint != null ? entry.replayed : null;
final TesrMaterial material = entry.inputs.material;
// begin() may have recycled the shadow pass's atlas region.
// A reused base upload must keep the original UVs for the glint texture.
final EntityTextureAtlas.Slot slot = (replayed == null || !replayed.reuseUpload) && !entry.hasTextureMatrix && entry.cubesInsideTexture
? atlasSlotFor(material, entry.inputs.textureId) : null;
final RenderLayer layer = replayLayer(entry, slot);
final Matrix4f texMatrix = entry.hasTextureMatrix ? entry.textureMatrix : null;
if (entry.source != null) {
activeGroups.replayInstances(entry.source, entry.start, entry.end, layer, material, entry.entityId,
replayTransform, texMatrix, slot, replayed);
parts += entry.end - entry.start;
} else {
replayTransform.mul(entry.modelView, replayView);
final EntityRenderCommands.Instance instance = entry.instance;
activeGroups.queueReplay(instance.template, entry.cubes, entry.scale, layer, material, replayView,
instance.light, instance.color, instance.overlay, instance.entityInfo, entry.entityId, texMatrix, slot, replayed);
parts++;
}
}
void replay(EntityRenderCommands.ModelGlint entry) {
final RenderLayer layer = replayLayer(entry, null);
for (EntityRenderCommands.Model base = entry.base; base != null; base = base.nextGlintBase) {
final RetainedTesrGroups.InstanceRange range = base.replayed;
if (glintBaseMissing(range)) continue;
activeGroups.referenceGlintLayers(range.columns, range.run, range.start, range.end, layer, entry.inputs.material,
VANILLA_GLINT_COLOR, GlintClock.firstArmorMatrix(), GlintClock.secondArmorMatrix());
parts += range.end - range.start;
}
}
void replay(EntityRenderCommands.ItemGlint entry) {
final RetainedTesrGroups.InstanceRange base = entry.base.replayed;
if (glintBaseMissing(base)) return;
final RenderLayer layer = replayLayer(entry, null);
base.columns.pose(base.end - 1, replayView);
final EntityRenderCommands.Instance instance = entry.instance;
activeGroups.queueGlintLayers(instance.template, layer, entry.inputs.material, replayView,
instance.light, instance.color, instance.overlay, instance.entityInfo, ItemGlint.firstMatrix(), ItemGlint.secondMatrix());
parts++;
}
private boolean glintBaseMissing(RetainedTesrGroups.InstanceRange base) {
if (base.columns != null) return false;
if (!loggedDroppedGlint) {
loggedDroppedGlint = true;
AngelicaMod.LOGGER.warn("Entity shadow replay dropped a glint render because its base was not drawn instanced");
}
return true;
}
void replay(EntityRenderCommands.Stream entry) {
final RenderLayer layer = replayLayer(entry, null);
replayTransform.mul(entry.modelView, replayView);
final CapturedRenderingState state = CapturedRenderingState.INSTANCE;
state.pushEntityState();
try {
state.setCurrentEntityAndItem(entry.entity, entry.item);
state.setCurrentBlockEntity(entry.blockEntity);
state.setCurrentEntityColor(entry.red, entry.green, entry.blue, entry.alpha);
activeGroups.streamVertices(layer, entry.entityId, entry.vertices, entry.vertexCount, replayView);
} finally {
state.popEntityState();
}
return EntityTextureAtlas.INSTANCE.slotFor(lastBoundGlId, shaderPipeline != null);
}
private enum TessellatorRoute { DRAW_NOW, DRAW_NOW_EXPECTED, QUEUE }
@@ -870,27 +684,23 @@ public final class ModelPartBatcher {
if (!entityFire && (mode != Mode.BLOCK_ENTITIES || !BatchEligibility.insideRenderer())) return TessellatorRoute.DRAW_NOW;
if (!tess.isDrawing || tess.drawMode != GL11.GL_QUADS || tess.vertexCount == 0 || (tess.vertexCount & 3) != 0) return TessellatorRoute.DRAW_NOW;
if (shaderPipeline != null && TessellatorEntityIds.hasIdChanges(tess)) return TessellatorRoute.DRAW_NOW;
if (DisplayListManager.isRecording() || !GLStateManager.getDepthTest().isEnabled() || !unitTexIdentity()) return TessellatorRoute.DRAW_NOW;
if (!passProgramBound()) return TessellatorRoute.DRAW_NOW;
if (DisplayListManager.isRecording() || !GLStateManager.glIsEnabled(GL11.GL_DEPTH_TEST) || !unitTexIdentity()) return TessellatorRoute.DRAW_NOW;
if (GLStateManager.getActiveProgram() != (shaderPipeline != null ? shaderPipeline.getActivePassProgramId() : 0)) return TessellatorRoute.DRAW_NOW;
final TesrMaterial material = EntityMaterials.fromCurrentState(true);
if (material == null || material == EntityMaterials.OVERLAY) return TessellatorRoute.DRAW_NOW;
if (lastBoundLocation == null || GLStateManager.getBoundTextureForServerState() != lastBoundGlId) return TessellatorRoute.DRAW_NOW;
if (!entityFire && !BatchEligibility.batchingAllowed()) return TessellatorRoute.DRAW_NOW_EXPECTED;
GLStateManager.requireExitDrawState(false);
final PassOverride pass = shaderPipeline != null ? PassOverride.capture() : PassOverride.NONE;
final EntityRenderCommands.DrawInputs inputs = liveInputs(lastBoundLocation, lastBoundGlId, material, pass, ShaderGlint.NO_TINT,
DrawState.liveCull(material), DrawState.liveLit(material), DrawState.liveLightmap(), false);
final RenderLayer layer = layerFor(lastBoundLocation, inputs);
final boolean offset = GLStateManager.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL);
final PolygonState polygon = GLStateManager.getPolygonState();
final int cullCode = DrawState.liveCull(material);
final RenderLayer layer = layerFor(lastBoundLocation, material, pass, offset ? polygon.getOffsetFactor() : 0.0f, offset ? polygon.getOffsetUnits() : 0.0f,
ShaderGlint.NO_TINT, cullCode, DrawState.liveLit(material), DrawState.liveLightmap());
// Flames carry entity_flame, which some packs may voxelize as a light source in the shadow pass
final int groupId = groupId(shaderPipeline != null, mode == Mode.ENTITIES || pass.isEntityPhase(), CapturedRenderingState.INSTANCE.getCurrentRenderedEntity(), CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity());
TessellatorStreamingDrawer.fillMissingAttributes(tess);
final Matrix4f modelView = currentModelView();
if (entityFire) {
activeGroups.streamAndRecord(layer, groupId, tess.rawBuffer, tess.vertexCount, modelView, inputs);
} else {
activeGroups.streamVertices(layer, groupId, tess.rawBuffer, tess.vertexCount, modelView);
}
activeGroups.streamVertices(layer, groupId, tess.rawBuffer, tess.vertexCount, currentModelView());
return TessellatorRoute.QUEUE;
}
@@ -983,13 +793,8 @@ public final class ModelPartBatcher {
entry.cubesInsideTexture = CubeParams.allInsideTexture(cubes);
}
private RenderLayer layerFor(ResourceLocation texture, EntityRenderCommands.DrawInputs inputs) {
return layerFor(texture, inputs.material, inputs.pass, inputs.offsetEnabled, inputs.offsetFactor, inputs.offsetUnits,
inputs.glintSlot, inputs.cull, inputs.lit, inputs.lightmap, inputs.afterGlint);
}
private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material, PassOverride pass, boolean offsetEnabled, float offsetFactor, float offsetUnits, int glintSlot, int cullCode, boolean lit, boolean lightmap, boolean afterGlint) {
return layerFor(texture, material, pass, offsetEnabled ? offsetFactor : 0, offsetEnabled ? offsetUnits : 0, glintSlot, cullCode, lit, lightmap, afterGlint);
private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cullCode, boolean lit, boolean lightmap) {
return layerFor(texture, material, pass, offsetFactor, offsetUnits, glintSlot, cullCode, lit, lightmap, false);
}
private RenderLayer layerFor(ResourceLocation texture, TesrMaterial material, PassOverride pass, float offsetFactor, float offsetUnits, int glintSlot, int cullCode, boolean lit, boolean lightmap, boolean afterGlint) {
@@ -1022,10 +827,11 @@ public final class ModelPartBatcher {
}
public void clear() {
if (AngelicaConfig.entityShadowReplayEnabled()) EntityShadowReplay.clear();
entityFireDepth = 0;
shadows.reset();
for (RetainedTesrGroups group : allGroups) group.clear();
groups.clear();
entityGroups.clear();
shadowGroups.clear();
templates.clear();
entitySource.discard();
bufferSource.discard();
@@ -1046,7 +852,7 @@ public final class ModelPartBatcher {
}
public String getDebugString() {
final String line = String.format("Parts: %d grp, inst %d, live %d", sumGroups(RetainedTesrGroups::groupCount), parts - lastParts, liveFallbacks - lastLiveFallbacks);
final String line = String.format("Parts: %d grp, inst %d, live %d", groups.groupCount() + entityGroups.groupCount() + shadowGroups.groupCount(), parts - lastParts, liveFallbacks - lastLiveFallbacks);
lastParts = parts;
lastLiveFallbacks = liveFallbacks;
return line;
@@ -1,36 +0,0 @@
package com.gtnewhorizons.angelica.rendering.tesr;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.function.Consumer;
import java.util.function.Supplier;
/** Reuses per-frame objects. */
final class Pool<T> {
private final ObjectArrayList<T> entries = new ObjectArrayList<>();
private final Supplier<T> factory;
private final Consumer<T> reset;
private int used;
Pool(Supplier<T> factory, Consumer<T> reset) {
this.factory = factory;
this.reset = reset;
}
T acquire() {
if (used == entries.size()) entries.add(factory.get());
return entries.get(used++);
}
int size() { return used; }
void clear() {
for (int i = 0; i < used; i++) reset.accept(entries.get(i));
used = 0;
}
void release() {
clear();
entries.clear();
}
}
@@ -48,7 +48,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
private final Matrix4f baseMV = new Matrix4f();
private final Matrix4f baseMVInv = new Matrix4f();
private final Matrix4f scratchMat = new Matrix4f();
private final Matrix4f replayModelView = new Matrix4f();
private final Vector3f scratchVec = new Vector3f();
private final Matrix4f drawMV = new Matrix4f();
private final Matrix4f savedMV = new Matrix4f();
@@ -96,31 +95,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
final IntArrayList uploadCounts = new IntArrayList();
}
static final class InstanceRange {
InstanceColumns columns;
TexRun run;
int start, end;
boolean reuseUpload;
void set(InstanceColumns columns, TexRun run, int start, int end) {
this.columns = columns;
this.run = run;
this.start = start;
this.end = end;
}
void reset() {
columns = null;
run = null;
start = end = 0;
}
void clear() {
reset();
reuseUpload = false;
}
}
static final class InstanceColumns {
final ObjectArrayList<TemplateBuffer> templates = new ObjectArrayList<>();
final ObjectArrayList<CubeParams[]> cubes = new ObjectArrayList<>();
@@ -135,20 +109,17 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
float[] matrices = new float[16 * 64];
int size;
int referencedParts;
long version;
boolean hasInstances() {
return size > 0 || referencedParts > 0;
}
private void reserveMatrices(int count) {
final int need = (size + count) * 16;
if (need > matrices.length) matrices = Arrays.copyOf(matrices, Math.max(need, matrices.length << 1));
}
void add(TemplateBuffer templateOrNull, Matrix4fc mv, int light, int color, int overlay, long info) {
reserveMatrices(1);
mv.get(matrices, size * 16);
final int base = size * 16;
if (base + 16 > matrices.length) {
matrices = Arrays.copyOf(matrices, matrices.length << 1);
}
mv.get(matrices, base);
if (templateOrNull != null) templates.add(templateOrNull);
lights.add(light);
colors.add(color);
@@ -165,10 +136,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
return run.source != null ? null : atlasSlot(i);
}
Matrix4f pose(int i, Matrix4f dest) {
return dest.set(matrices, i * 16);
}
void addCubes(TemplateBuffer templateOrNull, CubeParams[] cubeParams, float scale, Matrix4fc mv, int light, int color, int overlay, long info, EntityTextureAtlas.Slot atlasSlot) {
cubes.add(cubeParams);
atlasSlots.add(atlasSlot);
@@ -179,14 +146,17 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
void appendRange(InstanceColumns src, int start, int end, int light, int color, int overlay) {
final int n = end - start;
reserveMatrices(n);
final int need = (size + n) * 16;
if (need > matrices.length) {
matrices = Arrays.copyOf(matrices, Math.max(need, matrices.length << 1));
}
System.arraycopy(src.matrices, start * 16, matrices, size * 16, n * 16);
if (src.templates.size() == src.size) {
for (int i = start; i < end; i++) templates.add(src.templates.get(i));
}
if (src.cubes.size() == src.size) {
for (int i = start; i < end; i++) cubes.add(src.cubes.get(i));
atlasSlots.size(size + n);
atlasSlots.size(atlasSlots.size() + n);
scales.addElements(scales.size(), src.scales.elements(), start, n);
}
fill(lights, n, light);
@@ -196,52 +166,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
size += n;
}
void appendReplayRange(InstanceColumns src, int start, int end, Matrix4fc transform, EntityTextureAtlas.Slot atlasSlot) {
final int n = end - start;
reserveMatrices(n);
// Modelview matrices are affine, so only the 3x4 upper part needs multiplying.
// Also, JOML would copy each matrix into a Matrix4f and back out, so this is faster to do by hand
final float t00 = transform.m00(), t01 = transform.m01(), t02 = transform.m02();
final float t10 = transform.m10(), t11 = transform.m11(), t12 = transform.m12();
final float t20 = transform.m20(), t21 = transform.m21(), t22 = transform.m22();
final float t30 = transform.m30(), t31 = transform.m31(), t32 = transform.m32();
final float[] s = src.matrices;
final float[] d = matrices;
for (int in = start * 16, out = size * 16, stop = end * 16; in < stop; in += 16, out += 16) {
for (int c = 0; c < 12; c += 4) {
final float x = s[in + c], y = s[in + c + 1], z = s[in + c + 2];
d[out + c] = t00 * x + t10 * y + t20 * z;
d[out + c + 1] = t01 * x + t11 * y + t21 * z;
d[out + c + 2] = t02 * x + t12 * y + t22 * z;
d[out + c + 3] = 0.0f;
}
final float x = s[in + 12], y = s[in + 13], z = s[in + 14];
d[out + 12] = t00 * x + t10 * y + t20 * z + t30;
d[out + 13] = t01 * x + t11 * y + t21 * z + t31;
d[out + 14] = t02 * x + t12 * y + t22 * z + t32;
d[out + 15] = 1.0f;
}
appendObjects(templates, src.templates, start, n);
lights.addElements(lights.size(), src.lights.elements(), start, n);
colors.addElements(colors.size(), src.colors.elements(), start, n);
overlays.addElements(overlays.size(), src.overlays.elements(), start, n);
infos.addElements(infos.size(), src.infos.elements(), start, n);
if (!src.cubes.isEmpty()) {
appendObjects(cubes, src.cubes, start, n);
scales.addElements(scales.size(), src.scales.elements(), start, n);
atlasSlots.size(size + n);
if (atlasSlot != null) {
Arrays.fill(atlasSlots.elements(), size, size + n, atlasSlot);
anyAtlasSlot = true;
}
}
size += n;
}
private static <T> void appendObjects(ObjectArrayList<T> dst, ObjectArrayList<T> src, int start, int count) {
dst.addElements(dst.size(), src.elements(), start, count);
}
private static void fill(IntArrayList list, int n, int value) {
final int from = list.size();
list.size(from + n);
@@ -249,7 +173,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
}
void clear() {
version++;
templates.clear();
cubes.clear();
atlasSlots.clear();
@@ -541,7 +464,11 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
return head;
}
private void replayRange(InstanceColumns cols, TexRun original, int start, int end, int instances, Matrix4f matrix, boolean stableMatrix) {
void replayRange(InstanceColumns cols, TexRun original, int start, int end, int instances, Matrix4f matrix) {
replayRange(cols, original, start, end, instances, matrix, false);
}
void replayRange(InstanceColumns cols, TexRun original, int start, int end, int instances, Matrix4f matrix, boolean stableMatrix) {
TexRun head = null;
for (int i = cols.runs.size() - 1; i >= 0; i--) {
final TexRun candidate = cols.runs.get(i);
@@ -753,17 +680,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
}
void streamVertices(RenderLayer layer, int id, int[] packed, int vertexCount, Matrix4fc mv) {
EntityShadowReplay.rejectForeignDraw();
storeVertices(layer, id, packed, vertexCount, mv);
}
void streamAndRecord(RenderLayer layer, int id, int[] packed, int vertexCount, Matrix4fc mv, EntityRenderCommands.DrawInputs inputs) {
final boolean record = recordsEntitySubmission(inputs);
storeVertices(layer, id, packed, vertexCount, mv);
if (record) EntityShadowReplay.recordStream(mv, packed, vertexCount, id, inputs);
}
private void storeVertices(RenderLayer layer, int id, int[] packed, int vertexCount, Matrix4fc mv) {
if (glintCapture != null) glintCapture.invalidate();
source.getBuffer(layer, id).addPackedVertices(packed, vertexCount, mv, scratchVec);
streamedInstances++;
@@ -778,42 +694,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
}
void queue(long scope, TemplateBuffer template, CubeParams[] cubes, float scale, RenderLayer layer, TesrMaterial material, Matrix4f currentMV, int packedLight, int colorABGR, int overlayABGR, long entityInfo, int id, Matrix4f texMatrix, EntityTextureAtlas.Slot atlasSlot) {
EntityShadowReplay.rejectForeignDraw();
storeModel(scope, template, cubes, scale, layer, material, currentMV, packedLight, colorABGR, overlayABGR, entityInfo, id, texMatrix, atlasSlot, null);
}
void queueAndRecord(TemplateBuffer template, CubeParams[] cubes, float scale, RenderLayer layer, Matrix4f currentMV,
int packedLight, int colorABGR, int overlayABGR, long entityInfo, int id, Matrix4f texMatrix,
EntityTextureAtlas.Slot atlasSlot, boolean cubesInsideTexture, EntityRenderCommands.DrawInputs inputs) {
final boolean record = recordsEntitySubmission(inputs);
final InstanceColumns captured = storeModel(0, template, cubes, scale, layer, inputs.material, currentMV, packedLight,
colorABGR, overlayABGR, entityInfo, id, texMatrix, atlasSlot, null);
if (!record) return;
// Replay copies templates by index, so a range can only be shared when every instance has one.
final InstanceColumns source = captured != null && captured.templates.size() == captured.size ? captured : null;
final EntityRenderCommands.Model entry = EntityShadowReplay.recordModel(source, layer, inputs, id, cubesInsideTexture, currentMV, texMatrix);
if (entry != null && source == null) {
entry.instance.set(template, packedLight, colorABGR, overlayABGR, entityInfo);
entry.cubes = cubes;
entry.scale = scale;
}
}
private boolean recordsEntitySubmission(EntityRenderCommands.DrawInputs inputs) {
if (!EntityShadowReplay.isRecording()) return false;
if (!ModelPartBatcher.INSTANCE.recordsEntitySubmissions(this)) {
EntityShadowReplay.rejectForeignDraw();
return false;
}
GLStateManager.requireExitTextureFilter(inputs.textureId, inputs.material.isUnfilteredAtlas());
return true;
}
private boolean keepsBaseUpload(InstanceRange replayed) {
return replayed != null && replayed.reuseUpload || glintCapture != null && glintCapture.keepsBaseUpload();
}
private InstanceColumns storeModel(long scope, TemplateBuffer template, CubeParams[] cubes, float scale, RenderLayer layer, TesrMaterial material, Matrix4f currentMV, int packedLight, int colorABGR, int overlayABGR, long entityInfo, int id, Matrix4f texMatrix, EntityTextureAtlas.Slot atlasSlot, InstanceRange replayed) {
final Group group = groupFor(layer, material, id);
touch(group, layer);
if (group.couplable) linkDepthScope(group, scope);
@@ -824,7 +704,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
if (group.probe != null && group.probe.probing && (instancedPath || segments)) {
probe(group, template, currentMV, packedLight, colorABGR, texMatrix);
}
final boolean capturingBase = keepsBaseUpload(replayed);
final boolean capturingBase = glintCapture != null && glintCapture.capturesBase();
if (instancedPath && cubes != null && material.shader() == null) {
final TexRun run = group.openOrExtendRun(group.cubeColumns, texMatrix, false, capturingBase && group.opaque);
group.cubeColumns.addCubes(deferred != null ? template : null, cubes, scale, currentMV, packedLight, colorABGR, overlayABGR, entityInfo, atlasSlot);
@@ -832,8 +712,7 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
run.parts++;
run.instances += cubes.length;
if (glintCapture != null) glintCapture.record(group, group.cubeColumns, run, group.cubeColumns.size - 1, group.cubeColumns.size, cubes.length);
if (replayed != null) replayed.set(group.cubeColumns, run, group.cubeColumns.size - 1, group.cubeColumns.size);
return deferred != null ? group.cubeColumns : null;
return;
}
if (instancedPath) {
final TexRun run = group.openOrExtendRun(group.templateColumns, texMatrix, false, capturingBase && group.opaque);
@@ -842,14 +721,13 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
run.parts++;
run.instances++;
if (glintCapture != null) glintCapture.record(group, group.templateColumns, run, group.templateColumns.size - 1, group.templateColumns.size, 1);
if (replayed != null) replayed.set(group.templateColumns, run, group.templateColumns.size - 1, group.templateColumns.size);
return group.templateColumns;
return;
}
if (group.stream && glintCapture != null) glintCapture.invalidate();
if (segments) {
source.getBuffer(layer, id).addTemplateInstance(template, currentMV, scratchVec, colorABGR, packedLight, texMatrix);
streamedInstances++;
return null;
return;
}
if (!group.stream) {
final DepthEqualSet set = activeDepthEqualSet(group);
@@ -869,62 +747,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
if (!group.dirty && !group.snapshot.matches(index, template, cols.matrices, index * 16, packedLight, colorABGR, bakedTexMatrix)) {
group.dirty = true;
}
return null;
}
void replayInstances(InstanceColumns src, int start, int end, RenderLayer layer, TesrMaterial material, int id,
Matrix4fc transform, Matrix4f texMatrix, EntityTextureAtlas.Slot atlasSlot, InstanceRange replayed) {
if (replayed != null) replayed.reset();
final boolean cubes = !src.cubes.isEmpty();
final Group group = streamsInstanced(material) && (!cubes || material.shader() == null) ? groupFor(layer, material, id) : null;
if (group != null) touch(group, layer);
if (group != null && drawsInstanced(group) && (group.probe == null || !group.probe.probing)) {
group.queuedThisFrame = true;
final InstanceColumns dst = cubes ? group.cubeColumns : group.templateColumns;
final TexRun run = group.openOrExtendRun(dst, texMatrix, false, keepsBaseUpload(replayed) && group.opaque);
final int first = dst.size;
dst.appendReplayRange(src, start, end, transform, atlasSlot);
run.last.end = dst.size;
run.parts += end - start;
final int instances = instanceCount(src, start, end, cubes);
run.instances += instances;
if (glintCapture != null) glintCapture.record(group, dst, run, first, dst.size, instances);
if (replayed != null) replayed.set(dst, run, first, dst.size);
} else {
final CapturedRenderingState state = CapturedRenderingState.INSTANCE;
state.pushEntityState();
try {
for (int i = start; i < end; i++) {
transform.mul(src.pose(i, replayModelView), replayModelView);
storeReplay(src.templates.get(i), cubes ? src.cubes.get(i) : null, cubes ? src.scales.getFloat(i) : 1,
layer, material, replayModelView, src.lights.getInt(i), src.colors.getInt(i), src.overlays.getInt(i),
src.infos.getLong(i), id, texMatrix, atlasSlot, null);
}
} finally {
state.popEntityState();
}
}
}
void queueReplay(TemplateBuffer template, CubeParams[] cubes, float scale, RenderLayer layer, TesrMaterial material,
Matrix4f modelView, int light, int color, int overlay, long entityInfo, int id, Matrix4f texMatrix, EntityTextureAtlas.Slot atlasSlot, InstanceRange replayed) {
if (replayed != null) replayed.reset();
final CapturedRenderingState state = CapturedRenderingState.INSTANCE;
state.pushEntityState();
try {
storeReplay(template, cubes, scale, layer, material, modelView, light, color, overlay, entityInfo, id, texMatrix, atlasSlot, replayed);
} finally {
state.popEntityState();
}
}
private void storeReplay(TemplateBuffer template, CubeParams[] cubes, float scale, RenderLayer layer, TesrMaterial material,
Matrix4f modelView, int light, int color, int overlay, long entityInfo, int id, Matrix4f texMatrix, EntityTextureAtlas.Slot atlasSlot, InstanceRange replayed) {
final CapturedRenderingState state = CapturedRenderingState.INSTANCE;
state.setCurrentEntityAndItem(InstancedAttribs.unpackId(entityInfo, 0), InstancedAttribs.unpackId(entityInfo, 32));
state.setCurrentBlockEntity(InstancedAttribs.unpackId(entityInfo, 16));
AngelicaBufferSource.setEntityColor(overlay);
storeModel(0, template, cubes, scale, layer, material, modelView, light, color, overlay, entityInfo, id, texMatrix, atlasSlot, replayed);
}
private void linkDepthScope(Group group, long scope) {
@@ -1066,7 +888,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
}
void copyInstances(InstanceColumns src, int start, int end, RenderLayer layer, TesrMaterial material, int packedLight, int colorABGR, int overlayABGR, Matrix4f texMatrix) {
EntityShadowReplay.rejectForeignDraw();
final Group group = groupFor(layer, material, 0);
touch(group, layer);
final boolean cubes = !src.cubes.isEmpty();
@@ -1086,7 +907,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
}
void referenceInstances(InstanceColumns src, int start, int end, RenderLayer layer, TesrMaterial material, int colorABGR, Matrix4f texMatrix) {
EntityShadowReplay.rejectForeignDraw();
final Group group = groupFor(layer, material, 0);
touch(group, layer);
final boolean cubes = !src.cubes.isEmpty();
@@ -1101,7 +921,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
}
void referenceGlintLayers(InstanceColumns src, TexRun sourceRun, int start, int end, RenderLayer layer, TesrMaterial material, int colorABGR, Matrix4f first, Matrix4f second) {
EntityShadowReplay.rejectForeignDraw();
final Group group = groupFor(layer, material, 0);
touch(group, layer);
final boolean cubes = !src.cubes.isEmpty();
@@ -1116,7 +935,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
}
void queueGlintLayers(TemplateBuffer template, RenderLayer layer, TesrMaterial material, Matrix4f currentMV, int packedLight, int colorABGR, int overlayABGR, long entityInfo, Matrix4f first, Matrix4f second) {
EntityShadowReplay.rejectForeignDraw();
final Group group = groupFor(layer, material, 0);
touch(group, layer);
final InstanceColumns dst = group.templateColumns;
@@ -1129,11 +947,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
group.replayRange(dst, run, index, index + 1, 1, second, true);
}
void replayRange(Group group, InstanceColumns columns, TexRun original, int start, int end, int instances, Matrix4f matrix) {
EntityShadowReplay.rejectForeignDraw();
group.replayRange(columns, original, start, end, instances, matrix, false);
}
private static int instanceCount(InstanceColumns src, int start, int end, boolean cubes) {
if (!cubes) return end - start;
int instances = 0;
@@ -1626,7 +1439,6 @@ final class RetainedTesrGroups implements AngelicaBufferSource.LayerDrawHook {
void clear() {
for (final ObjectArrayList<Group> list : byLayer.values()) {
for (int i = 0, n = list.size(); i < n; i++) {
list.get(i).clearInstances();
list.get(i).mesh.delete();
}
}
@@ -5,7 +5,6 @@ import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GlintColorHandler;
import com.gtnewhorizons.angelica.iris.IrisDisplayListState;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
import net.coderbot.iris.pipeline.ShadowRenderer;
import net.irisshaders.iris.api.v0.IrisApi;
@@ -59,7 +58,6 @@ public final class ShaderGlint {
private ShaderGlint() {}
public static void beginGlint() {
if (ShadowRenderer.ACTIVE) EntityShadowReplay.rejectShadowOnly();
IrisDisplayListState.recordGlintSpan(true);
// If a glint span is already active, keep the outer span's saved state and
@@ -122,12 +120,7 @@ public final class ShaderGlint {
}
public static void bindTintedGlint(int slot) {
if (slot < 0) return;
if (ShadowRenderer.ACTIVE) {
EntityShadowReplay.rejectShadowOnly();
return;
}
if (!IrisApi.getInstance().isShaderPackInUse()) return;
if (slot < 0 || ShadowRenderer.ACTIVE || !IrisApi.getInstance().isShaderPackInUse()) return;
final ITextureObject mask = Minecraft.getMinecraft().getTextureManager().getTexture(GLINT_MASK);
if (mask == null || GLStateManager.getBoundTextureForServerState() != mask.getGlTextureId()) return;
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, slotTextures[slot]);
@@ -847,6 +847,29 @@ public class SodiumGameOptionPages {
.build());
}
groups.add(OptionGroup.createBuilder()
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
.setName(I18n.format("options.angelica.menuBlur"))
.setTooltip(I18n.format("options.angelica.menuBlur.tooltip"))
.setControl(TickBoxControl::new)
.setImpact(OptionImpact.LOW)
.setBinding((opts, value) -> AngelicaConfig.enableMenuBlur = value, opts -> AngelicaConfig.enableMenuBlur)
.build())
.add(OptionImpl.createBuilder(int.class, angelicaOpts)
.setName(I18n.format("options.angelica.menuBlurRadius"))
.setTooltip(I18n.format("options.angelica.menuBlurRadius.tooltip"))
.setControl(option -> new SliderControl(option, 1, 100, 1, ControlValueFormatter.number()))
.setImpact(OptionImpact.LOW)
.setBinding((opts, value) -> AngelicaConfig.menuBlurRadius = value, opts -> AngelicaConfig.menuBlurRadius)
.build())
.add(OptionImpl.createBuilder(int.class, angelicaOpts)
.setName(I18n.format("options.angelica.menuBlurFadeTime"))
.setTooltip(I18n.format("options.angelica.menuBlurFadeTime.tooltip"))
.setControl(option -> new SliderControl(option, 0, 2000, 50, ControlValueFormatter.milliseconds()))
.setBinding((opts, value) -> AngelicaConfig.menuBlurFadeTime = value, opts -> AngelicaConfig.menuBlurFadeTime)
.build())
.build());
return new OptionPage(I18n.format("sodium.options.pages.appearance"), ImmutableList.copyOf(groups));
}
@@ -43,6 +43,10 @@ public interface ControlValueFormatter {
return (v) -> v + "s";
}
static ControlValueFormatter milliseconds() {
return (v) -> v + "ms";
}
static ControlValueFormatter quantity(String name) {
return (v) -> I18n.format(name, v);
}
@@ -205,13 +205,17 @@ public class AngelicaBufferSource implements Groupable {
private void restoreIrisState() {
if (!irisStateSaved) return;
CapturedRenderingState.INSTANCE.popEntityState();
CapturedRenderingState.INSTANCE.popCurrentEntityColor();
CapturedRenderingState.INSTANCE.popCurrentBlockEntity();
CapturedRenderingState.INSTANCE.popCurrentEntityAndItem();
irisStateSaved = false;
}
private void saveIrisState() {
if (irisStateSaved) return;
CapturedRenderingState.INSTANCE.pushEntityState();
CapturedRenderingState.INSTANCE.pushCurrentEntityAndItem();
CapturedRenderingState.INSTANCE.pushCurrentBlockEntity();
CapturedRenderingState.INSTANCE.pushCurrentEntityColor();
irisStateSaved = true;
}
@@ -17,7 +17,6 @@ public class AlphaTestStorage {
@Getter
private static boolean alphaTestLocked;
@Getter
private static boolean overrideHeld;
public static void overrideAlphaTest(AlphaTest override) {
@@ -42,17 +42,12 @@ public final class Program extends GlResource {
}
public void use() {
final boolean suspended = GLStateManager.suspendExitCapture();
try {
RenderSystem.memoryBarrier(GL42.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL42.GL_TEXTURE_FETCH_BARRIER_BIT | GL43.GL_SHADER_STORAGE_BARRIER_BIT);
bindManaged(getGlId());
RenderSystem.memoryBarrier(GL42.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL42.GL_TEXTURE_FETCH_BARRIER_BIT | GL43.GL_SHADER_STORAGE_BARRIER_BIT);
bindManaged(getGlId());
uniforms.update();
samplers.update();
images.update();
} finally {
GLStateManager.resumeExitCapture(suspended);
}
uniforms.update();
samplers.update();
images.update();
}
static void bindManaged(int program) {
@@ -66,14 +61,9 @@ public final class Program extends GlResource {
}
public static void unbind() {
final boolean suspended = GLStateManager.suspendExitCapture();
try {
ProgramUniforms.clearActiveUniforms();
ProgramSamplers.clearActiveSamplers();
GLStateManager.glUseProgram(0);
} finally {
GLStateManager.resumeExitCapture(suspended);
}
ProgramUniforms.clearActiveUniforms();
ProgramSamplers.clearActiveSamplers();
GLStateManager.glUseProgram(0);
}
@Override
@@ -1,6 +1,5 @@
package net.coderbot.iris.layer;
import net.coderbot.iris.gbuffer_overrides.matching.RenderCondition;
import net.coderbot.iris.gbuffer_overrides.matching.SpecialCondition;
import net.coderbot.iris.pipeline.WorldRenderingPhase;
@@ -61,11 +60,6 @@ public final class PassOverride {
return phase == WorldRenderingPhase.ENTITIES;
}
public RenderCondition entityCondition(boolean defaultTranslucent) {
if (special != null || phase != null && phase != WorldRenderingPhase.ENTITIES) return null;
return (translucent != null ? translucent : defaultTranslucent) ? RenderCondition.ENTITIES_TRANSLUCENT : RenderCondition.ENTITIES;
}
public String nameSuffix() {
if (special == null && translucent == null && phase == null) {
return "";
@@ -1437,24 +1437,10 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
void rebindProgram() {
if (program != null) program.use();
final boolean suspended = GLStateManager.suspendExitCapture();
try {
DeferredWorldRenderingPipeline.this.customUniforms.push(this);
} finally {
GLStateManager.resumeExitCapture(suspended);
}
DeferredWorldRenderingPipeline.this.customUniforms.push(this);
}
private void useNow() {
final boolean suspended = GLStateManager.suspendExitCapture();
try {
applyState();
} finally {
GLStateManager.resumeExitCapture(suspended);
}
}
private void applyState() {
DepthColorStorage.unlockDepthColor();
if (isBeforeTranslucent) {
@@ -1499,19 +1485,14 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
}
public void stopUsing() {
final boolean suspended = GLStateManager.suspendExitCapture();
try {
DepthColorStorage.unlockDepthColor();
DepthColorStorage.unlockDepthColor();
if (alphaTestOverride != null) {
AlphaTestOverride.restore();
}
if (alphaTestOverride != null) {
AlphaTestOverride.restore();
}
if (blendModeOverride != null || (bufferBlendOverrides != null && !bufferBlendOverrides.isEmpty())) {
BlendModeOverride.restore();
}
} finally {
GLStateManager.resumeExitCapture(suspended);
if (blendModeOverride != null || (bufferBlendOverrides != null && !bufferBlendOverrides.isEmpty())) {
BlendModeOverride.restore();
}
}
@@ -1623,11 +1604,6 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
return current != null && current.instancedVariant(kind) != null;
}
public boolean hasInstancedVariant(RenderCondition condition, InputAvailability availability, Instancing kind) {
final Pass pass = table.match(condition, availability);
return pass != null && pass.instancedVariant(kind) != null;
}
@Override
public void bindInstancedVariant(Instancing kind) {
activeInstancing = kind;
@@ -2520,34 +2496,25 @@ public class DeferredWorldRenderingPipeline implements WorldRenderingPipeline, R
@Override
public void onBindTexture(int id) {
if (shouldBindPBR && isRenderingWorld) {
final boolean suspended = GLStateManager.suspendExitCapture();
final PBRTextureHolder pbrHolder = PBRTextureManager.INSTANCE.getOrLoadHolder(id);
currentNormalTexture = pbrHolder.getNormalTexture().getGlTextureId();
currentSpecularTexture = pbrHolder.getSpecularTexture().getGlTextureId();
BatchStateGuard.suspend();
try {
updatePbrTextures(id);
final TextureFormat textureFormat = TextureFormatLoader.getFormat();
if (textureFormat != null) {
textureFormat.setupTextureParameters(PBRType.NORMAL, pbrHolder.getNormalTexture());
textureFormat.setupTextureParameters(PBRType.SPECULAR, pbrHolder.getSpecularTexture());
}
PBRTextureManager.notifyPBRTexturesChanged();
} finally {
GLStateManager.resumeExitCapture(suspended);
BatchStateGuard.resume();
}
}
}
private void updatePbrTextures(int id) {
final PBRTextureHolder pbrHolder = PBRTextureManager.INSTANCE.getOrLoadHolder(id);
currentNormalTexture = pbrHolder.getNormalTexture().getGlTextureId();
currentSpecularTexture = pbrHolder.getSpecularTexture().getGlTextureId();
BatchStateGuard.suspend();
try {
final TextureFormat textureFormat = TextureFormatLoader.getFormat();
if (textureFormat != null) {
textureFormat.setupTextureParameters(PBRType.NORMAL, pbrHolder.getNormalTexture());
textureFormat.setupTextureParameters(PBRType.SPECULAR, pbrHolder.getSpecularTexture());
}
PBRTextureManager.notifyPBRTexturesChanged();
} finally {
BatchStateGuard.resume();
}
}
private static CompletableFuture<Map<PatchShaderType, String>> submitCompositeTransform(ProgramSource source, TextureStage stage,
Object2ObjectMap<Tri<String, TextureType, TextureStage>, String> textureMap) {
return Iris.ShaderTransformExecutor.submitTracked(() -> TransformPatcher.patchComposite(
@@ -9,7 +9,6 @@ import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.profiling.RenderClassTimings;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import com.gtnewhorizons.angelica.rendering.tesr.PendingTesrDraws;
import com.gtnewhorizons.angelica.rendering.tesr.SectionBuiltBlockEntity;
@@ -591,12 +590,9 @@ public class ShadowRenderer {
}
for (Entity entity : renderedEntitiesList) {
final long start = Tracy.ENABLED ? System.nanoTime() : 0L;
final boolean recording = AngelicaConfig.entityShadowReplayEnabled() && EntityShadowReplay.beginEntity(entity, tickDelta);
boolean completed = false;
try {
completed = RenderManager.instance.renderEntitySimple(entity, tickDelta);
RenderManager.instance.renderEntitySimple(entity, tickDelta);
} finally {
if (recording) EntityShadowReplay.endEntity(completed);
if (Tracy.ENABLED) RenderClassTimings.SHADOW_ENTITY.add(entity.getClass(), System.nanoTime() - start);
}
}
@@ -161,18 +161,6 @@ public class CapturedRenderingState {
applyCurrentEntityColor(r, g, b, a);
}
public void pushEntityState() {
pushCurrentEntityAndItem();
pushCurrentBlockEntity();
pushCurrentEntityColor();
}
public void popEntityState() {
popCurrentEntityColor();
popCurrentBlockEntity();
popCurrentEntityAndItem();
}
private void applyCurrentEntityColor(float r, float g, float b, float a) {
if (currentEntityColor.equals(r, g, b, a)) return;
this.currentEntityColor.set(r, g, b, a);
@@ -391,6 +391,12 @@ options.angelica.disablef3=Disable F3 Additions
options.angelica.disablef3.tooltip=Disables Angelica's additions to the F3 debug screen.
options.angelica.verbosef3=Verbose F3
options.angelica.verbosef3.tooltip=Shows developer counters (fixed-function shaders, streaming, TESR batching, GPU transfer) on the F3 screen. Toggle in game with F3+V.
options.angelica.menuBlur=Menu Blur
options.angelica.menuBlur.tooltip=Blurs the world behind open menus like the inventory or the pause menu. Chat is never blurred. Excluded menus and background colors can be changed in the Angelica config.
options.angelica.menuBlurRadius=Menu Blur Strength
options.angelica.menuBlurRadius.tooltip=How strong the menu blur is. Higher values blur more.
options.angelica.menuBlurFadeTime=Menu Blur Fade Time
options.angelica.menuBlurFadeTime.tooltip=How long the menu blur takes to fade in after a menu opens. 0 shows it instantly.
angelica.debug.verbose.on=Verbose F3: shown
angelica.debug.verbose.off=Verbose F3: hidden
options.angelica.texteffects.rgb_colors=RGB Colors
@@ -1,10 +1,8 @@
package com.gtnewhorizons.angelica.mixins.early.angelica;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.hooks.FrameHooks;
import com.gtnewhorizons.angelica.rendering.GlintClock;
import com.gtnewhorizons.angelica.rendering.culling.GpuCulling;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
import net.minecraft.client.Minecraft;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@@ -29,9 +27,8 @@ public class MixinMinecraft_FrameHook {
}
@Inject(method = "runGameLoop", at = @At("HEAD"))
private void angelica$beginRenderFrame(CallbackInfo ci) {
private void angelica$beginGlintFrame(CallbackInfo ci) {
GlintClock.beginFrame(Minecraft.getSystemTime());
if (AngelicaConfig.entityShadowReplayEnabled()) EntityShadowReplay.beginFrame();
}
@Inject(method = "func_147120_f"/*resetSize*/, at = @At("HEAD"))
@@ -0,0 +1,19 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.blur;
import com.gtnewhorizons.angelica.blur.MenuBlur;
import net.minecraft.client.renderer.EntityRenderer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(EntityRenderer.class)
public class MixinEntityRenderer_MenuBlur {
@Inject(
method = "updateCameraAndRender",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/EntityRenderer;renderWorld(FJ)V", shift = At.Shift.AFTER))
private void angelica$blurWorldBehindGui(float partialTicks, CallbackInfo ci) {
MenuBlur.renderBlur();
}
}
@@ -0,0 +1,22 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.blur;
import com.gtnewhorizons.angelica.blur.MenuBlur;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import net.minecraft.client.gui.GuiScreen;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.Constant;
import org.spongepowered.asm.mixin.injection.ModifyConstant;
@Mixin(GuiScreen.class)
public class MixinGuiScreen_MenuBlur {
@ModifyConstant(method = "drawWorldBackground", constant = @Constant(intValue = 0xC0101010))
private int angelica$blurGradientTop(int original) {
return MenuBlur.getBackgroundColor(original, AngelicaConfig.menuBlurGradientTopColor);
}
@ModifyConstant(method = "drawWorldBackground", constant = @Constant(intValue = 0xD0101010))
private int angelica$blurGradientBottom(int original) {
return MenuBlur.getBackgroundColor(original, AngelicaConfig.menuBlurGradientBottomColor);
}
}
@@ -1,19 +0,0 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.entity;
import com.gtnewhorizons.angelica.experimental.surround.Surround;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
import net.minecraft.entity.boss.BossStatus;
import net.minecraft.entity.boss.IBossDisplayData;
import org.spongepowered.asm.mixin.Mixin;
@Mixin(BossStatus.class)
public class MixinBossStatus_EntityReplay {
@Surround(method = "setBossStatus")
private static void angelica$observeBossStatus() {
}
@Surround.Return
private static void angelica$recordBossStatus(IBossDisplayData boss, boolean colorModifier) {
EntityShadowReplay.recordBossStatus(boss, colorModifier);
}
}
@@ -1,28 +0,0 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.entity;
import com.gtnewhorizons.angelica.experimental.surround.Surround;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
import net.minecraft.client.renderer.RenderGlobal;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.Entity;
import net.minecraftforge.client.MinecraftForgeClient;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Slice;
@Mixin(value = RenderGlobal.class, priority = 900) // Lower priority due to Tracy
public class MixinRenderGlobal_EntityReplay {
@Surround(method = "renderEntities",
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/RenderManager;renderEntitySimple(Lnet/minecraft/entity/Entity;F)Z"),
slice = @Slice(
from = @At(value = "INVOKE_STRING", target = "Lnet/minecraft/profiler/Profiler;endStartSection(Ljava/lang/String;)V", args = "ldc=entities"),
to = @At(value = "INVOKE_STRING", target = "Lnet/minecraft/profiler/Profiler;endStartSection(Ljava/lang/String;)V", args = "ldc=blockentities")))
private void angelica$replayEntity(RenderManager manager, Entity entity, float partialTicks) {
@Surround.Skip final boolean replayed = EntityShadowReplay.replay(manager, entity, partialTicks, MinecraftForgeClient.getRenderPass());
}
@Surround.Skipped
private boolean angelica$entityReplayed() {
return true;
}
}
@@ -1,10 +1,8 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.entity;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.IBatchEligibility;
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.Entity;
@@ -19,15 +17,11 @@ public class MixinRenderManager_BatchEligibility {
private void angelica$observeDoRender(Render render, Entity entity, double x, double y, double z, float yaw, float partialTicks) {
final IBatchEligibility holder = (IBatchEligibility) render;
final byte state = holder.angelica$batchState();
final boolean recording = AngelicaConfig.entityShadowReplayEnabled() && EntityShadowReplay.beginRenderer(render, entity, partialTicks, state);
BatchEligibility.begin(state, GLStateManager.drawCalls);
boolean completed = false;
try {
render.doRender(entity, x, y, z, yaw, partialTicks);
completed = true;
} finally {
final byte next = BatchEligibility.end(state, GLStateManager.drawCalls);
if (recording) EntityShadowReplay.endRenderer(completed, next);
if (next != state) {
holder.angelica$setBatchState(next);
BatchEligibility.onStateChange(render, next);
@@ -3,7 +3,6 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.entity;
import com.gtnewhorizons.angelica.experimental.surround.Surround;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.rendering.tesr.BatchEligibility;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
import net.minecraft.client.renderer.entity.Render;
import org.spongepowered.asm.mixin.Mixin;
@@ -13,7 +12,6 @@ public abstract class MixinRender_NameTagDraws {
@Surround(method = "func_147906_a")
private void angelica$countNameTagDraws() {
EntityShadowReplay.rejectForeignDraw();
BatchEligibility.beginExpectedDraws(GLStateManager.drawCalls);
}
@@ -5,7 +5,6 @@ import com.gtnewhorizons.angelica.helpers.RendererLivingEntityHelper;
import com.gtnewhorizons.angelica.rendering.GlintClock;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
import com.gtnewhorizons.angelica.rendering.tesr.GlintCapture;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import com.llamalad7.mixinextras.injector.ModifyExpressionValue;
@@ -143,7 +142,6 @@ public abstract class MixinRendererLivingEntity_GlintClock {
@Inject(method = "doRender(Lnet/minecraft/entity/EntityLivingBase;DDDFF)V",
at = @At(value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;error(Ljava/lang/String;Ljava/lang/Throwable;)V", remap = false))
private void angelica$closeAfterFailedRender(CallbackInfo ci) {
EntityShadowReplay.rejectRenderFailed();
if (angelica$capturingBase) {
angelica$capturingBase = false;
GLStateManager.glPopMatrix();
@@ -1,7 +1,6 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.textures;
import com.gtnewhorizons.angelica.glsm.hooks.TextureUtilHooks;
import com.gtnewhorizons.angelica.experimental.surround.Surround;
import net.minecraft.client.renderer.texture.TextureUtil;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
@@ -21,29 +20,9 @@ public class MixinTextureUtil {
TextureUtilHooks.setMaxLevel(mipmapLevels);
}
@Surround(id = "saveFilter", method = "func_152777_a(ZZF)V")
private static void angelica$beginFilterSave(boolean blurred, boolean mipmap, float aniso) {
@Surround.Carry boolean captured = TextureUtilHooks.beginFilterSave(blurred, mipmap, aniso);
}
@Surround.Catch("saveFilter")
private static void angelica$filterSaveFailed(Throwable failure, @Surround.Carry boolean captured) {
TextureUtilHooks.filterFailed(captured);
}
@Surround.Finally("saveFilter")
private static void angelica$finishFilterSave(@Surround.Carry boolean captured) {
TextureUtilHooks.endFilterSave(captured);
}
@Surround(id = "restoreFilter", method = "func_147945_b()V")
private static void angelica$beginFilterRestore() {
@Surround.Carry boolean captured = TextureUtilHooks.beginFilterRestore();
}
@Surround.Catch("restoreFilter")
private static void angelica$filterRestoreFailed(Throwable failure, @Surround.Carry boolean captured) {
TextureUtilHooks.filterFailed(captured);
@Inject(method = "func_152777_a(ZZF)V", at = @At("HEAD"))
private static void angelica$captureSaveBoundId(boolean blurred, boolean mipmap, float aniso, CallbackInfo ci) {
TextureUtilHooks.captureBoundFilterTexture();
}
@Redirect(method = "func_147945_b()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/texture/TextureUtil;func_147952_b(II)V"))
@@ -56,8 +35,8 @@ public class MixinTextureUtil {
TextureUtilHooks.restoreAniso(aniso);
}
@Surround.Finally("restoreFilter")
private static void angelica$finishFilterRestore(@Surround.Carry boolean captured) {
TextureUtilHooks.endFilterRestore(captured);
@Inject(method = "func_147945_b()V", at = @At("RETURN"))
private static void angelica$clearSavedAfterRestore(CallbackInfo ci) {
TextureUtilHooks.clearSavedFilterTexture();
}
}
@@ -1,11 +1,9 @@
package com.gtnewhorizons.angelica.mixins.early.shaders;
import com.gtnewhorizons.angelica.compat.mojang.Camera;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.experimental.surround.Surround;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
import com.gtnewhorizons.angelica.rendering.tesr.EntityShadowReplay;
import com.llamalad7.mixinextras.sugar.Local;
import jss.notfine.core.SettingsManager;
import net.coderbot.iris.Iris;
@@ -90,7 +88,6 @@ public abstract class MixinEntityRenderer implements IResourceManagerReloadListe
if (renderer != null) {
renderer.setShadowPassPlayerViewport(((ViewportProvider) playerFrustum).sodium$createViewport());
}
if (AngelicaConfig.entityShadowReplayEnabled()) EntityShadowReplay.setMainView(playerFrustum);
Iris.getPipelineManager().getPipelineNullable().renderShadows((EntityRenderer) (Object) this, Camera.INSTANCE);
}
@@ -1,13 +1,9 @@
package com.gtnewhorizons.angelica.rendering.tesr;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.ExitState;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource;
import net.coderbot.iris.layer.PassOverride;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.ITextureObject;
import net.minecraft.client.renderer.texture.TextureManager;
@@ -16,9 +12,6 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
@@ -32,11 +25,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static com.gtnewhorizons.angelica.rendering.tesr.RetainedTesrTestSupport.cubes;
import static com.gtnewhorizons.angelica.rendering.tesr.RetainedTesrTestSupport.onlyGroup;
import static com.gtnewhorizons.angelica.rendering.tesr.RetainedTesrTestSupport.template;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -75,7 +63,6 @@ class EntityTextureAtlasGLTest {
@AfterEach
void tearDown() {
GLStateManager.cancelExitCapture();
EntityTextureAtlas.INSTANCE.freePages();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glDeleteFramebuffers(passFbo);
@@ -88,66 +75,6 @@ class EntityTextureAtlasGLTest {
return 0xFF000000 | (x * 50 + seed) << 16 | (y * 90 + seed) << 8 | 0x20;
}
@ParameterizedTest
@CsvSource({"false, false", "true, false", "false, true", "true, true"})
void entityReplayResolvesAtlasRegionsWithoutRemappingSharedGlintUploads(boolean shared, boolean glint) {
final boolean atlasEnabled = AngelicaConfig.enableEntityTextureAtlas;
final ModelPartBatcher batcher = ModelPartBatcher.INSTANCE;
final RetainedTesrGroups previousGroups = Reflect.get(batcher, "activeGroups");
final RetainedTesrGroups groups = new RetainedTesrGroups(new AngelicaBufferSource());
final int otherTexture = texture(GL11.GL_NEAREST, 12);
groups.beginPass(new Matrix4f(), 0, 0, 0, new InstancedTemplateRenderer(new InstanceRing()),
new RetainedTesrTestSupport.CountingPipeline(true, true));
try {
AngelicaConfig.enableEntityTextureAtlas = true;
Reflect.set(batcher, "activeGroups", groups);
final EntityTextureAtlas.Slot shadowSlot = EntityTextureAtlas.INSTANCE.slotFor(source, false);
assertNotNull(shadowSlot);
EntityTextureAtlas.INSTANCE.onTextureDeleted(source);
EntityTextureAtlas.INSTANCE.recycleReleasedRegions();
assertSame(shadowSlot, EntityTextureAtlas.INSTANCE.slotFor(otherTexture, false));
final EntityRenderCommands.Model entry = new EntityRenderCommands.Model();
entry.instance.template = template();
entry.cubes = cubes(1);
entry.cubesInsideTexture = true;
entry.scale = 1;
entry.inputs.material = EntityMaterials.CUTOUT;
entry.inputs.texture = new ResourceLocation("angelica", "test/replay");
entry.inputs.textureId = source;
entry.inputs.pass = PassOverride.NONE;
entry.inputs.glintSlot = -1;
if (glint) {
entry.glint = new EntityRenderCommands.ModelGlint();
entry.replayed = new RetainedTesrGroups.InstanceRange();
entry.replayed.reuseUpload = true;
}
if (shared) {
entry.source = new RetainedTesrGroups.InstanceColumns();
entry.source.addCubes(entry.instance.template, entry.cubes, 1, new Matrix4f(), 0, -1, 0, 0, shadowSlot);
entry.end = 1;
}
batcher.beginReplay(new Matrix4f());
batcher.replay(entry);
final RetainedTesrGroups.Group main = onlyGroup(groups);
final EntityTextureAtlas.Slot mainSlot = main.cubeColumns.atlasSlot(0);
if (glint) {
assertNull(mainSlot, "glint reuses the armor upload, whose UVs must still address the original texture");
assertEquals(entry.inputs.texture, main.layer.getTextureId());
} else {
assertNotNull(mainSlot);
assertNotSame(shadowSlot, mainSlot);
assertSame(EntityTextureAtlas.INSTANCE.slotFor(source, false), mainSlot);
}
} finally {
Reflect.set(batcher, "activeGroups", previousGroups);
AngelicaConfig.enableEntityTextureAtlas = atlasEnabled;
groups.clear();
GLStateManager.glDeleteTextures(otherTexture);
}
}
private static int texture(int filter, int seed) {
final int id = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
@@ -187,10 +114,7 @@ class EntityTextureAtlasGLTest {
@Test
void copiesTheTextureWithEdgeGuttersAndRestoresTheFramebuffers() {
final ExitState state = new ExitState();
GLStateManager.beginExitCapture(state);
final EntityTextureAtlas.Slot slot = EntityTextureAtlas.INSTANCE.slotFor(source, false);
assertTrue(GLStateManager.finishExitCapture(state), "first-use atlas resources are internal batch operations");
assertNotNull(slot);
assertEquals(passFbo, GLStateManager.getDrawFramebuffer(), "draw framebuffer restored");
assertEquals(passFbo, GLStateManager.getReadFramebuffer(), "read framebuffer restored");
@@ -6,7 +6,6 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.api.tesr.TesrMaterial;
import com.gtnewhorizons.angelica.api.tesr.TesrShader;
import com.gtnewhorizons.angelica.compat.mojang.RenderLayer;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
@@ -21,7 +20,6 @@ import com.gtnewhorizons.angelica.glsm.states.BlendState;
import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.rendering.GlintClock;
import com.gtnewhorizons.angelica.rendering.items.ItemGlint;
import com.gtnewhorizons.angelica.rendering.tesr.RetainedTesrGroups.Group;
import com.gtnewhorizons.angelica.rendering.tesr.RetainedTesrGroups.InstanceColumns;
import com.gtnewhorizons.angelica.rendering.tesr.RetainedTesrGroups.TexRun;
@@ -29,13 +27,9 @@ import com.gtnewhorizons.angelica.rendering.tesr.RetainedTesrTestSupport.Countin
import com.gtnewhorizons.angelica.shadercompat.ShaderGlint;
import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource;
import net.coderbot.iris.layer.PassOverride;
import net.coderbot.iris.pipeline.ShadowRenderer;
import net.coderbot.iris.uniforms.CapturedStateScope;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.ItemRenderer;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.Entity;
import net.minecraft.util.ResourceLocation;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import org.joml.Matrix4f;
@@ -70,8 +64,6 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@GLCoreTest
class RetainedTesrGroupsGLTest {
@@ -763,10 +755,6 @@ class RetainedTesrGroupsGLTest {
}
private static int createGlintTexture() {
return createGlintTexture(false);
}
private static int createGlintTexture(boolean opaque) {
final int id = GLStateManager.glGenTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
@@ -777,7 +765,7 @@ class RetainedTesrGroupsGLTest {
final ByteBuffer texels = BufferUtils.createByteBuffer(4 * 4 * 4);
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
texels.put((byte) (64 + 48 * x)).put((byte) (64 + 48 * y)).put((byte) 200).put((byte) (opaque ? 255 : 64 + 48 * x));
texels.put((byte) (64 + 48 * x)).put((byte) (64 + 48 * y)).put((byte) 200).put((byte) (64 + 48 * x));
}
}
texels.flip();
@@ -1012,184 +1000,6 @@ class RetainedTesrGroupsGLTest {
}
}
@ParameterizedTest
@ValueSource(strings = {"armorTemplate", "armorCubes", "held", "heldMirrored", "dropped", "droppedStack"})
void shadowGlintReplayMatchesLiveMainPixels(String kind) {
glintState();
GLStateManager.glDeleteTextures(glintTexture);
glintTexture = createGlintTexture(true);
final boolean held = kind.startsWith("held"), dropped = kind.startsWith("dropped"), cubes = kind.equals("armorCubes");
final TemplateBuffer baseTemplate;
if (held || dropped) {
final AngelicaTesrMeshCache.GtnhMeshBackend backend = new AngelicaTesrMeshCache.GtnhMeshBackend();
ItemRenderer.renderItemIn2D(backend.beginCapture(DefaultVertexFormat.POSITION_TEXTURE_NORMAL), 0.75f, 0.1f, 0.25f, 0.9f, 16, 32, 0.0625f);
baseTemplate = backend.endCaptureToTemplate();
ItemRenderer.renderItemIn2D(backend.beginCapture(DefaultVertexFormat.POSITION_TEXTURE_NORMAL), 0, 0, 1, 1, 16, 32, 0.0625f);
glintTemplate = backend.endCaptureToTemplate();
} else {
if (!cubes) glintTemplate = capturedQuad(0.5);
baseTemplate = glintTemplate;
}
final ModelPartBatcher batcher = ModelPartBatcher.INSTANCE;
final boolean previousBatching = AngelicaConfig.enableEntityBatching;
final boolean previousReplay = AngelicaConfig.enableEntityShadowReplay;
final boolean previousAtlas = AngelicaConfig.enableEntityTextureAtlas;
final boolean previousShadow = ShadowRenderer.ACTIVE;
final double previousX = RenderManager.renderPosX, previousY = RenderManager.renderPosY, previousZ = RenderManager.renderPosZ;
final RetainedTesrGroups shadowGroups = new RetainedTesrGroups(new AngelicaBufferSource());
final Matrix4f shadowView = new Matrix4f().rotateXYZ(0.6f, -0.7f, 0.2f).translate(2, -1, 3);
final Matrix4f mainView = new Matrix4f().rotateXYZ(-0.1f, 0.2f, 0.17f);
final Matrix4f pose = new Matrix4f().rotateXYZ(0.13f, 0.41f, -0.27f);
if (held || dropped) pose.scale(0.8f, kind.equals("heldMirrored") ? -0.8f : 0.8f, 0.8f).translate(-0.5f, -0.5f, 0);
final Entity entity = mock(Entity.class);
final Render renderer = mock(Render.class);
final RenderManager manager = mock(RenderManager.class);
when(manager.getEntityRenderObject(entity)).thenReturn(renderer);
BatchStateGuard.suspend();
try {
AngelicaConfig.enableEntityBatching = true;
AngelicaConfig.enableEntityShadowReplay = true;
AngelicaConfig.enableEntityTextureAtlas = false;
EntityShadowReplay.beginFrame();
Reflect.set(batcher, "active", true);
Reflect.set(batcher, "mode", ModelPartBatcher.Mode.ENTITIES);
Reflect.set(batcher, "instancedThisCycle", true);
GLStateManager.getTextures().getTextureUnitStates(1).setEnabled(false);
GLStateManager.setOverlayColor(0, 0, 0, 0);
GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
RenderManager.renderPosX = 1000000;
RenderManager.renderPosY = RenderManager.renderPosZ = 0;
groups.beginPass(new Matrix4f(), 0, 0, 0, instanced);
Reflect.set(batcher, "activeGroups", groups);
Reflect.set(batcher, "shadow", false);
ShadowRenderer.ACTIVE = false;
GLStateManager.setModelViewMatrix(mainView);
queueEntityGlint(batcher, kind, baseTemplate, pose, true);
final int[][] reference = drawEntityGlint(kind);
shadowGroups.beginPass(new Matrix4f(), 0, 0, 0, instanced, new CountingPipeline(true, true));
Reflect.set(batcher, "activeGroups", shadowGroups);
Reflect.set(batcher, "shadow", true);
ShadowRenderer.ACTIVE = true;
GLStateManager.setModelViewMatrix(shadowView);
EntityShadowReplay.beginPass(true, 0);
BatchStateGuard.resume();
try {
assertTrue(EntityShadowReplay.beginEntity(entity, 0.25f));
assertTrue(EntityShadowReplay.beginRenderer(renderer, entity, 0.25f, BatchEligibility.SAFE));
queueEntityGlint(batcher, kind, baseTemplate, pose, false);
EntityShadowReplay.endRenderer(true, BatchEligibility.SAFE);
EntityShadowReplay.endEntity(true);
} finally {
BatchStateGuard.suspend();
}
assertEquals(1, shadowGroups.groupCount(), "glint must not add a shadow draw group");
groups.beginPass(new Matrix4f(), 0, 0, 0, instanced);
Reflect.set(batcher, "activeGroups", groups);
Reflect.set(batcher, "shadow", false);
ShadowRenderer.ACTIVE = false;
GLStateManager.setModelViewMatrix(mainView);
EntityShadowReplay.beginPass(false, 0);
assertTrue(EntityShadowReplay.replay(manager, entity, 0.25f, 0));
final int[][] replayed = drawEntityGlint(kind);
FfpFixture.assertOverlayFootprint(reference[0], reference[1], BACKGROUND, "live glint");
FfpFixture.assertOverlayFootprint(replayed[0], replayed[1], BACKGROUND, "replayed glint");
// Replay reaches the main pose through the shadow view, about 1 ulp off the live pose. On llvmpipe that
// flips a single glint pixel on the dropped item's thin edge strips.
FfpFixture.assertPixelParity("replayed glint pixels", reference[1], replayed[1], SIZE, BACKGROUND, 8, 1, 2, RetainedTesrGroupsGLTest::describe);
} finally {
shadowGroups.clear();
batcher.clear();
EntityShadowReplay.clear();
AngelicaConfig.enableEntityBatching = previousBatching;
AngelicaConfig.enableEntityShadowReplay = previousReplay;
AngelicaConfig.enableEntityTextureAtlas = previousAtlas;
ShadowRenderer.ACTIVE = previousShadow;
RenderManager.renderPosX = previousX;
RenderManager.renderPosY = previousY;
RenderManager.renderPosZ = previousZ;
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
BatchStateGuard.resume();
}
}
private void queueEntityGlint(ModelPartBatcher batcher, String kind, TemplateBuffer baseTemplate, Matrix4f pose, boolean legacyDropped) {
final int copies = kind.equals("droppedStack") ? 4 : 1;
for (int copy = 0; copy < copies; copy++) {
queueEntityGlintCopy(batcher, kind, baseTemplate, new Matrix4f(pose).translate(copy * 0.12f, copy * 0.06f, copy * 0.085f), legacyDropped);
}
}
private void queueEntityGlintCopy(ModelPartBatcher batcher, String kind, TemplateBuffer baseTemplate, Matrix4f pose, boolean legacyDropped) {
final boolean held = kind.startsWith("held"), dropped = kind.startsWith("dropped"), cubes = kind.equals("armorCubes");
bindBase();
restoreTexMatrix();
GLStateManager.glPushMatrix();
GLStateManager.applyMultMatrix(pose);
GLStateManager.glColor4f(0.4f, 0.6f, 0.8f, 1);
ModelPartBatcher.onTextureBind(new ResourceLocation("angelica", "test/base"), baseTexture);
final GlintCapture capture = new GlintCapture();
assertTrue(BatchEligibility.begin(BatchEligibility.SAFE, GLStateManager.drawCalls));
try {
if (!held && !dropped) assertTrue(batcher.beginArmorCapture(capture));
assertTrue(Reflect.<Boolean>invoke(batcher, "queueTemplate",
new Class<?>[] {TemplateBuffer.class, CubeParams[].class, boolean.class, float.class, TesrMaterial.class, boolean.class},
baseTemplate, cubes ? glintCubes : null, false, CUBE_SCALE, held || dropped ? EntityMaterials.DROPPED_ITEM_CUTOUT : EntityMaterials.CUTOUT, false));
if (held) {
assertTrue(batcher.queueSkippedItemGlint(glintTemplate, EntityMaterials.ITEM_GLINT));
} else if (dropped && legacyDropped) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, glintTexture);
ModelPartBatcher.onTextureBind(GLINT_TEXTURE, glintTexture);
GLStateManager.glColor4f(0.5f * 0.76f, 0.25f * 0.76f, 0.8f * 0.76f, 1);
for (Matrix4f matrix : new Matrix4f[] {ItemGlint.firstMatrix(), ItemGlint.secondMatrix()}) {
GLStateManager.setTextureMatrix(0, matrix);
assertTrue(batcher.queueTemplate(glintTemplate, EntityMaterials.GLINT));
}
restoreTexMatrix();
} else if (dropped) {
assertTrue(batcher.queueSkippedItemGlint(glintTemplate, EntityMaterials.GLINT));
} else {
capture.endWithRestoredPose();
assertTrue(batcher.queueSkippedArmorGlint(capture, GLINT_TEXTURE));
}
} finally {
BatchEligibility.end(BatchEligibility.SAFE, GLStateManager.drawCalls);
capture.reset();
GLStateManager.glPopMatrix();
}
}
private int[][] drawEntityGlint(String kind) {
final boolean held = kind.startsWith("held"), dropped = kind.startsWith("dropped");
FfpFixture.clear();
final int depth = GLStateManager.pushState(StateSet.BATCH);
try {
int[] base = null;
for (TesrMaterial material : new TesrMaterial[] {held || dropped ? EntityMaterials.DROPPED_ITEM_CUTOUT : EntityMaterials.CUTOUT,
held ? EntityMaterials.ITEM_GLINT : EntityMaterials.GLINT}) {
for (var layers : RetainedTesrTestSupport.byLayer(groups).values()) for (Group group : layers) {
if (group.material != material) continue;
group.layer.getState().apply();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, base == null ? baseTexture : glintTexture);
GLStateManager.enableTexture();
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
GLStateManager.glStencilFunc(GL11.GL_ALWAYS, 3, 0xFF);
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
groups.drawLayer(group.layer);
assertEquals(GL11.GL_ALWAYS, GL11.glGetInteger(GL11.GL_STENCIL_FUNC));
assertEquals(3, GL11.glGetInteger(GL11.GL_STENCIL_REF));
}
if (base == null) base = FfpFixture.readRegion(SIZE);
}
ring.endFrame();
return new int[][] {base, FfpFixture.readRegion(SIZE)};
} finally {
GLStateManager.popStateTo(depth);
}
}
@Test
void mirroredModelViewUnderCullFrontBatchesLikeTheImmediateDraw() {
GLStateManager.glViewport(0, 0, SIZE, SIZE);
@@ -60,198 +60,6 @@ class RetainedTesrGroupsTest {
layer = testLayer();
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void replayRangeMatchesIndividualQueueingAcrossCapacityGrowth(boolean cubes) {
final TemplateBuffer template = template();
final var cubeParams = cubes ? cubes(2) : null;
final var input = new RetainedTesrGroups.InstanceColumns();
for (int i = 0; i < 80; i++) {
final Matrix4f matrix = new Matrix4f().translation(i, -i, 2).rotateY(i * 0.1f).scale(0.5f);
if (cubes) input.addCubes(template, cubeParams, i + 1, matrix, i, ~i, i * 2, i * 3L, null);
else input.add(template, matrix, i, ~i, i * 2, i * 3L);
}
final var expected = new RetainedTesrGroups(new AngelicaBufferSource());
final var pipeline = new RetainedTesrTestSupport.CountingPipeline(true, true);
groups.beginPass(new Matrix4f(), 0, 0, 0, new InstancedTemplateRenderer(ring), pipeline);
expected.beginPass(new Matrix4f(), 0, 0, 0, new InstancedTemplateRenderer(ring), pipeline);
final RenderLayer layer = layerFor(STREAM);
final Matrix4f transform = new Matrix4f().rotateX(0.2f).translate(5, 7, 3);
final Matrix4f texture = new Matrix4f().translation(0.2f, 0.4f, 0);
for (RetainedTesrGroups dst : new RetainedTesrGroups[] {groups, expected}) {
dst.queue(0, template, cubeParams, 1, layer, STREAM, new Matrix4f(), 0, -1, 0, 0, 0, texture, null);
}
groups.replayInstances(input, 5, 75, layer, STREAM, 0, transform, texture, null, null);
for (int i = 5; i < 75; i++) {
final Matrix4f matrix = new Matrix4f(transform).mul(new Matrix4f().set(input.matrices, i * 16));
expected.queue(0, template, cubeParams, cubes ? input.scales.getFloat(i) : 1, layer, STREAM, matrix,
input.lights.getInt(i), input.colors.getInt(i), input.overlays.getInt(i), input.infos.getLong(i), 0, texture, null);
}
final var actualGroup = onlyGroup(groups);
final var expectedGroup = onlyGroup(expected);
final var actual = cubes ? actualGroup.cubeColumns : actualGroup.templateColumns;
final var correct = cubes ? expectedGroup.cubeColumns : expectedGroup.templateColumns;
assertEquals(correct.size, actual.size);
for (int i = 0; i < actual.size * 16; i++) assertEquals(correct.matrices[i], actual.matrices[i], 0.00001f);
assertEquals(correct.templates, actual.templates);
assertEquals(correct.cubes, actual.cubes);
assertEquals(correct.scales, actual.scales);
assertEquals(correct.colors, actual.colors);
assertEquals(correct.lights, actual.lights);
assertEquals(correct.overlays, actual.overlays);
assertEquals(correct.infos, actual.infos);
assertEquals(correct.runs.getFirst().parts, actual.runs.getFirst().parts);
assertEquals(correct.runs.getFirst().instances, actual.runs.getFirst().instances);
}
@Test
void replayRangeRetainedFallbackPreservesTheModelTransform() {
final TemplateBuffer triangles = new TemplateBuffer(new int[3 * VERTEX_SIZE], 3, GL11.GL_TRIANGLES);
final Matrix4f model = new Matrix4f().translation(3, 2, -6).rotateY(0.7f).scale(0.5f);
final Matrix4f transform = new Matrix4f().rotateX(0.2f).translate(5, 7, 3);
final var input = new RetainedTesrGroups.InstanceColumns();
input.add(triangles, model, 0, -1, 0, 0);
final var expected = new RetainedTesrGroups(new AngelicaBufferSource());
final RenderLayer layer = layerFor(STREAM);
groups.beginPass(new Matrix4f(), 0, 0, 0);
expected.beginPass(new Matrix4f(), 0, 0, 0);
groups.replayInstances(input, 0, 1, layer, STREAM, 0, transform, null, null, null);
expected.queue(0, triangles, layer, STREAM, new Matrix4f(transform).mul(model), 0, -1, 0, 0, 0, null);
final var actual = onlyGroup(groups);
assertEquals(0, groups.streamedInstances);
assertEquals(1, actual.instTexMatrices.size(), "the draw-mode mismatch must take the retained fallback");
assertTrue(new Matrix4f().set(onlyGroup(expected).templateColumns.matrices)
.equals(new Matrix4f().set(actual.templateColumns.matrices), 0.00001f));
assertTrue(model.equals(new Matrix4f().set(input.matrices), 0));
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void replayRangeProbeMatchesIndividualQueueing(boolean queueBeforeReplay) {
final TemplateBuffer template = template();
final Matrix4f base = new Matrix4f().rotateX(0.3f).translate(4, -2, 5);
final Matrix4f model = new Matrix4f().translation(3, 2, -6).rotateY(0.7f).scale(0.5f);
final Matrix4f transform = new Matrix4f().rotateX(0.2f).translate(5, 7, 3);
final var input = new RetainedTesrGroups.InstanceColumns();
input.add(template, model, 0, -1, 0, 0);
final var expected = new RetainedTesrGroups(new AngelicaBufferSource(), () -> clock[0]);
final var renderer = new InstancedTemplateRenderer(ring);
final var state = CapturedRenderingState.INSTANCE;
try (CapturedStateScope ignored = new CapturedStateScope()) {
state.setCurrentEntityAndItem(0, 0);
state.setCurrentBlockEntity(0);
state.setCurrentEntityColor(0, 0, 0, 0);
for (RetainedTesrGroups dst : new RetainedTesrGroups[] {groups, expected}) {
dst.beginPass(base, 100.5, 64.25, -100.75, renderer);
dst.forceStreaming(layer, TesrMaterial.CURRENT_STATE, 0);
dst.queue(0, template, layer, TesrMaterial.CURRENT_STATE, base, 0, -1, 0, 0, 0, null);
}
clock[0] += RetainedTesrGroups.STABILITY_PROBE_MS;
for (RetainedTesrGroups dst : new RetainedTesrGroups[] {groups, expected}) {
dst.beginPass(base, 100.5, 64.25, -100.75, renderer);
if (queueBeforeReplay) {
dst.queue(0, template, layer, TesrMaterial.CURRENT_STATE, base, 0, -1, 0, 0, 0, null);
assertTrue(onlyGroup(dst).probe.probing);
}
}
groups.replayInstances(input, 0, 1, layer, TesrMaterial.CURRENT_STATE, 0, transform, null, null, null);
expected.queue(0, template, layer, TesrMaterial.CURRENT_STATE, new Matrix4f(transform).mul(model), 0, -1, 0, 0, 0, null);
final var actual = onlyGroup(groups);
final var correct = onlyGroup(expected);
final int offset = queueBeforeReplay ? 16 : 0;
assertTrue(actual.probe.probing);
assertEquals(queueBeforeReplay ? 2 : 1, correct.probe.count);
assertEquals(correct.probe.count, actual.probe.count);
assertTrue(new Matrix4f().set(correct.probe.snapshot.matrices, offset)
.equals(new Matrix4f().set(actual.probe.snapshot.matrices, offset), 0.00001f));
assertTrue(new Matrix4f().set(correct.templateColumns.matrices, offset)
.equals(new Matrix4f().set(actual.templateColumns.matrices, offset), 0.00001f));
assertTrue(model.equals(new Matrix4f().set(input.matrices), 0));
}
}
@Test
void replayRangeAfterIdleUsesRetainedStorage() {
final TemplateBuffer template = template();
final Matrix4f base = new Matrix4f().rotateX(0.3f).translate(4, -2, 5);
final Matrix4f model = new Matrix4f().translation(3, 2, -6).rotateY(0.7f).scale(0.5f);
final Matrix4f transform = new Matrix4f().rotateX(0.2f).translate(5, 7, 3);
final var input = new RetainedTesrGroups.InstanceColumns();
input.add(template, model, 0, -1, 0, 0);
final var expected = new RetainedTesrGroups(new AngelicaBufferSource(), () -> clock[0]);
final var renderer = new InstancedTemplateRenderer(ring);
final var state = CapturedRenderingState.INSTANCE;
try (CapturedStateScope ignored = new CapturedStateScope()) {
state.setCurrentEntityAndItem(0, 0);
state.setCurrentBlockEntity(0);
state.setCurrentEntityColor(0, 0, 0, 0);
for (RetainedTesrGroups dst : new RetainedTesrGroups[] {groups, expected}) {
dst.beginPass(base, 100.5, 64.25, -100.75, renderer);
dst.forceStreaming(layer, TesrMaterial.CURRENT_STATE, 0);
dst.queue(0, template, layer, TesrMaterial.CURRENT_STATE, base, 0, -1, 0, 0, 0, null);
assertTrue(onlyGroup(dst).streamPath);
for (int i = 0; i <= RetainedTesrGroups.IDLE_RESET_FRAMES; i++) {
dst.beginPass(base, 100.5, 64.25, -100.75, renderer);
}
}
groups.replayInstances(input, 0, 1, layer, TesrMaterial.CURRENT_STATE, 0, transform, null, null, null);
expected.queue(0, template, layer, TesrMaterial.CURRENT_STATE, new Matrix4f(transform).mul(model), 0, -1, 0, 0, 0, null);
final var actual = onlyGroup(groups);
final var correct = onlyGroup(expected);
assertFalse(actual.streamPath);
assertEquals(1, correct.instTexMatrices.size());
assertEquals(correct.instTexMatrices.size(), actual.instTexMatrices.size());
assertTrue(actual.templateColumns.runs.isEmpty());
assertTrue(new Matrix4f().set(correct.templateColumns.matrices)
.equals(new Matrix4f().set(actual.templateColumns.matrices), 0.00001f));
assertTrue(model.equals(new Matrix4f().set(input.matrices), 0));
}
}
@ParameterizedTest
@ValueSource(booleans = {false, true})
void replayRangeUsesCapturedStateForNonStreamGroups(boolean liveGroupPromoted) {
final TemplateBuffer template = template();
final Matrix4f model = new Matrix4f().translation(3, 2, -6).rotateY(0.7f).scale(0.5f);
final long info = InstancedAttribs.packEntityInfo(11, 21, 13);
final int overlay = AngelicaBufferSource.packAbgr(1, 0, 0, 0.5f);
final var input = new RetainedTesrGroups.InstanceColumns();
input.add(template, model, 0, -1, overlay, info);
final var state = CapturedRenderingState.INSTANCE;
try (CapturedStateScope ignored = new CapturedStateScope()) {
groups.beginPass(new Matrix4f(), 0, 0, 0, new InstancedTemplateRenderer(ring));
state.setCurrentEntityAndItem(11, 13);
state.setCurrentBlockEntity(21);
AngelicaBufferSource.setEntityColor(overlay);
groups.forceStreaming(layer, TesrMaterial.CURRENT_STATE, 21);
final var capturedGroup = onlyGroup(groups);
state.setCurrentEntityAndItem(12, 14);
state.setCurrentBlockEntity(22);
state.setCurrentEntityColor(0, 1, 0, 0.5f);
final int liveOverlay = AngelicaBufferSource.packEntityColor(state.getCurrentEntityColor());
if (liveGroupPromoted) groups.forceStreaming(layer, TesrMaterial.CURRENT_STATE, 21);
groups.replayInstances(input, 0, 1, layer, TesrMaterial.CURRENT_STATE, 21, new Matrix4f(), null, null, null);
assertEquals(liveGroupPromoted ? 2 : 1, groups.groupCount());
assertEquals(1, capturedGroup.templateColumns.size);
assertEquals(info, capturedGroup.templateColumns.infos.getLong(0));
assertEquals(overlay, capturedGroup.templateColumns.overlays.getInt(0));
assertEquals(12, state.getCurrentRenderedEntity());
assertEquals(22, state.getCurrentRenderedBlockEntity());
assertEquals(14, state.getCurrentRenderedItem());
assertEquals(liveOverlay, AngelicaBufferSource.packEntityColor(state.getCurrentEntityColor()));
assertTrue(model.equals(new Matrix4f().set(input.matrices), 0));
}
}
@Test
void fallbackKeepsHighMaterialIdsAndTheUnmappedSentinel() {
for (int id : new int[] { -1, 0, 32767, 32768, 45020, 50020, 50072, 65534 }) {
@@ -86,8 +86,8 @@ public final class RetainedTesrTestSupport {
static RenderLayer capturedLayer(TesrMaterial material) {
return Reflect.invoke(ModelPartBatcher.INSTANCE, "layerFor",
new Class<?>[] { ResourceLocation.class, TesrMaterial.class, PassOverride.class, float.class, float.class, int.class, int.class, boolean.class, boolean.class, boolean.class },
null, material, PassOverride.NONE, 0f, 0f, ShaderGlint.NO_TINT, DrawState.liveCull(material), DrawState.liveLit(material), DrawState.liveLightmap(), false);
new Class<?>[] { ResourceLocation.class, TesrMaterial.class, PassOverride.class, float.class, float.class, int.class, int.class, boolean.class, boolean.class },
null, material, PassOverride.NONE, 0f, 0f, ShaderGlint.NO_TINT, DrawState.liveCull(material), DrawState.liveLit(material), DrawState.liveLightmap());
}
static final class CountingPipeline implements TesrInstancingPipeline {
@@ -48,11 +48,6 @@ public final class VanillaLayerRig {
return AlphaTestStorage.isAlphaTestLocked();
}
@Override
public boolean isOverrideHeld() {
return AlphaTestStorage.isOverrideHeld();
}
@Override
public boolean deferAlphaTestToggle(boolean enabled) {
return AlphaTestStorage.deferAlphaTestToggle(enabled);