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
152 changed files with 1257 additions and 9012 deletions

No files matched your search

+8 -8
View File
@@ -1,14 +1,14 @@
## Summary
<!-- Briefly describe what this PR changes and why. -->
# Description of your *PR* and other info
<!-- Please include a summary of the change -->
<!-- Any details that you think are important to review this PR? -->
<!-- Are there any Issues / PRs related to this one? -->
<!-- Add a screenshot or a video demonstration for new features -->
<details open><summary>Template</summary> <!-- OPEN is intentional -->
## Checklist
- [ ] I have tested this PR in DevEnv
- [ ] I have tested this PR in Fullpack
- [ ] This PR is in compliance with the [GTNH AI Policy](https://github.com/GTNewHorizons/GTNH-Dev-Doc/blob/master/AI_POLICY.md)
- [ ] I have at least [skimmed](https://github.com/GTNewHorizons/Angelica/search?q=is%3Apr&type=Issues) the tracker for similar pull requests
- [ ] This PR requires another PR in order to merge
### Before submitting a *pull request* make sure you have:
- [ ] Read the [AI Contribution Policy](https://github.com/GTNewHorizons/Angelica/blob/master/AI_POLICY.md)
- [ ] At least [skimmed](https://github.com/GTNewHorizons/Angelica/search?q=is%3Apr&type=Issues) the tracker for similar pull requests
</details>
+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
-7
View File
@@ -175,14 +175,8 @@ val glsmRedirectAgent: Configuration by configurations.creating {
isCanBeResolved = true
}
val glsmMutableStaticsAgent: Configuration by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
glsmRedirectAgent(project(":glsm", "redirectAgent"))
glsmMutableStaticsAgent(project(":glsm", "mutableStaticsAgent"))
archScanAsm(libs.asm.tree.test)
archScanAsm(libs.asm.commons.test)
}
@@ -195,7 +189,6 @@ tasks.test {
useJUnitPlatform { excludeTags = setOf("gl-core") }
configureAngelicaJava8()
classpath = archScanAsm + classpath
javaAgent(glsmMutableStaticsAgent)
jvmArgs("-Dceleritas.lwjglService=org.taumc.celeritas.lwjgl.HeadlessTestLWJGLService")
dependsOn(":glsm:classes", ":lwjgl3-backend:classes", ":sdl-gpu:classes")
}
-15
View File
@@ -158,7 +158,6 @@ dependencies {
testFixturesApi(platform(libs.junit.bom))
testFixturesApi(libs.junit.jupiter.api)
testFixturesApi(libs.lwjgl2)
testFixturesCompileOnly(libs.asm.tree.test)
// Test
testImplementation(sourceSets["stubs"].output)
@@ -258,13 +257,6 @@ fun Test.configureGlsmJava8() {
}
}
val mutableStaticsAgentJar by tasks.registering(Jar::class) {
archiveClassifier = "mutable-statics-agent"
manifest {
attributes("Premain-Class" to "com.gtnewhorizons.angelica.glsm.testutil.MutableStatics")
}
}
tasks.test {
useJUnitPlatform {
excludeTags = GL_TASK_TAGS.values.toSet() + "lwjgl3"
@@ -273,7 +265,6 @@ tasks.test {
excludeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator*Test")
}
configureGlsmJava8()
javaAgent(files(mutableStaticsAgentJar))
}
val redirectAgentJar by tasks.registering(Jar::class) {
@@ -288,14 +279,8 @@ val redirectAgent by configurations.creating {
isCanBeResolved = false
}
val mutableStaticsAgent by configurations.creating {
isCanBeConsumed = true
isCanBeResolved = false
}
artifacts {
add(redirectAgent.name, redirectAgentJar)
add(mutableStaticsAgent.name, mutableStaticsAgentJar)
}
val glTestTasks = GL_TASK_TAGS.map { (taskName, tag) ->
@@ -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<>();
@@ -275,7 +274,6 @@ public class GLContextState {
public float savedFilterAniso = -1.0f;
public boolean lineQuadsActive = false;
public boolean lineStippleActive = false;
public int textureSupersamplingSamples = 1;
public Instancing ffpInstancing = Instancing.NONE;
public float lastBrightnessX;
public float lastBrightnessY;
File diff suppressed because it is too large. Load diff
@@ -158,8 +158,8 @@ public class RenderSystem {
maxSSBOBindings = 0;
}
supportsSamplerObjects = isGLES ? glesVersion >= 300
: GLStateManager.capabilities.OpenGL33 || GLStateManager.capabilities.GL_ARB_sampler_objects;
// Check for sampler objects support (GL 3.3+ or ARB extension)
supportsSamplerObjects = GLStateManager.capabilities.OpenGL33 || GLStateManager.capabilities.GL_ARB_sampler_objects;
if (supportsSamplerObjects) {
maxCombinedTextureImageUnits = RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
}
@@ -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);
}
@@ -713,7 +663,12 @@ public class RenderSystem {
public static void unbindAllSamplers() {
if (!supportsSamplerObjects) return;
GLStateManager.unbindAllSamplers();
final int count = GLStateManager.getSamplerUnitCount();
for (int i = 0; i < count; i++) {
if (GLStateManager.getSamplerBinding(i) != 0) {
GLStateManager.glBindSampler(i, 0);
}
}
}
/** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460". Strips
@@ -11,14 +11,13 @@ import java.util.function.IntSupplier;
* @param samplerNames samplers the graphics program may not have, because only the stripped writes read them
* @param samplerUnits per sampler name, the texture unit it reads at each draw, or -1 to use samplerTextures
* @param samplerTextures per sampler name, its texture when samplerUnits is -1
* @param samplerObjects per sampler name, supplies its current GL sampler object when samplerUnits is -1;
* null for unassigned or external bindings, and returns 0 to use the texture's own filtering
* @param samplerObjects per sampler name, the GL sampler object to sample it with when samplerUnits is -1, or 0
* @param vertexBufferBinding storage buffer binding of the first vertex buffer the replay program declares
* @param vertexBufferCount how many vertex buffers it declares, at consecutive bindings
* @param indexBufferBinding storage buffer binding of its index buffer
*/
public record VertexWriteReplaySetup(int replayProgram, String[] inputNames, int[] inputLocations,
IntSupplier[] images, int writtenImageCount,
String[] samplerNames, int[] samplerUnits, IntSupplier[] samplerTextures, IntSupplier[] samplerObjects,
String[] samplerNames, int[] samplerUnits, IntSupplier[] samplerTextures, int[] samplerObjects,
int vertexBufferBinding, int vertexBufferCount, int indexBufferBinding) {
}
@@ -104,7 +104,6 @@ public final class FFPUniformBlock {
public static final int TEX_ENV_COLOR_3 = vec4("u_TexEnvColor3");
public static final int OVERLAY_COLOR = vec4("u_OverlayColor");
public static final int SECONDARY_COLOR = vec3("u_SecondaryColor");
public static final int ATLAS_GUTTER = flt("u_AtlasGutter");
public static final int FOG_PARAMS = vec4("u_FogParams");
public static final int FOG_COLOR = vec4("u_FogColor");
@@ -6,7 +6,6 @@ import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
@@ -83,7 +82,7 @@ public final class FragmentKey {
private static final int BIT_ALPHA_TEST = 2; // 1 bit
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
private static final int BIT_SEPARATE_SPECULAR = 6; // 1 bit
private static final int BIT_TEXTURE_SUPERSAMPLING = 7; // 3 bits, log2(samples)
private static final int BIT_NR_ENABLED_UNITS = 7; // 3 bits
private static final int BIT_OVERLAY_ENABLED = 10; // 1 bit
private static final int BIT_LINE_STIPPLE = 11; // 1 bit
private static final int BIT_COLOR_SUM = 12; // 1 bit
@@ -173,10 +172,7 @@ public final class FragmentKey {
}
final int nrEnabled = highestEnabled + 1;
if (glCtx.textureSupersamplingSamples > 1 && (unit0Bits & 1L) != 0
&& TextureInfoCache.INSTANCE.getInfo(textures.getTextureUnitBindings(0).getBinding()).getAtlasGutter() > 0) {
global |= (long) Integer.numberOfTrailingZeros(glCtx.textureSupersamplingSamples) << BIT_TEXTURE_SUPERSAMPLING;
}
global |= ((long) (nrEnabled & 0x7)) << BIT_NR_ENABLED_UNITS;
global |= (unit0Bits << GLOBAL_BITS);
out[0] = global;
@@ -260,13 +256,12 @@ public final class FragmentKey {
public int fogMode() { return (int) (packed[0] & 0x3); }
public boolean combinedGlint() { return (packed[0] & (1L << BIT_COMBINED_GLINT)) != 0; }
public boolean glintReplaceAlpha() { return (packed[0] & (1L << BIT_GLINT_REPLACE_ALPHA)) != 0; }
public int textureSupersamplingSamples() { return 1 << (int) ((packed[0] >> BIT_TEXTURE_SUPERSAMPLING) & 0x7); }
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
public boolean colorSum() { return ((packed[0] >> BIT_COLOR_SUM) & 1) != 0; }
public boolean lineStipple() { return ((packed[0] >> BIT_LINE_STIPPLE) & 1) != 0; }
public int nrEnabledUnits() { return packed.length == 1 && !unitEnabled(0) ? 0 : packed.length; }
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
public boolean overlayEnabled() { return ((packed[0] >> BIT_OVERLAY_ENABLED) & 1) != 0; }
public boolean overlayInstanced() { return ((packed[0] >> BIT_OVERLAY_INSTANCED) & 1) != 0; }
@@ -405,7 +400,6 @@ public final class FragmentKey {
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d", fogName, alphaTestEnabled(),
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-", separateSpecular(), colorSum(),
overlayEnabled() || overlayInstanced(), nrEnabledUnits()));
if (textureSupersamplingSamples() > 1) sb.append(" samples=").append(textureSupersamplingSamples());
for (int i = 0; i < nrEnabledUnits(); i++) {
if (!unitEnabled(i)) {
sb.append(String.format(" u%d=OFF", i));
@@ -28,10 +28,6 @@ public final class FragmentShaderGenerator {
return sb.toString();
}
sb.append("out vec4 fragColor;\n\n");
if (key.textureSupersamplingSamples() > 1) {
sb.append("#define TEXTURE_SUPERSAMPLING_SAMPLES ").append(key.textureSupersamplingSamples()).append('\n');
sb.append(ShaderLoader.getShaderSource("angelica:texture_supersampling.glsl"));
}
sb.append("void main() {\n");
emitLineStipple(sb, key);
@@ -90,8 +86,7 @@ public final class FragmentShaderGenerator {
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
final String sample = i == 0 && key.textureSupersamplingSamples() > 1 ? "sampleSupersampledTexture" : "textureProj";
sb.append(" vec4 tex").append(i).append("Color = ").append(sample).append("(u_Sampler").append(i).append(", v_TexCoord").append(i).append(");\n");
sb.append(" vec4 tex").append(i).append("Color = textureProj(u_Sampler").append(i).append(", v_TexCoord").append(i).append(");\n");
}
}
@@ -190,10 +190,6 @@ public final class ShaderManager {
}
}
boolean isTextureSupersampling() {
return currentProgram != null && currentProgram.getFragmentKey().textureSupersamplingSamples() > 1;
}
public static void endFrame() {
final ShaderManager sm = GLStateManager.ctx().ffp;
sm.lastFramePreDrawCalls = sm.preDrawCalls;
@@ -203,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++;
@@ -11,7 +11,6 @@ import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import com.gtnewhorizons.angelica.glsm.streaming.UniformRingBuffer;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import org.joml.Math;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
@@ -50,7 +49,6 @@ public class Uniforms {
private int viewportX = Integer.MIN_VALUE, viewportY = Integer.MIN_VALUE, viewportWidth = -1, viewportHeight = -1;
private int lineStipple = -1;
private int weatherGen = -1;
private int atlasGutter = -1;
int blockWrites;
int blockSkips;
@@ -172,15 +170,6 @@ public class Uniforms {
stagedFragment++;
dirty = true;
}
if (glCtx.textureSupersamplingSamples > 1 && ffp.isTextureSupersampling()) {
final int gutter = TextureInfoCache.INSTANCE.getInfo(glCtx.textures.getTextureUnitBindings(0).getBinding()).getAtlasGutter();
if (gutter != atlasGutter) {
putFloat(FFPUniformBlock.ATLAS_GUTTER, gutter);
atlasGutter = gutter;
stagedMisc++;
dirty = true;
}
}
final LineState line = glCtx.lineState;
final float lw = line.getWidth();
if (lw != lineWidth) {
@@ -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();
@@ -1,15 +1,12 @@
package com.gtnewhorizons.angelica.glsm.states;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import lombok.Getter;
public class SamplerUnitArray {
private static final int MIN_UNITS = 16;
private int[] samplers;
@Getter
private int highestBoundUnit = -1;
public int get(int unit) {
final int[] local = samplers;
@@ -25,27 +22,15 @@ public class SamplerUnitArray {
}
if (unit >= local.length) return false;
local[unit] = sampler;
if (sampler != 0) {
highestBoundUnit = Math.max(highestBoundUnit, unit);
} else if (unit == highestBoundUnit) {
trimUnboundUnits();
}
return true;
}
public void unbindEverywhere(int sampler) {
final int[] local = samplers;
if (local == null) return;
for (int i = 0; i <= highestBoundUnit; i++) {
for (int i = 0; i < local.length; i++) {
if (local[i] == sampler) local[i] = 0;
}
trimUnboundUnits();
}
private void trimUnboundUnits() {
while (highestBoundUnit >= 0 && samplers[highestBoundUnit] == 0) {
highestBoundUnit--;
}
}
public int size() {
@@ -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) {
@@ -21,7 +21,6 @@ public class TextureInfo {
protected int resolvedInternalFormat = -1;
protected int width = -1;
protected int height = -1;
@Getter @Setter private int atlasGutter;
protected int minFilter = UNSET_INT;
protected int magFilter = UNSET_INT;
@@ -1,58 +0,0 @@
const vec2 SAMPLE_OFFSETS[TEXTURE_SUPERSAMPLING_SAMPLES] = vec2[](
#if TEXTURE_SUPERSAMPLING_SAMPLES == 2
vec2(0.25, 0.25), vec2(-0.25, -0.25)
#elif TEXTURE_SUPERSAMPLING_SAMPLES == 4
vec2(0.125, 0.375), vec2(-0.125, -0.375),
vec2(0.375, -0.125), vec2(-0.375, 0.125)
#elif TEXTURE_SUPERSAMPLING_SAMPLES == 8
vec2(-0.1875, -0.0625), vec2( 0.3125, 0.0625),
vec2(-0.0625, 0.1875), vec2(-0.3125, -0.1875),
vec2( 0.1875, -0.3125), vec2( 0.4375, -0.4375),
vec2(-0.4375, 0.3125), vec2( 0.0625, 0.4375)
#elif TEXTURE_SUPERSAMPLING_SAMPLES == 16
vec2( 0.0625, 0.0625), vec2(-0.0625, -0.1875),
vec2(-0.1875, 0.1250), vec2( 0.2500, -0.0625),
vec2(-0.3125, -0.1250), vec2( 0.1250, 0.3125),
vec2( 0.3125, 0.1875), vec2( 0.1875, -0.3125),
vec2(-0.1250, 0.3750), vec2( 0.0000, -0.4375),
vec2(-0.2500, -0.3750), vec2(-0.3750, 0.2500),
vec2(-0.5000, 0.0000), vec2( 0.4375, -0.2500),
vec2( 0.3750, 0.4375), vec2(-0.4375, -0.5000)
#endif
);
vec4 sampleSupersampledTexture(sampler2D tex, vec4 coord) {
vec2 uv = coord.xy / coord.w;
vec2 dx = dFdx(uv);
vec2 dy = dFdy(uv);
ivec2 size = textureSize(tex, 0);
vec2 offsets[TEXTURE_SUPERSAMPLING_SAMPLES];
vec2 extent = vec2(0.0);
for (int i = 0; i < TEXTURE_SUPERSAMPLING_SAMPLES; i++) {
offsets[i] = SAMPLE_OFFSETS[i].x * dx + SAMPLE_OFFSETS[i].y * dy;
extent = max(extent, abs(offsets[i]));
}
extent *= vec2(size);
float footprint = max(extent.x, extent.y);
if (footprint >= u_AtlasGutter) return textureGrad(tex, uv, dx, dy);
float supersampling = 1.0 - smoothstep(0.75 * u_AtlasGutter, u_AtlasGutter, footprint);
// The atlas gutter is 2^maxMipLevel.
float texelsPerPixel = max(length(dx * vec2(size)), length(dy * vec2(size)));
int lod = int(min(ceil(log2(max(texelsPerPixel / sqrt(float(TEXTURE_SUPERSAMPLING_SAMPLES)), 1.0))),
log2(u_AtlasGutter)));
// Transparent texels must not tint the visible samples.
vec4 color = vec4(0.0);
for (int i = 0; i < TEXTURE_SUPERSAMPLING_SAMPLES; i++) {
ivec2 texel = min(ivec2(fract(uv + offsets[i]) * vec2(size)), size - 1);
vec4 sampleColor = texelFetch(tex, texel >> lod, lod);
color += vec4(sampleColor.rgb * sampleColor.a, sampleColor.a);
}
color /= float(TEXTURE_SUPERSAMPLING_SAMPLES);
if (supersampling < 1.0) {
vec4 filtered = textureGrad(tex, uv, dx, dy);
color = mix(vec4(filtered.rgb * filtered.a, filtered.a), color, supersampling);
}
return vec4(color.rgb / max(color.a, 0.00001), color.a);
}
@@ -1,121 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
/** The executed half of a GL_COMPILE_AND_EXECUTE glDrawArrays sees transforms issued since the last matrix barrier. */
@GLCoreTest
public class GLSM_DisplayList_CompileAndExecuteArrays_GLTest {
private static final int W = 32;
private static final int POSITION_LOC = VertexFormatElement.Usage.POSITION.getAttributeLocation();
private final ByteBuffer pixels = BufferUtils.createByteBuffer(W * 4);
private int savedX, savedY, savedW, savedH;
private int vao, vbo, list;
@BeforeEach
void setUp() {
final ViewportState vp = GLStateManager.getViewportState();
savedX = vp.x;
savedY = vp.y;
savedW = vp.width;
savedH = vp.height;
GLStateManager.glUseProgram(0);
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
final ByteBuffer verts = ByteBuffer.allocateDirect(3 * 12).order(ByteOrder.nativeOrder());
verts.putFloat(0f).putFloat(0.5f).putFloat(0f).putFloat(W).putFloat(0.5f).putFloat(0f).flip();
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, verts, GL15.GL_STATIC_DRAW);
GLStateManager.glVertexAttribPointer(POSITION_LOC, 3, GL11.GL_FLOAT, false, 12, 0L);
GLStateManager.glEnableVertexAttribArray(POSITION_LOC);
GLStateManager.glViewport(0, 0, W, 1);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glOrtho(0, W, 0, 1, -1, 1);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
GLStateManager.glDisable(GL11.GL_BLEND);
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
GLStateManager.glColor4f(1f, 1f, 1f, 1f);
}
@AfterEach
void tearDown() {
if (list > 0) GLStateManager.glDeleteLists(list, 1);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPopMatrix();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPopMatrix();
GLStateManager.glViewport(savedX, savedY, savedW, savedH);
GLStateManager.glBindVertexArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
if (vbo != 0) GLStateManager.glDeleteBuffers(vbo);
if (vao != 0) GLStateManager.glDeleteVertexArrays(vao);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
private void clear() {
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
}
private boolean[] readCoverage() {
pixels.clear();
GLStateManager.glReadPixels(0, 0, W, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "GL error during readback");
final boolean[] covered = new boolean[W];
for (int i = 0; i < W; i++) {
covered[i] = (pixels.get(i * 4) & 0xFF) > 127;
}
return covered;
}
private static boolean[] leftHalf() {
final boolean[] e = new boolean[W];
for (int i = 0; i < W / 2; i++) e[i] = true;
return e;
}
/** The scale is issued inside the list, after the push that flushed everything before it - AE2 monitor's shape. */
private void compileScaledDraw(int mode) {
list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, mode);
GLStateManager.glPushMatrix();
GLStateManager.glScalef(0.5f, 1f, 1f);
GLStateManager.glDrawArrays(GL11.GL_LINES, 0, 2);
GLStateManager.glPopMatrix();
GLStateManager.glEndList();
}
// Replay is not asserted here: a compiled list plays back through FFP client arrays, which this core context lacks,
// and in the compat context this attribute setup draws nothing at all.
@Test
void compileAndExecuteDrawsWithPendingScale() {
clear();
compileScaledDraw(GL11.GL_COMPILE_AND_EXECUTE);
assertArrayEquals(leftHalf(), readCoverage(), "executed half of GL_COMPILE_AND_EXECUTE");
}
}
@@ -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());
}
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -11,7 +10,6 @@ import org.lwjgl.opengl.GL33;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@@ -117,88 +115,37 @@ class GLSM_SamplerBinding_GLTest {
@Test
void unbindAllSamplersClearsUnitsBoundThroughGlsm() {
final int sampler = RenderSystem.genSampler();
final Runnable previousFlush = BatchStateGuard.flush;
final AtomicInteger flushes = new AtomicInteger();
try {
GLStateManager.glBindSampler(1, sampler);
GLStateManager.glBindSampler(3, sampler);
BatchStateGuard.flush = () -> {
assertEquals(sampler, driverBinding(1), "deferred draws must see the original bindings");
assertEquals(sampler, driverBinding(3), "deferred draws must see the original bindings");
flushes.incrementAndGet();
};
RenderSystem.unbindAllSamplers();
RenderSystem.unbindAllSamplers();
assertEquals(1, flushes.get(), "bulk unbind should drain deferred draws once");
assertEquals(0, driverBinding(1), "unit 1 still bound on the driver");
assertEquals(0, driverBinding(3), "unit 3 still bound on the driver");
assertEquals(0, GLStateManager.getSamplerBinding(1));
assertEquals(0, GLStateManager.getSamplerBinding(3));
} finally {
BatchStateGuard.flush = previousFlush;
RenderSystem.destroySampler(sampler);
}
}
@Test
void replayForwardsCachedSamplerBindings() {
final int highUnit = RenderSystem.getMaxCombinedTextureImageUnits() - 1;
final int sampler = RenderSystem.genSampler();
try {
GLStateManager.glBindSampler(2, sampler);
GLStateManager.glBindSampler(highUnit, sampler);
GL33.glBindSampler(2, 0);
GL33.glBindSampler(highUnit, 0);
assertEquals(0, driverBinding(2), "setup failed to diverge the driver");
assertEquals(0, driverBinding(highUnit), "setup failed to diverge the driver");
GLStateManager.replayStateToBackend();
assertEquals(sampler, driverBinding(2), "replay dropped a cached sampler binding");
assertEquals(sampler, driverBinding(highUnit), "replay skipped the highest bound unit");
} finally {
RenderSystem.destroySampler(sampler);
}
}
@Test
void sparseBindingsKeepTheCleanupLimitAccurateAfterUnbindAndDeletion() {
final int highUnit = RenderSystem.getMaxCombinedTextureImageUnits() - 1;
final int low = RenderSystem.genSampler();
final int high = RenderSystem.genSampler();
try {
GLStateManager.glBindSampler(1, low);
GLStateManager.glBindSampler(3, high);
GLStateManager.glBindSampler(highUnit, high);
assertEquals(highUnit, GLStateManager.ctx().samplerUnits.getHighestBoundUnit());
GLStateManager.glBindSampler(highUnit, 0);
assertEquals(3, GLStateManager.ctx().samplerUnits.getHighestBoundUnit());
assertEquals(0, driverBinding(highUnit));
GLStateManager.glBindSampler(highUnit, high);
RenderSystem.destroySampler(high);
assertEquals(1, GLStateManager.ctx().samplerUnits.getHighestBoundUnit());
assertEquals(0, driverBinding(highUnit));
assertEquals(0, driverBinding(3));
RenderSystem.unbindAllSamplers();
assertEquals(-1, GLStateManager.ctx().samplerUnits.getHighestBoundUnit());
assertEquals(0, driverBinding(1));
GLStateManager.glBindSampler(highUnit, low);
RenderSystem.unbindAllSamplers();
RenderSystem.unbindAllSamplers();
assertEquals(-1, GLStateManager.ctx().samplerUnits.getHighestBoundUnit());
assertEquals(0, driverBinding(highUnit));
} finally {
RenderSystem.destroySampler(low);
RenderSystem.destroySampler(high);
}
}
@Test
void intBufferGenFillsFromPositionWithBindableSamplers() {
final IntBuffer buf = ints(-1, 0, 0);
@@ -202,6 +202,7 @@ public class GLSM_SharedDrawableContext_GLTest {
DrawableContexts.run(sharedDrawable, "SharedDrawableContext-Reentry-Thread", () -> {
final ShaderManager sm = ShaderManager.getInstance();
final boolean wasEnabled = ShaderManager.isEnabled();
ShaderManager.enable();
sm.activate();
try {
@@ -227,6 +228,7 @@ public class GLSM_SharedDrawableContext_GLTest {
GLStateManager.glDeleteBuffers(vbo);
} finally {
sm.deactivate();
if (!wasEnabled) ShaderManager.disable();
}
});
}
@@ -271,6 +273,7 @@ public class GLSM_SharedDrawableContext_GLTest {
void indexedListCompiledOnSharedDrawableReplaysOnDisplay() throws Exception {
final int list = compileIndexedListOnSharedDrawable();
final ShaderManager sm = ShaderManager.getInstance();
final boolean wasEnabled = ShaderManager.isEnabled();
ShaderManager.enable();
sm.activate();
try {
@@ -280,6 +283,7 @@ public class GLSM_SharedDrawableContext_GLTest {
assertEquals(indexedBatch().getSharedEBO(), GL11.glGetInteger(GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING));
} finally {
sm.deactivate();
if (!wasEnabled) ShaderManager.disable();
GLStateManager.glBindVertexArray(0);
}
}
@@ -73,6 +73,7 @@ class FFPVertexLightingGLTest {
GLStateManager.glDisable(GL11.GL_COLOR_MATERIAL);
final ShaderManager sm = ShaderManager.getInstance();
if (sm.isActive()) sm.deactivate();
ShaderManager.disable();
GLStateManager.glBindVertexArray(0);
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
@@ -1,302 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
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.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.function.IntBinaryOperator;
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.TEXTURE_BIT;
import static org.joml.Math.clamp;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
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 TextureSupersamplingGLTest {
private static final int SIZE = 200;
private int texture;
private int vao;
private int vbo;
private static int dotColor(int x, int y) {
final boolean dot = (x == 3 || x == 6 || x == 9 || x == 12)
&& (y == 3 || y == 6 || y == 9 || y == 12)
&& (x == 3 || x == 12 || y == 3 || y == 12);
return dot ? 0x444444FF : 0xFFFFFFFF;
}
private static int uploadTexture(int spriteSize, int gutter, IntBinaryOperator color) {
final int atlasSize = Integer.highestOneBit(spriteSize + 2 * gutter) << 1;
final ByteBuffer pixels = BufferUtils.createByteBuffer(atlasSize * atlasSize * 4);
for (int y = 0; y < atlasSize; y++) {
for (int x = 0; x < atlasSize; x++) {
final int rgba = x < spriteSize + 2 * gutter && y < spriteSize + 2 * gutter
? color.applyAsInt(clamp(0, spriteSize - 1, x - gutter), clamp(0, spriteSize - 1, y - gutter))
: 0xFF00FFFF;
pixels.put((byte) (rgba >>> 24)).put((byte) (rgba >>> 16)).put((byte) (rgba >>> 8)).put((byte) rgba);
}
}
pixels.flip();
final int texture = GLStateManager.glGenTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
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);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 0);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, atlasSize, atlasSize, 0,
GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
return texture;
}
private static void vertex(ByteBuffer vertices, float x, float z, float u, float v) {
vertices.putFloat(x).putFloat(0.5f).putFloat(z).putFloat(u).putFloat(v);
}
private static int countDotRegions(int[] pixels) {
final boolean[] remaining = new boolean[pixels.length];
for (int i = 0; i < pixels.length; i++) {
final int gray = pixels[i] & 255;
remaining[i] = gray > 0 && gray < 250
&& ((pixels[i] >> 8) & 255) == gray && ((pixels[i] >> 16) & 255) == gray;
}
int regions = 0;
final ArrayDeque<Integer> pending = new ArrayDeque<>();
for (int i = 0; i < remaining.length; i++) {
if (!remaining[i]) continue;
regions++;
remaining[i] = false;
pending.add(i);
while (!pending.isEmpty()) {
final int pixel = pending.removeFirst();
for (int neighbor : new int[]{pixel - 1, pixel + 1, pixel - SIZE, pixel + SIZE}) {
if (neighbor >= 0 && neighbor < remaining.length && remaining[neighbor]) {
remaining[neighbor] = false;
pending.add(neighbor);
}
}
}
}
return regions;
}
@BeforeEach
void setup() {
FfpFixture.resetFfpState();
GLStateManager.setTextureSupersamplingSamples(1);
GLStateManager.glDisable(GL11.GL_LIGHTING);
GLStateManager.glDisable(GL11.GL_FOG);
GLStateManager.glColor4f(1, 1, 1, 1);
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
GLStateManager.glViewport(0, 0, SIZE, SIZE);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glOrtho(0, SIZE, SIZE, 0, -100, 100);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
FfpFixture.activateFfp();
}
@AfterEach
void cleanup() {
GLStateManager.setTextureSupersamplingSamples(1);
FfpFixture.resetFfpState();
if (texture != 0) GLStateManager.glDeleteTextures(texture);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
if (vbo != 0) GLStateManager.glDeleteBuffers(vbo);
if (vao != 0) GLStateManager.glDeleteVertexArrays(vao);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glViewport(0, 0, 800, 600);
}
@ParameterizedTest
@ValueSource(ints = {4, 16})
void recoversTwelveDotsAtScaleThreeAndRestoresNearestAfterward(int samples) {
createDotAtlas();
final int[] nearest = drawTop(3);
assertEquals(0, countDotRegions(nearest));
final int previous = GLStateManager.setTextureSupersamplingSamples(samples);
assertEquals(12, countDotRegions(drawTop(3)));
GLStateManager.setTextureSupersamplingSamples(previous);
assertArrayEquals(nearest, drawTop(3));
}
@ParameterizedTest
@ValueSource(ints = {4, 16})
void highResolutionCheckerboardsStaySmoothWithTheDefaultAtlasGutter(int samples) {
createAtlas(256, 16, (x, y) -> ((x ^ y) & 1) == 0 ? 0xFFFFFFFF : 0x000000FF);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 4);
GLStateManager.glGenerateMipmap(GL11.GL_TEXTURE_2D);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR_MIPMAP_LINEAR);
final int[] filtered = drawFlat();
GLStateManager.setTextureSupersamplingSamples(samples);
final int[] supersampled = drawFlat();
for (int y = 97; y <= 102; y++) {
for (int x = 97; x <= 102; x++) {
final int index = y * SIZE + x;
assertEquals(128, filtered[index] & 255, 1, "The mip-filtered checkerboard must average to gray");
assertEquals(filtered[index] & 255, supersampled[index] & 255, 1,
"Supersampling must preserve the mip average instead of aliasing the base-level checkerboard");
}
}
}
@ParameterizedTest
@ValueSource(ints = {4, 16})
void transparentTexelsDoNotTintPartialCoverage(int samples) {
createAtlas(16, (x, y) -> x < 8 ? 0xFF0000FF : 0x0000FF00);
GLStateManager.glEnable(GL11.GL_BLEND);
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
GLStateManager.setTextureSupersamplingSamples(samples);
final int[] pixels = drawTop(3);
int partialPixels = 0;
for (int pixel : pixels) {
final int red = pixel & 255;
if (red > 0 && red < 255) {
partialPixels++;
assertEquals(0, (pixel >> 8) & 255);
assertEquals(0, (pixel >> 16) & 255, "Transparent blue texels must not tint the red edge");
}
}
assertTrue(partialPixels > 0, "The test must cover a partially transparent pixel");
}
@Test
void modelTexturesKeepTheirOwnFilteringWhileSupersamplingIsActive() {
createDotAtlas();
final int modelTexture = uploadTexture(16, 1, TextureSupersamplingGLTest::dotColor);
try {
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, GL11.GL_REPEAT);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
final int[] normal = drawTop(3);
GLStateManager.setTextureSupersamplingSamples(16);
assertArrayEquals(normal, drawTop(3));
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
assertFalse(Arrays.equals(normal, drawTop(3)));
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, modelTexture);
assertArrayEquals(normal, drawTop(3));
} finally {
GLStateManager.glDeleteTextures(modelTexture);
}
}
@Test
void atlasFetchesIgnoreSamplerFilteringAndRepeatAcrossNegativeUvs() {
createAtlas(16, 4, TextureSupersamplingGLTest::dotColor);
GLStateManager.setTextureSupersamplingSamples(16);
final int[] nearest = drawTop(3);
final int sampler = RenderSystem.genSampler();
try {
RenderSystem.samplerParameteri(sampler, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
RenderSystem.samplerParameteri(sampler, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
RenderSystem.samplerParameteri(sampler, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
RenderSystem.samplerParameteri(sampler, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
RenderSystem.bindSamplerToUnit(0, sampler);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glTranslatef(-2, 3, 0);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
assertArrayEquals(nearest, drawTop(3));
} finally {
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
RenderSystem.bindSamplerToUnit(0, 0);
RenderSystem.destroySampler(sampler);
}
}
@Test
void changingTheAtlasGutterSwitchesBetweenMipFilteringAndFullFootprintSampling() {
createAtlas(32, 4, (x, y) -> 0xFF0000FF);
final int[] baseLevel = drawFlat();
for (int level = 1; level <= 6; level++) {
final int size = 64 >> level;
final ByteBuffer pixels = BufferUtils.createByteBuffer(size * size * 4);
for (int i = 0; i < size * size; i++) pixels.putInt(0xFFFF0000);
pixels.flip();
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA8, size, size, 0,
GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
}
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 6);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST_MIPMAP_NEAREST);
final int[] mipFiltered = drawFlat();
assertFalse(Arrays.equals(baseLevel, mipFiltered));
GLStateManager.setTextureSupersamplingSamples(16);
assertArrayEquals(baseLevel, drawFlat());
GLStateManager.markTextureAsAtlas(texture, 1);
assertArrayEquals(mipFiltered, drawFlat());
GLStateManager.markTextureAsAtlas(texture, 4);
assertArrayEquals(baseLevel, drawFlat());
}
private int[] drawFlat() {
FfpFixture.clear();
GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(100.25f, 100.125f, 0);
GLStateManager.glScalef(11, 11, 1);
GLStateManager.glRotatef(90, 1, 0, 0);
GLStateManager.glDrawArrays(GL11.GL_QUADS, 0, 4);
return FfpFixture.readRegion(SIZE);
}
private void createDotAtlas() {
createAtlas(16, TextureSupersamplingGLTest::dotColor);
}
private void createAtlas(int spriteSize, IntBinaryOperator color) {
createAtlas(spriteSize, 1, color);
}
private void createAtlas(int spriteSize, int gutter, IntBinaryOperator color) {
texture = uploadTexture(spriteSize, gutter, color);
GLStateManager.markTextureAsAtlas(texture, gutter);
final int atlasSize = Integer.highestOneBit(spriteSize + 2 * gutter) << 1;
final float minUv = (gutter + 0.01f) / atlasSize;
final float maxUv = (spriteSize + gutter - 0.01f) / atlasSize;
final ByteBuffer vertices = BufferUtils.createByteBuffer(4 * 20);
vertex(vertices, -0.5f, -0.5f, minUv, minUv);
vertex(vertices, 0.5f, -0.5f, maxUv, minUv);
vertex(vertices, 0.5f, 0.5f, maxUv, maxUv);
vertex(vertices, -0.5f, 0.5f, minUv, maxUv);
vertices.flip();
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, vertices, GL15.GL_STATIC_DRAW);
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, 20, 0);
FfpFixture.attrib(2, 2, GL11.GL_FLOAT, false, 20, 12);
VAOManager.setCurrentVertexFlags(TEXTURE_BIT);
}
private int[] drawTop(int scale) {
FfpFixture.clear();
GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(100 + 8 * scale, 100 + 8 * scale, 0);
GLStateManager.glScalef(10 * scale, 10 * scale, -10 * scale);
GLStateManager.glRotatef(210, 1, 0, 0);
GLStateManager.glRotatef(45, 0, 1, 0);
GLStateManager.glDrawArrays(GL11.GL_QUADS, 0, 4);
return FfpFixture.readRegion(SIZE);
}
}
@@ -16,7 +16,6 @@ import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.PixelFormat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
/**
@@ -83,11 +82,8 @@ public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback,
public void afterEach(ExtensionContext context) {
final int error = GL11.glGetError();
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
final boolean ffpDisabled = !ShaderManager.isEnabled();
if (ffpDisabled) ShaderManager.enable();
GlsmRedirectAgent.assertNoFailures();
assertEquals(GL11.GL_NO_ERROR, error, () -> "GL Error: 0x" + Integer.toHexString(error));
if (ffpDisabled) fail("test left FFP disabled");
}
@Override
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.loading.ClassDump;
import com.gtnewhorizons.angelica.glsm.loading.ModRedirector;
import com.gtnewhorizons.angelica.glsm.testutil.MutableStatics;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
@@ -28,7 +27,6 @@ public final class GlsmRedirectAgent {
}
public static void install(Instrumentation instrumentation) {
MutableStatics.install(instrumentation);
final ModRedirector redirector = new ModRedirector(ClassDump.DISABLED);
addTransformer(instrumentation, (className, classfile) -> {
if (!className.startsWith(GTNHLIB)) return null;
@@ -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) {
@@ -237,6 +233,7 @@ public final class FfpFixture {
GLStateManager.setFfpInstancing(Instancing.NONE);
final ShaderManager sm = ShaderManager.getInstance();
if (sm.isActive()) sm.deactivate();
ShaderManager.disable();
GLStateManager.glBindVertexArray(0);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
@@ -1,61 +0,0 @@
package com.gtnewhorizons.angelica.glsm.testutil;
import com.gtnewhorizons.angelica.glsm.GlsmRedirectAgent;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import java.lang.instrument.Instrumentation;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/* Test agent: strips ACC_FINAL from listed static finals at load to avoid JIT inlining them when tests want to mutate them */
public final class MutableStatics {
private static final Map<String, Set<String>> FIELDS_BY_OWNER = new HashMap<>();
static {
add("com/gtnewhorizons/angelica/glsm/backend/BackendManager", "RENDER_BACKEND");
add("com/gtnewhorizons/angelica/glsm/GLStateManager", "MainThread");
add("com/gtnewhorizons/angelica/config/SystemProperties", "UNMAPPED_GL", "SDL_DISABLE_IN_PASS_CLEAR");
add("net/coderbot/iris/Iris", "enabled");
}
private MutableStatics() {}
private static void add(String owner, String... fields) {
FIELDS_BY_OWNER.put(owner, new HashSet<>(Arrays.asList(fields)));
}
public static void premain(String args, Instrumentation instrumentation) {
install(instrumentation);
}
public static void install(Instrumentation instrumentation) {
GlsmRedirectAgent.addTransformer(instrumentation, (className, classfile) -> {
final Set<String> fields = FIELDS_BY_OWNER.get(className);
return fields == null ? null : stripFinal(classfile, fields);
});
}
private static byte[] stripFinal(byte[] classfile, Set<String> fields) {
final ClassNode node = new ClassNode();
new ClassReader(classfile).accept(node, 0);
int patched = 0;
for (FieldNode field : node.fields) {
if (fields.contains(field.name)) {
field.access &= ~Opcodes.ACC_FINAL;
patched++;
}
}
if (patched != fields.size()) throw new IllegalStateException(node.name + " lacks one of " + fields);
final ClassWriter writer = new ClassWriter(0);
node.accept(writer);
return writer.toByteArray();
}
}
@@ -1,12 +1,12 @@
package com.gtnewhorizons.angelica.glsm.testutil;
import com.gtnewhorizons.angelica.glsm.GlsmRedirectAgent;
import com.gtnewhorizon.gtnhlib.reflect.Fields;
import com.gtnewhorizon.gtnhlib.reflect.Fields.LookupType;
import sun.misc.Unsafe;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public final class Reflect {
@@ -59,13 +59,7 @@ public final class Reflect {
}
public static <F> void setStaticFinal(Class<?> owner, String name, Class<F> type, F value) {
final Field f = field(owner, name);
if (f.getType() != type) throw new AssertionError(owner.getName() + "." + name + " is " + f.getType().getName() + ", not " + type.getName());
if (Modifier.isFinal(f.getModifiers())) {
GlsmRedirectAgent.assertNoFailures();
throw new AssertionError(owner.getName() + "." + name + " is still final; add it to MutableStatics");
}
setStatic(owner, name, value);
Fields.ofClass(owner).getField(LookupType.DECLARED_IN_HIERARCHY, name, type).setValue(null, value);
}
public static void setDeclared(Class<?> owner, Object target, String name, Object value) {
@@ -47,6 +47,7 @@ public final class VertexWriteReplayer {
private static final int MAX_DIVISOR = 0xFFFF;
private static final int UNCHANGED = -1;
private static final int TEXTURE_UNITS = 32;
private static final int SAMPLER_OBJECT_OF_DRAW = -1;
public static final class Replay {
final int replayProgram;
@@ -59,7 +60,7 @@ public final class VertexWriteReplayer {
int[] uniformTargets;
int[] samplerUnits;
IntSupplier[] samplerSources;
IntSupplier[] samplerObjects;
int[] samplerObjects;
int[] packStorageSlots;
int[] packRwStorageSlots;
int[] uniformBlockIndices;
@@ -142,7 +143,7 @@ public final class VertexWriteReplayer {
private void resolveSamplers(ShaderManager.ProgramObject graphics, ShaderManager.ProgramObject replay, List<String> names) {
final IntArrayList units = new IntArrayList();
final List<IntSupplier> sources = new ArrayList<>();
final List<IntSupplier> objects = new ArrayList<>();
final IntArrayList objects = new IntArrayList();
final IntArrayList ownTextureIndices = new IntArrayList();
final List<String> ownTextureNames = new ArrayList<>();
final List<String> pipelineNames = Arrays.asList(setup.samplerNames());
@@ -163,7 +164,7 @@ public final class VertexWriteReplayer {
if (!units.contains(unit)) {
units.add(unit);
sources.add(null);
objects.add(null);
objects.add(SAMPLER_OBJECT_OF_DRAW);
}
}
int free = TEXTURE_UNITS - 1;
@@ -178,7 +179,7 @@ public final class VertexWriteReplayer {
}
samplerUnits = units.toIntArray();
samplerSources = sources.toArray(new IntSupplier[0]);
samplerObjects = objects.toArray(new IntSupplier[0]);
samplerObjects = objects.toIntArray();
}
}
@@ -305,8 +306,7 @@ public final class VertexWriteReplayer {
final int unit = replay.samplerUnits[i];
final IntSupplier source = replay.samplerSources[i];
c.samplerTextures[i] = source != null ? source.getAsInt() : st.boundTextures[unit];
final IntSupplier sampler = replay.samplerObjects[i];
final int samplerObject = sampler != null ? sampler.getAsInt() : st.boundSamplerObjects[unit];
final int samplerObject = replay.samplerObjects[i] != SAMPLER_OBJECT_OF_DRAW ? replay.samplerObjects[i] : st.boundSamplerObjects[unit];
c.samplerHandles[i] = computeBinder.samplerHandle(samplerObject, c.samplerTextures[i]);
}
if (c.imageTextures.length != images.length) c.imageTextures = new int[images.length];
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import org.junit.jupiter.api.Tag;
@@ -13,9 +12,11 @@ import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import sun.misc.Unsafe;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.BLACK;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.RED;
@@ -33,8 +34,17 @@ class GlsmSdlAttachmentClearTest {
return GlsmSdlHeadlessRig.frameManager().frame();
}
// Reflect.setStaticFinal rejects primitive fields.
private static void setDisableInPassClear(boolean value) {
Reflect.setStaticFinal(SystemProperties.class, "SDL_DISABLE_IN_PASS_CLEAR", boolean.class, value);
try {
final Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
final Unsafe unsafe = (Unsafe) theUnsafe.get(null);
final Field field = SystemProperties.class.getDeclaredField("SDL_DISABLE_IN_PASS_CLEAR");
unsafe.putBoolean(unsafe.staticFieldBase(field), unsafe.staticFieldOffset(field), value);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
}
/** Clears colour to black and depth to 0.9, then draws a quad so a render pass is open. */
@@ -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;
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.common;
import com.gtnewhorizons.angelica.rendering.TextureAtlasType;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.block.Block;
@@ -40,7 +39,7 @@ public class BlockError extends Block {
@SubscribeEvent
@SideOnly(Side.CLIENT)
public void onTextureStitch(TextureStitchEvent.Pre event) {
if (event.map.getTextureType() == TextureAtlasType.BLOCK) {
if (event.map.getTextureType() == 0) {
for (int i = 0; i < icons.length; i++) {
icons[i] = event.map.registerIcon("angelica:error_block_" + i);
}
@@ -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
@@ -206,11 +192,6 @@ public class AngelicaConfig {
@Config.RequiresMcRestart
public static boolean enableTESRProviderDispatch;
@Config.Comment("Cache the lightmap the tile-entity dispatcher looks up per tile per frame, refreshing it once a tick")
@Config.DefaultBoolean(true)
@Config.RequiresMcRestart
public static boolean enableTESRLightCache;
@Config.Comment("Enable full RGB color support (16.7M colors) using &#RRGGBB syntax in text")
@Config.DefaultBoolean(true)
public static boolean enableRGBColors;
@@ -295,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
@@ -567,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);
@@ -68,12 +68,7 @@ public enum Mixins implements IMixins {
, "angelica.optimizations.MixinRenderBiped_ArmorResource"
, "angelica.optimizations.MixinRendererLivingEntity"
, "angelica.rendering.MixinRenderGlobal_SelectionBox"
, "angelica.textures.MixinForgeHooksClient_GuiSupersampling"
, "angelica.textures.MixinRenderBlocks_GuiSupersampling"
, "angelica.textures.MixinRenderItem_GuiSupersampling"
, "angelica.textures.MixinTileEntityRendererDispatcher_GuiSupersampling"
, "angelica.gui.MixinGuiIngameForge_ModernF3"
, "angelica.textures.MixinRenderItem_GuiSampler"
)
),
@@ -212,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)
@@ -457,7 +443,6 @@ public enum Mixins implements IMixins {
, "celeritas.frustum.MixinClippingHelperImpl"
, "celeritas.frustum.MixinFrustrum"
, "celeritas.features.culling.MixinEffectRenderer"
, "celeritas.features.mipmaps.MixinEffectRenderer"
, "celeritas.features.mipmaps.MixinTextureAtlasSprite"
, "celeritas.features.mipmaps.MixinTextureMetadataSection"
, "celeritas.features.mipmaps.MixinTextureMetadataSectionSerializer"
@@ -632,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(
@@ -663,11 +656,6 @@ public enum Mixins implements IMixins {
.setApplyIf(() -> AngelicaConfig.optimizeWorldUpdateLight)
.addCommonMixins("angelica.lighting.MixinWorld_FixLightUpdateLag")),
TESR_LIGHT_CACHE(new MixinBuilder("Cache the tile-entity dispatcher's per-tile lightmap lookup for one tick")
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enableTESRLightCache)
.addClientMixins("angelica.lighting.MixinTileEntityRendererDispatcher_LightCache")),
SPEEDUP_VANILLA_ANIMATIONS(new MixinBuilder()
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.speedupAnimations)
@@ -896,11 +884,6 @@ public enum Mixins implements IMixins {
.setApplyIf(() -> AngelicaConfig.enableIris)
.addClientMixins("client.dragonapi.MixinThrottleableEffectRenderer")
),
DRAGONAPI_PARTICLE_CUTOFF(new MixinBuilder("Modern 26.2 mipmap alpha cutoff for DragonAPI's terrain particles")
.setPhase(Phase.LATE)
.addRequiredMod(TargetedMod.DRAGON_API)
.addClientMixins("client.dragonapi.MixinThrottleableEffectRenderer_MipmapCutoff")
),
DRAGONAPI_PARTICLE_BATCH(new MixinBuilder("Particle instancing for DragonAPI's replacement particle renderer")
.setPhase(Phase.LATE)
.addRequiredMod(TargetedMod.DRAGON_API)
@@ -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"),
@@ -7,8 +7,4 @@ public interface TextureMetadataExtension {
void angelica$setMipmapStrategy(MipmapStrategy strategy);
MipmapStrategy angelica$getMipmapStrategy();
void angelica$setAlphaCutoffBias(float bias);
float angelica$getAlphaCutoffBias();
}
@@ -51,7 +51,6 @@ import com.gtnewhorizons.angelica.config.AngelicaConfig;
import com.gtnewhorizons.angelica.rendering.TileEntityRenderBoundsRegistry;
import com.gtnewhorizons.angelica.rendering.tesr.AngelicaTesrMeshCache;
import com.gtnewhorizons.angelica.rendering.tesr.EntityTextureAtlas;
import com.gtnewhorizons.angelica.rendering.tesr.TesrLightCache;
import com.gtnewhorizons.angelica.config.CompatConfig;
import com.gtnewhorizons.angelica.config.ConfigMigrator;
import com.gtnewhorizons.angelica.config.SystemProperties;
@@ -79,7 +78,6 @@ import com.gtnewhorizons.angelica.rendering.AngelicaBlockSafetyRegistry;
import com.gtnewhorizons.angelica.rendering.FpsReducer;
import com.gtnewhorizons.angelica.rendering.FramePacer;
import com.gtnewhorizons.angelica.rendering.IsbrhTestRenderer;
import com.gtnewhorizons.angelica.rendering.TextureSamplers;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasSetup;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
@@ -170,7 +168,6 @@ public final class ClientProxy extends CommonProxy {
HUDCaching.init();
}
CeleritasSetup.ensureInitialized();
TextureSamplers.initRenderer();
DarkModeUtils.init();
final SodiumGameOptions opts = options();
if (opts.advanced.vsyncMode == null) {
@@ -423,10 +420,6 @@ public final class ClientProxy extends CommonProxy {
@SubscribeEvent
public void onTick(TickEvent.ClientTickEvent event) {
if (event.phase == TickEvent.Phase.END) {
TesrLightCache.onClientTick();
}
if (event.phase == TickEvent.Phase.END && mc.theWorld != null) {
CloudRenderer.getCloudRenderer().checkSettings();
}
@@ -646,7 +646,7 @@ final class CloudVertexMesh {
vao.bind();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vao.getVBO().getId());
GLStateManager.glEnableVertexAttribArray(FACE_SHADE_ATTRIB);
GLStateManager.glVertexAttribPointer(FACE_SHADE_ATTRIB, 4, GL11.GL_UNSIGNED_BYTE, true, vertexStride, normalOffset);
GLStateManager.glVertexAttribPointer(FACE_SHADE_ATTRIB, 1, GL11.GL_UNSIGNED_BYTE, true, vertexStride, normalOffset + 3);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
vao.unbind();
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.render;
import com.gtnewhorizons.angelica.rendering.TextureAtlasType;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@@ -23,7 +22,7 @@ public class EmissiveTextureAutoloader {
@SubscribeEvent
public void onTextureStitchPre(TextureStitchEvent.Pre event) {
if (event.map.getTextureType() != TextureAtlasType.BLOCK) return;
if (event.map.getTextureType() != 0) return;
TextureMap map = event.map;
EmissiveTextureHelper.clearCache();
@@ -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,
@@ -1,18 +0,0 @@
package com.gtnewhorizons.angelica.rendering;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
public final class GuiBlockSupersampling {
private GuiBlockSupersampling() {}
public static int begin() {
if (!RenderingState.INSTANCE.isRenderingGuiItem() || RenderingState.INSTANCE.isRenderingWorld()) return 0;
final int samples = ClientProxy.options().quality.guiBlockSupersampling;
return samples > 1 ? GLStateManager.setTextureSupersamplingSamples(samples) : 0;
}
public static void end(int previous) {
if (previous != 0) GLStateManager.setTextureSupersamplingSamples(previous);
}
}
@@ -23,12 +23,6 @@ public class RenderingState {
@Getter
@Setter
private float fov;
@Getter
@Setter
private boolean renderingWorld;
@Getter
@Setter
private boolean renderingGuiItem;
public void setCameraPosition(double x, double y, double z) {
@@ -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);
}
}
@@ -1,8 +0,0 @@
package com.gtnewhorizons.angelica.rendering;
public final class TextureAtlasType {
public static final int BLOCK = 0;
public static final int ITEM = 1;
private TextureAtlasType() {}
}
@@ -1,32 +0,0 @@
package com.gtnewhorizons.angelica.rendering;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import lombok.Getter;
import net.coderbot.iris.gl.sampler.GlSampler;
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
public final class TextureSamplers {
private static final GlSampler[][] TERRAIN_SAMPLERS = new GlSampler[2][17];
@Getter
private static GlSampler guiSampler;
private TextureSamplers() {
}
public static void initRenderer() {
guiSampler = new GlSampler(false, false, false, false, false, true);
}
public static GlSampler getTerrainSampler(boolean linear, int anisotropy) {
final GlSampler[] samplers = TERRAIN_SAMPLERS[linear ? 1 : 0];
GlSampler sampler = samplers[anisotropy];
if (sampler == null) {
sampler = new GlSampler(linear, true, true, false, false, true);
if (anisotropy > 1) {
RenderSystem.samplerParameterf(sampler.getId(), EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy);
}
samplers[anisotropy] = sampler;
}
return sampler;
}
}
@@ -72,12 +72,8 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
private final float[] fluidQuadPos = new float[12];
private ModelQuadFacing fluidQuadLightFace;
@Getter
private final WorldClient world;
public AngelicaChunkBuildContext(RenderPassConfiguration<?> renderPassConfiguration, WorldClient world) {
super(renderPassConfiguration);
this.world = world;
this.textureAtlas = (TextureMapExtension) Minecraft.getMinecraft().getTextureMapBlocks();
this.worldSlice = new WorldSlice(world);
// Shade by the quad normal so sloped quads blend the face shades instead of snapping to the dominant axis;
@@ -101,7 +97,7 @@ public class AngelicaChunkBuildContext extends ChunkBuildContext {
smoothLightPipeline.reset();
flatLightPipeline.reset();
lightPipelineReady = true;
cachedSkylightSubtracted = this.world.skylightSubtracted;
cachedSkylightSubtracted = Minecraft.getMinecraft().theWorld.skylightSubtracted;
}
public WorldSlice swapWorldSlice(WorldSlice replacement) {
@@ -267,7 +267,7 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
}
} else {
// Normal block rendering
canRender = !canRenderOffThread || block.canRenderInPass(pass);
canRender = block.canRenderInPass(pass);
}
if (canRender) {
@@ -407,10 +407,6 @@ public abstract class AngelicaChunkBuilderMeshingTask extends ChunkBuilderTask<C
final DeferredBlock deferred = blocks.get(i);
blockPos.set(deferred.x(), deferred.y(), deferred.z());
slice.setRenderingBlock(deferred.block());
if (!deferred.isShaderPackOverride()) {
setRenderPass(deferred.pass());
if (!deferred.block().canRenderInPass(deferred.pass())) continue;
}
renderBlock(deferred.block(), deferred.meta(), deferred.x(), deferred.y(), deferred.z(), deferred.pass(),
tessellator, renderBlocks, m.buffers, ctx, blockRenderContext, minX, minY, minZ,
deferred.materialOverride(), deferred.isShaderPackOverride(), m.teMap, m.currentTick);
@@ -8,7 +8,6 @@ import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.rendering.culling.GpuTerrainCuller;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import com.gtnewhorizons.angelica.rendering.TextureSamplers;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProvider;
import com.gtnewhorizons.angelica.rendering.celeritas.api.IrisShaderProviderHolder;
import com.gtnewhorizons.angelica.rendering.culling.GpuCulling;
@@ -21,7 +20,6 @@ import net.coderbot.iris.Iris;
import net.coderbot.iris.gl.program.ComputeProgram;
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.coderbot.iris.samplers.IrisSamplers;
import net.coderbot.iris.shadows.ShadowRenderingState;
import org.embeddedt.embeddium.impl.gl.device.CommandList;
import org.embeddedt.embeddium.impl.gl.device.DirectMultiDrawBatch;
@@ -54,6 +52,9 @@ import org.embeddedt.embeddium.impl.render.chunk.terrain.TerrainRenderPass;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL20;
import java.util.ArrayList;
@@ -64,7 +65,15 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
private static final Tracy.ZoneId Z_CHUNK_BEGIN = Tracy.zoneId("chunkBegin", Tracy.COLOR_TERRAIN);
private static final Tracy.ZoneId Z_CHUNK_ASSEMBLE_REGION = Tracy.zoneId("chunkAssembleRegion", Tracy.COLOR_TERRAIN);
private GlProgram<ChunkShaderInterface> irisProgram;
private GlProgram<? extends ChunkShaderInterface> irisProgram;
private boolean usingIrisProgram;
private int terrainSampler;
private boolean terrainSamplerResolved;
private int terrainSamplerAnisotropy = -1;
private boolean terrainSamplerNearest;
private int packTerrainSampler;
private boolean packTerrainSamplerResolved;
private int packTerrainSamplerAnisotropy = -1;
private boolean terrainSamplerBound;
private final GpuTerrainCuller culler;
private final SdlShadowVoxelizationSink shadowVoxelSink = new SdlShadowVoxelizationSink();
@@ -151,19 +160,21 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
// Check if Iris shaders are active and we have an override
if (provider != null && provider.isShadersEnabled()) {
final GlProgram<ChunkShaderInterface> override = provider.getShaderOverride(pass);
final GlProgram<? extends ChunkShaderInterface> override = provider.getShaderOverride(pass);
if (override != null) {
pass.startDrawing();
this.irisProgram = override;
this.activeProgram = override;
override.bind();
override.getInterface().setupState(pass);
this.activeProgram = (GlProgram<ChunkShaderInterface>) override;
this.irisProgram = override;
this.usingIrisProgram = true;
bindPackTerrainSampler();
return;
}
}
// Fall back to default shader
this.usingIrisProgram = false;
this.irisProgram = null;
beginDefault(pass);
bindTerrainSampler();
@@ -200,43 +211,85 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
@Override
protected void end(TerrainRenderPass pass) {
if (irisProgram != null) {
endIrisPass(pass);
} else {
super.end(pass);
}
}
unbindTerrainSampler();
private void endIrisPass(TerrainRenderPass pass) {
final GlProgram<ChunkShaderInterface> program = irisProgram;
irisProgram = null;
activeProgram = null;
try {
program.getInterface().restoreState();
} finally {
try {
program.unbind();
} finally {
pass.endDrawing();
}
if (usingIrisProgram && irisProgram != null) {
irisProgram.getInterface().restoreState();
irisProgram.unbind();
irisProgram = null;
usingIrisProgram = false;
this.activeProgram = null;
pass.endDrawing();
return;
}
super.end(pass);
}
private void bindTerrainSampler() {
if (SodiumGameOptions.usesTerrainTexelSnap()) {
bindBlockSampler(TextureSamplers.getTerrainSampler(!ClientProxy.options().quality.texelSampling.isNearest(),
SodiumGameOptions.resolvedAnisotropicFiltering()).getId());
if (!SodiumGameOptions.usesTerrainTexelSnap()) {
return;
}
final int anisotropy = SodiumGameOptions.resolvedAnisotropicFiltering();
final boolean nearest = ClientProxy.options().quality.texelSampling.isNearest();
if (!terrainSamplerResolved || terrainSamplerAnisotropy != anisotropy
|| terrainSamplerNearest != nearest) {
if (!terrainSamplerResolved) {
terrainSampler = RenderSystem.genSampler();
if (terrainSampler == 0) {
AngelicaMod.LOGGER.warn("Sampler objects unavailable; terrain filtering will fall back to the atlas texture's own parameters");
}
}
terrainSamplerResolved = true;
terrainSamplerAnisotropy = anisotropy;
terrainSamplerNearest = nearest;
configureTerrainSampler(terrainSampler, nearest, anisotropy);
}
if (terrainSampler != 0) {
RenderSystem.bindSamplerToUnit(BLOCK_TEXTURE_UNIT, terrainSampler);
terrainSamplerBound = true;
}
}
private void bindPackTerrainSampler() {
bindBlockSampler(IrisSamplers.getTerrainSampler(SodiumGameOptions.resolvedAnisotropicFiltering()).getId());
final int anisotropy = SodiumGameOptions.resolvedAnisotropicFiltering();
if (!packTerrainSamplerResolved || packTerrainSamplerAnisotropy != anisotropy) {
if (!packTerrainSamplerResolved) {
packTerrainSampler = RenderSystem.genSampler();
if (packTerrainSampler == 0) {
AngelicaMod.LOGGER.warn("Sampler objects unavailable; shader pack terrain filtering will fall back to the atlas texture's own parameters");
}
}
packTerrainSamplerResolved = true;
packTerrainSamplerAnisotropy = anisotropy;
configureTerrainSampler(packTerrainSampler, true, anisotropy);
}
if (packTerrainSampler != 0) {
RenderSystem.bindSamplerToUnit(BLOCK_TEXTURE_UNIT, packTerrainSampler);
terrainSamplerBound = true;
}
}
private void bindBlockSampler(int id) {
if (id != 0) {
RenderSystem.bindSamplerToUnit(BLOCK_TEXTURE_UNIT, id);
terrainSamplerBound = true;
private static void configureTerrainSampler(int sampler, boolean nearest, int anisotropy) {
if (sampler == 0) {
return;
}
RenderSystem.samplerParameteri(sampler, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
RenderSystem.samplerParameteri(sampler, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
RenderSystem.samplerParameteri(sampler, GL11.GL_TEXTURE_MIN_FILTER,
nearest ? GL11.GL_NEAREST_MIPMAP_LINEAR : GL11.GL_LINEAR_MIPMAP_LINEAR);
RenderSystem.samplerParameteri(sampler, GL11.GL_TEXTURE_MAG_FILTER,
nearest ? GL11.GL_NEAREST : GL11.GL_LINEAR);
if (SodiumGameOptions.anisotropySupported()) {
RenderSystem.samplerParameterf(sampler,
EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, (float) anisotropy);
}
}
@@ -258,6 +311,15 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
shadowVoxelSink.delete();
unbindTerrainSampler();
RenderSystem.destroySampler(terrainSampler);
terrainSampler = 0;
terrainSamplerResolved = false;
terrainSamplerAnisotropy = -1;
terrainSamplerNearest = false;
RenderSystem.destroySampler(packTerrainSampler);
packTerrainSampler = 0;
packTerrainSamplerResolved = false;
packTerrainSamplerAnisotropy = -1;
}
@Override
@@ -295,11 +357,8 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
voxelizeShadowTerrain(matrices, renderLists, renderPass, occlusionCamera, camera);
super.render(matrices, commandList, renderLists, renderPass, occlusionCamera, camera);
} finally {
try {
if (irisProgram != null) endIrisPass(renderPass);
} finally {
unbindTerrainSampler();
if (culler != null) culler.endPass();
if (culler != null) {
culler.endPass();
}
}
}
@@ -410,7 +469,7 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
@Override
protected void configureShaderInterface(ChunkShaderInterface shader) {
if (irisProgram == null) {
if (!usingIrisProgram) {
shader.setTextureSlot(ChunkShaderTextureSlot.BLOCK, 0);
shader.setTextureSlot(ChunkShaderTextureSlot.LIGHT, 1);
}
@@ -8,7 +8,6 @@ import com.gtnewhorizons.angelica.utils.SpritePadding;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
import lombok.Getter;
import me.jellysquid.mods.sodium.client.gui.SodiumGameOptions;
import me.jellysquid.mods.sodium.client.gui.options.named.MipmapGeneration;
import org.embeddedt.embeddium.impl.render.chunk.RenderPassConfiguration;
import org.embeddedt.embeddium.impl.render.chunk.compile.sorting.QuadPrimitiveType;
import org.embeddedt.embeddium.impl.render.chunk.terrain.TerrainRenderPass;
@@ -28,11 +27,9 @@ public class AngelicaRenderPassConfiguration {
@Getter
private static boolean rgssEnabled;
private static TerrainRenderPass.TerrainRenderPassBuilder builderForRenderType(int pass, boolean disableAlphaTest,
AlphaCutoffParameter alphaCutoff, ChunkVertexType vertexType) {
private static TerrainRenderPass.TerrainRenderPassBuilder builderForRenderType(int pass, boolean disableAlphaTest, ChunkVertexType vertexType) {
final TerrainRenderPass.TerrainRenderPassBuilder builder = TerrainRenderPass.builder()
.pipelineState(new AngelicaPipelineState(pass, disableAlphaTest, alphaCutoff))
.fragmentDiscard(!disableAlphaTest && alphaCutoff != AlphaCutoffParameter.ZERO)
.pipelineState(new AngelicaPipelineState(pass, disableAlphaTest))
.vertexType(vertexType)
.primitiveType(QuadPrimitiveType.TRIANGULATED);
@@ -64,25 +61,27 @@ public class AngelicaRenderPassConfiguration {
public static RenderPassConfiguration<BlockRenderLayer> build(ChunkVertexType vertexType) {
rgssEnabled = SodiumGameOptions.effectiveTextureFilterMode().usesRgss();
final AlphaCutoffParameter translucentCutoff = MipmapGeneration.alphaFloorCutoff();
SOLID_PASS = builderForRenderType(0, true, AlphaCutoffParameter.HALF, vertexType)
SOLID_PASS = builderForRenderType(0, true, vertexType)
.name("solid")
.fragmentDiscard(false)
.useReverseOrder(false)
.build();
CUTOUT_MIPPED_PASS = builderForRenderType(0, false, AlphaCutoffParameter.HALF, vertexType)
CUTOUT_MIPPED_PASS = builderForRenderType(0, false, vertexType)
.name("cutout_mipped")
.fragmentDiscard(true)
.useReverseOrder(false)
.build();
TRANSLUCENT_PASS = builderForRenderType(1, false, translucentCutoff, vertexType)
TRANSLUCENT_PASS = builderForRenderType(1, false, vertexType)
.name("translucent")
.fragmentDiscard(false)
.useReverseOrder(true)
.useTranslucencySorting(ClientProxy.options().performance.translucencySorting)
.build();
TRANSLUCENT_MATERIAL = new Material(TRANSLUCENT_PASS, translucentCutoff, true);
TRANSLUCENT_MATERIAL = new Material(TRANSLUCENT_PASS, AlphaCutoffParameter.ZERO, true);
SOLID_MATERIAL = new Material(SOLID_PASS, AlphaCutoffParameter.ZERO, true);
CUTOUT_MIPPED_MATERIAL = new Material(CUTOUT_MIPPED_PASS, AlphaCutoffParameter.HALF, true);
CUTOUT_MATERIAL = new Material(CUTOUT_MIPPED_PASS, AlphaCutoffParameter.ONE_TENTH, false);
@@ -106,29 +105,24 @@ public class AngelicaRenderPassConfiguration {
private static final class AngelicaPipelineState implements TerrainRenderPass.PipelineState {
private final int pass;
private final boolean disableAlphaTest;
private final float alphaCutoff;
private int alphaStateDepth = -1;
private AngelicaPipelineState(int pass, boolean disableAlphaTest, AlphaCutoffParameter alphaCutoff) {
private AngelicaPipelineState(int pass, boolean disableAlphaTest) {
this.pass = pass;
this.disableAlphaTest = disableAlphaTest;
this.alphaCutoff = alphaCutoff.cutoff();
}
@Override
public void setup() {
GLStateManager.glDepthMask(true);
if (alphaCutoff > 0.0f) {
if (pass == 0) {
if (alphaStateDepth >= 0) {
GLStateManager.popStateTo(alphaStateDepth);
alphaStateDepth = -1;
}
alphaStateDepth = GLStateManager.pushState(StateSet.ALPHA);
GLStateManager.glAlphaFunc(GL11.GL_GREATER, alphaCutoff);
if (pass == 1) {
GLStateManager.enableAlphaTest();
}
GLStateManager.glAlphaFunc(GL11.GL_GREATER, AlphaCutoffParameter.HALF.cutoff());
}
if (disableAlphaTest) {
GLStateManager.disableAlphaTest();
@@ -11,11 +11,7 @@ public interface SpriteExtension {
/** One-shot: returns true if marked, then resets. */
boolean celeritas$shouldUpdate();
void celeritas$setMipmapStrategy(MipmapStrategy strategy, boolean explicit);
void angelica$setTextureType(int textureType);
void angelica$setAlphaCutoffBias(float bias);
void celeritas$setMipmapStrategy(MipmapStrategy strategy, boolean explicit, int textureType);
/** Width of the replicated-edge border reserved around this sprite in its stitcher slot. */
int angelica$getGutterWidth();
@@ -19,7 +19,7 @@ public interface IrisShaderProvider {
boolean shouldUseFaceCulling();
@Nullable GlProgram<ChunkShaderInterface> getShaderOverride(TerrainRenderPass pass);
@Nullable GlProgram<? extends ChunkShaderInterface> getShaderOverride(TerrainRenderPass pass);
ChunkVertexType getVertexType(ChunkVertexType defaultType);
@@ -58,7 +58,7 @@ public class ThreadedAngelicaChunkBuilderMeshingTask extends AngelicaChunkBuilde
buildContext.setupLightPipeline(minX, minY, minZ);
} else {
// Fallback: direct world access (main thread only)
final var world = buildContext.getWorld();
final var world = Minecraft.getMinecraft().theWorld;
this.blockAccess = new ChunkCache(world, minX - 1, minY - 1, minZ - 1, minX + 17, minY + 17, minZ + 17, 1);
this.biomeColorCache = ((WorldClientExtension) world).celeritas$getSmoothBiomeColorCache();
this.biomeColorCache.update(new SectionPos(this.render.getChunkX(), this.render.getChunkY(), this.render.getChunkZ()));
@@ -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;
}
@@ -1,20 +0,0 @@
package com.gtnewhorizons.angelica.rendering.particles;
import me.jellysquid.mods.sodium.client.gui.options.named.MipmapGeneration;
import org.embeddedt.embeddium.impl.render.chunk.terrain.material.parameters.AlphaCutoffParameter;
public final class ParticleAlphaCutoff {
private static final int TERRAIN_PARTICLE_LAYER = 1;
private ParticleAlphaCutoff() {
}
public static float forLayer(float original, int layer) {
if (layer != TERRAIN_PARTICLE_LAYER) {
return original;
}
final AlphaCutoffParameter cutoff = MipmapGeneration.alphaFloorCutoff();
return cutoff == AlphaCutoffParameter.ZERO ? original : cutoff.cutoff();
}
}
@@ -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();
}
}
@@ -1,58 +0,0 @@
package com.gtnewhorizons.angelica.rendering.tesr;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import net.minecraft.world.World;
/**
* Remembers the lightmap the tile entity dispatcher looks up per tile per frame, for one client tick.
* Light only changes inside the tick, so the cache is dropped by age instead of by light updates.
*/
public final class TesrLightCache {
private static final int MISSING = Integer.MIN_VALUE;
private static final Long2IntOpenHashMap CACHE = new Long2IntOpenHashMap();
static {
CACHE.defaultReturnValue(MISSING);
}
private static int tick;
private static int cachedTick = Integer.MIN_VALUE;
private TesrLightCache() {}
public static void onClientTick() {
tick++;
}
public static void clear() {
CACHE.clear();
cachedTick = Integer.MIN_VALUE;
}
public static int getLightBrightnessForSkyBlocks(World world, int x, int y, int z, int minBlockLight) {
if (minBlockLight != 0 || !GLStateManager.isMainThread()) {
return world.getLightBrightnessForSkyBlocks(x, y, z, minBlockLight);
}
if (tick != cachedTick) {
CACHE.clear();
cachedTick = tick;
}
final long key = key(x, y, z);
int packed = CACHE.get(key);
if (packed == MISSING) {
packed = world.getLightBrightnessForSkyBlocks(x, y, z, 0);
CACHE.put(key, packed);
}
return packed;
}
// 26 bits for x and z cover the world border, 8 bits for y; no two positions share a key
private static long key(int x, int y, int z) {
return ((long) (x & 0x3FFFFFF) << 34) | ((long) (y & 0xFF) << 26) | (long) (z & 0x3FFFFFF);
}
}
@@ -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]);
@@ -1,230 +0,0 @@
package com.gtnewhorizons.angelica.utils;
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
import org.embeddedt.embeddium.api.util.ColorARGB;
import org.embeddedt.embeddium.impl.texture.MipmapHelper;
import static com.gtnewhorizons.angelica.utils.MipmapGenerator.ALPHA_BIAS;
import static org.joml.Math.clamp;
final class CeleritasMipmapGenerator {
private CeleritasMipmapGenerator() {
}
static int[][] generateMipLevels(int mipLevel, int width, int[][] currentMips,
boolean isCutout, boolean darkCutout, float cutoutRef) {
final int[][] result = new int[mipLevel + 1][];
final int[] base = currentMips[0];
if (mipLevel > 0 && (currentMips.length == 1 || currentMips[1] == null)) {
if (darkCutout) {
MipmapGenerator.fillEmptyAreasWithDarkColor(base, 1);
} else {
solidify(base, width, base.length / width);
}
}
result[0] = base;
if (mipLevel <= 0) {
return result;
}
final float originalCoverage = isCutout ? alphaTestCoverage(base, width, cutoutRef, 1.0f) : 0.0f;
for (int level = 1; level <= mipLevel; level++) {
final int levelWidth = width >> level;
final int[] provided = level < currentMips.length ? currentMips[level] : null;
if (provided != null) {
result[level] = provided;
} else {
final int[] input = result[level - 1];
final int prevWidth = width >> (level - 1);
final int prevHeight = input.length / prevWidth;
final int levelHeight = prevHeight >> 1;
final int[] output = new int[levelWidth * levelHeight];
for (int y = 0; y < levelHeight; y++) {
for (int x = 0; x < levelWidth; x++) {
final int i = x * 2 + y * 2 * prevWidth;
output[x + y * levelWidth] = MipmapHelper.weightedAverageColor(
MipmapHelper.weightedAverageColor(input[i], input[i + 1]),
MipmapHelper.weightedAverageColor(input[i + prevWidth], input[i + prevWidth + 1]));
}
}
result[level] = output;
}
if (isCutout) {
scaleAlphaToCoverage(result[level], levelWidth, originalCoverage, cutoutRef);
}
}
return result;
}
private static void solidify(int[] image, int width, int height) {
final int size = Math.min(width * height, image.length);
if (size <= 0) {
return;
}
boolean hasTransparent = false;
for (int i = 0; i < size; i++) {
if (ColorARGB.unpackAlpha(image[i]) == 0) {
hasTransparent = true;
break;
}
}
if (!hasTransparent) {
return;
}
final int[] color = new int[size];
final IntArrayFIFOQueue queue = new IntArrayFIFOQueue(64);
for (int i = 0; i < size; i++) {
final int pixel = image[i];
if (ColorARGB.unpackAlpha(pixel) == 0) {
continue;
}
color[i] = pixel;
if (hasTransparentNeighbor(image, i, width, size)) {
queue.enqueue(i);
}
}
if (queue.isEmpty()) {
return;
}
while (!queue.isEmpty()) {
final int index = queue.dequeueInt();
final int x = index % width;
final int source = color[index];
if (x > 0) {
fill(index - 1, source, color, queue);
}
if (x < width - 1) {
fill(index + 1, source, color, queue);
}
if (index >= width) {
fill(index - width, source, color, queue);
}
if (index + width < size) {
fill(index + width, source, color, queue);
}
}
for (int i = 0; i < size; i++) {
if (ColorARGB.unpackAlpha(image[i]) == 0) {
image[i] = color[i] & 0x00FFFFFF;
}
}
}
private static boolean hasTransparentNeighbor(int[] image, int index, int width, int size) {
final int x = index % width;
return (x > 0 && ColorARGB.unpackAlpha(image[index - 1]) == 0)
|| (x < width - 1 && ColorARGB.unpackAlpha(image[index + 1]) == 0)
|| (index >= width && ColorARGB.unpackAlpha(image[index - width]) == 0)
|| (index + width < size && ColorARGB.unpackAlpha(image[index + width]) == 0);
}
private static void fill(int neighbor, int source, int[] color, IntArrayFIFOQueue queue) {
if (color[neighbor] == 0) {
color[neighbor] = source;
queue.enqueue(neighbor);
}
}
private static float alphaTestCoverage(int[] image, int width, float alphaRef, float alphaScale) {
final int height = image.length / width;
final float alphaFactor = alphaScale / 255.0f;
float total = 0.0f;
int quads = 0;
for (int y = 0; y < height - 1; y++) {
for (int x = 0; x < width - 1; x++) {
final int i = x + y * width;
final float a00 = scaledAlpha(image[i], alphaFactor);
final float a10 = scaledAlpha(image[i + 1], alphaFactor);
final float a01 = scaledAlpha(image[i + width], alphaFactor);
final float a11 = scaledAlpha(image[i + width + 1], alphaFactor);
final float lo = Math.min(Math.min(a00, a10), Math.min(a01, a11));
if (lo > alphaRef) {
total += 1.0f;
quads++;
continue;
}
final float hi = Math.max(Math.max(a00, a10), Math.max(a01, a11));
if (hi <= alphaRef) {
quads++;
continue;
}
int hits = 0;
for (int sy = 0; sy < 4; sy++) {
final float fy = (sy + 0.5f) / 4.0f;
for (int sx = 0; sx < 4; sx++) {
final float fx = (sx + 0.5f) / 4.0f;
final float top = a00 + (a10 - a00) * fx;
final float bottom = a01 + (a11 - a01) * fx;
if (top + (bottom - top) * fy > alphaRef) {
hits++;
}
}
}
total += hits / 16.0f;
quads++;
}
}
if (quads == 0) {
return scaledAlpha(image[0], alphaFactor) > alphaRef ? 1.0f : 0.0f;
}
return total / quads;
}
private static float scaledAlpha(int color, float alphaFactor) {
return clamp(0.0f, 1.0f, ColorARGB.unpackAlpha(color) * alphaFactor);
}
private static void scaleAlphaToCoverage(int[] image, int width, float desiredCoverage, float alphaRef) {
float min = 0.0f;
float max = 4.0f;
float scale = 1.0f;
float bestScale = 1.0f;
float bestError = Float.POSITIVE_INFINITY;
for (int i = 0; i < 5; i++) {
final float coverage = alphaTestCoverage(image, width, alphaRef, scale);
final float error = Math.abs(coverage - desiredCoverage);
if (error < bestError) {
bestError = error;
bestScale = scale;
}
if (coverage < desiredCoverage) {
min = scale;
} else if (!(coverage > desiredCoverage)) {
break;
} else {
max = scale;
}
scale = (min + max) / 2.0f;
}
for (int i = 0; i < image.length; i++) {
final int color = image[i];
final int rawAlpha = ColorARGB.unpackAlpha(color);
final float bias = rawAlpha > 0 ? ALPHA_BIAS : 0.0f;
final int alpha = (int) Math.floor(clamp(0.0f, 1.0f, rawAlpha / 255.0f * bestScale + bias) * 255.0f);
image[i] = ColorARGB.withAlpha(color, alpha);
}
}
}
@@ -1,16 +1,19 @@
package com.gtnewhorizons.angelica.utils;
import me.jellysquid.mods.sodium.client.gui.options.named.MipmapGeneration;
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
import org.embeddedt.embeddium.api.util.ColorARGB;
import org.embeddedt.embeddium.impl.texture.MipmapHelper;
import static org.joml.Math.clamp;
/**
* Mipmap downsampling for atlas sprites, replacing vanilla's {@code TextureUtil.generateMipmapData}.
*/
public final class MipmapGenerator {
static final float ALPHA_BIAS = 0.025f;
private static final float CUTOUT_ALPHA_REF = 0.5f;
private static final float STRICT_CUTOUT_ALPHA_REF = 0.3f;
private static final float ALPHA_BIAS = 0.025f;
private MipmapGenerator() {
}
@@ -22,29 +25,101 @@ public final class MipmapGenerator {
return strategy;
}
private static boolean isCutoutStrategy(MipmapStrategy resolved) {
static boolean isCutoutStrategy(MipmapStrategy strategy, boolean hasTransparentPixel) {
final MipmapStrategy resolved = resolve(strategy, hasTransparentPixel);
return resolved == MipmapStrategy.CUTOUT
|| resolved == MipmapStrategy.STRICT_CUTOUT
|| resolved == MipmapStrategy.DARK_CUTOUT;
}
// Scan order controls which equally dark color wins.
static void fillEmptyAreasWithDarkColor(int[] image, int columns) {
final int rows = image.length / columns;
int darkest = ColorARGB.pack(255, 255, 255, 255);
int darkestSum = Integer.MAX_VALUE;
static void solidify(int[] image, int width, int height) {
final int size = Math.min(width * height, image.length);
if (size <= 0) {
return;
}
for (int x = 0; x < columns; x++) {
for (int y = 0; y < rows; y++) {
final int pixel = image[x + y * columns];
if (ColorARGB.unpackAlpha(pixel) == 0) {
continue;
}
final int sum = ColorARGB.unpackRed(pixel) + ColorARGB.unpackGreen(pixel) + ColorARGB.unpackBlue(pixel);
if (sum < darkestSum) {
darkestSum = sum;
darkest = pixel;
}
boolean hasTransparent = false;
for (int i = 0; i < size; i++) {
if (ColorARGB.unpackAlpha(image[i]) == 0) {
hasTransparent = true;
break;
}
}
if (!hasTransparent) {
return;
}
final int[] color = new int[size];
final IntArrayFIFOQueue queue = new IntArrayFIFOQueue(64);
for (int i = 0; i < size; i++) {
final int pixel = image[i];
if (ColorARGB.unpackAlpha(pixel) == 0) {
continue;
}
color[i] = pixel;
if (hasTransparentNeighbour(image, i, width, size)) {
queue.enqueue(i);
}
}
if (queue.isEmpty()) {
return;
}
while (!queue.isEmpty()) {
final int index = queue.dequeueInt();
final int x = index % width;
final int source = color[index];
if (x > 0) {
fill(index - 1, source, color, queue);
}
if (x < width - 1) {
fill(index + 1, source, color, queue);
}
if (index >= width) {
fill(index - width, source, color, queue);
}
if (index + width < size) {
fill(index + width, source, color, queue);
}
}
for (int i = 0; i < size; i++) {
if (ColorARGB.unpackAlpha(image[i]) == 0) {
image[i] = color[i] & 0x00FFFFFF;
}
}
}
private static boolean hasTransparentNeighbour(int[] image, int index, int width, int size) {
final int x = index % width;
return (x > 0 && ColorARGB.unpackAlpha(image[index - 1]) == 0)
|| (x < width - 1 && ColorARGB.unpackAlpha(image[index + 1]) == 0)
|| (index >= width && ColorARGB.unpackAlpha(image[index - width]) == 0)
|| (index + width < size && ColorARGB.unpackAlpha(image[index + width]) == 0);
}
private static void fill(int neighbour, int source, int[] color, IntArrayFIFOQueue queue) {
if (color[neighbour] == 0) {
color[neighbour] = source;
queue.enqueue(neighbour);
}
}
static void fillEmptyAreasWithDarkColor(int[] image) {
int darkest = ColorARGB.pack(255, 255, 255, 255);
int darkestSum = 255 * 3;
for (final int pixel : image) {
if (ColorARGB.unpackAlpha(pixel) == 0) {
continue;
}
final int sum = ColorARGB.unpackRed(pixel) + ColorARGB.unpackGreen(pixel) + ColorARGB.unpackBlue(pixel);
if (sum < darkestSum) {
darkestSum = sum;
darkest = pixel;
}
}
@@ -58,22 +133,152 @@ public final class MipmapGenerator {
}
}
/**
* Estimates the fraction of the image that survives, with bilinear filtering modeled by
* supersampling each texel quad on a 4x4 interior grid.
*/
static float alphaTestCoverage(int[] image, int width, float alphaRef, float alphaScale) {
final int height = image.length / width;
final float alphaFactor = alphaScale / 255.0f;
float total = 0.0f;
int quads = 0;
for (int y = 0; y < height - 1; y++) {
for (int x = 0; x < width - 1; x++) {
final int i = x + y * width;
final float a00 = scaledAlpha(image[i], alphaFactor);
final float a10 = scaledAlpha(image[i + 1], alphaFactor);
final float a01 = scaledAlpha(image[i + width], alphaFactor);
final float a11 = scaledAlpha(image[i + width + 1], alphaFactor);
final float lo = Math.min(Math.min(a00, a10), Math.min(a01, a11));
if (lo > alphaRef) {
total += 1.0f;
quads++;
continue;
}
final float hi = Math.max(Math.max(a00, a10), Math.max(a01, a11));
if (hi <= alphaRef) {
quads++;
continue;
}
int hits = 0;
for (int sy = 0; sy < 4; sy++) {
final float fy = (sy + 0.5f) / 4.0f;
for (int sx = 0; sx < 4; sx++) {
final float fx = (sx + 0.5f) / 4.0f;
final float top = a00 + (a10 - a00) * fx;
final float bottom = a01 + (a11 - a01) * fx;
if (top + (bottom - top) * fy > alphaRef) {
hits++;
}
}
}
total += hits / 16.0f;
quads++;
}
}
if (quads == 0) {
return scaledAlpha(image[0], alphaFactor) > alphaRef ? 1.0f : 0.0f;
}
return total / quads;
}
private static float scaledAlpha(int color, float alphaFactor) {
return clamp(0.0f, 1.0f, ColorARGB.unpackAlpha(color) * alphaFactor);
}
static void scaleAlphaToCoverage(int[] image, int width, float desiredCoverage, float alphaRef) {
float min = 0.0f;
float max = 4.0f;
float scale = 1.0f;
float bestScale = 1.0f;
float bestError = Float.POSITIVE_INFINITY;
for (int i = 0; i < 5; i++) {
final float coverage = alphaTestCoverage(image, width, alphaRef, scale);
final float error = Math.abs(coverage - desiredCoverage);
if (error < bestError) {
bestError = error;
bestScale = scale;
}
if (coverage < desiredCoverage) {
min = scale;
} else if (!(coverage > desiredCoverage)) {
break;
} else {
max = scale;
}
scale = (min + max) / 2.0f;
}
for (int i = 0; i < image.length; i++) {
final int color = image[i];
final int rawAlpha = ColorARGB.unpackAlpha(color);
final float bias = rawAlpha > 0 ? ALPHA_BIAS : 0.0f;
final int alpha = (int) Math.floor(clamp(0.0f, 1.0f, rawAlpha / 255.0f * bestScale + bias) * 255.0f);
image[i] = ColorARGB.withAlpha(color, alpha);
}
}
public static int[][] generateMipLevels(int mipLevel, int width, int[][] currentMips,
MipmapStrategy strategy, boolean hasTransparentPixel, MipmapGeneration generation,
float alphaCutoffBias, boolean isItem) {
MipmapStrategy strategy, boolean hasTransparentPixel) {
if (generation == MipmapGeneration.VANILLA_1_7_10) {
return VanillaMipmapGenerator.generateMipLevels(mipLevel, width, currentMips);
}
final MipmapStrategy resolved = resolve(strategy, hasTransparentPixel);
final boolean isCutout = isCutoutStrategy(resolved);
final boolean isCutout = isCutoutStrategy(strategy, hasTransparentPixel);
final boolean darkCutout = resolved == MipmapStrategy.DARK_CUTOUT;
final float cutoutRef = resolved == MipmapStrategy.STRICT_CUTOUT ? STRICT_CUTOUT_ALPHA_REF : CUTOUT_ALPHA_REF;
if (generation == MipmapGeneration.MODERN_VANILLA) {
return ModernMipmapGenerator.generateMipLevels(mipLevel, width, currentMips,
isCutout, darkCutout, cutoutRef, alphaCutoffBias, isItem);
final int[][] result = new int[mipLevel + 1][];
final int[] base = currentMips[0];
if (mipLevel > 0 && (currentMips.length == 1 || currentMips[1] == null)) {
if (darkCutout) {
fillEmptyAreasWithDarkColor(base);
} else {
solidify(base, width, base.length / width);
}
}
return CeleritasMipmapGenerator.generateMipLevels(mipLevel, width, currentMips, isCutout, darkCutout, cutoutRef);
result[0] = base;
if (mipLevel <= 0) {
return result;
}
final float cutoutRef = resolved == MipmapStrategy.STRICT_CUTOUT ? STRICT_CUTOUT_ALPHA_REF : CUTOUT_ALPHA_REF;
final float originalCoverage = isCutout ? alphaTestCoverage(base, width, cutoutRef, 1.0f) : 0.0f;
for (int level = 1; level <= mipLevel; level++) {
final int levelWidth = width >> level;
final int[] provided = level < currentMips.length ? currentMips[level] : null;
if (provided != null) {
result[level] = provided;
} else {
final int[] input = result[level - 1];
final int prevWidth = width >> (level - 1);
final int prevHeight = input.length / prevWidth;
final int levelHeight = prevHeight >> 1;
final int[] output = new int[levelWidth * levelHeight];
for (int y = 0; y < levelHeight; y++) {
for (int x = 0; x < levelWidth; x++) {
final int i = x * 2 + y * 2 * prevWidth;
output[x + y * levelWidth] = MipmapHelper.weightedAverageColor(
MipmapHelper.weightedAverageColor(input[i], input[i + 1]),
MipmapHelper.weightedAverageColor(input[i + prevWidth], input[i + prevWidth + 1]));
}
}
result[level] = output;
}
if (isCutout) {
scaleAlphaToCoverage(result[level], levelWidth, originalCoverage, cutoutRef);
}
}
return result;
}
}
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.utils;
import com.gtnewhorizons.angelica.rendering.TextureAtlasType;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.block.Block;
import net.minecraft.block.BlockFlower;
@@ -56,7 +55,7 @@ public final class MipmapStrategies {
}
MipmapStrategy strategy = STRATEGY_STACK.get().peek();
if (strategy == null && textureType == TextureAtlasType.BLOCK && iconName.contains("leaves")) {
if (strategy == null && textureType == 0 && iconName.contains("leaves")) {
strategy = MipmapStrategy.DARK_CUTOUT;
}
if (strategy == null) {
@@ -68,7 +67,7 @@ public final class MipmapStrategies {
}
public static MipmapStrategy inheritedFor(int textureType, String iconName) {
if (iconName == null || textureType != TextureAtlasType.BLOCK) {
if (iconName == null || textureType != 0) {
return null;
}
return SPRITE_RULES.get(iconName);
@@ -1,77 +0,0 @@
package com.gtnewhorizons.angelica.utils;
import org.embeddedt.embeddium.api.util.ColorARGB;
/**
* Minecraft 26.2's ARGB.meanLinear and MipmapGenerator.darkenedAlphaBlend, using its 10-bit linear color tables.
*/
final class ModernMipmapBlender {
private static final int[] SRGB_TO_LINEAR = new int[256];
private static final float[] SRGB_TO_LINEAR_FLOAT = new float[256];
private static final int[] LINEAR_TO_SRGB = new int[1024];
static {
for (int i = 0; i < SRGB_TO_LINEAR.length; i++) {
final float channel = i / 255.0f;
final float linear = channel >= 0.04045f ? (float) Math.pow((channel + 0.055) / 1.055, 2.4) : channel / 12.92f;
SRGB_TO_LINEAR[i] = Math.round(linear * 1023.0f);
SRGB_TO_LINEAR_FLOAT[i] = SRGB_TO_LINEAR[i] / 1023.0f;
}
for (int i = 0; i < LINEAR_TO_SRGB.length; i++) {
final float channel = i / 1023.0f;
final float srgb = channel >= 0.0031308f ? (float) (1.055 * Math.pow(channel, 1.0 / 2.4) - 0.055) : 12.92f * channel;
LINEAR_TO_SRGB[i] = Math.round(srgb * 255.0f);
}
}
private ModernMipmapBlender() {
}
static int blend(int a, int b, int c, int d, boolean darkCutout) {
if (darkCutout) {
return darkenedAlphaBlend(a, b, c, d);
}
return ColorARGB.pack(meanChannel(a, b, c, d, 16), meanChannel(a, b, c, d, 8), meanChannel(a, b, c, d, 0), (ColorARGB.unpackAlpha(a) + ColorARGB.unpackAlpha(b) + ColorARGB.unpackAlpha(c) + ColorARGB.unpackAlpha(d)) / 4);
}
private static int meanChannel(int a, int b, int c, int d, int shift) {
final int linear = (SRGB_TO_LINEAR[(a >>> shift) & 255] + SRGB_TO_LINEAR[(b >>> shift) & 255] + SRGB_TO_LINEAR[(c >>> shift) & 255] + SRGB_TO_LINEAR[(d >>> shift) & 255]) / 4;
return LINEAR_TO_SRGB[linear];
}
private static int darkenedAlphaBlend(int a, int b, int c, int d) {
float alpha = 0.0f;
float red = 0.0f;
float green = 0.0f;
float blue = 0.0f;
if (ColorARGB.unpackAlpha(a) != 0) {
alpha += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackAlpha(a)];
red += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackRed(a)];
green += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackGreen(a)];
blue += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackBlue(a)];
}
if (ColorARGB.unpackAlpha(b) != 0) {
alpha += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackAlpha(b)];
red += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackRed(b)];
green += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackGreen(b)];
blue += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackBlue(b)];
}
if (ColorARGB.unpackAlpha(c) != 0) {
alpha += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackAlpha(c)];
red += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackRed(c)];
green += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackGreen(c)];
blue += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackBlue(c)];
}
if (ColorARGB.unpackAlpha(d) != 0) {
alpha += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackAlpha(d)];
red += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackRed(d)];
green += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackGreen(d)];
blue += SRGB_TO_LINEAR_FLOAT[ColorARGB.unpackBlue(d)];
}
return ColorARGB.pack(toSrgb(red), toSrgb(green), toSrgb(blue), toSrgb(alpha));
}
private static int toSrgb(float sum) {
return LINEAR_TO_SRGB[(int) (sum * 0.25f * 1023.0f)];
}
}
@@ -1,238 +0,0 @@
package com.gtnewhorizons.angelica.utils;
import it.unimi.dsi.fastutil.ints.IntArrayFIFOQueue;
import it.unimi.dsi.fastutil.ints.IntArrays;
import org.embeddedt.embeddium.api.util.ColorARGB;
import java.util.Arrays;
import static com.gtnewhorizons.angelica.utils.MipmapGenerator.ALPHA_BIAS;
import static org.joml.Math.clamp;
/**
* Minecraft 26.2's mipmap generation and texture preparation.
*/
final class ModernMipmapGenerator {
private static final float[] NORMALIZED_ALPHA = new float[256];
static {
for (int i = 0; i < NORMALIZED_ALPHA.length; i++) {
NORMALIZED_ALPHA[i] = i / 255.0f;
}
}
private ModernMipmapGenerator() {
}
static int[][] generateMipLevels(int mipLevel, int width, int[][] currentMips, boolean isCutout, boolean darkCutout, float cutoutRef, float alphaCutoffBias, boolean isItem) {
final int[] base = currentMips[0];
// Unlike modern NativeImage arrays, 1.7.10 preallocates null slots for missing mip levels.
boolean hasProvidedMips = false;
boolean complete = mipLevel < currentMips.length;
int maxPixels = base.length;
for (int level = 1; level < currentMips.length; level++) {
if (currentMips[level] != null) {
hasProvidedMips = true;
if (level <= mipLevel) {
maxPixels = Math.max(maxPixels, currentMips[level].length);
}
} else if (level <= mipLevel) {
complete = false;
}
}
if (!hasProvidedMips && !isItem && isCutout) {
if (darkCutout) {
MipmapGenerator.fillEmptyAreasWithDarkColor(base, width);
} else {
solidify(base, width);
}
}
if (complete) {
return currentMips.length == mipLevel + 1 ? currentMips : Arrays.copyOf(currentMips, mipLevel + 1);
}
final int[][] result = Arrays.copyOf(currentMips, mipLevel + 1);
final float[] alphaScratch = isCutout ? new float[maxPixels] : null;
final float originalCoverage = isCutout ? alphaTestCoverage(base, width, cutoutRef, 1.0f, alphaScratch) : 0.0f;
for (int level = 1; level <= mipLevel; level++) {
final int levelWidth = width >> level;
if (levelWidth == 0) {
if (result[level] == null) {
result[level] = IntArrays.EMPTY_ARRAY;
}
continue;
}
if (result[level] == null) {
final int[] input = result[level - 1];
final int prevWidth = width >> (level - 1);
final int levelHeight = (input.length / prevWidth) >> 1;
if (levelHeight == 0) {
result[level] = IntArrays.EMPTY_ARRAY;
continue;
}
final int[] output = new int[levelWidth * levelHeight];
for (int y = 0; y < levelHeight; y++) {
for (int x = 0; x < levelWidth; x++) {
final int i = x * 2 + y * 2 * prevWidth;
output[x + y * levelWidth] = ModernMipmapBlender.blend(input[i], input[i + 1], input[i + prevWidth], input[i + prevWidth + 1], darkCutout);
}
}
result[level] = output;
}
if (isCutout && result[level].length > 0) {
scaleAlphaToCoverage(result[level], levelWidth, originalCoverage, cutoutRef, alphaCutoffBias, alphaScratch);
}
}
return result;
}
private static void solidify(int[] image, int width) {
boolean hasTransparentPixel = false;
boolean hasOpaquePixel = false;
for (int color : image) {
if (ColorARGB.unpackAlpha(color) == 0) {
hasTransparentPixel = true;
} else {
hasOpaquePixel = true;
}
if (hasTransparentPixel && hasOpaquePixel) {
break;
}
}
if (!hasTransparentPixel) {
return;
}
if (!hasOpaquePixel) {
Arrays.fill(image, 0);
return;
}
final int height = image.length / width;
final int[] nearestColor = new int[image.length];
final IntArrayFIFOQueue queue = new IntArrayFIFOQueue(64);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
final int i = x + y * width;
if (ColorARGB.unpackAlpha(image[i]) != 0) {
nearestColor[i] = image[i];
if (hasTransparentNeighbor(image, width, i, x)) {
queue.enqueue(i);
}
}
}
}
while (!queue.isEmpty()) {
final int i = queue.dequeueInt();
final int x = i % width;
final int color = nearestColor[i];
if (x < width - 1) {
fill(i + 1, color, nearestColor, queue);
}
if (x > 0) {
fill(i - 1, color, nearestColor, queue);
}
if (i + width < image.length) {
fill(i + width, color, nearestColor, queue);
}
if (i >= width) {
fill(i - width, color, nearestColor, queue);
}
}
for (int i = 0; i < image.length; i++) {
if (ColorARGB.unpackAlpha(image[i]) == 0) {
image[i] = nearestColor[i] & 0x00FFFFFF;
}
}
}
private static boolean hasTransparentNeighbor(int[] image, int width, int i, int x) {
return (x < width - 1 && ColorARGB.unpackAlpha(image[i + 1]) == 0) || (x > 0 && ColorARGB.unpackAlpha(image[i - 1]) == 0) || (i + width < image.length && ColorARGB.unpackAlpha(image[i + width]) == 0)
|| (i >= width && ColorARGB.unpackAlpha(image[i - width]) == 0);
}
private static void fill(int i, int color, int[] nearestColor, IntArrayFIFOQueue queue) {
if (nearestColor[i] == 0) {
nearestColor[i] = color;
queue.enqueue(i);
}
}
private static float alphaTestCoverage(int[] image, int width, float alphaRef, float alphaScale, float[] alphaScratch) {
final int height = image.length / width;
for (int i = 0; i < image.length; i++) {
alphaScratch[i] = clamp(0.0f, 1.0f, NORMALIZED_ALPHA[ColorARGB.unpackAlpha(image[i])] * alphaScale);
}
final float margin = 8.0f * Math.ulp(alphaRef);
final float safelyAbove = alphaRef + margin;
final float safelyBelow = alphaRef - margin;
float coverage = 0.0f;
for (int y = 0; y < height - 1; y++) {
for (int x = 0; x < width - 1; x++) {
final int i = x + y * width;
final float a00 = alphaScratch[i];
final float a10 = alphaScratch[i + 1];
final float a01 = alphaScratch[i + width];
final float a11 = alphaScratch[i + width + 1];
if (a00 > safelyAbove && a10 > safelyAbove && a01 > safelyAbove && a11 > safelyAbove) {
coverage += 1.0f;
continue;
}
if (a00 < safelyBelow && a10 < safelyBelow && a01 < safelyBelow && a11 < safelyBelow) {
continue;
}
float texelCoverage = 0.0f;
for (int sy = 0; sy < 4; sy++) {
final float fy = (sy + 0.5f) / 4.0f;
for (int sx = 0; sx < 4; sx++) {
final float fx = (sx + 0.5f) / 4.0f;
final float alpha = a00 * (1.0f - fx) * (1.0f - fy) + a10 * fx * (1.0f - fy) + a01 * (1.0f - fx) * fy + a11 * fx * fy;
if (alpha > alphaRef) {
texelCoverage++;
}
}
}
coverage += texelCoverage / 16.0f;
}
}
return coverage / ((width - 1) * (height - 1));
}
private static void scaleAlphaToCoverage(int[] image, int width, float desiredCoverage, float alphaRef, float alphaCutoffBias, float[] alphaScratch) {
float min = 0.0f;
float max = 4.0f;
float scale = 1.0f;
float bestScale = 1.0f;
float bestError = Float.MAX_VALUE;
if (width > 1 && image.length / width > 1) {
for (int i = 0; i < 5; i++) {
final float coverage = alphaTestCoverage(image, width, alphaRef, scale, alphaScratch);
final float error = Math.abs(coverage - desiredCoverage);
if (error < bestError) {
bestError = error;
bestScale = scale;
}
if (coverage < desiredCoverage) {
min = scale;
} else if (coverage > desiredCoverage) {
max = scale;
} else {
break;
}
scale = (min + max) * 0.5f;
}
}
for (int i = 0; i < image.length; i++) {
final int color = image[i];
final float alpha = NORMALIZED_ALPHA[ColorARGB.unpackAlpha(color)] * bestScale + alphaCutoffBias + ALPHA_BIAS;
image[i] = ColorARGB.withAlpha(color, (int) (clamp(0.0f, 1.0f, alpha) * 255.0f));
}
}
}
@@ -1,97 +0,0 @@
package com.gtnewhorizons.angelica.utils;
import it.unimi.dsi.fastutil.ints.IntArrays;
import org.embeddedt.embeddium.api.util.ColorARGB;
/**
* Minecraft 1.7.10's TextureUtil mipmap generator without its shared mutable color buffer.
*/
final class VanillaMipmapGenerator {
private static final float[] TO_LINEAR = new float[256];
static {
for (int i = 0; i < TO_LINEAR.length; i++) {
TO_LINEAR[i] = (float) Math.pow(i / 255.0f, 2.2);
}
}
private VanillaMipmapGenerator() {
}
static int[][] generateMipLevels(int mipLevel, int width, int[][] currentMips) {
final int[][] result = new int[mipLevel + 1][];
result[0] = currentMips[0];
if (mipLevel <= 0) {
return result;
}
boolean hasTransparentPixel = false;
for (int i = 0; i < Math.min(currentMips.length, result[0].length); i++) {
if (ColorARGB.unpackAlpha(result[0][i]) == 0) {
hasTransparentPixel = true;
break;
}
}
for (int level = 1; level <= mipLevel; level++) {
final int[] provided = level < currentMips.length ? currentMips[level] : null;
if (provided != null) {
result[level] = provided;
continue;
}
final int levelWidth = width >> level;
if (levelWidth == 0) {
result[level] = IntArrays.EMPTY_ARRAY;
continue;
}
final int[] input = result[level - 1];
final int[] output = new int[input.length >> 2];
final int levelHeight = output.length / levelWidth;
final int stride = levelWidth << 1;
for (int y = 0; y < levelHeight; y++) {
for (int x = 0; x < levelWidth; x++) {
final int i = 2 * (x + y * stride);
output[x + y * levelWidth] = blend(input[i], input[i + 1], input[i + stride], input[i + stride + 1], hasTransparentPixel);
}
}
result[level] = output;
}
return result;
}
private static int blend(int a, int b, int c, int d, boolean hasTransparentPixel) {
if (hasTransparentPixel) {
int alpha = transparentChannel(a, b, c, d, 24);
if (alpha < 96) {
alpha = 0;
}
return ColorARGB.pack(transparentChannel(a, b, c, d, 16), transparentChannel(a, b, c, d, 8), transparentChannel(a, b, c, d, 0), alpha);
}
return ColorARGB.pack(meanChannel(a, b, c, d, 16), meanChannel(a, b, c, d, 8), meanChannel(a, b, c, d, 0), meanChannel(a, b, c, d, 24));
}
private static int meanChannel(int a, int b, int c, int d, int shift) {
final float sum = TO_LINEAR[(a >>> shift) & 255] + TO_LINEAR[(b >>> shift) & 255] + TO_LINEAR[(c >>> shift) & 255] + TO_LINEAR[(d >>> shift) & 255];
final float value = (float) Math.pow(sum * 0.25, 1.0 / 2.2);
return (int) (value * 255.0);
}
private static int transparentChannel(int a, int b, int c, int d, int shift) {
float sum = 0.0f;
if (ColorARGB.unpackAlpha(a) != 0) {
sum += TO_LINEAR[(a >>> shift) & 255];
}
if (ColorARGB.unpackAlpha(b) != 0) {
sum += TO_LINEAR[(b >>> shift) & 255];
}
if (ColorARGB.unpackAlpha(c) != 0) {
sum += TO_LINEAR[(c >>> shift) & 255];
}
if (ColorARGB.unpackAlpha(d) != 0) {
sum += TO_LINEAR[(d >>> shift) & 255];
}
return (int) (Math.pow(sum / 4.0f, 1.0 / 2.2) * 255.0);
}
}
@@ -12,44 +12,38 @@ import jss.notfine.config.MCPatcherForgeConfig;
public class GlassPaneRenderer {
private static final ThreadLocal<GlassPaneRenderer> STATE = ThreadLocal.withInitial(GlassPaneRenderer::new);
public static boolean skipPaneRendering;
public static boolean skipTopEdgeRendering;
public static boolean skipBottomEdgeRendering;
public boolean skipPaneRendering;
public boolean skipTopEdgeRendering;
public boolean skipBottomEdgeRendering;
private static final IIcon[] icons = new IIcon[6];
private final IIcon[] icons = new IIcon[6];
private static double uLeft; // left edge
private static double uLeftThin; // 7/16 point
private static double uRightThin; // 9/16 point
private static double uRight; // right edge
private static double vTop; // top edge
private static double vBottom; // bottom edge
private double uLeft; // left edge
private double uLeftThin; // 7/16 point
private double uRightThin; // 9/16 point
private double uRight; // right edge
private double vTop; // top edge
private double vBottom; // bottom edge
private static double u1Scaled;
private static double u2Scaled;
private double u1Scaled;
private double u2Scaled;
public static GlassPaneRenderer renderThin(RenderBlocks renderBlocks, Block blockPane, IIcon origIcon, int x, int y, int z,
public static void renderThin(RenderBlocks renderBlocks, Block blockPane, IIcon origIcon, int x, int y, int z,
boolean connectNorth, boolean connectSouth, boolean connectWest, boolean connectEast) {
GlassPaneRenderer state = STATE.get();
if (state.setupIcons(renderBlocks, blockPane, origIcon, x, y, z)) {
state.render(x, y, z, connectNorth, connectSouth, connectWest, connectEast, 0.0, 0.0, 0.0, false);
if (setupIcons(renderBlocks, blockPane, origIcon, x, y, z)) {
render(x, y, z, connectNorth, connectSouth, connectWest, connectEast, 0.0, 0.0, 0.0, false);
}
return state;
}
public static GlassPaneRenderer renderThick(RenderBlocks renderBlocks, Block blockPane, IIcon origIcon, int x, int y, int z,
public static void renderThick(RenderBlocks renderBlocks, Block blockPane, IIcon origIcon, int x, int y, int z,
boolean connectNorth, boolean connectSouth, boolean connectWest, boolean connectEast) {
GlassPaneRenderer state = STATE.get();
if (state.setupIcons(renderBlocks, blockPane, origIcon, x, y, z)) {
state.setupPaneEdges(renderBlocks.blockAccess, blockPane, x, y, z);
state.render(x, y, z, connectNorth, connectSouth, connectWest, connectEast, 0.0625, 1.0, 0.001, true);
if (setupIcons(renderBlocks, blockPane, origIcon, x, y, z)) {
setupPaneEdges(renderBlocks.blockAccess, blockPane, x, y, z);
render(x, y, z, connectNorth, connectSouth, connectWest, connectEast, 0.0625, 1.0, 0.001, true);
}
return state;
}
private boolean setupIcons(RenderBlocks renderBlocks, Block blockPane, IIcon origIcon, int x, int y, int z) {
private static boolean setupIcons(RenderBlocks renderBlocks, Block blockPane, IIcon origIcon, int x, int y, int z) {
skipPaneRendering = skipBottomEdgeRendering = skipTopEdgeRendering = false;
if (!MCPatcherForgeConfig.ConnectedTextures.glassPane) {
return false;
@@ -67,7 +61,7 @@ public class GlassPaneRenderer {
return skipPaneRendering;
}
private void setupPaneEdges(IBlockAccess blockAccess, Block blockPane, int x, int y, int z) {
private static void setupPaneEdges(IBlockAccess blockAccess, Block blockPane, int x, int y, int z) {
int metadata = blockAccess.getBlockMetadata(x, y, z);
skipBottomEdgeRendering = blockAccess.getBlock(x, y - 1, z) == blockPane
&& blockAccess.getBlockMetadata(x, y - 1, z) == metadata;
@@ -75,7 +69,7 @@ public class GlassPaneRenderer {
&& blockAccess.getBlockMetadata(x, y + 1, z) == metadata;
}
private void render(int x, int y, int z, boolean connectNorth, boolean connectSouth, boolean connectWest,
private static void render(int x, int y, int z, boolean connectNorth, boolean connectSouth, boolean connectWest,
boolean connectEast, double thickness, double uOffset, double yOffset, boolean edges) {
final double west = x;
final double westThin = x + 0.5 - thickness;
@@ -232,7 +226,7 @@ public class GlassPaneRenderer {
}
}
private void setupTileCoords(int face) {
private static void setupTileCoords(int face) {
final IIcon icon = icons[face];
uLeft = icon.getMinU();
uLeftThin = icon.getInterpolatedU(u1Scaled);
@@ -252,9 +246,8 @@ public class GlassPaneRenderer {
}
protected static void clear() {
GlassPaneRenderer state = STATE.get();
Arrays.fill(state.icons, null);
state.skipPaneRendering = false;
Arrays.fill(icons, null);
skipPaneRendering = false;
}
}
@@ -42,7 +42,6 @@ import me.jellysquid.mods.sodium.client.gui.options.named.BiomeBlendMode;
import me.jellysquid.mods.sodium.client.gui.options.named.GraphicsMode;
import me.jellysquid.mods.sodium.client.gui.options.named.GraphicsQuality;
import me.jellysquid.mods.sodium.client.gui.options.named.LightingQuality;
import me.jellysquid.mods.sodium.client.gui.options.named.MipmapGeneration;
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
import me.jellysquid.mods.sodium.client.gui.options.named.ParticleMode;
import me.jellysquid.mods.sodium.client.gui.options.named.TexelSampling;
@@ -287,15 +286,6 @@ public class SodiumGameOptionPages {
.add(texelSampling)
.add(anisotropicFilteringSlider(vanillaOpts, textureFilterMode::getValue),
SodiumGameOptions.anisotropySupported())
.add(OptionImpl.createBuilder(int.class, sodiumOpts)
.setName(I18n.format("sodium.options.gui_block_supersampling.name"))
.setTooltip(I18n.format("sodium.options.gui_block_supersampling.tooltip"))
.setControl(option -> new SliderControl(option, 0, 4, 1,
value -> value == 0 ? I18n.format("options.off") : (1 << value) + "x"))
.setBinding((opts, value) -> opts.quality.guiBlockSupersampling = 1 << value,
opts -> Integer.numberOfTrailingZeros(opts.quality.guiBlockSupersampling))
.setImpact(OptionImpact.LOW)
.build())
.add(biomeBlendRadius)
.add(biomeBlendMode)
// TODO
@@ -314,14 +304,6 @@ public class SodiumGameOptionPages {
groups.add(OptionGroup.createBuilder()
.add(mipmapLevels)
.add(OptionImpl.createBuilder(MipmapGeneration.class, sodiumOpts)
.setName(I18n.format("sodium.options.mipmap_generation.name"))
.setTooltip(I18n.format("sodium.options.mipmap_generation.tooltip"))
.setControl(option -> new CyclingControl<>(option, MipmapGeneration.class))
.setBinding((opts, value) -> opts.quality.mipmapGeneration = value, opts -> opts.quality.mipmapGeneration)
.setImpact(OptionImpact.LOW)
.setFlags(OptionFlag.REQUIRES_ASSET_RELOAD)
.build())
.build());
groups.add(OptionGroup.createBuilder()
.add(Settings.MODE_GLINT_INV.option)
@@ -774,6 +756,7 @@ public class SodiumGameOptionPages {
(opts, value) -> opts.anisotropicFiltering = 1 << MathHelper.clamp_int(value, min, max),
opts -> MathHelper.clamp_int(Integer.numberOfTrailingZeros(Math.max(opts.anisotropicFiltering, 1)), min, max))
.setImpact(OptionImpact.MEDIUM)
.setFlags(OptionFlag.REQUIRES_ASSET_RELOAD)
.build();
option.iris$dynamicallyEnable(() -> mode.get().usesAnisotropy() && SodiumGameOptions.hasAnisotropyRange());
@@ -864,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));
}
@@ -11,7 +11,6 @@ import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import me.jellysquid.mods.sodium.client.gui.options.named.BiomeBlendMode;
import me.jellysquid.mods.sodium.client.gui.options.named.GraphicsQuality;
import me.jellysquid.mods.sodium.client.gui.options.named.MipmapGeneration;
import me.jellysquid.mods.sodium.client.gui.options.named.MultiDrawMode;
import me.jellysquid.mods.sodium.client.gui.options.named.TexelSampling;
import me.jellysquid.mods.sodium.client.gui.options.named.TextureFilterMode;
@@ -62,8 +61,7 @@ public class SodiumGameOptions {
final TextureMap atlas = mc.getTextureMapBlocks();
atlas.setMipmapLevels(mc.gameSettings.mipmapLevels);
// Drivers may ignore NEAREST when anisotropy is enabled
atlas.setAnisotropicFiltering(1);
atlas.setAnisotropicFiltering(resolvedAnisotropicFiltering());
}
public static int resolvedAnisotropicFiltering() {
@@ -153,11 +151,6 @@ public class SodiumGameOptions {
if (config.quality.texelSampling == null) {
config.quality.texelSampling = TexelSampling.LINEAR;
}
config.quality.guiBlockSupersampling = Integer.highestOneBit(MathHelper.clamp_int(config.quality.guiBlockSupersampling, 1, 16));
if (config.quality.mipmapGeneration == null) {
config.quality.mipmapGeneration = MipmapGeneration.MODERN_VANILLA;
}
if (GLStateManager.capabilities != null) {
if (!GLStateManager.capabilities.OpenGL32) {
@@ -257,8 +250,6 @@ public class SodiumGameOptions {
public boolean useCeleritasSmoothLighting = true;
public TextureFilterMode textureFilterMode = TextureFilterMode.RGSS_ANISOTROPIC;
public TexelSampling texelSampling = TexelSampling.LINEAR;
public MipmapGeneration mipmapGeneration = MipmapGeneration.MODERN_VANILLA;
public int guiBlockSupersampling = 1;
}
public static class ReducerSettings {
@@ -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);
}
@@ -1,33 +0,0 @@
package me.jellysquid.mods.sodium.client.gui.options.named;
import com.gtnewhorizons.angelica.proxy.ClientProxy;
import org.embeddedt.embeddium.impl.render.chunk.terrain.material.parameters.AlphaCutoffParameter;
public enum MipmapGeneration implements NamedState {
VANILLA_1_7_10("sodium.options.mipmap_generation.vanilla_1_7_10"),
CELERITAS("sodium.options.mipmap_generation.celeritas"),
MODERN_VANILLA("sodium.options.mipmap_generation.modern_vanilla");
private final String name;
MipmapGeneration(String name) {
this.name = name;
}
public static MipmapGeneration current() {
return ClientProxy.options().quality.mipmapGeneration;
}
public static boolean usesModernMipmaps() {
return current() == MODERN_VANILLA;
}
public static AlphaCutoffParameter alphaFloorCutoff() {
return usesModernMipmaps() ? AlphaCutoffParameter.ONE_TENTH : AlphaCutoffParameter.ZERO;
}
@Override
public String getKey() {
return this.name;
}
}
@@ -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;
}
@@ -93,7 +93,6 @@ public class BlockRenderingSettings {
private float ambientOcclusionLevel;
private boolean disableDirectionalShading;
private boolean useSeparateAo;
@Setter private boolean breaksAnisotropy;
private boolean useExtendedVertexFormat;
@Setter
private boolean hasSnowyEntries;
@@ -214,10 +213,6 @@ public class BlockRenderingSettings {
return useSeparateAo;
}
public boolean breaksAnisotropy() {
return breaksAnisotropy;
}
public void setUseSeparateAo(boolean useSeparateAo) {
if (useSeparateAo == this.useSeparateAo) {
return;
@@ -27,7 +27,7 @@ import java.util.Optional;
import java.util.function.Function;
public class IrisCeleritasChunkProgramOverrides {
private final EnumMap<IrisTerrainPass, GlProgram<ChunkShaderInterface>> programs = new EnumMap<>(IrisTerrainPass.class);
private final EnumMap<IrisTerrainPass, GlProgram<IrisCeleritasChunkShaderInterface>> programs = new EnumMap<>(IrisTerrainPass.class);
private boolean shadersCreated = false;
private int versionCounterForShaderReload = -1;
@@ -80,19 +80,19 @@ public class IrisCeleritasChunkProgramOverrides {
return key;
}
private static final class SavedProgram extends GlProgram<ChunkShaderInterface> {
SavedProgram(int program, Function<ShaderBindingContext, ChunkShaderInterface> factory) {
private static final class SavedProgram extends GlProgram<IrisCeleritasChunkShaderInterface> {
SavedProgram(int program, Function<ShaderBindingContext, IrisCeleritasChunkShaderInterface> factory) {
super(program, factory);
}
}
@Nullable
private GlProgram<ChunkShaderInterface> createShader(IrisTerrainPass pass, CeleritasTerrainPipeline pipeline, ChunkVertexType vertexType) {
private GlProgram<IrisCeleritasChunkShaderInterface> createShader(IrisTerrainPass pass, CeleritasTerrainPipeline pipeline, ChunkVertexType vertexType) {
final CeleritasTerrainPipeline.PassInfo passInfo = pipeline.getPassInfo(pass);
final String name = "iris:celeritas-chunk-" + pass.getName();
final BlendModeOverride blendOverride = passInfo.blendModeOverride();
final List<BufferBlendOverride> bufferOverrides = passInfo.bufferBlendOverrides();
final Function<ShaderBindingContext, ChunkShaderInterface> factory = context -> new IrisCeleritasChunkShaderInterface(
final Function<ShaderBindingContext, IrisCeleritasChunkShaderInterface> factory = context -> new IrisCeleritasChunkShaderInterface(
((GlObject) context).handle(), context, pipeline, pass.isShadow(), blendOverride, bufferOverrides, pipeline.getCustomUniforms());
final ProgramBinaryCache.Key cacheKey = cacheKey(passInfo, vertexType);
@@ -130,7 +130,7 @@ public class IrisCeleritasChunkProgramOverrides {
builder.bindAttribute(attr.getName(), attrIndex++);
}
final GlProgram<ChunkShaderInterface> program = builder.link(factory);
final GlProgram<IrisCeleritasChunkShaderInterface> program = builder.link(factory);
if (cacheKey != null) {
ProgramBinaryCache.save(cacheKey, program.handle());
}
@@ -169,7 +169,7 @@ public class IrisCeleritasChunkProgramOverrides {
}
@Nullable
public GlProgram<ChunkShaderInterface> getProgramOverride(TerrainRenderPass pass, RenderPassConfiguration<?> configuration) {
public GlProgram<? extends ChunkShaderInterface> getProgramOverride(TerrainRenderPass pass, RenderPassConfiguration<?> configuration) {
// Check for shader pack reload
if (versionCounterForShaderReload != Iris.getPipelineManager().getVersionCounterForSodiumShaderReload()) {
versionCounterForShaderReload = Iris.getPipelineManager().getVersionCounterForSodiumShaderReload();
@@ -186,13 +186,23 @@ public class IrisCeleritasChunkProgramOverrides {
createShaders(celeritasPipeline, configuration);
}
final boolean isShadow = ShadowRenderingState.areShadowsCurrentlyBeingRendered();
if (isShadow) {
if (ShadowRenderingState.areShadowsCurrentlyBeingRendered()) {
if (celeritasPipeline != null && !celeritasPipeline.hasShadowPass()) {
throw new IllegalStateException("Shadow program requested, but shader pack has no shadow pass");
}
if (pass.isReverseOrder()) {
return programs.get(IrisTerrainPass.SHADOW_TRANSLUCENT);
}
return programs.get(pass.supportsFragmentDiscard() ? IrisTerrainPass.SHADOW_CUTOUT : IrisTerrainPass.SHADOW);
} else {
if (pass.supportsFragmentDiscard()) {
return programs.get(IrisTerrainPass.GBUFFER_CUTOUT);
} else if (pass.isReverseOrder()) {
return programs.get(IrisTerrainPass.GBUFFER_TRANSLUCENT);
} else {
return programs.get(IrisTerrainPass.GBUFFER_SOLID);
}
}
return programs.get(IrisTerrainPass.fromTerrainPass(pass, isShadow));
}
public void deleteShaders() {
@@ -241,7 +241,20 @@ public class IrisCeleritasChunkShaderInterface implements ChunkShaderInterface {
}
final boolean isShadow = ShadowRenderingState.areShadowsCurrentlyBeingRendered();
final IrisTerrainPass irisPass = IrisTerrainPass.fromTerrainPass(pass, isShadow);
final IrisTerrainPass irisPass;
if (isShadow) {
if (pass.isReverseOrder()) {
irisPass = IrisTerrainPass.SHADOW_TRANSLUCENT;
} else {
irisPass = pass.supportsFragmentDiscard() ? IrisTerrainPass.SHADOW_CUTOUT : IrisTerrainPass.SHADOW;
}
} else if (pass.isReverseOrder()) {
irisPass = IrisTerrainPass.GBUFFER_TRANSLUCENT;
} else if (pass.supportsFragmentDiscard()) {
irisPass = IrisTerrainPass.GBUFFER_CUTOUT;
} else {
irisPass = IrisTerrainPass.GBUFFER_SOLID;
}
final GlFramebuffer framebuffer = pipeline.getPassInfo(irisPass).framebuffer();
if (framebuffer != null) {
@@ -40,7 +40,7 @@ public class IrisCeleritasShaderProvider implements IrisShaderProvider {
@Override
@Nullable
public GlProgram<ChunkShaderInterface> getShaderOverride(TerrainRenderPass pass) {
public GlProgram<? extends ChunkShaderInterface> getShaderOverride(TerrainRenderPass pass) {
if (!isShadersEnabled() || renderPassConfiguration == null) {
return null;
}
@@ -41,10 +41,10 @@ public enum IrisTerrainPass {
return SHADOW_TRANSLUCENT;
}
return pass.supportsFragmentDiscard() ? SHADOW_CUTOUT : SHADOW;
} else if (pass.isReverseOrder()) {
return GBUFFER_TRANSLUCENT;
} else if (pass.supportsFragmentDiscard()) {
return GBUFFER_CUTOUT;
} else if (pass.isReverseOrder()) {
return GBUFFER_TRANSLUCENT;
} else {
return GBUFFER_SOLID;
}
@@ -17,7 +17,6 @@ public class AlphaTestStorage {
@Getter
private static boolean alphaTestLocked;
@Getter
private static boolean overrideHeld;
public static void overrideAlphaTest(AlphaTest override) {
@@ -93,7 +93,6 @@ public final class ComputeProgram extends GlResource {
public static void unbind() {
ProgramUniforms.clearActiveUniforms();
ProgramSamplers.clearActiveSamplers();
GLStateManager.glUseProgram(0);
}
@@ -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
@@ -18,7 +18,6 @@ import com.gtnewhorizons.angelica.glsm.texture.TextureType;
import org.jetbrains.annotations.Nullable;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
public class ProgramBuilder extends ProgramUniforms.Builder implements SamplerHolder, ImageHolder {
private final int program;
@@ -197,17 +196,17 @@ public class ProgramBuilder extends ProgramUniforms.Builder implements SamplerHo
}
@Override
public boolean addDefaultSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, Supplier<GlSampler> sampler, String... names) {
public boolean addDefaultSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, GlSampler sampler, String... names) {
return samplers.addDefaultSampler(type, texture, notifier, sampler, names);
}
@Override
public boolean addDynamicSampler(TextureType type, IntSupplier texture, Supplier<GlSampler> sampler, String... names) {
public boolean addDynamicSampler(TextureType type, IntSupplier texture, GlSampler sampler, String... names) {
return samplers.addDynamicSampler(type, texture, sampler, names);
}
@Override
public boolean addDynamicSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, Supplier<GlSampler> sampler, String... names) {
public boolean addDynamicSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, GlSampler sampler, String... names) {
return samplers.addDynamicSampler(type, texture, notifier, sampler, names);
}
@@ -19,7 +19,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
public class ProgramSamplers {
private static ProgramSamplers active;
@@ -40,25 +39,23 @@ public class ProgramSamplers {
active = this;
int activeTexture = GLStateManager.getActiveTextureUnit();
try {
if (initializer != null) {
for (GlUniform1iCall call : initializer) {
RenderSystem.uniform1i(call.getLocation(), call.getValue());
}
initializer = null;
if (initializer != null) {
for (GlUniform1iCall call : initializer) {
RenderSystem.uniform1i(call.getLocation(), call.getValue());
}
for (int i = 0, n = samplerBindings.size(); i < n; i++) {
samplerBindings.get(i).update();
}
} catch (RuntimeException | Error e) {
clearActiveSamplers();
throw e;
} finally {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + activeTexture);
initializer = null;
}
// We need to keep the active texture intact, since if we mess it up
// in the middle of RenderType setup, bad things will happen.
int activeTexture = GLStateManager.getActiveTextureUnit();
for (int i = 0, n = samplerBindings.size(); i < n; i++) {
samplerBindings.get(i).update();
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + activeTexture);
}
public void removeListeners() {
@@ -73,7 +70,6 @@ public class ProgramSamplers {
if (active != null) {
active.removeListeners();
}
RenderSystem.unbindAllSamplers();
}
public static Builder builder(int program, Set<Integer> reservedTextureUnits) {
@@ -152,30 +148,31 @@ public class ProgramSamplers {
}
@Override
public boolean addDefaultSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, Supplier<GlSampler> sampler, String... names) {
public boolean addDefaultSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, GlSampler sampler, String... names) {
if (nextUnit != 0) {
// TODO: Relax this restriction!
throw new IllegalStateException("Texture unit 0 is already used.");
}
if (notifier != null) {
notifiersToReset.add(notifier);
}
return addDynamicSampler(type, texture, sampler, true, notifier, names);
}
@Override
public boolean addDynamicSampler(TextureType type, IntSupplier texture, Supplier<GlSampler> sampler, String... names) {
public boolean addDynamicSampler(TextureType type, IntSupplier texture, GlSampler sampler, String... names) {
return addDynamicSampler(type, texture, sampler, false, null, names);
}
@Override
public boolean addDynamicSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, Supplier<GlSampler> sampler, String... names) {
public boolean addDynamicSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, GlSampler sampler, String... names) {
notifiersToReset.add(notifier);
return addDynamicSampler(type, texture, sampler, false, notifier, names);
}
private boolean addDynamicSampler(TextureType type, IntSupplier texture, Supplier<GlSampler> sampler, boolean used, ValueUpdateNotifier notifier, String... names) {
if (notifier != null) {
notifiersToReset.add(notifier);
}
private boolean addDynamicSampler(TextureType type, IntSupplier texture, GlSampler sampler, boolean used, ValueUpdateNotifier notifier, String... names) {
for (String name : names) {
int location = RenderSystem.getUniformLocation(program, name);
@@ -239,22 +236,26 @@ public class ProgramSamplers {
this.deactivatedOverrides = deactivatedOverrides.build();
}
private TextureAccess getOverride(String... names) {
private IntSupplier getOverride(IntSupplier existing, String... names) {
for (String name : names) {
if (customTextureIds.containsKey(name) && !deactivatedOverrides.contains(name)) {
return customTextureIds.get(name);
return customTextureIds.get(name).getTextureId();
}
}
return null;
return existing;
}
@Override
public void addExternalSampler(int textureUnit, String... names) {
TextureAccess override = getOverride(names);
IntSupplier override = getOverride(null, names);
if (override != null) {
addOverride(override, textureUnit == 0, names);
if (textureUnit == 0) {
samplerHolder.addDefaultSampler(override, names);
} else {
samplerHolder.addDynamicSampler(override, names);
}
} else {
samplerHolder.addExternalSampler(textureUnit, names);
}
@@ -266,34 +267,21 @@ public class ProgramSamplers {
}
@Override
public boolean addDefaultSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, Supplier<GlSampler> sampler, String... names) {
return addSampler(type, texture, notifier, sampler, true, names);
public boolean addDefaultSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, GlSampler sampler, String... names) {
texture = getOverride(texture, names);
return samplerHolder.addDefaultSampler(type, texture, notifier, sampler, names);
}
@Override
public boolean addDynamicSampler(TextureType type, IntSupplier texture, Supplier<GlSampler> sampler, String... names) {
return addSampler(type, texture, null, sampler, false, names);
public boolean addDynamicSampler(TextureType type, IntSupplier texture, GlSampler sampler, String... names) {
texture = getOverride(texture, names);
return samplerHolder.addDynamicSampler(type, texture, sampler, names);
}
@Override
public boolean addDynamicSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, Supplier<GlSampler> sampler, String... names) {
return addSampler(type, texture, notifier, sampler, false, names);
}
private boolean addSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, Supplier<GlSampler> sampler, boolean defaultSampler, String... names) {
final TextureAccess override = getOverride(names);
if (override != null) {
return addOverride(override, defaultSampler, names);
}
return defaultSampler
? samplerHolder.addDefaultSampler(type, texture, notifier, sampler, names)
: samplerHolder.addDynamicSampler(type, texture, notifier, sampler, names);
}
private boolean addOverride(TextureAccess override, boolean defaultSampler, String... names) {
return defaultSampler
? samplerHolder.addDefaultSampler(override.getType(), override.getTextureId(), null, null, names)
: samplerHolder.addDynamicSampler(override.getType(), override.getTextureId(), null, names);
public boolean addDynamicSampler(TextureType type, IntSupplier texture, ValueUpdateNotifier notifier, GlSampler sampler, String... names) {
texture = getOverride(texture, names);
return samplerHolder.addDynamicSampler(type, texture, notifier, sampler, names);
}
}
}
@@ -7,10 +7,8 @@ import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import java.util.function.Supplier;
public class GlSampler extends GlObject {
public GlSampler(boolean linear, boolean linearMips, boolean mipmapped, boolean shadow, boolean hardwareShadow, boolean clamp) {
public GlSampler(boolean linear, boolean mipmapped, boolean shadow, boolean hardwareShadow) {
if (!RenderSystem.supportsSamplerObjects()) {
this.setHandle(0);
return;
@@ -18,16 +16,13 @@ public class GlSampler extends GlObject {
this.setHandle(RenderSystem.genSampler());
RenderSystem.samplerParameteri(getId(), GL11.GL_TEXTURE_MIN_FILTER, linear ? GL11.GL_LINEAR : GL11.GL_NEAREST);
RenderSystem.samplerParameteri(getId(), GL11.GL_TEXTURE_MAG_FILTER, linear ? GL11.GL_LINEAR : GL11.GL_NEAREST);
RenderSystem.samplerParameteri(getId(), GL11.GL_TEXTURE_WRAP_S, clamp ? GL12.GL_CLAMP_TO_EDGE : GL11.GL_REPEAT);
RenderSystem.samplerParameteri(getId(), GL11.GL_TEXTURE_WRAP_T, clamp ? GL12.GL_CLAMP_TO_EDGE : GL11.GL_REPEAT);
RenderSystem.samplerParameteri(getId(), GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
RenderSystem.samplerParameteri(getId(), GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
if (mipmapped) {
RenderSystem.samplerParameteri(getId(), GL11.GL_TEXTURE_MIN_FILTER, linearMips
? (linear ? GL11.GL_LINEAR_MIPMAP_LINEAR : GL11.GL_NEAREST_MIPMAP_LINEAR)
: (linear ? GL11.GL_LINEAR_MIPMAP_NEAREST : GL11.GL_NEAREST_MIPMAP_NEAREST));
} else {
RenderSystem.samplerParameteri(getId(), GL11.GL_TEXTURE_MIN_FILTER, linear ? GL11.GL_LINEAR : GL11.GL_NEAREST);
RenderSystem.samplerParameteri(getId(), GL11.GL_TEXTURE_MIN_FILTER, linear ? GL11.GL_LINEAR_MIPMAP_LINEAR : GL11.GL_NEAREST_MIPMAP_NEAREST);
}
if (hardwareShadow) {
@@ -43,9 +38,4 @@ public class GlSampler extends GlObject {
public int getId() {
return handle();
}
public static int idOf(Supplier<GlSampler> supplier) {
final GlSampler sampler = supplier == null ? null : supplier.get();
return sampler == null ? 0 : sampler.getId();
}
}
@@ -5,21 +5,20 @@ import net.coderbot.iris.gl.state.ValueUpdateNotifier;
import com.gtnewhorizons.angelica.glsm.texture.TextureType;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
public class SamplerBinding {
private final Runnable updateListener = this::updateSampler;
private final TextureType textureType;
private final int textureUnit;
private final IntSupplier texture;
private final Supplier<GlSampler> sampler;
private final int sampler;
private final ValueUpdateNotifier notifier;
public SamplerBinding(TextureType type, int textureUnit, IntSupplier texture, Supplier<GlSampler> sampler, ValueUpdateNotifier notifier) {
public SamplerBinding(TextureType type, int textureUnit, IntSupplier texture, GlSampler sampler, ValueUpdateNotifier notifier) {
this.textureType = type;
this.textureUnit = textureUnit;
this.texture = texture;
this.sampler = sampler;
this.sampler = sampler == null ? 0 : sampler.getId();
this.notifier = notifier;
}
@@ -32,7 +31,7 @@ public class SamplerBinding {
}
private void updateSampler() {
RenderSystem.bindSamplerToUnit(textureUnit, GlSampler.idOf(sampler));
RenderSystem.bindSamplerToUnit(textureUnit, sampler);
RenderSystem.bindTextureToUnit(textureType.getGlType(), textureUnit, texture.getAsInt());
}
}
Loaded 100 of 152 files, more files were not shown because too many files have changed in this diff. Show more