Initial commit for glsm-exit-state-capture (#2292)

This commit is contained in:
Eclipse-Sol authored and GitHub committed 2026-10-24 15:58:17 -07:00
1 parent 8af9340be6
commit 102c6cad86
18 files changed
+2141 -90

No files matched your search

@@ -0,0 +1,45 @@
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;
}
}
@@ -0,0 +1,598 @@
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 = DRAW_INPUTS;
if (materialFromState) {
fields |= MATERIAL_INPUTS;
if (!context.blendMode.isEffectivelyEnabled()) fields &= ~(1L << BLEND_FUNC);
if (!context.alphaTest.isEffectivelyEnabled()) fields &= ~(1L << ALPHA_FUNC);
}
result.dependencies |= fields & ~result.writes;
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 (active() && filterTexture != 0 && texture == filterTexture && !nearest) 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 (active()) recordMatrixChange(context.matrixMode.getMode(), 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,6 +59,7 @@ 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,14 +198,17 @@ 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();
@@ -220,6 +223,7 @@ public class RenderSystem {
}
public static void uniformMatrix4fv(int location, boolean transpose, FloatBuffer matrix) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.uniformMatrix4(location, transpose, matrix);
}
@@ -228,61 +232,78 @@ 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) {RENDER_BACKEND.uniform3i(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 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();
@@ -290,6 +311,7 @@ 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();
@@ -297,6 +319,7 @@ 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();
@@ -304,6 +327,7 @@ 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();
@@ -311,6 +335,7 @@ 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);
@@ -318,6 +343,7 @@ 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);
@@ -325,26 +351,32 @@ 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();
@@ -355,30 +387,37 @@ 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);
}
@@ -475,18 +514,21 @@ 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) {
RENDER_BACKEND.dispatchCompute(workGroups.x, workGroups.y, workGroups.z);
dispatchCompute(workGroups.x, workGroups.y, workGroups.z);
}
public static void dispatchComputeIndirect(long offset) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.dispatchComputeIndirect(offset);
}
@@ -495,6 +537,7 @@ public class RenderSystem {
}
public static void memoryBarrier(int barriers) {
GLStateManager.rejectExitCapture();
if (supportsCompute) {
RENDER_BACKEND.memoryBarrier(barriers);
}
@@ -517,12 +560,14 @@ public class RenderSystem {
}
public static void bindTextureToUnit(int unit, int texture) {
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
if (unit == 0) GLStateManager.recordExitTextureBind(GL11.GL_TEXTURE_2D, 0);
else if (GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
dsaState.bindTextureToUnit(unit, texture);
}
public static void bindTextureToUnit(int target, int unit, int texture) {
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
if (unit == 0) GLStateManager.recordExitTextureBind(target, 0);
else if (GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
dsaState.bindTextureToUnit(target, unit, texture);
}
@@ -542,6 +587,7 @@ 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 {
@@ -552,10 +598,12 @@ public class RenderSystem {
}
public static int createFramebuffer() {
GLStateManager.rejectExitCapture();
return dsaState.createFramebuffer();
}
public static int createTexture(int target) {
GLStateManager.rejectExitCapture();
return dsaState.createTexture(target);
}
@@ -573,6 +621,7 @@ public class RenderSystem {
}
public static int getIndexedBufferBinding(int target, int index) {
GLStateManager.rejectExitCapture();
return GLStateManager.getIndexedBufferBinding(target, index);
}
@@ -604,6 +653,7 @@ 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,18 +199,21 @@ 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,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.hooks;
public interface DeferredAlphaHandler {
boolean isAlphaTestLocked();
boolean isOverrideHeld();
boolean deferAlphaTestToggle(boolean enabled);
boolean deferAlphaFunc(int function, float reference);
}
@@ -47,6 +47,31 @@ 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,6 +96,7 @@ 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,6 +24,7 @@ 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,6 +197,7 @@ 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) {
@@ -0,0 +1,151 @@
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);
}
});
}
}
@@ -0,0 +1,561 @@
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);
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();
}
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);
GLStateManager.suspendExitCapture();
try {
GLStateManager.glPushAttrib(GL11.GL_CURRENT_BIT);
} finally {
GLStateManager.resumeExitCapture();
}
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);
GLStateManager.suspendExitCapture();
try {
GLStateManager.glPopAttrib();
} finally {
GLStateManager.resumeExitCapture();
}
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) {}
}
}
@@ -0,0 +1,208 @@
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());
}
}
@@ -161,6 +161,11 @@ 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);
@@ -17,6 +17,7 @@ public class AlphaTestStorage {
@Getter
private static boolean alphaTestLocked;
@Getter
private static boolean overrideHeld;
public static void overrideAlphaTest(AlphaTest override) {
@@ -1,6 +1,7 @@
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;
@@ -20,9 +21,29 @@ public class MixinTextureUtil {
TextureUtilHooks.setMaxLevel(mipmapLevels);
}
@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();
@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);
}
@Redirect(method = "func_147945_b()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/texture/TextureUtil;func_147952_b(II)V"))
@@ -35,8 +56,8 @@ public class MixinTextureUtil {
TextureUtilHooks.restoreAniso(aniso);
}
@Inject(method = "func_147945_b()V", at = @At("RETURN"))
private static void angelica$clearSavedAfterRestore(CallbackInfo ci) {
TextureUtilHooks.clearSavedFilterTexture();
@Surround.Finally("restoreFilter")
private static void angelica$finishFilterRestore(@Surround.Carry boolean captured) {
TextureUtilHooks.endFilterRestore(captured);
}
}
@@ -48,6 +48,11 @@ public final class VanillaLayerRig {
return AlphaTestStorage.isAlphaTestLocked();
}
@Override
public boolean isOverrideHeld() {
return AlphaTestStorage.isOverrideHeld();
}
@Override
public boolean deferAlphaTestToggle(boolean enabled) {
return AlphaTestStorage.deferAlphaTestToggle(enabled);