mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-14 17:24:43 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bbb0537ee | ||
|
|
a019b7eed8 | ||
|
|
1772e9eae2 | ||
|
|
17a2ed7f37 | ||
|
|
34ed1d101b | ||
|
|
7cc9cea337 | ||
|
|
6c5e146f96 | ||
|
|
ea50e0a0b6 | ||
|
|
713509a87b | ||
|
|
102c6cad86 | ||
|
|
8af9340be6 | ||
|
|
fc8d3e8b89 |
No files matched your search
@@ -1,14 +1,14 @@
|
||||
# Description of your *PR* and other info
|
||||
<!-- Please include a summary of the change -->
|
||||
## Summary
|
||||
<!-- Briefly describe what this PR changes and why. -->
|
||||
<!-- 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 -->
|
||||
|
||||
### 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>
|
||||
## 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
|
||||
@@ -175,8 +175,14 @@ 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)
|
||||
}
|
||||
@@ -189,6 +195,7 @@ 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")
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ DependencyHandlerScope.of(dependencies).apply {
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BetterLoadingScreen:1.7.9-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BetterQuesting:3.8.72-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BuildCraft:7.1.63:dev")
|
||||
compileOnly("com.github.GTNewHorizons:Botania:1.13.32-GTNH:dev") { isTransitive = false }
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Botania:1.13.32-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:LogisticsPipes:1.5.32-GTNH:dev")
|
||||
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenComputers:1.12.64-GTNH:dev")
|
||||
|
||||
@@ -158,6 +158,7 @@ dependencies {
|
||||
testFixturesApi(platform(libs.junit.bom))
|
||||
testFixturesApi(libs.junit.jupiter.api)
|
||||
testFixturesApi(libs.lwjgl2)
|
||||
testFixturesCompileOnly(libs.asm.tree.test)
|
||||
|
||||
// Test
|
||||
testImplementation(sourceSets["stubs"].output)
|
||||
@@ -257,6 +258,13 @@ 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"
|
||||
@@ -265,6 +273,7 @@ tasks.test {
|
||||
excludeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator*Test")
|
||||
}
|
||||
configureGlsmJava8()
|
||||
javaAgent(files(mutableStaticsAgentJar))
|
||||
}
|
||||
|
||||
val redirectAgentJar by tasks.registering(Jar::class) {
|
||||
@@ -279,8 +288,14 @@ 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) ->
|
||||
|
||||
@@ -69,7 +69,13 @@ public final class SystemProperties {
|
||||
public static final boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
|
||||
public static final float FLYBY_PITCH = parseFloat("angelica.flyby.pitch");
|
||||
public static final int FLYBY_SCREENSHOTS = Integer.getInteger("angelica.flyby.screenshots", 0);
|
||||
public static final boolean FLYBY_DEBUG_HUD = Boolean.getBoolean("angelica.flyby.debugHud");
|
||||
public static final Boolean FLYBY_DEBUG_HUD = parseBooleanOverride("angelica.flyby.debugHud");
|
||||
public static final String FLYBY_DEBUG_PROFILE = System.getProperty("angelica.flyby.debugProfile", "");
|
||||
public static final String FLYBY_DEBUG_ENTRIES = System.getProperty("angelica.flyby.debugEntries", "");
|
||||
public static final Boolean FLYBY_DEBUG_FLAT = parseBooleanOverride("angelica.flyby.debugFlat");
|
||||
public static final Boolean FLYBY_HUD_CACHING = parseBooleanOverride("angelica.flyby.hudCaching");
|
||||
public static final Boolean FLYBY_FPS_GRAPH = parseBooleanOverride("angelica.flyby.fpsGraph");
|
||||
public static final boolean FLYBY_DEBUG_DUMP = Boolean.getBoolean("angelica.flyby.debugDump");
|
||||
public static final String FLYBY_CRASH_TEST = System.getProperty("angelica.flyby.crashTest", "");
|
||||
|
||||
// Debug
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.states.AlphaState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.Color4;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ColorMask;
|
||||
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public final class ExitState {
|
||||
public static final int POLYGON_OFFSET_ENABLE_WRITTEN = 1;
|
||||
public static final int POLYGON_OFFSET_VALUES_WRITTEN = 2;
|
||||
final Values entry = new Values();
|
||||
final Values value = new Values();
|
||||
final MaterialState frontMaterial = new MaterialState(GL11.GL_FRONT);
|
||||
final MaterialState backMaterial = new MaterialState(GL11.GL_BACK);
|
||||
long writes, dependencies;
|
||||
boolean valid;
|
||||
int textureDeleteCount;
|
||||
|
||||
public void clear() {
|
||||
valid = false;
|
||||
writes = dependencies = 0;
|
||||
}
|
||||
|
||||
static final class Values {
|
||||
final Color4 color = new Color4();
|
||||
final Vector3f normal = new Vector3f();
|
||||
final Vector4f texCoord = new Vector4f();
|
||||
final Vector4f shaderColor = new Vector4f();
|
||||
final Vector4f overlayColor = new Vector4f();
|
||||
final BlendState blend = new BlendState();
|
||||
final AlphaState alpha = new AlphaState();
|
||||
final ColorMask colorMask = new ColorMask();
|
||||
final Matrix4f textureMatrix = new Matrix4f();
|
||||
long enabled;
|
||||
int activeUnit, clientActiveUnit, matrixMode, texture, depthFunction, cullFace, frontFace, shadeModel;
|
||||
int materialFace, materialParameter;
|
||||
boolean depthMask;
|
||||
float lightmapX, lightmapY, offsetFactor, offsetUnits, offsetClamp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,597 @@
|
||||
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,6 +59,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GLContextState {
|
||||
final ExitStateCapture exitCapture = new ExitStateCapture(this);
|
||||
private final List<IStateStack<?>> allStacksBuilder = new ArrayList<>();
|
||||
public IStateStack<?>[] allStacks;
|
||||
final List<BooleanStateStack> allBooleanStates = new ArrayList<>();
|
||||
@@ -274,6 +275,7 @@ 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;
|
||||
}
|
||||
|
||||
// Check for sampler objects support (GL 3.3+ or ARB extension)
|
||||
supportsSamplerObjects = GLStateManager.capabilities.OpenGL33 || GLStateManager.capabilities.GL_ARB_sampler_objects;
|
||||
supportsSamplerObjects = isGLES ? glesVersion >= 300
|
||||
: GLStateManager.capabilities.OpenGL33 || GLStateManager.capabilities.GL_ARB_sampler_objects;
|
||||
if (supportsSamplerObjects) {
|
||||
maxCombinedTextureImageUnits = RENDER_BACKEND.getInteger(GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS);
|
||||
}
|
||||
@@ -198,14 +198,17 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void generateMipmaps(int texture, int mipmapTarget) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.generateMipmaps(texture, mipmapTarget);
|
||||
}
|
||||
|
||||
public static void bindAttributeLocation(int program, int index, CharSequence name) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.bindAttribLocation(program, index, name);
|
||||
}
|
||||
|
||||
public static void texImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
final GLStateManager.GLESTexImageRemap remap = GLStateManager.remapTexImageForGLES(internalformat, format, type);
|
||||
internalformat = remap.internalFormat();
|
||||
format = remap.format();
|
||||
@@ -220,6 +223,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void uniformMatrix4fv(int location, boolean transpose, FloatBuffer matrix) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniformMatrix4(location, transpose, matrix);
|
||||
}
|
||||
|
||||
@@ -228,61 +232,78 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void uniform1f(int location, float v0) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform1f(location, v0);
|
||||
}
|
||||
|
||||
public static void uniform1i(int location, int v0) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform1i(location, v0);
|
||||
}
|
||||
|
||||
public static void uniform2f(int location, float v0, float v1) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform2f(location, v0, v1);
|
||||
}
|
||||
|
||||
public static void uniform2i(int location, int v0, int v1) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform2i(location, v0, v1);
|
||||
}
|
||||
|
||||
public static void uniform3f(int location, float v0, float v1, float v2) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform3f(location, v0, v1, v2);
|
||||
}
|
||||
|
||||
public static void uniform3i(int location, int v0, int v1, int v2) {RENDER_BACKEND.uniform3i(location, v0, v1, v2);}
|
||||
public static void uniform3i(int location, int v0, int v1, int v2) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform3i(location, v0, v1, v2);
|
||||
}
|
||||
|
||||
public static void uniform4f(int location, float v0, float v1, float v2, float v3) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform4f(location, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
public static void uniform4i(int location, int v0, int v1, int v2, int v3) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.uniform4i(location, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
public static int getAttribLocation(int programId, String name) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return RENDER_BACKEND.getAttribLocation(programId, name);
|
||||
}
|
||||
|
||||
public static int getUniformLocation(int programId, String name) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return RENDER_BACKEND.getUniformLocation(programId, name);
|
||||
}
|
||||
|
||||
public static void texParameteriv(int texture, int target, int pname, IntBuffer params) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.texParameteriv(texture, target, pname, params);
|
||||
}
|
||||
|
||||
public static void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.copyTexSubImage2D(destTexture, target, i, i1, i2, i3, i4, width, height);
|
||||
if (i == 0) TextureInfoCache.INSTANCE.onContentsChanged(destTexture);
|
||||
}
|
||||
|
||||
public static void texParameteri(int texture, int target, int pname, int param) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.texParameteri(texture, target, pname, param);
|
||||
}
|
||||
|
||||
public static void texParameterf(int texture, int target, int pname, float param) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.texParameterf(texture, target, pname, param);
|
||||
}
|
||||
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
@@ -290,6 +311,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
@@ -297,6 +319,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
@@ -304,6 +327,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
@@ -311,6 +335,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void texImage1D(int texture, int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
GLStateManager.glBindTexture(target, texture);
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
RENDER_BACKEND.texImage1D(target, level, internalformat, width, border, format, type, pixels);
|
||||
@@ -318,6 +343,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void texImage3D(int texture, int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
GLStateManager.glBindTexture(target, texture);
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
|
||||
@@ -325,26 +351,32 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static String getProgramInfoLog(int program) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return RENDER_BACKEND.getProgramInfoLog(program, RENDER_BACKEND.getProgrami(program, GL20.GL_INFO_LOG_LENGTH));
|
||||
}
|
||||
|
||||
public static String getShaderInfoLog(int shader) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return RENDER_BACKEND.getShaderInfoLog(shader, RENDER_BACKEND.getShaderi(shader, GL20.GL_INFO_LOG_LENGTH));
|
||||
}
|
||||
|
||||
public static void drawBuffers(int framebuffer, IntBuffer buffers) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.drawBuffers(framebuffer, buffers);
|
||||
}
|
||||
|
||||
public static void readBuffer(int framebuffer, int buffer) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.readBuffer(framebuffer, buffer);
|
||||
}
|
||||
|
||||
public static String getActiveUniform(int program, int index, int maxLength, IntBuffer sizeType) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return RENDER_BACKEND.getActiveUniform(program, index, maxLength, sizeType);
|
||||
}
|
||||
|
||||
public static void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
GLStateManager.suspendPixelPackBuffer();
|
||||
RENDER_BACKEND.readPixels(x, y, width, height, format, type, pixels);
|
||||
GLStateManager.restorePixelPackBuffer();
|
||||
@@ -355,30 +387,37 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static int bufferStorage(int target, FloatBuffer data, int usage) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return dsaState.bufferStorage(target, data, usage);
|
||||
}
|
||||
|
||||
public static void vertexAttrib4f(int index, float v0, float v1, float v2, float v3) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.vertexAttrib4f(index, v0, v1, v2, v3);
|
||||
}
|
||||
|
||||
public static void detachShader(int program, int shader) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.detachShader(program, shader);
|
||||
}
|
||||
|
||||
public static void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
dsaState.framebufferTexture2D(fb, fbtarget, attachment, target, texture, levels);
|
||||
}
|
||||
|
||||
public static int getTexParameteri(int texture, int target, int pname) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return dsaState.getTexParameteri(texture, target, pname);
|
||||
}
|
||||
|
||||
public static float getTexParameterf(int texture, int target, int pname) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return dsaState.getTexParameterf(texture, target, pname);
|
||||
}
|
||||
|
||||
public static int getTexLevelParameteri(int texture, int level, int pname) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return dsaState.getTexLevelParameteri(texture, level, pname);
|
||||
}
|
||||
|
||||
@@ -475,18 +514,21 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void getProgramiv(int program, int value, IntBuffer storage) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.getProgramiv(program, value, storage);
|
||||
}
|
||||
|
||||
public static void dispatchCompute(int workX, int workY, int workZ) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.dispatchCompute(workX, workY, workZ);
|
||||
}
|
||||
|
||||
public static void dispatchCompute(Vector3i workGroups) {
|
||||
RENDER_BACKEND.dispatchCompute(workGroups.x, workGroups.y, workGroups.z);
|
||||
dispatchCompute(workGroups.x, workGroups.y, workGroups.z);
|
||||
}
|
||||
|
||||
public static void dispatchComputeIndirect(long offset) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.dispatchComputeIndirect(offset);
|
||||
}
|
||||
|
||||
@@ -495,6 +537,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void memoryBarrier(int barriers) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
if (supportsCompute) {
|
||||
RENDER_BACKEND.memoryBarrier(barriers);
|
||||
}
|
||||
@@ -517,12 +560,14 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void bindTextureToUnit(int unit, int texture) {
|
||||
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
|
||||
if (unit == 0) GLStateManager.recordExitTextureBind(GL11.GL_TEXTURE_2D, 0);
|
||||
else if (GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
|
||||
dsaState.bindTextureToUnit(unit, texture);
|
||||
}
|
||||
|
||||
public static void bindTextureToUnit(int target, int unit, int texture) {
|
||||
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
|
||||
if (unit == 0) GLStateManager.recordExitTextureBind(target, 0);
|
||||
else if (GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
|
||||
dsaState.bindTextureToUnit(target, unit, texture);
|
||||
}
|
||||
|
||||
@@ -542,6 +587,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2, int bufferChoice, int filter) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
final boolean scissor = GLStateManager.glIsEnabled(GL11.GL_SCISSOR_TEST);
|
||||
if (scissor) GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
try {
|
||||
@@ -552,10 +598,12 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static int createFramebuffer() {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return dsaState.createFramebuffer();
|
||||
}
|
||||
|
||||
public static int createTexture(int target) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return dsaState.createTexture(target);
|
||||
}
|
||||
|
||||
@@ -573,6 +621,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static int getIndexedBufferBinding(int target, int index) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
return GLStateManager.getIndexedBufferBinding(target, index);
|
||||
}
|
||||
|
||||
@@ -604,6 +653,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static void clearTexImage(int texture, int target, int level, int format, int type) {
|
||||
GLStateManager.rejectExitCapture();
|
||||
RENDER_BACKEND.clearTexImage(texture, level, format, type);
|
||||
}
|
||||
|
||||
@@ -663,12 +713,7 @@ public class RenderSystem {
|
||||
|
||||
public static void unbindAllSamplers() {
|
||||
if (!supportsSamplerObjects) return;
|
||||
final int count = GLStateManager.getSamplerUnitCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (GLStateManager.getSamplerBinding(i) != 0) {
|
||||
GLStateManager.glBindSampler(i, 0);
|
||||
}
|
||||
}
|
||||
GLStateManager.unbindAllSamplers();
|
||||
}
|
||||
|
||||
/** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460". Strips
|
||||
|
||||
+6
-1
@@ -42,6 +42,9 @@ import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.ShortBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetAddress;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.Pointer.POINTER_SIZE;
|
||||
import static org.taumc.celeritas.lwjgl.LWJGLServiceProvider.LWJGL;
|
||||
|
||||
/**
|
||||
@@ -380,7 +383,9 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
public void multiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex) {
|
||||
throw new UnsupportedOperationException("GLRenderBackend.multiDrawElementsBaseVertex should not be called — LWJGL service handles this");
|
||||
for (int i = 0; i < drawcount; i++) {
|
||||
GL32.glDrawElementsBaseVertex(mode, memGetInt(pCount + 4L * i), type, memGetAddress(pIndices + (long) i * POINTER_SIZE), memGetInt(pBaseVertex + 4L * i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -26,6 +26,8 @@ import java.util.function.Predicate;
|
||||
*/
|
||||
public abstract class RenderBackend {
|
||||
|
||||
public static final int MAX_FRAMES_IN_FLIGHT = 3;
|
||||
|
||||
public void onPostWindowCreate(long window) {}
|
||||
|
||||
public abstract void init();
|
||||
|
||||
+3
-2
@@ -11,13 +11,14 @@ 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, the GL sampler object to sample it with when samplerUnits is -1, or 0
|
||||
* @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 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, int[] samplerObjects,
|
||||
String[] samplerNames, int[] samplerUnits, IntSupplier[] samplerTextures, IntSupplier[] samplerObjects,
|
||||
int vertexBufferBinding, int vertexBufferCount, int indexBufferBinding) {
|
||||
}
|
||||
@@ -104,6 +104,7 @@ 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,6 +6,7 @@ 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;
|
||||
@@ -82,7 +83,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_NR_ENABLED_UNITS = 7; // 3 bits
|
||||
private static final int BIT_TEXTURE_SUPERSAMPLING = 7; // 3 bits, log2(samples)
|
||||
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
|
||||
@@ -172,7 +173,10 @@ public final class FragmentKey {
|
||||
}
|
||||
|
||||
final int nrEnabled = highestEnabled + 1;
|
||||
global |= ((long) (nrEnabled & 0x7)) << BIT_NR_ENABLED_UNITS;
|
||||
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 |= (unit0Bits << GLOBAL_BITS);
|
||||
out[0] = global;
|
||||
|
||||
@@ -256,12 +260,13 @@ 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 (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
|
||||
public int nrEnabledUnits() { return packed.length == 1 && !unitEnabled(0) ? 0 : packed.length; }
|
||||
public boolean overlayEnabled() { return ((packed[0] >> BIT_OVERLAY_ENABLED) & 1) != 0; }
|
||||
public boolean overlayInstanced() { return ((packed[0] >> BIT_OVERLAY_INSTANCED) & 1) != 0; }
|
||||
|
||||
@@ -400,6 +405,7 @@ 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,6 +28,10 @@ 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);
|
||||
@@ -86,7 +90,8 @@ 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;
|
||||
sb.append(" vec4 tex").append(i).append("Color = textureProj(u_Sampler").append(i).append(", v_TexCoord").append(i).append(");\n");
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -190,6 +190,10 @@ 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;
|
||||
@@ -199,18 +203,21 @@ public final class ShaderManager {
|
||||
}
|
||||
|
||||
public void setNormal(float x, float y, float z) {
|
||||
GLStateManager.recordExitNormal();
|
||||
normalStack.beforeModify();
|
||||
currentNormal.set(x, y, z);
|
||||
normalGeneration++;
|
||||
}
|
||||
|
||||
public void setTexCoord(float s, float t, float r, float q) {
|
||||
GLStateManager.recordExitTexCoord(0);
|
||||
texCoordStack.beforeModify();
|
||||
currentTexCoords[0].set(s, t, r, q);
|
||||
texCoordGeneration++;
|
||||
}
|
||||
|
||||
public void setTexCoord(int unit, float s, float t, float r, float q) {
|
||||
GLStateManager.recordExitTexCoord(unit);
|
||||
if (unit == 0) texCoordStack.beforeModify();
|
||||
currentTexCoords[unit].set(s, t, r, q);
|
||||
texCoordGeneration++;
|
||||
|
||||
@@ -11,6 +11,7 @@ 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;
|
||||
@@ -49,6 +50,7 @@ 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;
|
||||
@@ -170,6 +172,15 @@ 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) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
|
||||
/**
|
||||
* Generates GLSL 330 core vertex shaders for FFP emulation.
|
||||
@@ -19,6 +20,9 @@ public final class VertexShaderGenerator {
|
||||
emitAttributes(sb, key);
|
||||
emitUniforms(sb, key);
|
||||
emitOutputs(sb, key);
|
||||
if (key.lineQuads()) {
|
||||
sb.append(ShaderLoader.getShaderSource("angelica:include/line_quads.glsl")).append('\n');
|
||||
}
|
||||
|
||||
sb.append("void main() {\n");
|
||||
if (key.instancing() == Instancing.WEATHER) {
|
||||
@@ -142,7 +146,7 @@ public final class VertexShaderGenerator {
|
||||
} else {
|
||||
sb.append(" gl_Position = u_MVPMatrix * pos4;\n");
|
||||
if (key.lineQuads()) {
|
||||
emitLineQuadWidening(sb);
|
||||
emitLineQuadWidening(sb, key);
|
||||
}
|
||||
|
||||
// Eye position needed for lighting, fog, EYE_LINEAR texgen, and clip planes
|
||||
@@ -156,42 +160,15 @@ public final class VertexShaderGenerator {
|
||||
sb.append('\n');
|
||||
}
|
||||
|
||||
private static void emitLineQuadWidening(StringBuilder sb) {
|
||||
private static void emitLineQuadWidening(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" int lineCorner = gl_VertexID % 6;\n");
|
||||
sb.append(" bool lineStart = lineCorner == 0 || lineCorner == 1 || lineCorner == 3;\n");
|
||||
sb.append(" bool lineStart = angelica_lineStart(lineCorner);\n");
|
||||
sb.append(" vec4 lineOther = u_MVPMatrix * vec4(a_LineOtherEnd, 1.0);\n");
|
||||
sb.append(" vec4 lineC0 = lineStart ? gl_Position : lineOther;\n");
|
||||
sb.append(" vec4 lineC1 = lineStart ? lineOther : gl_Position;\n");
|
||||
sb.append(" vec4 lineCenter = lineC0;\n");
|
||||
sb.append(" float lineT = 0.0;\n");
|
||||
sb.append(" float lineD0 = lineC0.z + lineC0.w;\n");
|
||||
sb.append(" float lineD1 = lineC1.z + lineC1.w;\n");
|
||||
sb.append(" if (lineD0 < 0.0 && lineD1 < 0.0) {\n");
|
||||
sb.append(" gl_Position = vec4(0.0, 0.0, 2.0, 1.0);\n");
|
||||
sb.append(" } else {\n");
|
||||
sb.append(" float lineT0 = lineD0 < 0.0 ? lineD0 / (lineD0 - lineD1) : 0.0;\n");
|
||||
sb.append(" float lineT1 = lineD1 < 0.0 ? lineD0 / (lineD0 - lineD1) : 1.0;\n");
|
||||
sb.append(" lineT = lineStart ? lineT0 : lineT1;\n");
|
||||
sb.append(" vec4 lineP0 = mix(lineC0, lineC1, lineT0);\n");
|
||||
sb.append(" vec4 lineP1 = mix(lineC0, lineC1, lineT1);\n");
|
||||
sb.append(" vec2 lineN0 = lineP0.xy / lineP0.w;\n");
|
||||
sb.append(" vec2 lineN1 = lineP1.xy / lineP1.w;\n");
|
||||
sb.append(" vec2 linePixel = 2.0 / u_ViewportSize;\n");
|
||||
sb.append(" float lineHalfWidth = 0.5 * u_LineWidth;\n");
|
||||
sb.append(" vec2 lineOffset;\n");
|
||||
sb.append(" vec2 lineShift;\n");
|
||||
sb.append(" vec2 lineSpan = abs(lineN1 - lineN0) * u_ViewportSize;\n");
|
||||
sb.append(" if (lineSpan.x > lineSpan.y) {\n");
|
||||
sb.append(" lineOffset = vec2(0.0, lineHalfWidth * linePixel.y);\n");
|
||||
sb.append(" lineShift = vec2(lineN0.x < lineN1.x ? -0.5 : 0.5, -0.125) * linePixel;\n");
|
||||
sb.append(" } else {\n");
|
||||
sb.append(" lineOffset = vec2(lineHalfWidth * linePixel.x, 0.0);\n");
|
||||
sb.append(" lineShift = vec2(0.125, lineN0.y < lineN1.y ? -0.5 : 0.5) * linePixel;\n");
|
||||
sb.append(" }\n");
|
||||
sb.append(" vec4 lineP = lineStart ? lineP0 : lineP1;\n");
|
||||
sb.append(" vec2 lineN = lineStart ? lineN0 : lineN1;\n");
|
||||
sb.append(" gl_Position = vec4((lineN + (lineCorner % 2 == 0 ? lineOffset : -lineOffset) + lineShift) * lineP.w, lineP.z, lineP.w);\n");
|
||||
sb.append(" }\n");
|
||||
if (key.lineStipple()) {
|
||||
sb.append(" vec4 lineCenter = lineStart ? gl_Position : lineOther;\n");
|
||||
}
|
||||
sb.append(" float lineT;\n");
|
||||
sb.append(" gl_Position = angelica_lineQuad(gl_Position, lineOther, lineCorner, u_ViewportSize, u_LineWidth, lineT);\n");
|
||||
}
|
||||
|
||||
private static void emitNormalTransform(StringBuilder sb, VertexKey key) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
|
||||
public interface DeferredAlphaHandler {
|
||||
boolean isAlphaTestLocked();
|
||||
boolean isOverrideHeld();
|
||||
boolean deferAlphaTestToggle(boolean enabled);
|
||||
boolean deferAlphaFunc(int function, float reference);
|
||||
}
|
||||
@@ -47,6 +47,31 @@ public final class TextureUtilHooks {
|
||||
GLStateManager.ctx().savedFilterTextureId = 0;
|
||||
}
|
||||
|
||||
public static boolean beginFilterSave(boolean blurred, boolean mipmap, float aniso) {
|
||||
captureBoundFilterTexture();
|
||||
return GLStateManager.beginExitTextureFilter(!blurred && !mipmap && aniso == 1);
|
||||
}
|
||||
|
||||
public static void endFilterSave(boolean captured) {
|
||||
if (captured) GLStateManager.finishExitTextureFilterSave();
|
||||
}
|
||||
|
||||
public static boolean beginFilterRestore() {
|
||||
return GLStateManager.beginExitTextureFilterRestore();
|
||||
}
|
||||
|
||||
public static void endFilterRestore(boolean captured) {
|
||||
try {
|
||||
if (captured) GLStateManager.finishExitTextureFilterRestore();
|
||||
} finally {
|
||||
clearSavedFilterTexture();
|
||||
}
|
||||
}
|
||||
|
||||
public static void filterFailed(boolean captured) {
|
||||
if (captured) GLStateManager.failExitTextureFilter();
|
||||
}
|
||||
|
||||
public static void restoreFilter(int min, int mag) {
|
||||
final int id = GLStateManager.ctx().savedFilterTextureId;
|
||||
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.LineQuads;
|
||||
@@ -122,18 +121,14 @@ public final class DisplayListVBO {
|
||||
}
|
||||
|
||||
private void renderLineQuads() {
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
lineQuads.bind();
|
||||
VAOManager.setCurrentVertexFlags(lineQuadFlags);
|
||||
final boolean culled = LineQuads.disableCulling();
|
||||
glCtx.lineQuadsActive = true;
|
||||
final boolean culled = LineQuads.beginDraw(lineQuadFlags);
|
||||
FfpExtendedAttribs.beginInternalDraw();
|
||||
try {
|
||||
lineQuads.draw(GL11.GL_TRIANGLES, lineQuadStart, lineQuadCount);
|
||||
} finally {
|
||||
FfpExtendedAttribs.endInternalDraw();
|
||||
glCtx.lineQuadsActive = false;
|
||||
LineQuads.restoreCulling(culled);
|
||||
LineQuads.endDraw(culled);
|
||||
}
|
||||
lineQuads.unbind();
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ public class BooleanStateStack extends BooleanState implements CowStateStack<Boo
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
final GLContextState o = cow.owner;
|
||||
GLStateManager.recordExitCapability(o, glCap, -1);
|
||||
if (o != null && o.attribDepth > 0
|
||||
&& (stateUnknown || !GLStateManager.isCachingEnabled() || enabled != vanillaValue())) {
|
||||
beforeModify();
|
||||
|
||||
+1
@@ -24,6 +24,7 @@ public final class TextureUnitBooleanStateStack extends BooleanStateStack {
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
GLStateManager.recordExitCapability(cowDepths().owner, glCap, unitIndex);
|
||||
if (!GLStateManager.isCachingEnabled() || enabled != this.enabled) {
|
||||
GLStateManager.beforeUncapturedTextureUnitChange(glCap, unitIndex);
|
||||
beforeModify();
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
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;
|
||||
@@ -22,15 +25,27 @@ 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 < local.length; i++) {
|
||||
for (int i = 0; i <= highestBoundUnit; i++) {
|
||||
if (local[i] == sampler) local[i] = 0;
|
||||
}
|
||||
trimUnboundUnits();
|
||||
}
|
||||
|
||||
private void trimUnboundUnits() {
|
||||
while (highestBoundUnit >= 0 && samplers[highestBoundUnit] == 0) {
|
||||
highestBoundUnit--;
|
||||
}
|
||||
}
|
||||
|
||||
public int size() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
|
||||
@@ -72,6 +73,18 @@ public final class LineQuads {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean beginDraw(int vertexFlags) {
|
||||
VAOManager.setCurrentVertexFlags(vertexFlags);
|
||||
final boolean culled = disableCulling();
|
||||
GLStateManager.ctx().lineQuadsActive = true;
|
||||
return culled;
|
||||
}
|
||||
|
||||
public static void endDraw(boolean culled) {
|
||||
GLStateManager.ctx().lineQuadsActive = false;
|
||||
restoreCulling(culled);
|
||||
}
|
||||
|
||||
public static VertexFormat quadFormat(VertexFormat lineFormat) {
|
||||
return QUAD_FORMATS[lineFormat.getVertexFlags() | VertexFlags.NORMAL_BIT];
|
||||
}
|
||||
|
||||
+1
@@ -197,6 +197,7 @@ public class TessellatorStreamingDrawer {
|
||||
final boolean faceNormals = !tess.hasNormals && normalHandler != null
|
||||
&& ImmediateExtendedAttribHandler.extPrimVerts(tess.drawMode, vertexCount) == 4 && normalHandler.wantsFaceNormals();
|
||||
final boolean fillNormal = !tess.hasNormals && !faceNormals;
|
||||
GLStateManager.requireExitVertexDefaults(fillNormal, !tess.hasTexture, !tess.hasColor, !tess.hasBrightness);
|
||||
|
||||
final int defaultNormal;
|
||||
if (fillNormal) {
|
||||
|
||||
@@ -21,6 +21,7 @@ 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;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
bool angelica_lineStart(int corner) {
|
||||
return corner == 0 || corner == 1 || corner == 3;
|
||||
}
|
||||
|
||||
vec4 angelica_lineQuad(vec4 clipThis, vec4 clipOther, int corner, vec2 viewport, float width, out float lineT) {
|
||||
bool lineStart = angelica_lineStart(corner);
|
||||
vec4 lineC0 = lineStart ? clipThis : clipOther;
|
||||
vec4 lineC1 = lineStart ? clipOther : clipThis;
|
||||
lineT = 0.0;
|
||||
float lineD0 = lineC0.z + lineC0.w;
|
||||
float lineD1 = lineC1.z + lineC1.w;
|
||||
if (lineD0 < 0.0 && lineD1 < 0.0) {
|
||||
return vec4(0.0, 0.0, 2.0, 1.0);
|
||||
} else {
|
||||
float lineT0 = lineD0 < 0.0 ? lineD0 / (lineD0 - lineD1) : 0.0;
|
||||
float lineT1 = lineD1 < 0.0 ? lineD0 / (lineD0 - lineD1) : 1.0;
|
||||
lineT = lineStart ? lineT0 : lineT1;
|
||||
vec4 lineP0 = mix(lineC0, lineC1, lineT0);
|
||||
vec4 lineP1 = mix(lineC0, lineC1, lineT1);
|
||||
vec2 lineN0 = lineP0.xy / lineP0.w;
|
||||
vec2 lineN1 = lineP1.xy / lineP1.w;
|
||||
vec2 linePixel = 2.0 / viewport;
|
||||
float lineHalfWidth = 0.5 * width;
|
||||
vec2 lineOffset;
|
||||
vec2 lineShift;
|
||||
vec2 lineSpan = abs(lineN1 - lineN0) * viewport;
|
||||
if (lineSpan.x > lineSpan.y) {
|
||||
lineOffset = vec2(0.0, lineHalfWidth * linePixel.y);
|
||||
lineShift = vec2(lineN0.x < lineN1.x ? -0.5 : 0.5, -0.125) * linePixel;
|
||||
} else {
|
||||
lineOffset = vec2(lineHalfWidth * linePixel.x, 0.0);
|
||||
lineShift = vec2(0.125, lineN0.y < lineN1.y ? -0.5 : 0.5) * linePixel;
|
||||
}
|
||||
vec4 lineP = lineStart ? lineP0 : lineP1;
|
||||
vec2 lineN = lineStart ? lineN0 : lineN1;
|
||||
return vec4((lineN + (corner % 2 == 0 ? lineOffset : -lineOffset) + lineShift) * lineP.w, lineP.z, lineP.w);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
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);
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.AbstractInsnNode;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.MethodInsnNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class GLSM_ExitCaptureCoverage_UnitTest {
|
||||
|
||||
private static final String PACKAGE = "com/gtnewhorizons/angelica/glsm/";
|
||||
private static final String MANAGER = PACKAGE + "GLStateManager";
|
||||
private static final String RENDER_SYSTEM = PACKAGE + "RenderSystem";
|
||||
private static final String CAPTURE = PACKAGE + "ExitStateCapture";
|
||||
|
||||
private static final Set<String> RECORDING = Set.of(
|
||||
"reject", "write", "writeBlend", "writeAlpha", "writeDepthColor", "capability", "material",
|
||||
"matrixChange", "pushAttrib", "popAttrib", "pushMatrix", "popMatrix",
|
||||
"requireDrawState", "requireMaterialBakeInputs", "requireVertexDefaults", "requireTextureFilter",
|
||||
"beginTextureFilter", "finishTextureFilterSave", "beginTextureFilterRestore", "finishTextureFilterRestore",
|
||||
"failTextureFilter");
|
||||
|
||||
private static final Set<String> IGNORED = Set.of(
|
||||
// glBegin and glEnd reject
|
||||
"glVertex2d", "glVertex2f", "glVertex2i", "glVertex3d", "glVertex3f", "glVertex3i",
|
||||
// No render state
|
||||
"glDebugMessageCallback", "glDebugMessageControl", "glDebugMessageInsert", "glObjectLabel",
|
||||
"glPushDebugGroup", "glPopDebugGroup",
|
||||
// Others
|
||||
"glInitNames", "glPushName", "glPopName", "glLoadName", "isFramebufferEnabled", "gluUnProject", "gluErrorString");
|
||||
|
||||
private final Map<String, ClassNode> classes = new HashMap<>();
|
||||
private final Set<String> reaching = new HashSet<>();
|
||||
private final Set<String> notReaching = new HashSet<>();
|
||||
private Set<String> counted = RECORDING;
|
||||
|
||||
@Test
|
||||
void everyEntryPointRejectsOrRecordsTheExitCapture() {
|
||||
final Set<String> entryPoints = new HashSet<>(GLSMRedirector.getGLPrefixMethodRedirects().values());
|
||||
GLSMRedirector.getNamedClassMethodRedirects().values().forEach(redirects -> entryPoints.addAll(redirects.values()));
|
||||
final List<String> missing = missing(MANAGER, name -> name.startsWith("gl") || entryPoints.contains(name), method -> reachesCapture(MANAGER, method));
|
||||
assertTrue(missing.isEmpty(), "Entry points that neither reject nor record an exit capture:\n " + String.join("\n ", missing));
|
||||
}
|
||||
|
||||
// None of RenderSystem's writes are modeled. A method that drives the backend or DSA itself must reject itself, since
|
||||
// a GLSM call it also makes (glBindTexture) may only record.
|
||||
@Test
|
||||
void everyRenderSystemCallRejectsTheExitCapture() {
|
||||
counted = Set.of("reject");
|
||||
final List<String> missing = missing(RENDER_SYSTEM, name -> !name.equals("initRenderer") && !name.equals("getVRAM")
|
||||
&& !name.matches("(supports|has|is|getMax|getGl|parse|probe).*"), method -> reachesCapture(RENDER_SYSTEM, method)
|
||||
&& (!calls(method, owner -> owner.startsWith(PACKAGE + "backend/") || owner.startsWith(PACKAGE + "dsa/"))
|
||||
|| calls(method, owner -> owner.equals(MANAGER), "rejectExitCapture", "beforeUncapturedStateChange")));
|
||||
assertTrue(missing.isEmpty(), "RenderSystem methods that do not reject an exit capture:\n " + String.join("\n ", missing));
|
||||
}
|
||||
|
||||
private List<String> missing(String owner, Predicate<String> checked, Predicate<MethodNode> covered) {
|
||||
final List<String> missing = new ArrayList<>();
|
||||
for (MethodNode method : load(owner).methods) {
|
||||
if ((method.access & (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC)) != (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC)) continue;
|
||||
if ((method.access & Opcodes.ACC_SYNTHETIC) != 0) continue;
|
||||
if (!checked.test(method.name) || IGNORED.contains(method.name)) continue;
|
||||
if (!covered.test(method)) missing.add(method.name + method.desc);
|
||||
}
|
||||
missing.sort(null);
|
||||
return missing;
|
||||
}
|
||||
|
||||
private static boolean calls(MethodNode method, Predicate<String> owner, String... names) {
|
||||
final Set<String> wanted = Set.of(names);
|
||||
for (AbstractInsnNode insn : method.instructions) {
|
||||
if (insn instanceof MethodInsnNode call && owner.test(call.owner) && (wanted.isEmpty() || wanted.contains(call.name))) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean reachesCapture(String owner, MethodNode root) {
|
||||
final String key = owner + '.' + root.name + root.desc;
|
||||
if (reaching.contains(key)) return true;
|
||||
if (notReaching.contains(key)) return false;
|
||||
final Set<String> visited = new HashSet<>();
|
||||
if (search(owner, root, visited)) {
|
||||
reaching.add(key);
|
||||
return true;
|
||||
}
|
||||
notReaching.addAll(visited);
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean search(String owner, MethodNode method, Set<String> visited) {
|
||||
final String key = owner + '.' + method.name + method.desc;
|
||||
if (reaching.contains(key)) return true;
|
||||
if (notReaching.contains(key) || !visited.add(key)) return false;
|
||||
for (AbstractInsnNode insn : method.instructions) {
|
||||
if (!(insn instanceof MethodInsnNode call)) continue;
|
||||
if (call.owner.equals(CAPTURE)) {
|
||||
if (counted.contains(call.name)) return true;
|
||||
continue;
|
||||
}
|
||||
if (!call.owner.startsWith(PACKAGE)) continue;
|
||||
final String[] declaring = new String[1];
|
||||
final MethodNode target = resolve(call.owner, call.name, call.desc, declaring);
|
||||
if (target != null && search(declaring[0], target, visited)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private MethodNode resolve(String owner, String name, String desc, String[] declaring) {
|
||||
for (String type = owner; type != null && type.startsWith(PACKAGE); ) {
|
||||
final ClassNode node = load(type);
|
||||
for (MethodNode method : node.methods) {
|
||||
if (method.name.equals(name) && method.desc.equals(desc)) {
|
||||
if (method.instructions.size() == 0) return null;
|
||||
declaring[0] = type;
|
||||
return method;
|
||||
}
|
||||
}
|
||||
type = node.superName;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ClassNode load(String internalName) {
|
||||
return classes.computeIfAbsent(internalName, name -> {
|
||||
try (InputStream in = GLSM_ExitCaptureCoverage_UnitTest.class.getClassLoader().getResourceAsStream(name + ".class")) {
|
||||
if (in == null) throw new IllegalStateException("Missing class file for " + name);
|
||||
final ClassNode node = new ClassNode();
|
||||
new ClassReader(in).accept(node, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||
return node;
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaBooleanLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.VanillaStateLayer;
|
||||
import com.gtnewhorizons.angelica.glsm.states.BlendState;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector4f;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@GLCoreTest
|
||||
class GLSM_ExitState_GLTest {
|
||||
private final Matrix4f modelView = new Matrix4f();
|
||||
private final Matrix4f projection = new Matrix4f();
|
||||
private final Matrix4f texture = new Matrix4f();
|
||||
private final Vector4f overlay = new Vector4f();
|
||||
private final Vector4f shaderColor = new Vector4f();
|
||||
private int outer;
|
||||
private int clientActiveUnit;
|
||||
private float lightmapX, lightmapY;
|
||||
private ImmediateExtendedAttribHandler extendedHandler;
|
||||
private DeferredBlendHandler blendHandler;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
final GLContextState context = GLStateManager.ctx();
|
||||
modelView.set(context.modelViewMatrix);
|
||||
projection.set(context.projectionMatrix);
|
||||
texture.set(context.textures.getTextureUnitMatrix(0));
|
||||
overlay.set(context.overlayR, context.overlayG, context.overlayB, context.overlayA);
|
||||
shaderColor.set(context.shaderColorR, context.shaderColorG, context.shaderColorB, context.shaderColorA);
|
||||
lightmapX = context.lastBrightnessX;
|
||||
lightmapY = context.lastBrightnessY;
|
||||
extendedHandler = GLSMHooks.immediateExtendedHandler;
|
||||
blendHandler = GLSMHooks.blendHandler;
|
||||
clientActiveUnit = context.clientActiveTextureUnit;
|
||||
outer = GLStateManager.pushState(StateSet.forMask(GL11.GL_ALL_ATTRIB_BITS));
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLStateManager.setTextureMatrix(0, new Matrix4f());
|
||||
GLStateManager.glColor4f(1, 1, 1, 1);
|
||||
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
|
||||
GLStateManager.disableColorMaterial();
|
||||
GLStateManager.enableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.glPolygonOffset(2, 3);
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
GLStateManager.cancelExitCapture();
|
||||
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0 + clientActiveUnit);
|
||||
GLSMHooks.blendHandler = blendHandler;
|
||||
GLSMHooks.immediateExtendedHandler = extendedHandler;
|
||||
GLStateManager.getBlendMode().setVanillaLayer(null);
|
||||
GLStateManager.getBlendState().setVanillaLayer(null);
|
||||
GLStateManager.setModelViewMatrix(modelView);
|
||||
GLStateManager.setProjectionMatrix(projection);
|
||||
GLStateManager.setTextureMatrix(0, texture);
|
||||
GLStateManager.setOverlayColor(overlay.x, overlay.y, overlay.z, overlay.w);
|
||||
GLStateManager.setShaderColor(shaderColor.x, shaderColor.y, shaderColor.z, shaderColor.w);
|
||||
GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, lightmapX, lightmapY);
|
||||
GLStateManager.popStateTo(outer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void unchangedWritesStillApplyAndLeaveOtherPolygonStateAlone() {
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
GLStateManager.enableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.glCullFace(GL11.GL_FRONT);
|
||||
GLStateManager.glEnable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.glPolygonOffset(7, 11);
|
||||
assertTrue(GLStateManager.canApplyExitState(state));
|
||||
GLStateManager.applyExitState(state);
|
||||
assertTrue(GLStateManager.getCullState().isEnabled());
|
||||
assertEquals(GL11.GL_BACK, GLStateManager.getPolygonState().getCullFaceMode());
|
||||
assertTrue(GLStateManager.getPolygonOffsetFillState().isEnabled());
|
||||
assertEquals(7, GLStateManager.getPolygonState().getOffsetFactor());
|
||||
assertEquals(11, GLStateManager.getPolygonState().getOffsetUnits());
|
||||
}
|
||||
|
||||
@Test
|
||||
void attribPopUndoesTheWritesItRestores() {
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
GLStateManager.glEnable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
assertEquals(ExitState.POLYGON_OFFSET_ENABLE_WRITTEN, GLStateManager.getExitPolygonOffsetWrites());
|
||||
GLStateManager.glPushAttrib(GL11.GL_POLYGON_BIT | GL11.GL_CURRENT_BIT);
|
||||
GLStateManager.glPolygonOffset(5, 6);
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.glColor4f(0, 0, 0, 1);
|
||||
assertEquals(3, GLStateManager.getExitPolygonOffsetWrites());
|
||||
GLStateManager.glPopAttrib();
|
||||
assertEquals(ExitState.POLYGON_OFFSET_ENABLE_WRITTEN, GLStateManager.getExitPolygonOffsetWrites());
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.glColor4f(0.25F, 0.5F, 0.75F, 1);
|
||||
GLStateManager.glPolygonOffset(8, 9);
|
||||
GLStateManager.glDisable(GL11.GL_POLYGON_OFFSET_FILL);
|
||||
GLStateManager.applyExitState(state);
|
||||
assertFalse(GLStateManager.getCullState().isEnabled());
|
||||
assertEquals(0.25F, GLStateManager.getColor().getRed());
|
||||
assertEquals(8, GLStateManager.getPolygonState().getOffsetFactor());
|
||||
assertTrue(GLStateManager.getPolygonOffsetFillState().isEnabled());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {false, true})
|
||||
void drawsDependOnTheAlphaFunctionOnlyWhenTheyReadItFromState(boolean materialFromState) {
|
||||
GLStateManager.enableAlphaTest();
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
GLStateManager.glColor4f(1, 1, 1, 1);
|
||||
GLStateManager.requireExitDrawState(materialFromState);
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
GLStateManager.glColor4f(0, 0, 0, 1);
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.6F);
|
||||
assertEquals(!materialFromState, GLStateManager.canApplyExitState(state));
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
|
||||
assertTrue(GLStateManager.canApplyExitState(state));
|
||||
GLStateManager.disableCull();
|
||||
assertFalse(GLStateManager.canApplyExitState(state));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {0, 1, 2})
|
||||
void blendAndAlphaFunctionsMatterOnlyToDrawsWithThemEnabled(int mode) {
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
GLStateManager.requireExitDrawState(true);
|
||||
if (mode != 0) {
|
||||
if (mode == 2) {
|
||||
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
|
||||
}
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.requireExitDrawState(true);
|
||||
}
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
assertEquals(mode != 1, GLStateManager.canApplyExitState(state));
|
||||
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.5F);
|
||||
assertEquals(mode != 1, GLStateManager.canApplyExitState(state));
|
||||
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1F);
|
||||
assertTrue(GLStateManager.canApplyExitState(state));
|
||||
}
|
||||
|
||||
@Test
|
||||
void settingTheClientTextureUnitToItsCurrentValueIsStillRecorded() {
|
||||
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
GLStateManager.glPushAttrib(GL11.GL_TEXTURE_BIT);
|
||||
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glPopAttrib();
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
|
||||
assertTrue(GLStateManager.canApplyExitState(state));
|
||||
GLStateManager.applyExitState(state);
|
||||
assertEquals(0, GLStateManager.getClientActiveTextureUnit());
|
||||
|
||||
GLStateManager.beginExitCapture(state);
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.applyExitState(state);
|
||||
assertEquals(1, GLStateManager.getClientActiveTextureUnit());
|
||||
}
|
||||
|
||||
@Test
|
||||
void drawAfterAnAttribPopDependsOnTheOriginalCullState() {
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.requireExitDrawState(false);
|
||||
GLStateManager.glPopAttrib();
|
||||
GLStateManager.requireExitDrawState(false);
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
GLStateManager.disableCull();
|
||||
assertFalse(GLStateManager.canApplyExitState(state));
|
||||
}
|
||||
|
||||
@Test
|
||||
void lightingAndShaderStateAreNotDrawDependencies() {
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
assertFalse(GLStateManager.isExitLightingWritten());
|
||||
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
|
||||
GLStateManager.enableLighting();
|
||||
assertTrue(GLStateManager.isExitLightingWritten());
|
||||
GLStateManager.glPopAttrib();
|
||||
assertFalse(GLStateManager.isExitLightingWritten());
|
||||
GLStateManager.requireExitDrawState(false);
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
|
||||
GLStateManager.getLightingState().setEnabled(!GLStateManager.getLightingState().isEnabled());
|
||||
GLStateManager.getNormalizeState().setEnabled(!GLStateManager.getNormalizeState().isEnabled());
|
||||
GLStateManager.getRescaleNormalState().setEnabled(!GLStateManager.getRescaleNormalState().isEnabled());
|
||||
GLStateManager.getLightStates()[0].setEnabled(!GLStateManager.getLightStates()[0].isEnabled());
|
||||
GLStateManager.getLightStates()[1].setEnabled(!GLStateManager.getLightStates()[1].isEnabled());
|
||||
GLStateManager.glShadeModel(GLStateManager.getShadeModelState().getValue() == GL11.GL_FLAT ? GL11.GL_SMOOTH : GL11.GL_FLAT);
|
||||
GLStateManager.setShaderColor(0.1F, 0.2F, 0.3F, 0.4F);
|
||||
GLStateManager.glNormal3f(0.5F, 0.25F, 0.75F);
|
||||
GLStateManager.glTexCoord2f(0.75F, 0.25F);
|
||||
assertTrue(GLStateManager.canApplyExitState(state));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {0, 1, 2, 3})
|
||||
void tessellatorDrawsDependOnlyOnTheCurrentValuesTheyFillIn(int mode) {
|
||||
final Tessellator tess = new Tessellator();
|
||||
tess.rawBuffer = new int[32];
|
||||
tess.vertexCount = 4;
|
||||
tess.drawMode = GL11.GL_QUADS;
|
||||
tess.hasNormals = mode != 1 && mode != 3;
|
||||
tess.hasTexture = mode != 2;
|
||||
tess.hasColor = tess.hasBrightness = true;
|
||||
GLSMHooks.immediateExtendedHandler = mode == 3 ? new FaceNormals() : null;
|
||||
GLStateManager.glNormal3f(0, 1, 0);
|
||||
GLStateManager.glTexCoord2f(0.25F, 0.5F);
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
TessellatorStreamingDrawer.fillMissingAttributes(tess);
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
if (mode == 1) assertEquals(0x7F00, tess.rawBuffer[6]);
|
||||
if (mode == 2) assertEquals(Float.floatToRawIntBits(0.25F), tess.rawBuffer[3]);
|
||||
if (mode == 3) assertEquals(0x7F, tess.rawBuffer[6]);
|
||||
GLStateManager.glNormal3f(1, 0, 0);
|
||||
GLStateManager.glTexCoord2f(0.75F, 0.5F);
|
||||
assertEquals(mode == 0 || mode == 3, GLStateManager.canApplyExitState(state));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {false, true})
|
||||
void deletingTheBoundTextureBlocksApplyingEvenIfTheCaptureUnboundIt(boolean explicitlyUnbound) {
|
||||
final int id = GLStateManager.glGenTextures();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
if (explicitlyUnbound) GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
assertTrue(GLStateManager.canApplyExitState(state));
|
||||
GLStateManager.glDeleteTextures(id);
|
||||
assertFalse(GLStateManager.canApplyExitState(state));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {false, true})
|
||||
void textureMatrixMustMatchOnlyWhenTheCaptureMultipliedOntoIt(boolean absolute) {
|
||||
GLStateManager.setTextureMatrix(0, new Matrix4f().translation(1, 2, 0));
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLStateManager.glPushMatrix();
|
||||
if (absolute) GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glTranslatef(3, 4, 0);
|
||||
GLStateManager.requireExitDrawState(false);
|
||||
GLStateManager.glPopMatrix();
|
||||
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
final Matrix4f mainTexture = new Matrix4f().translation(8, 9, 0);
|
||||
GLStateManager.setTextureMatrix(0, mainTexture);
|
||||
assertEquals(absolute, GLStateManager.canApplyExitState(state));
|
||||
if (absolute) {
|
||||
GLStateManager.applyExitState(state);
|
||||
assertEquals(mainTexture, GLStateManager.getTextures().getTextureUnitMatrix(0));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void changedOrUnbalancedViewMatricesReject() {
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
GLStateManager.glTranslatef(1, 0, 0);
|
||||
assertFalse(GLStateManager.finishExitCapture(state));
|
||||
GLStateManager.setModelViewMatrix(modelView);
|
||||
|
||||
GLStateManager.beginExitCapture(state);
|
||||
GLStateManager.glPushMatrix();
|
||||
assertFalse(GLStateManager.finishExitCapture(state));
|
||||
GLStateManager.glPopMatrix();
|
||||
|
||||
GLStateManager.beginExitCapture(state);
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glTranslatef(2, 3, 4);
|
||||
GLStateManager.glPopMatrix();
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
|
||||
GLStateManager.beginExitCapture(state);
|
||||
GLStateManager.glPushMatrix();
|
||||
GLStateManager.glLoadIdentity();
|
||||
GLStateManager.glPopMatrix();
|
||||
assertFalse(GLStateManager.finishExitCapture(state));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {0, 1, 2, 3, 4, 5})
|
||||
void untrackedCallsAndStateQueriesReject(int operation) {
|
||||
final ExitState state = new ExitState();
|
||||
final int program = GLStateManager.getActiveProgram();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
switch (operation) {
|
||||
case 0 -> GLStateManager.glIsEnabled(GL11.GL_CULL_FACE);
|
||||
case 1 -> GLStateManager.glGetInteger(GL11.GL_DEPTH_FUNC);
|
||||
case 2 -> GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
case 3 -> GLStateManager.glUseProgram(program);
|
||||
case 4 -> GLStateManager.glBlendEquation(GL14.GL_FUNC_ADD);
|
||||
case 5 -> GLStateManager.fogColor((float) GLStateManager.getFogState().getFogColor().x,
|
||||
(float) GLStateManager.getFogState().getFogColor().y, (float) GLStateManager.getFogState().getFogColor().z,
|
||||
GLStateManager.getFogState().getFogAlpha());
|
||||
default -> throw new AssertionError();
|
||||
}
|
||||
assertFalse(GLStateManager.finishExitCapture(state));
|
||||
}
|
||||
|
||||
@Test
|
||||
void suspendedCallsAreNeitherRecordedNorRejected() {
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
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) {}
|
||||
}
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.TextureUtilHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@GLCoreTest
|
||||
class GLSM_ExitTextureFilter_GLTest {
|
||||
private int outer, texture, otherTexture, savedMin, savedMag;
|
||||
private float savedAniso;
|
||||
private boolean failOperation;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
outer = GLStateManager.pushState(StateSet.forMask(GL11.GL_TEXTURE_BIT));
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
texture = GLStateManager.glGenTextures();
|
||||
otherTexture = GLStateManager.glGenTextures();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, otherTexture);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR_MIPMAP_LINEAR);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
GLStateManager.cancelExitCapture();
|
||||
TextureUtilHooks.clearSavedFilterTexture();
|
||||
GLStateManager.glDeleteTextures(texture);
|
||||
GLStateManager.glDeleteTextures(otherTexture);
|
||||
GLStateManager.popStateTo(outer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nearestFilterSaveAndRestoreIsAllowedAndApplyLeavesFiltersAlone() {
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
assertTrue(save(false, false, 1));
|
||||
assertTrue(GLStateManager.isExitCaptureActive());
|
||||
GLStateManager.requireExitTextureFilter(otherTexture, false);
|
||||
GLStateManager.requireExitTextureFilter(texture, true);
|
||||
restore();
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
assertFilters(GL11.GL_LINEAR_MIPMAP_LINEAR, GL11.GL_LINEAR);
|
||||
assertEquals(0, GLStateManager.ctx().savedFilterTextureId);
|
||||
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
assertTrue(GLStateManager.canApplyExitState(state));
|
||||
GLStateManager.applyExitState(state);
|
||||
assertFilters(GL11.GL_LINEAR, GL11.GL_NEAREST);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {false, true})
|
||||
void drawingTheTemporarilyFilteredTextureRequiresNearestFiltering(boolean nearest) {
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
save(false, false, 1);
|
||||
GLStateManager.requireExitTextureFilter(texture, nearest);
|
||||
restore();
|
||||
assertEquals(nearest, GLStateManager.finishExitCapture(state));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {0, 1, 2})
|
||||
void savingAnythingButNearestFilteringRejects(int request) {
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
assertFalse(save(request == 0, request == 1, request == 2 ? 2 : 1));
|
||||
restore();
|
||||
assertFalse(GLStateManager.finishExitCapture(state));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {0, 1, 2})
|
||||
void textureParameterReadsAndWritesStillReject(int operation) {
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
save(false, false, 1);
|
||||
switch (operation) {
|
||||
case 0 -> GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
|
||||
case 1 -> GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
case 2 -> GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
default -> throw new AssertionError();
|
||||
}
|
||||
restore();
|
||||
assertFalse(GLStateManager.finishExitCapture(state));
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingRestoreRejectsAndDoesNotAffectTheNextCapture() {
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
save(false, false, 1);
|
||||
assertFalse(GLStateManager.finishExitCapture(state));
|
||||
restore();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
}
|
||||
|
||||
@Test
|
||||
void secondSaveBeforeRestoreRejects() {
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
assertTrue(save(false, false, 1));
|
||||
assertFalse(save(false, false, 1));
|
||||
restore();
|
||||
assertFalse(GLStateManager.finishExitCapture(state));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {false, true})
|
||||
void restoreMustUseTheSameTextureAndOriginalParameters(boolean wrongTexture) {
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
save(false, false, 1);
|
||||
if (wrongTexture) GLStateManager.ctx().savedFilterTextureId = otherTexture;
|
||||
else savedMin = GL11.GL_NEAREST;
|
||||
restore();
|
||||
assertFalse(GLStateManager.finishExitCapture(state));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {false, true})
|
||||
void failedSaveOrRestoreRejectsAndTheNextCaptureStillWorks(boolean restoring) {
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
if (restoring) save(false, false, 1);
|
||||
failOperation = true;
|
||||
assertThrows(IllegalStateException.class, () -> {
|
||||
if (restoring) restore();
|
||||
else save(false, false, 1);
|
||||
});
|
||||
assertFalse(GLStateManager.finishExitCapture(state));
|
||||
TextureUtilHooks.clearSavedFilterTexture();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
assertTrue(GLStateManager.isExitCaptureActive());
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
}
|
||||
|
||||
@Test
|
||||
void openFilterSaveBlocksCapturingAndApplying() {
|
||||
final ExitState state = new ExitState();
|
||||
GLStateManager.beginExitCapture(state);
|
||||
assertTrue(GLStateManager.finishExitCapture(state));
|
||||
save(false, false, 1);
|
||||
assertFalse(GLStateManager.canApplyExitState(state));
|
||||
final ExitState nested = new ExitState();
|
||||
GLStateManager.beginExitCapture(nested);
|
||||
assertFalse(GLStateManager.finishExitCapture(nested));
|
||||
restore();
|
||||
assertTrue(GLStateManager.canApplyExitState(state));
|
||||
}
|
||||
|
||||
private boolean save(boolean blurred, boolean mipmap, float aniso) {
|
||||
final boolean captured = TextureUtilHooks.beginFilterSave(blurred, mipmap, aniso);
|
||||
try {
|
||||
savedMin = GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
|
||||
savedMag = GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER);
|
||||
savedAniso = GLStateManager.glGetTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
|
||||
blurred ? (mipmap ? GL11.GL_LINEAR_MIPMAP_LINEAR : GL11.GL_LINEAR)
|
||||
: (mipmap ? GL11.GL_NEAREST_MIPMAP_LINEAR : GL11.GL_NEAREST));
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, blurred ? GL11.GL_LINEAR : GL11.GL_NEAREST);
|
||||
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
|
||||
if (failOperation) throw new IllegalStateException("filter save failed");
|
||||
} catch (RuntimeException | Error failure) {
|
||||
TextureUtilHooks.filterFailed(captured);
|
||||
throw failure;
|
||||
} finally {
|
||||
TextureUtilHooks.endFilterSave(captured);
|
||||
}
|
||||
return captured;
|
||||
}
|
||||
|
||||
private void restore() {
|
||||
final boolean captured = TextureUtilHooks.beginFilterRestore();
|
||||
try {
|
||||
TextureUtilHooks.restoreFilter(savedMin, savedMag);
|
||||
TextureUtilHooks.restoreAniso(savedAniso);
|
||||
if (failOperation) throw new IllegalStateException("filter restore failed");
|
||||
} catch (RuntimeException | Error failure) {
|
||||
TextureUtilHooks.filterFailed(captured);
|
||||
throw failure;
|
||||
} finally {
|
||||
TextureUtilHooks.endFilterRestore(captured);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertFilters(int min, int mag) {
|
||||
final var info = TextureInfoCache.INSTANCE.getInfo(texture);
|
||||
assertEquals(min, info.getMinFilter());
|
||||
assertEquals(mag, info.getMagFilter());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -10,6 +11,7 @@ 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;
|
||||
@@ -115,37 +117,88 @@ 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);
|
||||
|
||||
-4
@@ -202,7 +202,6 @@ 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 {
|
||||
@@ -228,7 +227,6 @@ public class GLSM_SharedDrawableContext_GLTest {
|
||||
GLStateManager.glDeleteBuffers(vbo);
|
||||
} finally {
|
||||
sm.deactivate();
|
||||
if (!wasEnabled) ShaderManager.disable();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -273,7 +271,6 @@ 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 {
|
||||
@@ -283,7 +280,6 @@ 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,7 +73,6 @@ 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);
|
||||
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
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,6 +16,7 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -82,8 +83,11 @@ 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,6 +2,7 @@ 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;
|
||||
@@ -27,6 +28,7 @@ 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,6 +179,10 @@ 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++;
|
||||
@@ -196,7 +200,7 @@ public final class FfpFixture {
|
||||
}
|
||||
final int index = firstMismatch;
|
||||
final int differing = mismatches;
|
||||
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]));
|
||||
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]));
|
||||
}
|
||||
|
||||
private static boolean withinTolerance(int a, int b, int tolerance) {
|
||||
@@ -233,7 +237,6 @@ 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);
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
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.gtnewhorizon.gtnhlib.reflect.Fields;
|
||||
import com.gtnewhorizon.gtnhlib.reflect.Fields.LookupType;
|
||||
import com.gtnewhorizons.angelica.glsm.GlsmRedirectAgent;
|
||||
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,7 +59,13 @@ public final class Reflect {
|
||||
}
|
||||
|
||||
public static <F> void setStaticFinal(Class<?> owner, String name, Class<F> type, F value) {
|
||||
Fields.ofClass(owner).getField(LookupType.DECLARED_IN_HIERARCHY, name, type).setValue(null, 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);
|
||||
}
|
||||
|
||||
public static void setDeclared(Class<?> owner, Object target, String name, Object value) {
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
# ALSO_JFR=1 - with a .tracy output, also record a JFR alongside it (timings inflated, correlation only).
|
||||
# ASPROF_OPTS - comma separated async-profiler agent opts, passed through when a JFR is recorded.
|
||||
#
|
||||
# FLYBY_FULLSCREEN=0 - keep options.txt's window mode. Default: fullscreen:true for the run (avoids vsync pacing), restored on exit.
|
||||
#
|
||||
# The output extension selects the profiler: .tracy for Tracy, .jfr for an in-process async-profiler
|
||||
# JFR that starts near the end of warmup and stops when the measured run ends.
|
||||
#
|
||||
@@ -32,6 +34,9 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source=flyby-lib.sh
|
||||
. "$(dirname "$0")/flyby-lib.sh"
|
||||
|
||||
OUT="${1:?usage: flyby-capture.sh <output.tracy|output.jfr> [route] [length] [extra gradle args...]}"
|
||||
ROUTE="${2:-straight}"
|
||||
LENGTH="${3:-0}"
|
||||
@@ -148,12 +153,40 @@ if [ "$MODE" = "tracy" ]; then
|
||||
if kill -0 "$CAPTURE_PID" 2>/dev/null; then
|
||||
echo "==> stopping capture"
|
||||
kill -INT "$CAPTURE_PID" 2>/dev/null || true
|
||||
local waited=0
|
||||
while kill -0 "$CAPTURE_PID" 2>/dev/null && { { [ -e "$OUT" ] && [ "$waited" -lt 600 ]; } || [ "$waited" -lt 120 ]; }; do
|
||||
sleep 1
|
||||
waited=$((waited + 1))
|
||||
done
|
||||
if kill -0 "$CAPTURE_PID" 2>/dev/null; then
|
||||
echo "==> capture did not stop after ${waited}s (limit 120s before output exists, 600s after), killing it" >&2
|
||||
kill -9 "$CAPTURE_PID" 2>/dev/null || true
|
||||
fi
|
||||
wait "$CAPTURE_PID" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
fi
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
OPTIONS_TXT="$ROOT/run/client/options.txt"
|
||||
FS_DIR="$(mktemp -d)"
|
||||
|
||||
on_exit() {
|
||||
if [ -n "$CAPTURE_PID" ]; then
|
||||
cleanup
|
||||
fi
|
||||
if [ "$FULLSCREEN" != 0 ]; then
|
||||
flyby_fullscreen_restore "$OPTIONS_TXT" "$FS_DIR/options.txt.bak" || true
|
||||
fi
|
||||
rm -rf "$FS_DIR"
|
||||
}
|
||||
FULLSCREEN="${FLYBY_FULLSCREEN:-1}"
|
||||
if [ "$FULLSCREEN" != 0 ]; then
|
||||
flyby_fullscreen_on "$OPTIONS_TXT" "$FS_DIR/options.txt.bak"
|
||||
fi
|
||||
trap on_exit EXIT
|
||||
|
||||
echo "==> launching game (route=$ROUTE warmup=$WARMUP length=${LENGTH:-default} scene=${SCENE_ABS:-none})"
|
||||
if [ -n "$JFR_OUT" ]; then
|
||||
echo "==> recording JFR -> $JFR_ABS"
|
||||
@@ -178,7 +211,6 @@ fi
|
||||
echo "==> game exited, finalising capture"
|
||||
if [ "$MODE" = "tracy" ]; then
|
||||
cleanup
|
||||
trap - EXIT
|
||||
fi
|
||||
|
||||
if [ "$MODE" = "jfr" ]; then
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env bash
|
||||
# Shared file helpers for the flyby-*.sh scripts. Sourced, not executed: . "$(dirname "$0")/flyby-lib.sh"
|
||||
#
|
||||
# flyby_set_key <file> <key> <value> [sep] replace the first line starting with <key><sep> (default sep ":"), else append; creates the file
|
||||
# flyby_copy <src> <dst> copy bytes and verify with a compare
|
||||
# flyby_backup_file <file> <backup> copy to <backup>; if <file> is absent, remove any stale <backup> (records absence)
|
||||
# flyby_restore_file <backup> <file> flyby_backup_file with swapped arguments; prints one error line and returns 1 on failure.
|
||||
# Trap callers append '|| true'. Removes <file> if <backup> is absent: only call it after the backup was made.
|
||||
# flyby_fullscreen_on <options.txt> <backup> backs up, sets fullscreen:true
|
||||
# flyby_fullscreen_restore <options.txt> <backup> restores the backup; idempotent, call only after flyby_fullscreen_on
|
||||
# flyby_fresh_dir <dir> remove <dir> and recreate it empty
|
||||
# flyby_collect <run_dir> <variant_dir> <log>... copy <run_dir>/logs/<log> and <run_dir>/screenshots/flyby-shot-*.png into <variant_dir>
|
||||
# flyby_crash_list <dir> sorted names in <dir>, nothing if it is absent
|
||||
# flyby_new_crashes <dir> <before> <after> write the current crash list to <after>, print how many names are not in <before>
|
||||
|
||||
flyby_set_key() {
|
||||
python3 - "$1" "$2" "$3" "${4:-:}" <<'PY'
|
||||
import os, re, sys
|
||||
path, key, value, sep = sys.argv[1:5]
|
||||
text = ""
|
||||
if os.path.isfile(path):
|
||||
with open(path, newline="") as f:
|
||||
text = f.read()
|
||||
pat = re.compile(r"^" + re.escape(key + sep) + r"[^\r\n]*", re.M)
|
||||
line = key + sep + value
|
||||
if pat.search(text):
|
||||
text = pat.sub(lambda _: line, text, count=1)
|
||||
else:
|
||||
if text and not text.endswith("\n"):
|
||||
text += "\n"
|
||||
text += line + "\n"
|
||||
with open(path, "w", newline="") as f:
|
||||
f.write(text)
|
||||
PY
|
||||
}
|
||||
|
||||
flyby_copy() {
|
||||
python3 - "$1" "$2" <<'PY'
|
||||
import filecmp, shutil, sys
|
||||
src, dst = sys.argv[1:3]
|
||||
shutil.copyfile(src, dst)
|
||||
if not filecmp.cmp(src, dst, shallow=False):
|
||||
sys.exit("error: copy differs: " + dst)
|
||||
PY
|
||||
}
|
||||
|
||||
flyby_backup_file() {
|
||||
python3 - "$1" "$2" <<'PY'
|
||||
import filecmp, os, shutil, sys
|
||||
path, backup = sys.argv[1:3]
|
||||
if os.path.isfile(path):
|
||||
shutil.copyfile(path, backup)
|
||||
if not filecmp.cmp(path, backup, shallow=False):
|
||||
sys.exit("error: backup differs: " + backup)
|
||||
elif os.path.isfile(backup):
|
||||
os.remove(backup)
|
||||
PY
|
||||
}
|
||||
|
||||
flyby_restore_file() {
|
||||
flyby_backup_file "$1" "$2" || { echo "error: restore failed or differs: $2" >&2; return 1; }
|
||||
}
|
||||
|
||||
flyby_fullscreen_on() {
|
||||
flyby_backup_file "$1" "$2"
|
||||
flyby_set_key "$1" fullscreen true :
|
||||
}
|
||||
|
||||
flyby_fullscreen_restore() {
|
||||
flyby_restore_file "$2" "$1"
|
||||
}
|
||||
|
||||
flyby_fresh_dir() {
|
||||
python3 -c 'import shutil, sys; shutil.rmtree(sys.argv[1], ignore_errors=True)' "$1"
|
||||
mkdir -p "$1"
|
||||
}
|
||||
|
||||
flyby_collect() {
|
||||
python3 - "$@" <<'PY'
|
||||
import glob, os, shutil, sys
|
||||
run, vdir = sys.argv[1:3]
|
||||
for name in sys.argv[3:]:
|
||||
src = os.path.join(run, "logs", name)
|
||||
if os.path.isfile(src):
|
||||
shutil.copyfile(src, os.path.join(vdir, name))
|
||||
for src in glob.glob(os.path.join(run, "screenshots", "flyby-shot-*.png")):
|
||||
shutil.copyfile(src, os.path.join(vdir, os.path.basename(src)))
|
||||
PY
|
||||
}
|
||||
|
||||
flyby_crash_list() {
|
||||
if [ -d "$1" ]; then
|
||||
ls -1 "$1" | sort
|
||||
fi
|
||||
}
|
||||
|
||||
flyby_new_crashes() {
|
||||
flyby_crash_list "$1" >| "$3"
|
||||
comm -13 "$2" "$3" | wc -l | tr -d ' '
|
||||
}
|
||||
Executable
+480
@@ -0,0 +1,480 @@
|
||||
#!/usr/bin/env bash
|
||||
# Unattended flyby run of a test Angelica jar inside the real Prism GTNH instance, one launch per variant
|
||||
#
|
||||
# Usage: scripts/flyby-prism.sh <name> <variant>...
|
||||
# Variants: sdl-off | sdl-pack | gl-off | gl-pack (SDL-GPU vs OpenGL x shader pack off vs on)
|
||||
# gl-pack writes <pack>.txt from the pack's profile.HIGH first (never Ultra on OpenGL) and fails before launching without it.
|
||||
# Output: tmp/prism/<name>/<variant>/ (fml-client-latest.log, flyby-shot-*.png, console from the checker).
|
||||
# Prints one PASS/FAIL line per variant; exits non-zero if any fails. A run that never reaches the world
|
||||
# (no 'Flyby: world saving is off' in the log) is FAILED and stops the batch.
|
||||
# Needs a hands-off machine: the game must keep focus (waitForFocus) and runs fullscreen.
|
||||
#
|
||||
# PRISM_INSTANCE - instance id (default: GTNH-daily-2026-10-01+766-mmcprism-java17-26).
|
||||
# PRISM_BIN - launcher binary (default: /Applications/Prism Launcher.app/Contents/MacOS/prismlauncher).
|
||||
# PRISM_TIMEOUT - seconds before a variant's game is killed (default: 900).
|
||||
# PRISM_KEEP_JAR - 1 leaves the test jar installed and the old angelica jar(s) disabled after the script ends (default: 0).
|
||||
# PRISM_WORLD - save for QuickPlay (default: FlybyGTNH). No spaces.
|
||||
# PRISM_ENTRIES - id=status,... passed as -Dangelica.flyby.debugEntries on every variant and graded with
|
||||
# --require-renderers (default: all nine world renderers inOverlay).
|
||||
#
|
||||
# Sets fullscreen:true in the instance options.txt per variant (avoids vsync pacing).
|
||||
# Restores instance.cfg, options.txt, config/shaders.properties and the pack option file on exit (cmp-verified).
|
||||
# scripts/flyby-prism.sh --helper <jvmargs|profile> ... runs one pure step in isolation.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source=flyby-lib.sh
|
||||
. "$(dirname "$0")/flyby-lib.sh"
|
||||
|
||||
HELPER="$(cat <<'PY'
|
||||
import re, sys
|
||||
|
||||
|
||||
def read(path):
|
||||
with open(path, newline="") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def write(path, text):
|
||||
with open(path, "w", newline="") as f:
|
||||
f.write(text)
|
||||
|
||||
|
||||
def unquote(raw):
|
||||
if len(raw) >= 2 and raw[0] == '"' and raw[-1] == '"':
|
||||
return True, raw[1:-1].replace('\\"', '"').replace("\\\\", "\\")
|
||||
return False, raw
|
||||
|
||||
|
||||
def requote(value, quoted):
|
||||
if quoted or re.search(r"[\s,;\"]", value):
|
||||
return '"' + value.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
||||
return value
|
||||
|
||||
|
||||
def key_of(token):
|
||||
return token.split("=", 1)[0]
|
||||
|
||||
|
||||
def jvmargs(cfg, original, backend, extra):
|
||||
src = read(original)
|
||||
m = re.search(r"^JvmArgs=([^\r\n]*)", src, re.M)
|
||||
quoted, value = unquote(m.group(1)) if m else (True, "")
|
||||
new = ["-Dangelica.sdlgpu.enable=" + backend] + extra
|
||||
for t in new:
|
||||
if re.search(r"\s", t) or '"' in t:
|
||||
raise SystemExit("error: jvm arg contains whitespace or a quote: " + repr(t))
|
||||
drop = {key_of(t) for t in new}
|
||||
kept = [t for t in value.split() if key_of(t) not in drop]
|
||||
line = "JvmArgs=" + requote(" ".join(kept + new), quoted)
|
||||
text = read(cfg)
|
||||
if re.search(r"^JvmArgs=[^\r\n]*", text, re.M):
|
||||
text = re.sub(r"^JvmArgs=[^\r\n]*", lambda _: line, text, count=1, flags=re.M)
|
||||
elif re.search(r"^\[General\][^\r\n]*", text, re.M):
|
||||
text = re.sub(r"^(\[General\][^\r\n]*)(\r?\n)", lambda g: g.group(1) + g.group(2) + line + g.group(2), text, count=1, flags=re.M)
|
||||
else:
|
||||
text = line + "\n" + text
|
||||
if re.search(r"^OverrideJavaArgs=[^\r\n]*", text, re.M):
|
||||
text = re.sub(r"^OverrideJavaArgs=[^\r\n]*", "OverrideJavaArgs=true", text, count=1, flags=re.M)
|
||||
else:
|
||||
text = re.sub(r"^(JvmArgs=[^\r\n]*)(\r?\n|$)", lambda g: g.group(1) + (g.group(2) or "\n") + "OverrideJavaArgs=true" + g.group(2), text, count=1, flags=re.M)
|
||||
write(cfg, text)
|
||||
print(line)
|
||||
|
||||
|
||||
def logical_lines(text):
|
||||
out = []
|
||||
buf = ""
|
||||
for raw in text.splitlines():
|
||||
if buf:
|
||||
piece = raw.strip()
|
||||
else:
|
||||
piece = raw.rstrip()
|
||||
if piece.endswith("\\"):
|
||||
buf += piece[:-1] + " "
|
||||
continue
|
||||
out.append(buf + piece)
|
||||
buf = ""
|
||||
if buf:
|
||||
out.append(buf)
|
||||
return out
|
||||
|
||||
|
||||
def profile_tokens(defs, name, seen):
|
||||
if name in seen:
|
||||
raise SystemExit("error: profile cycle at " + name)
|
||||
if name not in defs:
|
||||
raise SystemExit("error: profile." + name + " not defined")
|
||||
out = []
|
||||
for tok in defs[name].split():
|
||||
if tok.startswith("profile."):
|
||||
out.extend(profile_tokens(defs, tok[len("profile."):], seen | {name}))
|
||||
else:
|
||||
out.append(tok)
|
||||
return out
|
||||
|
||||
|
||||
def profile(props, out_txt, name):
|
||||
defs = {}
|
||||
for line in logical_lines(read(props)):
|
||||
s = line.strip()
|
||||
if s.startswith("#") or s.startswith("!"):
|
||||
continue
|
||||
m = re.match(r"profile\.(\w+)\s*=(.*)$", s)
|
||||
if m:
|
||||
defs[m.group(1)] = m.group(2)
|
||||
if name not in defs:
|
||||
raise SystemExit("error: profile." + name + " not found in " + props)
|
||||
options = {}
|
||||
for tok in profile_tokens(defs, name, frozenset()):
|
||||
if "=" in tok:
|
||||
k, v = tok.split("=", 1)
|
||||
elif tok.startswith("!"):
|
||||
k, v = tok[1:], "false"
|
||||
else:
|
||||
k, v = tok, "true"
|
||||
if not re.match(r"^[A-Za-z_][A-Za-z0-9_.]*$", k) or v == "":
|
||||
raise SystemExit("error: cannot parse profile token " + repr(tok))
|
||||
options[k] = v
|
||||
if not options:
|
||||
raise SystemExit("error: profile." + name + " is empty")
|
||||
write(out_txt, "".join(k + "=" + v + "\n" for k, v in options.items()))
|
||||
print(len(options))
|
||||
|
||||
|
||||
def main(argv):
|
||||
cmd = argv[1]
|
||||
if cmd == "jvmargs":
|
||||
jvmargs(argv[2], argv[3], argv[4], argv[5:])
|
||||
elif cmd == "profile":
|
||||
profile(argv[2], argv[3], argv[4] if len(argv) > 4 else "HIGH")
|
||||
else:
|
||||
raise SystemExit("error: unknown helper " + cmd)
|
||||
|
||||
|
||||
main(sys.argv)
|
||||
PY
|
||||
)"
|
||||
|
||||
helper() {
|
||||
python3 -c "$HELPER" "$@"
|
||||
}
|
||||
|
||||
if [ "${1:-}" = "--helper" ]; then
|
||||
shift
|
||||
helper "$@"
|
||||
exit $?
|
||||
fi
|
||||
|
||||
NAME="${1:?usage: flyby-prism.sh <name> <variant>...}"
|
||||
shift
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "usage: flyby-prism.sh <name> <sdl-off|sdl-pack|gl-off|gl-pack>..." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
|
||||
PRISM_INSTANCE="${PRISM_INSTANCE:-GTNH-daily-2026-10-01+766-mmcprism-java17-26}"
|
||||
PRISM_BIN="${PRISM_BIN:-/Applications/Prism Launcher.app/Contents/MacOS/prismlauncher}"
|
||||
PRISM_TIMEOUT="${PRISM_TIMEOUT:-900}"
|
||||
PRISM_KEEP_JAR="${PRISM_KEEP_JAR:-0}"
|
||||
PRISM_WORLD="${PRISM_WORLD:-FlybyGTNH}"
|
||||
PRISM_ENTRIES="${PRISM_ENTRIES:-minecraft:chunk_borders=inOverlay,minecraft:chunk_section_visibility=inOverlay,minecraft:visualize_block_light_levels=inOverlay,minecraft:visualize_sky_light_levels=inOverlay,minecraft:visualize_heightmap=inOverlay,minecraft:visualize_collision_boxes=inOverlay,minecraft:visualize_solid_faces=inOverlay,minecraft:visualize_water_levels=inOverlay,minecraft:visualize_entity_supporting_block=inOverlay}"
|
||||
|
||||
for v in "$@"; do
|
||||
case "$v" in
|
||||
sdl-off|sdl-pack|gl-off|gl-pack) ;;
|
||||
*) echo "error: variant must be sdl-off, sdl-pack, gl-off or gl-pack, got: $v" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
case "$PRISM_WORLD" in
|
||||
*[[:space:]\"]*|"") echo "error: PRISM_WORLD must be non-empty without spaces or quotes: '$PRISM_WORLD'" >&2; exit 2 ;;
|
||||
esac
|
||||
case "$PRISM_ENTRIES" in
|
||||
*[[:space:]\"]*) echo "error: PRISM_ENTRIES must not contain spaces or quotes" >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
INST="$HOME/Library/Application Support/PrismLauncher/instances/$PRISM_INSTANCE"
|
||||
MC="$INST/.minecraft"
|
||||
CFG="$INST/instance.cfg"
|
||||
OPTS="$MC/options.txt"
|
||||
SHADERS="$MC/config/shaders.properties"
|
||||
for f in "$CFG" "$OPTS" "$SHADERS"; do
|
||||
if [ ! -f "$f" ]; then
|
||||
echo "error: missing $f" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
if [ ! -x "$PRISM_BIN" ]; then
|
||||
echo "error: launcher not executable: $PRISM_BIN" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INST_TAIL="${PRISM_INSTANCE#*+}"
|
||||
GAME_PAT="java.*$INST_TAIL"
|
||||
if pgrep -x prismlauncher >/dev/null 2>&1; then
|
||||
echo "error: Prism Launcher is already running; quit it first" >&2
|
||||
exit 1
|
||||
fi
|
||||
if pgrep -f "$GAME_PAT" >/dev/null 2>&1; then
|
||||
echo "error: a process matching '$GAME_PAT' (the instance's game?) is running" >&2
|
||||
exit 1
|
||||
fi
|
||||
if pgrep -f 'GradleStart|retrofuturabootstrap' >/dev/null 2>&1; then
|
||||
echo "error: a dev client is running" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./gradlew assemble
|
||||
JAR=""
|
||||
for f in "$ROOT"/build/libs/angelica-*.jar; do
|
||||
case "$f" in
|
||||
*-dev.jar|*-sources.jar|*-api.jar|*-dev-preshadow.jar|*-predowngrade.jar|*shadow-stripped.jar) continue ;;
|
||||
esac
|
||||
if [ -f "$f" ] && { [ -z "$JAR" ] || [ "$f" -nt "$JAR" ]; }; then
|
||||
JAR="$f"
|
||||
fi
|
||||
done
|
||||
if [ -z "$JAR" ]; then
|
||||
echo "error: no production angelica jar in build/libs" >&2
|
||||
exit 1
|
||||
fi
|
||||
JAR_NAME="$(basename "$JAR")"
|
||||
echo "==> test jar: $JAR"
|
||||
|
||||
OUT="$ROOT/tmp/prism/$NAME"
|
||||
BAK="$OUT/backup"
|
||||
|
||||
PACK="$(awk '/^shaderPack=/ { sub(/^[^=]*=/, ""); sub(/\r$/, ""); print; exit }' "$SHADERS")"
|
||||
PACK_TXT=""
|
||||
if [ -n "$PACK" ]; then
|
||||
PACK_TXT="$MC/shaderpacks/$PACK.txt"
|
||||
fi
|
||||
for v in "$@"; do
|
||||
if [ "$v" = "gl-pack" ] || [ "$v" = "sdl-pack" ]; then
|
||||
if [ -z "$PACK" ] || [ ! -d "$MC/shaderpacks/$PACK" ]; then
|
||||
echo "error: shaderPack '$PACK' missing from $MC/shaderpacks" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
STALE_BAK=""
|
||||
if [ -d "$BAK" ]; then
|
||||
STALE_BAK="$(find "$BAK" -mindepth 1 -maxdepth 1 | head -n 1)"
|
||||
fi
|
||||
STALE_DISABLED=""
|
||||
for f in "$MC"/mods/angelica-*.jar.disabled; do
|
||||
if [ -e "$f" ]; then
|
||||
STALE_DISABLED="$STALE_DISABLED $f"
|
||||
fi
|
||||
done
|
||||
if [ -n "$STALE_BAK" ] || [ -n "$STALE_DISABLED" ]; then
|
||||
echo "error: leftovers from an interrupted run; nothing was touched" >&2
|
||||
echo "undo steps:" >&2
|
||||
echo " 1. copy back from $BAK (if present): instance.cfg -> $CFG, options.txt -> $OPTS, shaders.properties -> $SHADERS, pack.txt -> ${PACK_TXT:-n/a}" >&2
|
||||
echo " 2. remove the installed test jar from $MC/mods (originals are listed in $BAK/renamed.list)" >&2
|
||||
echo " 3. drop the .disabled suffix from:${STALE_DISABLED:- (none found)}" >&2
|
||||
echo " 4. rm -rf '$BAK'" >&2
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$BAK"
|
||||
|
||||
flyby_copy "$CFG" "$BAK/instance.cfg"
|
||||
flyby_copy "$OPTS" "$BAK/options.txt"
|
||||
flyby_copy "$SHADERS" "$BAK/shaders.properties"
|
||||
if [ -n "$PACK_TXT" ]; then
|
||||
flyby_backup_file "$PACK_TXT" "$BAK/pack.txt"
|
||||
fi
|
||||
|
||||
RENAMED="$BAK/renamed.list"
|
||||
: >| "$RENAMED"
|
||||
|
||||
PRISM_PID=""
|
||||
GAME_PID=""
|
||||
|
||||
kill_game() {
|
||||
local pids
|
||||
pids="$(pgrep -f "$GAME_PAT" || true)"
|
||||
if [ -n "$pids" ]; then
|
||||
# shellcheck disable=SC2086
|
||||
kill $pids 2>/dev/null || true
|
||||
sleep 3
|
||||
pids="$(pgrep -f "$GAME_PAT" || true)"
|
||||
if [ -n "$pids" ]; then
|
||||
# shellcheck disable=SC2086
|
||||
kill -9 $pids 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
if [ -n "$PRISM_PID" ] && kill -0 "$PRISM_PID" 2>/dev/null; then
|
||||
kill "$PRISM_PID" 2>/dev/null || true
|
||||
sleep 1
|
||||
kill -9 "$PRISM_PID" 2>/dev/null || true
|
||||
fi
|
||||
}
|
||||
|
||||
RESTORED=0
|
||||
restore() {
|
||||
set +e
|
||||
if [ "$RESTORED" = "1" ]; then
|
||||
return 0
|
||||
fi
|
||||
RESTORED=1
|
||||
trap - EXIT INT TERM HUP
|
||||
kill_game
|
||||
local ok=1
|
||||
flyby_restore_file "$BAK/instance.cfg" "$CFG" || ok=0
|
||||
flyby_restore_file "$BAK/options.txt" "$OPTS" || ok=0
|
||||
flyby_restore_file "$BAK/shaders.properties" "$SHADERS" || ok=0
|
||||
if [ -n "$PACK_TXT" ]; then
|
||||
flyby_restore_file "$BAK/pack.txt" "$PACK_TXT" || ok=0
|
||||
fi
|
||||
if [ "$PRISM_KEEP_JAR" != "1" ]; then
|
||||
rm -f "$MC/mods/$JAR_NAME" || { echo "warning: could not remove $MC/mods/$JAR_NAME" >&2; ok=0; }
|
||||
while IFS= read -r f; do
|
||||
if [ -n "$f" ] && [ -f "$f.disabled" ]; then
|
||||
mv -f "$f.disabled" "$f" || { echo "warning: could not re-enable $f" >&2; ok=0; }
|
||||
fi
|
||||
done < "$RENAMED"
|
||||
if [ "$ok" = "1" ]; then
|
||||
rm -rf "$BAK"
|
||||
fi
|
||||
echo "==> test jar removed, original angelica jar(s) re-enabled"
|
||||
else
|
||||
echo "==> test jar LEFT installed: $MC/mods/$JAR_NAME"
|
||||
echo "==> to undo: rm -f '$MC/mods/$JAR_NAME'; then drop the .disabled suffix from: $(tr '\n' ' ' < "$RENAMED")"
|
||||
fi
|
||||
}
|
||||
trap restore EXIT
|
||||
trap 'exit 130' INT
|
||||
trap 'exit 143' TERM
|
||||
trap 'exit 129' HUP
|
||||
|
||||
for f in "$MC"/mods/angelica-*.jar; do
|
||||
if [ -f "$f" ]; then
|
||||
printf '%s\n' "$f" >> "$RENAMED"
|
||||
mv -n "$f" "$f.disabled"
|
||||
fi
|
||||
done
|
||||
flyby_copy "$JAR" "$MC/mods/$JAR_NAME"
|
||||
echo "==> installed $JAR_NAME; disabled: $(tr '\n' ' ' < "$RENAMED")"
|
||||
|
||||
game_pid() {
|
||||
pgrep -f "$GAME_PAT" | head -n 1 || true
|
||||
}
|
||||
|
||||
FAILED=0
|
||||
for VARIANT in "$@"; do
|
||||
VDIR="$OUT/$VARIANT"
|
||||
flyby_fresh_dir "$VDIR"
|
||||
|
||||
case "$VARIANT" in
|
||||
sdl-*) BACKEND=true ;;
|
||||
*) BACKEND=false ;;
|
||||
esac
|
||||
case "$VARIANT" in
|
||||
*-pack) SHADERS_ON=true ;;
|
||||
*) SHADERS_ON=false ;;
|
||||
esac
|
||||
|
||||
EXTRA=(
|
||||
-Dangelica.debug.tooling=true
|
||||
-Dangelica.flyby.route=static
|
||||
-Dangelica.flyby.length=400
|
||||
-Dangelica.flyby.warmupTicks=200
|
||||
-Dangelica.flyby.exitWhenDone=true
|
||||
-Dangelica.flyby.waitForFocus=true
|
||||
-Dangelica.flyby.screenshots=4
|
||||
-Dangelica.flyby.debugHud=true
|
||||
-Dangelica.flyby.debugDump=true
|
||||
"-DquickPlaySingleplayer=$PRISM_WORLD"
|
||||
)
|
||||
CHECK=(--modpack --shots 4 --expect-mode OVERLAY --require-group botania:botania --require-group journeymap:journeymap --forbid-dup --require-mod etfuturum)
|
||||
EXTRA+=("-Dangelica.flyby.debugEntries=$PRISM_ENTRIES")
|
||||
RENDERERS="$(printf '%s\n' "$PRISM_ENTRIES" | tr ',' '\n' | sed -e '/^$/d' -e 's/=[^=]*$//' | paste -sd, -)"
|
||||
CHECK+=(--require-renderers "$RENDERERS")
|
||||
|
||||
flyby_restore_file "$BAK/instance.cfg" "$CFG"
|
||||
echo "==> $NAME/$VARIANT: JvmArgs"
|
||||
helper jvmargs "$CFG" "$BAK/instance.cfg" "$BACKEND" "${EXTRA[@]}" | tee "$VDIR/jvmargs.txt"
|
||||
|
||||
flyby_restore_file "$BAK/shaders.properties" "$SHADERS"
|
||||
flyby_set_key "$SHADERS" enableShaders "$SHADERS_ON" =
|
||||
|
||||
if [ -n "$PACK_TXT" ]; then
|
||||
flyby_restore_file "$BAK/pack.txt" "$PACK_TXT"
|
||||
fi
|
||||
if [ "$VARIANT" = "gl-pack" ]; then
|
||||
PROPS="$MC/shaderpacks/$PACK/shaders/shaders.properties"
|
||||
if [ ! -f "$PROPS" ] || ! COUNT="$(helper profile "$PROPS" "$PACK_TXT" HIGH)"; then
|
||||
echo "FAIL $VARIANT: pack profile.HIGH could not be read from $PROPS; not launching"
|
||||
FAILED=1
|
||||
continue
|
||||
fi
|
||||
echo "==> $NAME/$VARIANT: wrote $COUNT HIGH options to $PACK_TXT"
|
||||
fi
|
||||
|
||||
flyby_restore_file "$BAK/options.txt" "$OPTS"
|
||||
flyby_set_key "$OPTS" fullscreen true :
|
||||
|
||||
mkdir -p "$MC/screenshots"
|
||||
rm -f "$MC"/screenshots/flyby-shot-*.png "$MC/logs/fml-client-latest.log"
|
||||
flyby_crash_list "$MC/crash-reports" >| "$VDIR/crashes.before"
|
||||
|
||||
echo "==> $NAME/$VARIANT: launching (timeout ${PRISM_TIMEOUT}s)"
|
||||
"$PRISM_BIN" -l "$PRISM_INSTANCE" >| "$VDIR/launcher.log" 2>&1 &
|
||||
PRISM_PID=$!
|
||||
|
||||
GAME_PID=""
|
||||
WAITED=0
|
||||
while [ "$WAITED" -lt 240 ]; do
|
||||
GAME_PID="$(game_pid)"
|
||||
if [ -n "$GAME_PID" ]; then
|
||||
break
|
||||
fi
|
||||
if ! kill -0 "$PRISM_PID" 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
WAITED=$((WAITED + 2))
|
||||
done
|
||||
|
||||
STATUS="0"
|
||||
if [ -z "$GAME_PID" ]; then
|
||||
STATUS="nogame"
|
||||
echo "==> $NAME/$VARIANT: no game process appeared within ${WAITED}s"
|
||||
else
|
||||
DEADLINE=$(( $(date +%s) + PRISM_TIMEOUT ))
|
||||
while kill -0 "$GAME_PID" 2>/dev/null; do
|
||||
if [ "$(date +%s)" -ge "$DEADLINE" ]; then
|
||||
echo "==> $NAME/$VARIANT: timeout, killing game"
|
||||
STATUS="timeout"
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
fi
|
||||
kill_game
|
||||
PRISM_PID=""
|
||||
sleep 2
|
||||
|
||||
NEW_CRASHES="$(flyby_new_crashes "$MC/crash-reports" "$VDIR/crashes.before" "$VDIR/crashes.after")"
|
||||
flyby_collect "$MC" "$VDIR" fml-client-latest.log
|
||||
|
||||
if ! grep -qF "Flyby: world saving is off" "$VDIR/fml-client-latest.log" 2>/dev/null; then
|
||||
echo "FAIL $VARIANT: never entered the world (no 'Flyby: world saving is off' in the log; QuickPlay did not fire?); stopping the batch"
|
||||
FAILED=1
|
||||
break
|
||||
fi
|
||||
|
||||
if [ "$STATUS" = "nogame" ]; then
|
||||
STATUS="1"
|
||||
fi
|
||||
if ! python3 "$ROOT/scripts/flyby-smoke-check.py" "$VDIR" "$STATUS" "$NEW_CRASHES" "${CHECK[@]}"; then
|
||||
FAILED=1
|
||||
fi
|
||||
done
|
||||
|
||||
exit "$FAILED"
|
||||
+117
-11
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
@@ -15,8 +16,18 @@ CATCH_MARK = "Caught an exception during ISBRH rendering"
|
||||
BAD_LINE = re.compile(
|
||||
r"InvalidInjectionException|InjectionError|MixinApplyError|MixinTransformerError|"
|
||||
r"InvalidMixinException|MixinPrepareError|skipped: the surrounded call was removed|"
|
||||
r"flyby scene command did not execute"
|
||||
r"flyby scene command did not execute|Grouped debug screen failed|Failed to set up the debug screen|"
|
||||
r"Debug screen entry \S+ failed and is disabled|needs the batching font renderer|"
|
||||
r"Flyby debugEntries:|Flyby: debug entry list is not available|Flyby: unknown debugProfile|"
|
||||
r"Debug screen compat entry for \S+ failed to register|"
|
||||
r"Failed to (?:load|save|set aside the unreadable) debug screen profile|toggle listeners kept changing"
|
||||
)
|
||||
OVERLAY_RE = re.compile(r"Flyby debug overlay shot=(\d+) mode=(\w+)(?:.* renderers=(\S*))?")
|
||||
LINE_RE = re.compile(r"Flyby debug line shot=(\d+) id=(\S+) text=(.*)")
|
||||
GROUP_RE = re.compile(r"Flyby debug group shot=(\d+) id=(\S+) ")
|
||||
DUP_IDS = ("angelica:other_left", "angelica:other_right")
|
||||
DUP_PREFIXES = ("Allocation rate:", "Java:", "GPU:", "OpenGL:", "CPU Cores:", "OS:", "LWJGL:", "IntCache:",
|
||||
"[Botania]", "[JM]", "[ChunkGen]", "hunger:", "HydroEnergy GPU RAM:", "renderDebugMode:")
|
||||
ANGELICA_FRAME = re.compile(
|
||||
r"com\.gtnewhorizons\.angelica|net\.coderbot|org\.embeddedt|org\.taumc|com\.prupe|"
|
||||
r"jss\.notfine|\$surround|angelica\$"
|
||||
@@ -44,7 +55,44 @@ def split_traces(lines):
|
||||
return traces
|
||||
|
||||
|
||||
def check(variant_dir, exit_status, new_crashes):
|
||||
def check_overlay(lines, opts, reasons):
|
||||
overlays = [m for m in (OVERLAY_RE.search(l) for l in lines) if m]
|
||||
mode = opts.get("expect_mode")
|
||||
if mode:
|
||||
if not overlays:
|
||||
reasons.append("no 'Flyby debug overlay' line")
|
||||
else:
|
||||
wrong = sorted({m.group(2) for m in overlays if m.group(2) != mode})
|
||||
if wrong:
|
||||
reasons.append("overlay mode " + ",".join(wrong) + " != expected " + mode)
|
||||
|
||||
dbg_lines = [m for m in (LINE_RE.search(l) for l in lines) if m]
|
||||
groups = [m for m in (GROUP_RE.search(l) for l in lines) if m]
|
||||
for ident in opts.get("require_group") or []:
|
||||
if not any(m.group(2) == ident for m in groups):
|
||||
reasons.append("missing debug group " + ident)
|
||||
|
||||
wanted = [r for r in (opts.get("require_renderers") or "").split(",") if r]
|
||||
if wanted:
|
||||
if not any(m.group(3) is not None and set(wanted) <= set(m.group(3).split(",")) for m in overlays):
|
||||
reasons.append("no overlay line with renderers " + ",".join(wanted))
|
||||
|
||||
for name in opts.get("require_mod") or []:
|
||||
if not any(name.lower() in l.lower() for l in lines):
|
||||
reasons.append("mod " + name + " not in log")
|
||||
|
||||
if opts.get("forbid_dup"):
|
||||
if not dbg_lines:
|
||||
reasons.append("no debug lines dumped")
|
||||
dups = sorted({m.group(3).lstrip().split(":")[0] + ":" for m in dbg_lines
|
||||
if m.group(2) in DUP_IDS and m.group(3).lstrip().startswith(DUP_PREFIXES)})
|
||||
if dups:
|
||||
reasons.append("duplicate vanilla lines in " + "/".join(DUP_IDS) + ": " + ", ".join(dups))
|
||||
|
||||
|
||||
def check(variant_dir, exit_status, new_crashes, opts=None):
|
||||
opts = opts or {}
|
||||
shots_required = opts.get("shots") or SHOTS_REQUIRED
|
||||
variant = os.path.basename(os.path.normpath(variant_dir))
|
||||
reasons = []
|
||||
details = []
|
||||
@@ -71,7 +119,11 @@ def check(variant_dir, exit_status, new_crashes):
|
||||
elif max(frames) == 0:
|
||||
reasons.append("flyby frames is 0")
|
||||
|
||||
bad = [l for l in lines if BAD_LINE.search(l)]
|
||||
if opts.get("modpack"):
|
||||
bad = [l for i, l in enumerate(lines) if BAD_LINE.search(l)
|
||||
and any("angelica" in x.lower() for x in lines[i:i + 4])]
|
||||
else:
|
||||
bad = [l for l in lines if BAD_LINE.search(l)]
|
||||
if bad:
|
||||
reasons.append(str(len(bad)) + " mixin/injection/scene failure line(s)")
|
||||
details.extend([" " + l.strip()[:200] for l in bad[:3]])
|
||||
@@ -94,12 +146,14 @@ def check(variant_dir, exit_status, new_crashes):
|
||||
if CATCH_MARK in l and any(CRASH_MARK in x for x in lines[i:i + 2]):
|
||||
caught = True
|
||||
break
|
||||
if not caught:
|
||||
if not caught and not opts.get("modpack"):
|
||||
reasons.append("no ISBRH catch line for crashtest")
|
||||
|
||||
check_overlay(lines, opts, reasons)
|
||||
|
||||
shots = sorted(glob.glob(os.path.join(variant_dir, "flyby-shot-*.png")))
|
||||
if len(shots) < SHOTS_REQUIRED:
|
||||
reasons.append("only " + str(len(shots)) + " of " + str(SHOTS_REQUIRED) + " screenshots")
|
||||
if len(shots) < shots_required:
|
||||
reasons.append("only " + str(len(shots)) + " of " + str(shots_required) + " screenshots")
|
||||
small = [os.path.basename(p) for p in shots if os.path.getsize(p) < MIN_SHOT_BYTES]
|
||||
if small:
|
||||
reasons.append("small screenshot(s): " + ", ".join(small))
|
||||
@@ -154,12 +208,56 @@ def self_test():
|
||||
("no-isbrh", [l for l in GOOD_LOG if CATCH_MARK not in l], {}, 1),
|
||||
("mixin-error", GOOD_LOG + ["[10:00:05] [main/ERROR]: InvalidInjectionException: bad"], {}, 1),
|
||||
("small-shot", GOOD_LOG, {"shot_size": 100}, 1),
|
||||
("bad-entries", GOOD_LOG + ["[10:00:06] [Client thread/ERROR]: Flyby debugEntries: unknown entry id 'test:nope'"], {}, 1),
|
||||
]
|
||||
for name, log, kw, want in cases:
|
||||
d = make_variant(root, name, log, **kw)
|
||||
got = check(d, 0, 0)
|
||||
if got != want:
|
||||
failures.append(name)
|
||||
no_isbrh = [l for l in GOOD_LOG if CATCH_MARK not in l]
|
||||
ov = [
|
||||
"[10:00:06] [Client thread/INFO]: Flyby debug overlay shot=0 mode=OVERLAY groups=2 renderers=vanilla,angelica",
|
||||
"[10:00:06] [Client thread/INFO]: Flyby debug group shot=0 id=angelica:other_left side=L x=0 y=0 w=1 h=1 facts= lines=2",
|
||||
"[10:00:06] [Client thread/INFO]: Flyby debug line shot=0 id=angelica:other_left text=Allocation rate: 1 MB/s",
|
||||
"[10:00:06] [Client thread/INFO]: Flyby debug line shot=0 id=angelica:other_left text=Second",
|
||||
"[10:00:06] [Client thread/INFO]: Flyby debug line shot=0 id=angelica:other_right text=Free",
|
||||
"[10:00:07] [main/INFO]: Loading mod Botania",
|
||||
]
|
||||
ov_dup = [l for l in ov if "Allocation" not in l]
|
||||
ov_dup += ["[10:00:06] [Client thread/INFO]: Flyby debug line shot=1 id=angelica:other_right text= GPU: foo"]
|
||||
flag_cases = [
|
||||
("modpack-ok", no_isbrh, {"modpack": True}, 0),
|
||||
("modpack-no-flag", no_isbrh, {}, 1),
|
||||
("modpack-foreign-bad", GOOD_LOG + ["[10:00:05] [main/ERROR]: InvalidInjectionException: x", "other", "other", "other", "angelica late"], {"modpack": True}, 0),
|
||||
("modpack-angelica-bad", no_isbrh + ["[10:00:05] [main/ERROR]: Grouped debug screen failed", "x", "angelica y"], {"modpack": True}, 1),
|
||||
("grouped-failed", GOOD_LOG + ["[10:00:05] [main/ERROR]: Grouped debug screen failed"], {}, 1),
|
||||
("setup-failed", GOOD_LOG + ["[10:00:05] [Client thread/ERROR] [Angelica]: Failed to set up the debug screen; it stays disabled for this session"], {}, 1),
|
||||
("entry-failed", GOOD_LOG + ["[10:00:05] [Client thread/ERROR] [Angelica]: Debug screen entry botania:debug failed and is disabled for the rest of this session"], {}, 1),
|
||||
("font-fallback", GOOD_LOG + ["[10:00:05] [Client thread/ERROR] [Angelica]: Grouped debug screen needs the batching font renderer on the HUD font; using the classic layout for the rest of this session"], {}, 1),
|
||||
("shots-ok", GOOD_LOG, {"shots": 3}, 0),
|
||||
("shots-short", GOOD_LOG, {"shots": 9}, 1),
|
||||
("mode-ok", GOOD_LOG + ov, {"expect_mode": "OVERLAY"}, 0),
|
||||
("mode-wrong", GOOD_LOG + ov, {"expect_mode": "FLAT"}, 1),
|
||||
("mode-none", GOOD_LOG, {"expect_mode": "FLAT"}, 1),
|
||||
("group-ok", GOOD_LOG + ov, {"require_group": ["angelica:other_left"]}, 0),
|
||||
("group-missing", GOOD_LOG + ov, {"require_group": ["angelica:nope"]}, 1),
|
||||
("renderers-ok", GOOD_LOG + ov, {"require_renderers": "angelica,vanilla"}, 0),
|
||||
("renderers-missing", GOOD_LOG + ov, {"require_renderers": "angelica,sodium"}, 1),
|
||||
("mod-ok", GOOD_LOG + ov, {"require_mod": ["botania"]}, 0),
|
||||
("mod-missing", GOOD_LOG + ov, {"require_mod": ["thaumcraft"]}, 1),
|
||||
("dup-fail", GOOD_LOG + ov, {"forbid_dup": True}, 1),
|
||||
("dup-ok", GOOD_LOG + ov_dup[:3] + ov_dup[4:5], {"forbid_dup": True}, 0),
|
||||
("dup-spaces-fail", GOOD_LOG + ov_dup, {"forbid_dup": True}, 1),
|
||||
("dup-no-dump", GOOD_LOG, {"forbid_dup": True}, 1),
|
||||
]
|
||||
line = "[10:00:06] [Client thread/INFO]: Flyby debug line shot=1 id=angelica:other_left text= [Botania] x"
|
||||
flag_cases.append(("dup-prefix", GOOD_LOG + ov_dup[:3] + [line], {"forbid_dup": True}, 1))
|
||||
flag_cases.append(("dup-prefix-other-id", GOOD_LOG + ov_dup[:3] + [line.replace("other_left", "other_elsewhere")], {"forbid_dup": True}, 0))
|
||||
for name, log, opts, want in flag_cases:
|
||||
d = make_variant(root, name, log, shots=3 if name == "shots-ok" else 8)
|
||||
if check(d, 0, 0, opts) != want:
|
||||
failures.append(name)
|
||||
d = make_variant(root, "bad-exit", GOOD_LOG)
|
||||
if check(d, "timeout", 1) != 1:
|
||||
failures.append("bad-exit")
|
||||
@@ -175,11 +273,19 @@ def self_test():
|
||||
def main(argv):
|
||||
if len(argv) == 2 and argv[1] == "--self-test":
|
||||
return self_test()
|
||||
if len(argv) != 4:
|
||||
print("usage: flyby-smoke-check.py <variant_dir> <exit_status|timeout> <new_crash_count>", file=sys.stderr)
|
||||
print(" flyby-smoke-check.py --self-test", file=sys.stderr)
|
||||
return 2
|
||||
return check(argv[1], argv[2], int(argv[3]))
|
||||
ap = argparse.ArgumentParser(prog="flyby-smoke-check.py")
|
||||
ap.add_argument("variant_dir")
|
||||
ap.add_argument("exit_status")
|
||||
ap.add_argument("new_crashes", type=int)
|
||||
ap.add_argument("--modpack", action="store_true")
|
||||
ap.add_argument("--shots", type=int)
|
||||
ap.add_argument("--expect-mode")
|
||||
ap.add_argument("--require-group", action="append", metavar="ID")
|
||||
ap.add_argument("--require-renderers", metavar="a,b")
|
||||
ap.add_argument("--require-mod", action="append", metavar="NAME")
|
||||
ap.add_argument("--forbid-dup", action="store_true")
|
||||
a = ap.parse_args(argv[1:])
|
||||
return check(a.variant_dir, a.exit_status, a.new_crashes, vars(a))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+27
-60
@@ -11,11 +11,14 @@
|
||||
# SMOKE_TIMEOUT - seconds before the client is killed (default: 600).
|
||||
# SMOKE_SCENE - scene command file (default: scripts/flyby-scenes/surround-smoke.txt).
|
||||
# SMOKE_PACK - shaderpack name for the pack variant (default: ComplementaryUnbound_r5.7.1 + EuphoriaPatches_1.8.1).
|
||||
# SMOKE_DEBUG_HUD - true forces F3 on; false keeps the saved setting (default: true).
|
||||
# SMOKE_DEBUG_HUD - true forces F3 on, false forces it off (default: true).
|
||||
# SMOKE_PITCH - camera pitch in degrees, positive looks down (default: 15).
|
||||
# SMOKE_WARMUP_TICKS - ticks at the first pose before the pan (default: 100).
|
||||
# SMOKE_ARGS - extra -D flags for the client, word-split (default: none).
|
||||
# SMOKE_CHECK_ARGS - extra flyby-smoke-check.py flags, word-split (default: none).
|
||||
# SMOKE_FULLSCREEN - 0 keeps options.txt's window mode (default: 1, fullscreen for the run).
|
||||
#
|
||||
# Runs from this script's own checkout. Restores run/client/config/shaders.properties and angelica-options.json on exit.
|
||||
# Runs from this script's own checkout, fullscreen unless SMOKE_FULLSCREEN=0 (fullscreen avoids vsync pacing). Restores run/client/config/shaders.properties, angelica-options.json and options.txt on exit.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -27,6 +30,8 @@ fi
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$ROOT"
|
||||
# shellcheck source=flyby-lib.sh
|
||||
. "$ROOT/scripts/flyby-lib.sh"
|
||||
|
||||
QUICKPLAY_WORLD="${QUICKPLAY_WORLD:-Flat World - No Mods}"
|
||||
FLYBY_ORIGIN="${FLYBY_ORIGIN:-1509.5,-924.5,0}"
|
||||
@@ -35,6 +40,9 @@ SMOKE_PACK="${SMOKE_PACK:-ComplementaryUnbound_r5.7.1 + EuphoriaPatches_1.8.1}"
|
||||
SMOKE_DEBUG_HUD="${SMOKE_DEBUG_HUD:-true}"
|
||||
SMOKE_PITCH="${SMOKE_PITCH:-15}"
|
||||
SMOKE_WARMUP_TICKS="${SMOKE_WARMUP_TICKS:-100}"
|
||||
SMOKE_ARGS="${SMOKE_ARGS:-}"
|
||||
SMOKE_CHECK_ARGS="${SMOKE_CHECK_ARGS:-}"
|
||||
SMOKE_FULLSCREEN="${SMOKE_FULLSCREEN:-1}"
|
||||
|
||||
SCENE="${SMOKE_SCENE:-$ROOT/scripts/flyby-scenes/surround-smoke.txt}"
|
||||
case "$SCENE" in
|
||||
@@ -47,6 +55,7 @@ OUT="$ROOT/tmp/smoke/$NAME"
|
||||
BACKUP="$OUT/shaders.properties.bak"
|
||||
OPTIONS="$RUN/config/angelica-options.json"
|
||||
OPTIONS_BACKUP="$OUT/angelica-options.json.bak"
|
||||
OPTIONS_TXT="$RUN/options.txt"
|
||||
|
||||
for v in "$@"; do
|
||||
case "$v" in
|
||||
@@ -66,48 +75,26 @@ for v in "$@"; do
|
||||
done
|
||||
|
||||
mkdir -p "$OUT"
|
||||
python3 - "$SHADERS" "$BACKUP" <<'PY'
|
||||
import shutil, sys
|
||||
shutil.copyfile(sys.argv[1], sys.argv[2])
|
||||
PY
|
||||
python3 - "$OPTIONS" "$OPTIONS_BACKUP" <<'PY'
|
||||
import shutil, sys
|
||||
shutil.copyfile(sys.argv[1], sys.argv[2])
|
||||
PY
|
||||
flyby_copy "$SHADERS" "$BACKUP"
|
||||
flyby_copy "$OPTIONS" "$OPTIONS_BACKUP"
|
||||
|
||||
restore_options() {
|
||||
python3 - "$OPTIONS_BACKUP" "$OPTIONS" <<'PY' || true
|
||||
import shutil, sys
|
||||
shutil.copyfile(sys.argv[1], sys.argv[2])
|
||||
PY
|
||||
flyby_restore_file "$OPTIONS_BACKUP" "$OPTIONS" || true
|
||||
}
|
||||
|
||||
restore() {
|
||||
python3 - "$BACKUP" "$SHADERS" <<'PY' || true
|
||||
import shutil, sys
|
||||
shutil.copyfile(sys.argv[1], sys.argv[2])
|
||||
PY
|
||||
flyby_restore_file "$BACKUP" "$SHADERS" || true
|
||||
restore_options
|
||||
if [ "$SMOKE_FULLSCREEN" = 1 ]; then flyby_fullscreen_restore "$OPTIONS_TXT" "$OUT/options.txt.bak" || true; fi
|
||||
}
|
||||
if [ "$SMOKE_FULLSCREEN" = 1 ]; then flyby_fullscreen_on "$OPTIONS_TXT" "$OUT/options.txt.bak"; fi
|
||||
trap restore EXIT
|
||||
|
||||
set_shaders() {
|
||||
python3 - "$SHADERS" "$1" "$2" <<'PY'
|
||||
import re, sys
|
||||
path, enable, pack = sys.argv[1:4]
|
||||
text = open(path).read()
|
||||
def put(text, key, value):
|
||||
pat = re.compile(r'^' + key + r'=.*$', re.M)
|
||||
if pat.search(text):
|
||||
return pat.sub(lambda m: key + '=' + value, text)
|
||||
if text and not text.endswith('\n'):
|
||||
text += '\n'
|
||||
return text + key + '=' + value + '\n'
|
||||
text = put(text, 'enableShaders', enable)
|
||||
if pack:
|
||||
text = put(text, 'shaderPack', pack)
|
||||
open(path, 'w').write(text)
|
||||
PY
|
||||
flyby_set_key "$SHADERS" enableShaders "$1" =
|
||||
if [ -n "$2" ]; then
|
||||
flyby_set_key "$SHADERS" shaderPack "$2" =
|
||||
fi
|
||||
}
|
||||
|
||||
set_vao() {
|
||||
@@ -123,20 +110,10 @@ with open(path, "w") as f:
|
||||
PY
|
||||
}
|
||||
|
||||
crash_list() {
|
||||
if [ -d "$RUN/crash-reports" ]; then
|
||||
ls "$RUN/crash-reports" | sort
|
||||
fi
|
||||
}
|
||||
|
||||
FAILED=0
|
||||
for VARIANT in "$@"; do
|
||||
VDIR="$OUT/$VARIANT"
|
||||
python3 - "$VDIR" <<'PY'
|
||||
import shutil, sys
|
||||
shutil.rmtree(sys.argv[1], ignore_errors=True)
|
||||
PY
|
||||
mkdir -p "$VDIR"
|
||||
flyby_fresh_dir "$VDIR"
|
||||
|
||||
restore_options
|
||||
if [ "$VARIANT" = "vao" ]; then
|
||||
@@ -151,7 +128,7 @@ PY
|
||||
|
||||
mkdir -p "$RUN/screenshots"
|
||||
rm -f "$RUN"/screenshots/flyby-shot-*.png
|
||||
crash_list >| "$VDIR/crashes.before"
|
||||
flyby_crash_list "$RUN/crash-reports" >| "$VDIR/crashes.before"
|
||||
|
||||
echo "==> $NAME/$VARIANT: launching (timeout ${SMOKE_TIMEOUT}s)"
|
||||
./gradlew runClient25 --console=plain \
|
||||
@@ -167,6 +144,7 @@ PY
|
||||
-Dangelica.flyby.crashTest=isbrh \
|
||||
-Dangelica.debug.testBlocks=true \
|
||||
-DquickPlaySingleplayer="$QUICKPLAY_WORLD" \
|
||||
$SMOKE_ARGS \
|
||||
>| "$VDIR/console.log" 2>&1 &
|
||||
PID=$!
|
||||
|
||||
@@ -190,21 +168,10 @@ PY
|
||||
STATUS="$RC"
|
||||
fi
|
||||
|
||||
crash_list >| "$VDIR/crashes.after"
|
||||
NEW_CRASHES="$(comm -13 "$VDIR/crashes.before" "$VDIR/crashes.after" | wc -l | tr -d ' ')"
|
||||
NEW_CRASHES="$(flyby_new_crashes "$RUN/crash-reports" "$VDIR/crashes.before" "$VDIR/crashes.after")"
|
||||
flyby_collect "$RUN" "$VDIR" fml-client-latest.log latest.log
|
||||
|
||||
for f in fml-client-latest.log latest.log; do
|
||||
if [ -f "$RUN/logs/$f" ]; then
|
||||
cp -f "$RUN/logs/$f" "$VDIR/$f"
|
||||
fi
|
||||
done
|
||||
for f in "$RUN"/screenshots/flyby-shot-*.png; do
|
||||
if [ -f "$f" ]; then
|
||||
cp -f "$f" "$VDIR/"
|
||||
fi
|
||||
done
|
||||
|
||||
if ! python3 "$ROOT/scripts/flyby-smoke-check.py" "$VDIR" "$STATUS" "$NEW_CRASHES"; then
|
||||
if ! python3 "$ROOT/scripts/flyby-smoke-check.py" "$VDIR" "$STATUS" "$NEW_CRASHES" $SMOKE_CHECK_ARGS; then
|
||||
FAILED=1
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -785,7 +785,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
device.fenceReleaser().drain();
|
||||
if (frameSync != 0L) {
|
||||
frameSyncs.enqueue(frameSync);
|
||||
while (frameSyncs.size() > Device.MAX_FRAMES_IN_FLIGHT - 1) {
|
||||
while (frameSyncs.size() > RenderBackend.MAX_FRAMES_IN_FLIGHT - 1) {
|
||||
final long sync = frameSyncs.dequeueLong();
|
||||
fenceTracker.clientWaitSync(sync, 0, Long.MAX_VALUE);
|
||||
fenceTracker.deleteSync(sync);
|
||||
|
||||
+6
-6
@@ -47,7 +47,6 @@ 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;
|
||||
@@ -60,7 +59,7 @@ public final class VertexWriteReplayer {
|
||||
int[] uniformTargets;
|
||||
int[] samplerUnits;
|
||||
IntSupplier[] samplerSources;
|
||||
int[] samplerObjects;
|
||||
IntSupplier[] samplerObjects;
|
||||
int[] packStorageSlots;
|
||||
int[] packRwStorageSlots;
|
||||
int[] uniformBlockIndices;
|
||||
@@ -143,7 +142,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 IntArrayList objects = new IntArrayList();
|
||||
final List<IntSupplier> objects = new ArrayList<>();
|
||||
final IntArrayList ownTextureIndices = new IntArrayList();
|
||||
final List<String> ownTextureNames = new ArrayList<>();
|
||||
final List<String> pipelineNames = Arrays.asList(setup.samplerNames());
|
||||
@@ -164,7 +163,7 @@ public final class VertexWriteReplayer {
|
||||
if (!units.contains(unit)) {
|
||||
units.add(unit);
|
||||
sources.add(null);
|
||||
objects.add(SAMPLER_OBJECT_OF_DRAW);
|
||||
objects.add(null);
|
||||
}
|
||||
}
|
||||
int free = TEXTURE_UNITS - 1;
|
||||
@@ -179,7 +178,7 @@ public final class VertexWriteReplayer {
|
||||
}
|
||||
samplerUnits = units.toIntArray();
|
||||
samplerSources = sources.toArray(new IntSupplier[0]);
|
||||
samplerObjects = objects.toIntArray();
|
||||
samplerObjects = objects.toArray(new IntSupplier[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,7 +305,8 @@ 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 int samplerObject = replay.samplerObjects[i] != SAMPLER_OBJECT_OF_DRAW ? replay.samplerObjects[i] : st.boundSamplerObjects[unit];
|
||||
final IntSupplier sampler = replay.samplerObjects[i];
|
||||
final int samplerObject = sampler != null ? sampler.getAsInt() : st.boundSamplerObjects[unit];
|
||||
c.samplerHandles[i] = computeBinder.samplerHandle(samplerObject, c.samplerTextures[i]);
|
||||
}
|
||||
if (c.imageTextures.length != images.length) c.imageTextures = new int[images.length];
|
||||
|
||||
@@ -37,9 +37,7 @@ import static org.lwjgl.system.MemoryUtil.memUTF8;
|
||||
public final class Device {
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
|
||||
public static final int MAX_FRAMES_IN_FLIGHT = 3;
|
||||
|
||||
private static final int FRAMES_IN_FLIGHT = Math.min(Math.max(SystemProperties.SDL_FRAMES_IN_FLIGHT, 1), MAX_FRAMES_IN_FLIGHT);
|
||||
private static final int FRAMES_IN_FLIGHT = Math.min(Math.max(SystemProperties.SDL_FRAMES_IN_FLIGHT, 1), RenderBackend.MAX_FRAMES_IN_FLIGHT);
|
||||
|
||||
public static int framesInFlight() { return FRAMES_IN_FLIGHT; }
|
||||
|
||||
|
||||
+4
-3
@@ -45,6 +45,7 @@ import static org.lwjgl.system.MemoryStack.*;
|
||||
import static org.lwjgl.system.MemoryUtil.memAlloc;
|
||||
import static org.lwjgl.system.MemoryUtil.memFree;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureStaging;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
@@ -697,7 +698,7 @@ public final class ResourceManager {
|
||||
final GpuBucket b = gpuBufferPool.get(Hashing.packHiLo(usage, bucket));
|
||||
if (b == null || b.handles.isEmpty()) return 0;
|
||||
for (int i = 0, n = b.handles.size(); i < n; i++) {
|
||||
if (gpuPoolCurrentFrame - b.frames.getLong(i) >= Device.MAX_FRAMES_IN_FLIGHT) {
|
||||
if (gpuPoolCurrentFrame - b.frames.getLong(i) >= RenderBackend.MAX_FRAMES_IN_FLIGHT) {
|
||||
final long handle = b.handles.getLong(i);
|
||||
final int last = n - 1;
|
||||
if (i != last) {
|
||||
@@ -1555,7 +1556,7 @@ public final class ResourceManager {
|
||||
for (int i = 0; i < n; i += 2) {
|
||||
final long handle = batchSegmentPending.getLong(i);
|
||||
final long stamp = batchSegmentPending.getLong(i + 1);
|
||||
if (currentFrame - stamp >= Device.MAX_FRAMES_IN_FLIGHT) {
|
||||
if (currentFrame - stamp >= RenderBackend.MAX_FRAMES_IN_FLIGHT) {
|
||||
batchSegmentFree.enqueue(handle);
|
||||
} else {
|
||||
batchSegmentPending.set(w, handle);
|
||||
@@ -2445,7 +2446,7 @@ public final class ResourceManager {
|
||||
}
|
||||
|
||||
private static final int DUMMY_VBO_SIZE = 16 * 16;
|
||||
public static final int ATTRIB_RING_BLOCKS = 2048 * Device.MAX_FRAMES_IN_FLIGHT;
|
||||
public static final int ATTRIB_RING_BLOCKS = 2048 * RenderBackend.MAX_FRAMES_IN_FLIGHT;
|
||||
public static final int ATTRIB_RING_CHUNK_BLOCKS = 64;
|
||||
private static final int ATTRIB_RING_CHUNKS = ATTRIB_RING_BLOCKS / ATTRIB_RING_CHUNK_BLOCKS;
|
||||
private final AtomicLong attribRingCounter = new AtomicLong();
|
||||
|
||||
+2
-12
@@ -2,6 +2,7 @@ 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;
|
||||
@@ -12,11 +13,9 @@ 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;
|
||||
@@ -34,17 +33,8 @@ class GlsmSdlAttachmentClearTest {
|
||||
return GlsmSdlHeadlessRig.frameManager().frame();
|
||||
}
|
||||
|
||||
// Reflect.setStaticFinal rejects primitive fields.
|
||||
private static void setDisableInPassClear(boolean 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);
|
||||
}
|
||||
Reflect.setStaticFinal(SystemProperties.class, "SDL_DISABLE_IN_PASS_CLEAR", boolean.class, value);
|
||||
}
|
||||
|
||||
/** Clears colour to black and depth to 0.9, then draws a quad so a render pass is open. */
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.gtnewhorizons.angelica.api.debug;
|
||||
|
||||
import net.minecraft.client.entity.EntityClientPlayerMP;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
|
||||
public interface DebugContext {
|
||||
|
||||
World world();
|
||||
|
||||
EntityClientPlayerMP player();
|
||||
|
||||
int blockX();
|
||||
|
||||
int blockY();
|
||||
|
||||
int blockZ();
|
||||
|
||||
Chunk chunk();
|
||||
|
||||
long nanoTime();
|
||||
|
||||
boolean isSampleDue();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.gtnewhorizons.angelica.api.debug;
|
||||
|
||||
public enum DebugEntryCategory {
|
||||
|
||||
SCREEN_TEXT("angelica.debug.options.category.text"),
|
||||
RENDERER("angelica.debug.options.category.renderer");
|
||||
|
||||
public final String translationKey;
|
||||
|
||||
DebugEntryCategory(String translationKey) {
|
||||
this.translationKey = translationKey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.api.debug;
|
||||
|
||||
public interface DebugEntryHandle {
|
||||
|
||||
String id();
|
||||
|
||||
boolean isEnabled();
|
||||
|
||||
boolean isFailed();
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.gtnewhorizons.angelica.api.debug;
|
||||
|
||||
public final class DebugEntrySpec {
|
||||
|
||||
public interface DebugEntryFactory {
|
||||
|
||||
DebugScreenEntry create(DebugEntryHandle entry);
|
||||
}
|
||||
|
||||
public interface DebugToggleListener {
|
||||
|
||||
void onEnabledChanged(DebugEntryHandle entry, boolean enabled);
|
||||
}
|
||||
|
||||
private final String id;
|
||||
private final DebugEntryCategory category;
|
||||
private String name;
|
||||
private DebugScreenEntryStatus defaultStatus;
|
||||
private DebugScreenEntryStatus performanceStatus = DebugScreenEntryStatus.NEVER;
|
||||
private DebugEntryFactory textFactory;
|
||||
private DebugToggleListener toggleListener;
|
||||
private boolean supportsAlways = true;
|
||||
|
||||
public DebugEntrySpec(String id, DebugEntryCategory category) {
|
||||
this.id = id;
|
||||
this.category = category;
|
||||
this.defaultStatus = category == DebugEntryCategory.SCREEN_TEXT ? DebugScreenEntryStatus.IN_OVERLAY : DebugScreenEntryStatus.NEVER;
|
||||
}
|
||||
|
||||
public DebugEntrySpec name(String keyOrLiteral) {
|
||||
this.name = keyOrLiteral;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DebugEntrySpec defaults(DebugScreenEntryStatus defaultStatus, DebugScreenEntryStatus performanceStatus) {
|
||||
this.defaultStatus = defaultStatus;
|
||||
this.performanceStatus = performanceStatus;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DebugEntrySpec text(DebugEntryFactory factory) {
|
||||
this.textFactory = factory;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DebugEntrySpec toggle(DebugToggleListener listener) {
|
||||
this.toggleListener = listener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DebugEntrySpec supportsAlways(boolean supportsAlways) {
|
||||
this.supportsAlways = supportsAlways;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public DebugEntryCategory getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public DebugScreenEntryStatus getDefaultStatus() {
|
||||
return defaultStatus;
|
||||
}
|
||||
|
||||
public DebugScreenEntryStatus getPerformanceStatus() {
|
||||
return performanceStatus;
|
||||
}
|
||||
|
||||
public DebugEntryFactory getTextFactory() {
|
||||
return textFactory;
|
||||
}
|
||||
|
||||
public DebugToggleListener getToggleListener() {
|
||||
return toggleListener;
|
||||
}
|
||||
|
||||
public boolean getSupportsAlways() {
|
||||
return supportsAlways;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.gtnewhorizons.angelica.api.debug;
|
||||
|
||||
public interface DebugFact {
|
||||
|
||||
DebugFact text(CharSequence text);
|
||||
|
||||
DebugFact text(char c);
|
||||
|
||||
DebugFact value(CharSequence value);
|
||||
|
||||
DebugFact value(int value);
|
||||
|
||||
DebugFact value(long value);
|
||||
|
||||
DebugFact value(double value, int decimals);
|
||||
|
||||
DebugFact padded(long value, int width);
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.gtnewhorizons.angelica.api.debug;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class DebugFactLines {
|
||||
|
||||
private static final int MAX_LABEL = 24;
|
||||
private static final String COLON = ": ";
|
||||
private static final String DASH = " - ";
|
||||
|
||||
private String[] sources = new String[8];
|
||||
private String[] names = new String[8];
|
||||
private String[] values = new String[8];
|
||||
private int cursor;
|
||||
private int size;
|
||||
private boolean open;
|
||||
|
||||
public void begin() {
|
||||
cursor = 0;
|
||||
open = true;
|
||||
}
|
||||
|
||||
public void end() {
|
||||
open = false;
|
||||
if (cursor < size) {
|
||||
Arrays.fill(sources, cursor, size, null);
|
||||
Arrays.fill(names, cursor, size, null);
|
||||
Arrays.fill(values, cursor, size, null);
|
||||
}
|
||||
size = cursor;
|
||||
}
|
||||
|
||||
public void fact(String name, String value) {
|
||||
requireOpen();
|
||||
if (value == null) throw new IllegalArgumentException("Debug fact value is null");
|
||||
reserve();
|
||||
sources[cursor] = null;
|
||||
names[cursor] = name;
|
||||
values[cursor] = value;
|
||||
cursor++;
|
||||
}
|
||||
|
||||
public void line(String line) {
|
||||
fact(null, line);
|
||||
}
|
||||
|
||||
public void split(String line) {
|
||||
requireOpen();
|
||||
if (line == null) throw new IllegalArgumentException("Debug fact line is null");
|
||||
reserve();
|
||||
if (line.equals(sources[cursor])) {
|
||||
cursor++;
|
||||
return;
|
||||
}
|
||||
sources[cursor] = line;
|
||||
parse(line);
|
||||
cursor++;
|
||||
}
|
||||
|
||||
public void emit(DebugScreenDisplayer displayer, DebugGroupHandle group) {
|
||||
if (open) throw new IllegalStateException("DebugFactLines.emit() called before end()");
|
||||
for (int i = 0; i < size; i++) {
|
||||
final String value = values[i];
|
||||
final String name = names[i];
|
||||
if (name == null) {
|
||||
displayer.line(group).text(value);
|
||||
} else {
|
||||
displayer.fact(group, name).value(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parse(String line) {
|
||||
int at = labelEnd(line, COLON);
|
||||
if (at >= 0) {
|
||||
names[cursor] = name(line, at);
|
||||
values[cursor] = line.substring(at + COLON.length());
|
||||
return;
|
||||
}
|
||||
at = labelEnd(line, DASH);
|
||||
if (at >= 0) {
|
||||
names[cursor] = name(line, at);
|
||||
values[cursor] = line.substring(at + DASH.length());
|
||||
return;
|
||||
}
|
||||
names[cursor] = null;
|
||||
values[cursor] = line;
|
||||
}
|
||||
|
||||
private String name(String line, int end) {
|
||||
final String previous = names[cursor];
|
||||
return previous != null && previous.length() == end && line.startsWith(previous) ? previous : line.substring(0, end);
|
||||
}
|
||||
|
||||
private void requireOpen() {
|
||||
if (!open) throw new IllegalStateException("DebugFactLines is not open: call begin() first");
|
||||
}
|
||||
|
||||
private void reserve() {
|
||||
if (cursor == sources.length) {
|
||||
final int capacity = cursor * 2;
|
||||
sources = Arrays.copyOf(sources, capacity);
|
||||
names = Arrays.copyOf(names, capacity);
|
||||
values = Arrays.copyOf(values, capacity);
|
||||
}
|
||||
}
|
||||
|
||||
static int labelEnd(String line, String separator) {
|
||||
final int at = line.indexOf(separator);
|
||||
if (at <= 0 || at > MAX_LABEL) return -1;
|
||||
for (int i = 0; i < at; i++) {
|
||||
final char c = line.charAt(i);
|
||||
if (c == '=' || c == '(') return -1;
|
||||
}
|
||||
return at;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.gtnewhorizons.angelica.api.debug;
|
||||
|
||||
public interface DebugGroupHandle {
|
||||
|
||||
int LEFT = 0;
|
||||
int RIGHT = 1;
|
||||
|
||||
String id();
|
||||
|
||||
int side();
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.gtnewhorizons.angelica.api.debug;
|
||||
|
||||
public final class DebugGroupSpec {
|
||||
|
||||
public static final int DEFAULT_ORDER = 500;
|
||||
|
||||
private final String id;
|
||||
private String titleKey;
|
||||
private int accent = -1;
|
||||
private int side = DebugGroupHandle.LEFT;
|
||||
private int order = DEFAULT_ORDER;
|
||||
|
||||
public DebugGroupSpec(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public DebugGroupSpec title(String keyOrLiteral) {
|
||||
this.titleKey = keyOrLiteral;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DebugGroupSpec accent(int rgb) {
|
||||
this.accent = rgb & 0xFFFFFF;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DebugGroupSpec side(int side) {
|
||||
this.side = side;
|
||||
return this;
|
||||
}
|
||||
|
||||
public DebugGroupSpec order(int order) {
|
||||
this.order = order;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getTitleKey() {
|
||||
return titleKey;
|
||||
}
|
||||
|
||||
public int getAccent() {
|
||||
return accent;
|
||||
}
|
||||
|
||||
public int getSide() {
|
||||
return side;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return order;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.gtnewhorizons.angelica.api.debug;
|
||||
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugRegistry;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugScreen;
|
||||
|
||||
public final class DebugScreenApi {
|
||||
|
||||
private DebugScreenApi() {}
|
||||
|
||||
public static DebugGroupHandle registerGroup(DebugGroupSpec spec) {
|
||||
DebugScreen.bootstrap();
|
||||
return DebugRegistry.MAIN.registerGroup(spec);
|
||||
}
|
||||
|
||||
public static DebugEntryHandle registerEntry(DebugEntrySpec spec) {
|
||||
DebugScreen.bootstrap();
|
||||
return DebugRegistry.MAIN.registerEntry(spec);
|
||||
}
|
||||
|
||||
public static DebugGroupHandle group(String id) {
|
||||
DebugScreen.bootstrap();
|
||||
return DebugRegistry.MAIN.groupById(id);
|
||||
}
|
||||
|
||||
public static DebugEntryHandle entry(String id) {
|
||||
DebugScreen.bootstrap();
|
||||
return DebugRegistry.MAIN.entryById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizons.angelica.api.debug;
|
||||
|
||||
public interface DebugScreenDisplayer {
|
||||
|
||||
DebugFact line(DebugGroupHandle group);
|
||||
|
||||
DebugFact fact(DebugGroupHandle group, String name);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.api.debug;
|
||||
|
||||
public interface DebugScreenEntry {
|
||||
|
||||
void display(DebugScreenDisplayer displayer, DebugContext context);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.gtnewhorizons.angelica.api.debug;
|
||||
|
||||
public enum DebugScreenEntryStatus {
|
||||
|
||||
ALWAYS_ON("alwaysOn"),
|
||||
IN_OVERLAY("inOverlay"),
|
||||
NEVER("never");
|
||||
|
||||
private static final DebugScreenEntryStatus[] VALUES = values();
|
||||
|
||||
public final String serializedName;
|
||||
|
||||
DebugScreenEntryStatus(String serializedName) {
|
||||
this.serializedName = serializedName;
|
||||
}
|
||||
|
||||
public static DebugScreenEntryStatus bySerializedName(String name) {
|
||||
for (final DebugScreenEntryStatus status : VALUES) {
|
||||
if (status.serializedName.equals(name)) return status;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@API(owner = "angelica", provides = "angelica|debug", apiVersion = "1")
|
||||
package com.gtnewhorizons.angelica.api.debug;
|
||||
|
||||
import cpw.mods.fml.common.API;
|
||||
@@ -17,7 +17,11 @@ public final class TesrMaterial {
|
||||
|
||||
public enum Transparency { OPAQUE, TRANSLUCENT, ADDITIVE, ADDITIVE_ALPHA, GLINT }
|
||||
|
||||
public enum SpecialRender { NONE, BEACON_BEAM, GLINT }
|
||||
public enum SpecialRender {
|
||||
NONE, BEACON_BEAM, GLINT, ITEM_GLINT;
|
||||
|
||||
public boolean isGlint() { return this == GLINT || this == ITEM_GLINT; }
|
||||
}
|
||||
|
||||
private static final ConcurrentHashMap<TesrMaterial, TesrMaterial> INTERNED = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -48,10 +48,6 @@ public final class FontEffectRegistry {
|
||||
return anyRegistered && lowerCode >= 'a' && lowerCode <= 'z' && EFFECTS[lowerCode - 'a'] != null;
|
||||
}
|
||||
|
||||
static long bit(char lowerCode) {
|
||||
return 1L << (lowerCode - 'a');
|
||||
}
|
||||
|
||||
static CustomGlyphEffect get(int bitIndex) {
|
||||
return EFFECTS[bitIndex];
|
||||
}
|
||||
|
||||
@@ -31,15 +31,19 @@ public final class FontProviderBitmap implements FontProvider {
|
||||
|
||||
private static final ResourceLocation DEFINITION = new ResourceLocation("angelica", "font/modern.json");
|
||||
private static final int DEFAULT_HEIGHT = 8;
|
||||
private static final FontProviderBitmap[] NONE = new FontProviderBitmap[0];
|
||||
static final FontProviderBitmap[] NONE = new FontProviderBitmap[0];
|
||||
|
||||
private static volatile FontProviderBitmap[] loaded;
|
||||
|
||||
public static void registerReloadListener() {
|
||||
((IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager()).registerReloadListener(manager -> loaded = null);
|
||||
((IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager()).registerReloadListener(manager -> {
|
||||
loaded = null;
|
||||
FontProviderCache.invalidate();
|
||||
});
|
||||
}
|
||||
|
||||
private final ResourceLocation sheet;
|
||||
private int texture;
|
||||
private final int columns;
|
||||
private final float uSize;
|
||||
private final float vSize;
|
||||
@@ -60,7 +64,7 @@ public final class FontProviderBitmap implements FontProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static FontProviderBitmap[] providers() {
|
||||
static FontProviderBitmap[] providers() {
|
||||
FontProviderBitmap[] local = loaded;
|
||||
if (local == null) {
|
||||
final IResourceManager resources = Minecraft.getMinecraft().getResourceManager();
|
||||
@@ -229,7 +233,12 @@ public final class FontProviderBitmap implements FontProvider {
|
||||
|
||||
@Override
|
||||
public int getTexture(char chr) {
|
||||
return FontStrategist.getIntFromResourceLocation(this.sheet);
|
||||
int texture = this.texture;
|
||||
if (texture == 0) {
|
||||
texture = FontStrategist.getIntFromResourceLocation(this.sheet);
|
||||
this.texture = texture;
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
|
||||
final class FontProviderCache {
|
||||
|
||||
private static final byte UNRESOLVED = 0;
|
||||
private static final byte MC = 1;
|
||||
private static final byte UNICODE = 2;
|
||||
private static final byte CUSTOM_PRIMARY = 3;
|
||||
private static final byte CUSTOM_FALLBACK = 4;
|
||||
private static final byte BITMAP = 5;
|
||||
private static final int MAX_BITMAPS = Byte.MAX_VALUE - BITMAP;
|
||||
private static final int VARIANT_COUNT = 8;
|
||||
|
||||
private static FontProviderCache[] variants = new FontProviderCache[VARIANT_COUNT];
|
||||
private static int generation;
|
||||
|
||||
private final boolean custom;
|
||||
private final boolean unicode;
|
||||
private final FontProviderBitmap[] bitmaps;
|
||||
private final byte[][] pages = new byte[256][];
|
||||
|
||||
private FontProviderCache(boolean custom, boolean unicode, boolean modern) {
|
||||
this.custom = custom;
|
||||
this.unicode = unicode;
|
||||
this.bitmaps = modern && !unicode ? FontProviderBitmap.providers() : FontProviderBitmap.NONE;
|
||||
}
|
||||
|
||||
static FontProvider lookup(char chr, boolean custom, boolean unicode) {
|
||||
final int variant = (custom ? 4 : 0) | (unicode ? 2 : 0) | (FontConfig.enableModernFont ? 1 : 0);
|
||||
final FontProviderCache[] tables = variants;
|
||||
FontProviderCache table = tables[variant];
|
||||
if (table == null) {
|
||||
table = new FontProviderCache(custom, unicode, (variant & 1) != 0);
|
||||
tables[variant] = table;
|
||||
}
|
||||
return table.bitmaps.length > MAX_BITMAPS ? null : table.get(chr);
|
||||
}
|
||||
|
||||
static void invalidate() {
|
||||
generation++;
|
||||
variants = new FontProviderCache[VARIANT_COUNT];
|
||||
}
|
||||
|
||||
static int generation() {
|
||||
return generation;
|
||||
}
|
||||
|
||||
private FontProvider get(char chr) {
|
||||
final int hi = chr >>> 8;
|
||||
byte[] page = pages[hi];
|
||||
if (page == null) {
|
||||
page = new byte[256];
|
||||
pages[hi] = page;
|
||||
}
|
||||
final int lo = chr & 0xFF;
|
||||
byte id = page[lo];
|
||||
if (id == UNRESOLVED) {
|
||||
id = resolve(chr);
|
||||
page[lo] = id;
|
||||
}
|
||||
return switch (id) {
|
||||
case MC -> FontProviderMC.get(false);
|
||||
case UNICODE -> FontProviderUnicode.get();
|
||||
case CUSTOM_PRIMARY -> FontProviderCustom.getPrimary();
|
||||
case CUSTOM_FALLBACK -> FontProviderCustom.getFallback();
|
||||
default -> bitmaps[id - BITMAP];
|
||||
};
|
||||
}
|
||||
|
||||
private byte resolve(char chr) {
|
||||
if (custom) {
|
||||
if (FontProviderCustom.getPrimary().isGlyphAvailable(chr)) {
|
||||
return CUSTOM_PRIMARY;
|
||||
}
|
||||
if (FontProviderCustom.getFallback().isGlyphAvailable(chr)) {
|
||||
return CUSTOM_FALLBACK;
|
||||
}
|
||||
final int bitmap = bitmapIndex(chr);
|
||||
return bitmap >= 0 ? (byte) (BITMAP + bitmap) : UNICODE;
|
||||
}
|
||||
final int bitmap = bitmapIndex(chr);
|
||||
if (bitmap >= 0) {
|
||||
return (byte) (BITMAP + bitmap);
|
||||
}
|
||||
return !unicode && FontProviderMC.get(false).isGlyphAvailable(chr) ? MC : UNICODE;
|
||||
}
|
||||
|
||||
private int bitmapIndex(char chr) {
|
||||
for (int i = 0; i < bitmaps.length; i++) {
|
||||
if (bitmaps[i].isGlyphAvailable(chr)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@ public final class FontProviderCustom implements FontProvider {
|
||||
this.atlasTextures[i] = 0;
|
||||
}
|
||||
}
|
||||
FontProviderCache.invalidate();
|
||||
}
|
||||
|
||||
public void reloadFont(int fontID) {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
|
||||
import jss.util.RandomXoshiro256StarStar;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class FontProviderMC implements FontProvider {
|
||||
|
||||
/**
|
||||
@@ -22,30 +24,38 @@ public final class FontProviderMC implements FontProvider {
|
||||
return (isSGA ? InstLoader.instanceSGA : InstLoader.instance);
|
||||
}
|
||||
|
||||
private static final Char2ShortOpenHashMap MCFONT_ASCII_MAP = new Char2ShortOpenHashMap();
|
||||
private static final short[] MCFONT_SLOTS;
|
||||
ResourceLocation locationFontTexture = null;
|
||||
private record TextureSlot(ResourceLocation location, int id) {}
|
||||
|
||||
private TextureSlot textureSlot = new TextureSlot(null, 0);
|
||||
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
|
||||
public int[] charWidth;
|
||||
|
||||
static {
|
||||
MCFONT_ASCII_MAP.defaultReturnValue((short) -1);
|
||||
int maxChar = 0;
|
||||
for (int i = 0; i < MCFONT_CHARS.length(); i++) {
|
||||
maxChar = Math.max(maxChar, MCFONT_CHARS.charAt(i));
|
||||
}
|
||||
MCFONT_SLOTS = new short[maxChar + 1];
|
||||
Arrays.fill(MCFONT_SLOTS, (short) -1);
|
||||
for (short i = 0; i < MCFONT_CHARS.length(); i++) {
|
||||
MCFONT_ASCII_MAP.put(MCFONT_CHARS.charAt(i), i);
|
||||
MCFONT_SLOTS[MCFONT_CHARS.charAt(i)] = i;
|
||||
}
|
||||
}
|
||||
|
||||
public int lookupMcFontPosition(char ch) {
|
||||
return MCFONT_ASCII_MAP.get(ch);
|
||||
return ch < MCFONT_SLOTS.length ? MCFONT_SLOTS[ch] : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGlyphAvailable(char chr) {
|
||||
return MCFONT_ASCII_MAP.containsKey(chr);
|
||||
return lookupMcFontPosition(chr) != -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* A random glyph of the same width, for {@code §k}. Only slots the reverse lookup
|
||||
* agrees with are eligible: {@link #MCFONT_CHARS} repeats {@code U+0000} and the map
|
||||
* agrees with are eligible: {@link #MCFONT_CHARS} repeats {@code U+0000} and the table
|
||||
* keeps the last, so other slots would measure from a different one. Bounded, since
|
||||
* a resource pack may ship widths where nothing matches.
|
||||
*/
|
||||
@@ -61,7 +71,7 @@ public final class FontProviderMC implements FontProvider {
|
||||
continue;
|
||||
}
|
||||
final char replacement = MCFONT_CHARS.charAt(candidate);
|
||||
if (MCFONT_ASCII_MAP.get(replacement) == candidate) {
|
||||
if (lookupMcFontPosition(replacement) == candidate) {
|
||||
return replacement;
|
||||
}
|
||||
}
|
||||
@@ -110,7 +120,13 @@ public final class FontProviderMC implements FontProvider {
|
||||
|
||||
@Override
|
||||
public int getTexture(char chr) {
|
||||
return FontStrategist.getIntFromResourceLocation(locationFontTexture);
|
||||
final ResourceLocation location = locationFontTexture;
|
||||
TextureSlot slot = textureSlot;
|
||||
if (!Objects.equals(slot.location(), location)) {
|
||||
slot = new TextureSlot(location, FontStrategist.getIntFromResourceLocation(location));
|
||||
textureSlot = slot;
|
||||
}
|
||||
return slot.id();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -34,6 +34,7 @@ public final class FontProviderUnicode implements FontProvider, IResourceManager
|
||||
public static FontProviderUnicode get() { return FontProviderUnicode.InstLoader.instance; }
|
||||
|
||||
private static final ResourceLocation[] unicodePageLocations = new ResourceLocation[256];
|
||||
private static final int[] unicodePageTextures = new int[256];
|
||||
private final byte[] glyphWidth = new byte[65536];
|
||||
private final RandomXoshiro256StarStar fontRandom = new RandomXoshiro256StarStar();
|
||||
|
||||
@@ -129,8 +130,12 @@ public final class FontProviderUnicode implements FontProvider, IResourceManager
|
||||
@Override
|
||||
public int getTexture(char chr) {
|
||||
final int uniPage = chr / 256;
|
||||
ResourceLocation rl = getUnicodePageLocation(uniPage);
|
||||
return FontStrategist.getIntFromResourceLocation(rl);
|
||||
int texture = unicodePageTextures[uniPage];
|
||||
if (texture == 0) {
|
||||
texture = FontStrategist.getIntFromResourceLocation(getUnicodePageLocation(uniPage));
|
||||
unicodePageTextures[uniPage] = texture;
|
||||
}
|
||||
return texture;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -161,6 +161,14 @@ public class FontStrategist {
|
||||
if (me.bookMode) {
|
||||
return FontProviderUnicode.get();
|
||||
}
|
||||
if (!me.isSplash && GLStateManager.isMainThread()) {
|
||||
final FontProvider cached = FontProviderCache.lookup(chr, customFontEnabled, forceUnicode);
|
||||
if (cached != null) { return cached; }
|
||||
}
|
||||
return searchFontProvider(me, chr, customFontEnabled, forceUnicode);
|
||||
}
|
||||
|
||||
static FontProvider searchFontProvider(BatchingFontRenderer me, char chr, boolean customFontEnabled, boolean forceUnicode) {
|
||||
if (customFontEnabled && !me.isSplash) {
|
||||
FontProvider fp;
|
||||
fp = FontProviderCustom.getPrimary();
|
||||
@@ -235,23 +243,30 @@ public class FontStrategist {
|
||||
static final int INVALID_TEXTURE = Integer.MIN_VALUE;
|
||||
private static int remap(int n) { return -1 - n; } // involution
|
||||
|
||||
private static final Object RESOURCE_LOCK = new Object();
|
||||
private static final List<ResourceLocation> intToResourceLocation = new ArrayList<>();
|
||||
private static final Map<ResourceLocation, Integer> resourceLocationToInt = new HashMap<>();
|
||||
|
||||
static int getIntFromResourceLocation(ResourceLocation rl) {
|
||||
Integer i = resourceLocationToInt.get(rl);
|
||||
if (i != null) { return i; }
|
||||
int idx = remap(intToResourceLocation.size());
|
||||
resourceLocationToInt.put(rl, idx);
|
||||
intToResourceLocation.add(rl);
|
||||
return idx;
|
||||
synchronized (RESOURCE_LOCK) {
|
||||
Integer i = resourceLocationToInt.get(rl);
|
||||
if (i != null) { return i; }
|
||||
int idx = remap(intToResourceLocation.size());
|
||||
resourceLocationToInt.put(rl, idx);
|
||||
intToResourceLocation.add(rl);
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
||||
static void bindIntTexture(BatchingFontRenderer owner, int texture) {
|
||||
if (texture > 0) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
} else if (texture < 0) {
|
||||
((FontRendererAccessor) owner.underlying).angelica$bindTexture(intToResourceLocation.get(remap(texture)));
|
||||
final ResourceLocation location;
|
||||
synchronized (RESOURCE_LOCK) {
|
||||
location = intToResourceLocation.get(remap(texture));
|
||||
}
|
||||
((FontRendererAccessor) owner.underlying).angelica$bindTexture(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.util.font.FontRendering;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.GRADIENT_PAYLOAD;
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_LENGTH;
|
||||
import static com.gtnewhorizons.angelica.client.font.ColorCodeUtils.SECTION_X_PAYLOAD;
|
||||
|
||||
public final class FormatCodes {
|
||||
|
||||
public static final int NONE = 0;
|
||||
public static final int HEX = 1;
|
||||
public static final int COLOR = 2;
|
||||
public static final int OBFUSCATED = 3;
|
||||
public static final int BOLD = 4;
|
||||
public static final int STRIKETHROUGH = 5;
|
||||
public static final int UNDERLINE = 6;
|
||||
public static final int ITALIC = 7;
|
||||
public static final int RAINBOW = 8;
|
||||
public static final int WAVE = 9;
|
||||
public static final int DINNERBONE = 10;
|
||||
public static final int SHADOW_COLOR = 11;
|
||||
public static final int SHADOW_TOGGLE = 12;
|
||||
public static final int GRADIENT = 13;
|
||||
public static final int CUSTOM = 14;
|
||||
public static final int RESET = 15;
|
||||
|
||||
private static final int EXTRA_SHIFT = 8;
|
||||
private static final int PAYLOAD_SHIFT = 16;
|
||||
private static final long RGB_MASK = 0xFFFFFFL;
|
||||
|
||||
private FormatCodes() {}
|
||||
|
||||
public static long parse(CharSequence s, int codeIndex, int end) {
|
||||
final char code = Character.toLowerCase(s.charAt(codeIndex));
|
||||
return switch (code) {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> pack(COLOR, 0, code - '0');
|
||||
case 'a', 'b', 'c', 'd', 'e', 'f' -> pack(COLOR, 0, code - 'a' + 10);
|
||||
case 'k' -> OBFUSCATED;
|
||||
case 'l' -> BOLD;
|
||||
case 'm' -> STRIKETHROUGH;
|
||||
case 'n' -> UNDERLINE;
|
||||
case 'o' -> ITALIC;
|
||||
case 'r' -> RESET;
|
||||
case 'q' -> AngelicaConfig.enableRainbow ? RAINBOW : NONE;
|
||||
case 'z' -> AngelicaConfig.enableWaveText ? WAVE : NONE;
|
||||
case 'v' -> AngelicaConfig.enableDinnerboneText ? DINNERBONE : NONE;
|
||||
case 'x' -> {
|
||||
if (!AngelicaConfig.enableRGBColors || codeIndex + SECTION_X_PAYLOAD >= end) yield NONE;
|
||||
final int rgb = ColorCodeUtils.parseHexPairs(s, codeIndex + 1, 6);
|
||||
yield rgb == -1 ? NONE : pack(HEX, SECTION_X_PAYLOAD, rgb);
|
||||
}
|
||||
case 'u' -> {
|
||||
if (!AngelicaConfig.enableDropShadow) yield NONE;
|
||||
final int rgb = codeIndex + SECTION_X_LENGTH < end ? ColorCodeUtils.parseSectionXAt(s, codeIndex + 1) : -1;
|
||||
yield rgb == -1 ? SHADOW_TOGGLE : pack(SHADOW_COLOR, SECTION_X_LENGTH, rgb);
|
||||
}
|
||||
case 'g' -> {
|
||||
if (!AngelicaConfig.enableGradients || codeIndex + GRADIENT_PAYLOAD >= end) yield NONE;
|
||||
final int from = ColorCodeUtils.parseSectionXAt(s, codeIndex + 1);
|
||||
final int to = ColorCodeUtils.parseSectionXAt(s, codeIndex + 1 + SECTION_X_LENGTH);
|
||||
yield from == -1 || to == -1 ? NONE : pack(GRADIENT, GRADIENT_PAYLOAD, ((long) from << 24) | to);
|
||||
}
|
||||
default -> FontEffectRegistry.isRegistered(code) ? pack(CUSTOM, 0, code - 'a') : NONE;
|
||||
};
|
||||
}
|
||||
|
||||
private static long pack(int kind, int extraChars, long payload) {
|
||||
return kind | ((long) extraChars << EXTRA_SHIFT) | (payload << PAYLOAD_SHIFT);
|
||||
}
|
||||
|
||||
public static int kind(long code) {
|
||||
return (int) (code & 0xFF);
|
||||
}
|
||||
|
||||
public static int extraChars(long code) {
|
||||
return (int) ((code >>> EXTRA_SHIFT) & 0xFF);
|
||||
}
|
||||
|
||||
public static int rgb(long code) {
|
||||
return (int) ((code >>> PAYLOAD_SHIFT) & RGB_MASK);
|
||||
}
|
||||
|
||||
public static int colorIndex(long code) {
|
||||
return (int) ((code >>> PAYLOAD_SHIFT) & 0xF);
|
||||
}
|
||||
|
||||
public static int gradientFrom(long code) {
|
||||
return (int) ((code >>> (PAYLOAD_SHIFT + 24)) & RGB_MASK);
|
||||
}
|
||||
|
||||
public static int gradientTo(long code) {
|
||||
return rgb(code);
|
||||
}
|
||||
|
||||
public static long customBit(long code) {
|
||||
return 1L << ((code >>> PAYLOAD_SHIFT) & 0x1F);
|
||||
}
|
||||
|
||||
public static boolean endsBold(long code) {
|
||||
return switch (kind(code)) {
|
||||
case COLOR, RESET -> true;
|
||||
case HEX -> FontRendering.hexColorResetsStyles();
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
|
||||
record GlyphScale(int scaleBits, int aspectBits, float x, float y) {
|
||||
|
||||
private static GlyphScale current = of(FontConfig.glyphScale, FontConfig.glyphAspect);
|
||||
|
||||
private static GlyphScale of(float scale, float aspect) {
|
||||
final double pow = Math.pow(2, aspect);
|
||||
return new GlyphScale(Float.floatToRawIntBits(scale), Float.floatToRawIntBits(aspect), (float) (scale * pow), (float) (scale / pow));
|
||||
}
|
||||
|
||||
static GlyphScale current() {
|
||||
final float scale = FontConfig.glyphScale;
|
||||
final float aspect = FontConfig.glyphAspect;
|
||||
GlyphScale holder = current;
|
||||
if (holder.scaleBits != Float.floatToRawIntBits(scale) || holder.aspectBits != Float.floatToRawIntBits(aspect)) {
|
||||
holder = of(scale, aspect);
|
||||
current = holder;
|
||||
}
|
||||
return holder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.util.font.FontRendering;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.config.FontConfig;
|
||||
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
|
||||
|
||||
public record GlyphSettings(int generation, int fontAAMode, int fontAAStrength, int shadowCopies, int boldCopies, int fontShadowOffset, int fontShadowOffsetUC,
|
||||
int glyphScale, int glyphAspect, int glyphSpacing, int whitespaceScale, int customFontScale, int waveAmplitude, int flags) {
|
||||
|
||||
private static final int CUSTOM_FONT = 1;
|
||||
private static final int MODERN_FONT = 1 << 1;
|
||||
private static final int GLYPH_REPLACEMENTS = 1 << 2;
|
||||
private static final int RGB_COLORS = 1 << 3;
|
||||
private static final int GRADIENTS = 1 << 4;
|
||||
private static final int RAINBOW = 1 << 5;
|
||||
private static final int WAVE = 1 << 6;
|
||||
private static final int DINNERBONE = 1 << 7;
|
||||
private static final int DROP_SHADOW = 1 << 8;
|
||||
private static final int HEX_RESETS_STYLES = 1 << 9;
|
||||
private static final int CACHE_OVERRIDE = 1 << 10;
|
||||
private static final int METRIC_FLAGS = CUSTOM_FONT | MODERN_FONT | GLYPH_REPLACEMENTS | RGB_COLORS | GRADIENTS | DROP_SHADOW | HEX_RESETS_STYLES;
|
||||
|
||||
// One slot per renderingCacheOverride value: the HUD-cache redraw flips it every refresh, and a single slot would allocate on each flip.
|
||||
private static final GlyphSettings[] CURRENT = new GlyphSettings[2];
|
||||
|
||||
public static GlyphSettings current() {
|
||||
final boolean cacheOverride = HUDCaching.renderingCacheOverride;
|
||||
final int generation = FontProviderCache.generation();
|
||||
final int fontAAMode = FontConfig.fontAAMode;
|
||||
final int fontAAStrength = FontConfig.fontAAStrength;
|
||||
final int shadowCopies = FontConfig.shadowCopies;
|
||||
final int boldCopies = FontConfig.boldCopies;
|
||||
final int fontShadowOffset = Float.floatToIntBits(FontConfig.fontShadowOffset);
|
||||
final int fontShadowOffsetUC = Float.floatToIntBits(FontConfig.fontShadowOffsetUC);
|
||||
final int glyphScale = Float.floatToIntBits(FontConfig.glyphScale);
|
||||
final int glyphAspect = Float.floatToIntBits(FontConfig.glyphAspect);
|
||||
final int glyphSpacing = Float.floatToIntBits(FontConfig.glyphSpacing);
|
||||
final int whitespaceScale = Float.floatToIntBits(FontConfig.whitespaceScale);
|
||||
final int customFontScale = Float.floatToIntBits(FontConfig.customFontScale);
|
||||
final int waveAmplitude = Float.floatToIntBits(AngelicaConfig.waveAmplitude);
|
||||
final int flags = (FontConfig.enableCustomFont ? CUSTOM_FONT : 0)
|
||||
| (FontConfig.enableModernFont ? MODERN_FONT : 0)
|
||||
| (FontConfig.enableGlyphReplacements ? GLYPH_REPLACEMENTS : 0)
|
||||
| (AngelicaConfig.enableRGBColors ? RGB_COLORS : 0)
|
||||
| (AngelicaConfig.enableGradients ? GRADIENTS : 0)
|
||||
| (AngelicaConfig.enableRainbow ? RAINBOW : 0)
|
||||
| (AngelicaConfig.enableWaveText ? WAVE : 0)
|
||||
| (AngelicaConfig.enableDinnerboneText ? DINNERBONE : 0)
|
||||
| (AngelicaConfig.enableDropShadow ? DROP_SHADOW : 0)
|
||||
| (FontRendering.hexColorResetsStyles() ? HEX_RESETS_STYLES : 0)
|
||||
| (cacheOverride ? CACHE_OVERRIDE : 0);
|
||||
final int slot = cacheOverride ? 1 : 0;
|
||||
final GlyphSettings cached = CURRENT[slot];
|
||||
if (cached != null
|
||||
&& cached.generation == generation
|
||||
&& cached.fontAAMode == fontAAMode
|
||||
&& cached.fontAAStrength == fontAAStrength
|
||||
&& cached.shadowCopies == shadowCopies
|
||||
&& cached.boldCopies == boldCopies
|
||||
&& cached.fontShadowOffset == fontShadowOffset
|
||||
&& cached.fontShadowOffsetUC == fontShadowOffsetUC
|
||||
&& cached.glyphScale == glyphScale
|
||||
&& cached.glyphAspect == glyphAspect
|
||||
&& cached.glyphSpacing == glyphSpacing
|
||||
&& cached.whitespaceScale == whitespaceScale
|
||||
&& cached.customFontScale == customFontScale
|
||||
&& cached.waveAmplitude == waveAmplitude
|
||||
&& cached.flags == flags) {
|
||||
return cached;
|
||||
}
|
||||
final GlyphSettings live = new GlyphSettings(generation, fontAAMode, fontAAStrength, shadowCopies, boldCopies, fontShadowOffset, fontShadowOffsetUC,
|
||||
glyphScale, glyphAspect, glyphSpacing, whitespaceScale, customFontScale, waveAmplitude, flags);
|
||||
CURRENT[slot] = live;
|
||||
return live;
|
||||
}
|
||||
|
||||
public boolean sameMetricsAs(GlyphSettings other) {
|
||||
return generation == other.generation
|
||||
&& glyphScale == other.glyphScale
|
||||
&& glyphAspect == other.glyphAspect
|
||||
&& glyphSpacing == other.glyphSpacing
|
||||
&& whitespaceScale == other.whitespaceScale
|
||||
&& customFontScale == other.customFontScale
|
||||
&& (flags & METRIC_FLAGS) == (other.flags & METRIC_FLAGS);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
final class QuadRegionAllocator {
|
||||
|
||||
private int[] freeStart = new int[16];
|
||||
private int[] freeLength = new int[16];
|
||||
private int freeCount;
|
||||
|
||||
private int[] retiredStart = new int[16];
|
||||
private int[] retiredLength = new int[16];
|
||||
private long[] retiredFrame = new long[16];
|
||||
private int retiredCount;
|
||||
|
||||
QuadRegionAllocator(int capacity) {
|
||||
reset(capacity);
|
||||
}
|
||||
|
||||
void reset(int capacity) {
|
||||
freeCount = 0;
|
||||
retiredCount = 0;
|
||||
if (capacity > 0) {
|
||||
freeStart[0] = 0;
|
||||
freeLength[0] = capacity;
|
||||
freeCount = 1;
|
||||
}
|
||||
}
|
||||
|
||||
int allocate(int quads) {
|
||||
for (int i = 0; i < freeCount; i++) {
|
||||
final int length = freeLength[i];
|
||||
if (length < quads) continue;
|
||||
final int start = freeStart[i];
|
||||
if (length == quads) {
|
||||
System.arraycopy(freeStart, i + 1, freeStart, i, freeCount - i - 1);
|
||||
System.arraycopy(freeLength, i + 1, freeLength, i, freeCount - i - 1);
|
||||
freeCount--;
|
||||
} else {
|
||||
freeStart[i] = start + quads;
|
||||
freeLength[i] = length - quads;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void retire(int start, int quads, long frame) {
|
||||
if (retiredCount == retiredStart.length) {
|
||||
final int n = retiredCount * 2;
|
||||
retiredStart = Arrays.copyOf(retiredStart, n);
|
||||
retiredLength = Arrays.copyOf(retiredLength, n);
|
||||
retiredFrame = Arrays.copyOf(retiredFrame, n);
|
||||
}
|
||||
retiredStart[retiredCount] = start;
|
||||
retiredLength[retiredCount] = quads;
|
||||
retiredFrame[retiredCount] = frame;
|
||||
retiredCount++;
|
||||
}
|
||||
|
||||
void advance(long frame) {
|
||||
final long reusableUpTo = frame - RenderBackend.MAX_FRAMES_IN_FLIGHT;
|
||||
int kept = 0;
|
||||
for (int i = 0; i < retiredCount; i++) {
|
||||
if (retiredFrame[i] <= reusableUpTo) {
|
||||
free(retiredStart[i], retiredLength[i]);
|
||||
} else {
|
||||
retiredStart[kept] = retiredStart[i];
|
||||
retiredLength[kept] = retiredLength[i];
|
||||
retiredFrame[kept] = retiredFrame[i];
|
||||
kept++;
|
||||
}
|
||||
}
|
||||
retiredCount = kept;
|
||||
}
|
||||
|
||||
private void free(int start, int quads) {
|
||||
int at = 0;
|
||||
while (at < freeCount && freeStart[at] < start) at++;
|
||||
final boolean joinsPrev = at > 0 && freeStart[at - 1] + freeLength[at - 1] == start;
|
||||
final boolean joinsNext = at < freeCount && start + quads == freeStart[at];
|
||||
if (joinsPrev && joinsNext) {
|
||||
freeLength[at - 1] += quads + freeLength[at];
|
||||
System.arraycopy(freeStart, at + 1, freeStart, at, freeCount - at - 1);
|
||||
System.arraycopy(freeLength, at + 1, freeLength, at, freeCount - at - 1);
|
||||
freeCount--;
|
||||
} else if (joinsPrev) {
|
||||
freeLength[at - 1] += quads;
|
||||
} else if (joinsNext) {
|
||||
freeStart[at] = start;
|
||||
freeLength[at] += quads;
|
||||
} else {
|
||||
if (freeCount == freeStart.length) {
|
||||
freeStart = Arrays.copyOf(freeStart, freeCount * 2);
|
||||
freeLength = Arrays.copyOf(freeLength, freeCount * 2);
|
||||
}
|
||||
System.arraycopy(freeStart, at, freeStart, at + 1, freeCount - at);
|
||||
System.arraycopy(freeLength, at, freeLength, at + 1, freeCount - at);
|
||||
freeStart[at] = start;
|
||||
freeLength[at] = quads;
|
||||
freeCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.FeedbackManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
final class RetainedTextStore {
|
||||
|
||||
private static final int INITIAL_QUADS = 4096;
|
||||
private static final long EVICT_AFTER_NANOS = 5_000_000_000L;
|
||||
|
||||
// Retired regions and buffers wait MAX_FRAMES_IN_FLIGHT frames: SDL-GPU runs a frame's uploads before its draws, so reusing one sooner overwrites bytes an in-flight draw still reads.
|
||||
private static final QuadRegionAllocator allocator = new QuadRegionAllocator(0);
|
||||
private static final ObjectArrayList<TextMesh> resident = new ObjectArrayList<>();
|
||||
private static int buffer;
|
||||
private static int capacityQuads = INITIAL_QUADS;
|
||||
private static int generation;
|
||||
private static long frame;
|
||||
private static long frameNanos = System.nanoTime();
|
||||
private static int[] retiredBuffers = new int[4];
|
||||
private static long[] retiredBufferFrames = new long[4];
|
||||
private static int retiredBufferCount;
|
||||
|
||||
private RetainedTextStore() {}
|
||||
|
||||
static int buffer() {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static boolean ensure(TextMesh mesh) {
|
||||
if (!GLStateManager.isMainThread() || mesh.quadCount > BatchingFontRenderer.QUAD_BUDGET || GLStateManager.isRecordingDisplayList() || FeedbackManager.isFeedbackMode()) {
|
||||
return false;
|
||||
}
|
||||
if (mesh.regionStart >= 0 && mesh.regionGeneration == generation) {
|
||||
mesh.lastUsedNanos = frameNanos;
|
||||
return true;
|
||||
}
|
||||
final int quads = mesh.quadCount;
|
||||
if (buffer == 0) {
|
||||
create(Math.max(capacityQuads, quads));
|
||||
}
|
||||
int start = allocator.allocate(quads);
|
||||
if (start < 0) {
|
||||
int capacity = capacityQuads * 2;
|
||||
while (capacity < quads) capacity *= 2;
|
||||
retireBuffer();
|
||||
create(capacity);
|
||||
start = allocator.allocate(quads);
|
||||
}
|
||||
upload(mesh, start);
|
||||
mesh.regionStart = start;
|
||||
mesh.regionQuads = quads;
|
||||
mesh.regionGeneration = generation;
|
||||
mesh.lastUsedNanos = frameNanos;
|
||||
resident.add(mesh);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void release(TextMesh mesh) {
|
||||
if (mesh.regionStart >= 0 && mesh.regionGeneration == generation) {
|
||||
final int i = resident.indexOf(mesh);
|
||||
if (i >= 0) {
|
||||
evict(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mesh.regionStart = -1;
|
||||
mesh.regionQuads = 0;
|
||||
}
|
||||
|
||||
static void endFrame() {
|
||||
frame++;
|
||||
final long now = System.nanoTime();
|
||||
frameNanos = now;
|
||||
allocator.advance(frame);
|
||||
if (retiredBufferCount > 0) {
|
||||
deleteExpiredBuffers();
|
||||
}
|
||||
if (resident.isEmpty()) {
|
||||
retireBuffer();
|
||||
return;
|
||||
}
|
||||
for (int i = resident.size() - 1; i >= 0; i--) {
|
||||
if (now - resident.get(i).lastUsedNanos > EVICT_AFTER_NANOS) {
|
||||
evict(i);
|
||||
}
|
||||
}
|
||||
if (resident.isEmpty()) {
|
||||
retireBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
private static void evict(int index) {
|
||||
final TextMesh mesh = resident.get(index);
|
||||
allocator.retire(mesh.regionStart, mesh.regionQuads, frame);
|
||||
final TextMesh last = resident.pop();
|
||||
if (index < resident.size()) {
|
||||
resident.set(index, last);
|
||||
}
|
||||
mesh.regionStart = -1;
|
||||
mesh.regionQuads = 0;
|
||||
}
|
||||
|
||||
private static void create(int capacity) {
|
||||
final int previous = GLStateManager.ctx().boundVBO;
|
||||
buffer = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, buffer);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, (long) capacity * BatchingFontRenderer.QUAD_BYTES, GL15.GL_DYNAMIC_DRAW);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, previous);
|
||||
capacityQuads = capacity;
|
||||
allocator.reset(capacity);
|
||||
resident.clear();
|
||||
generation++;
|
||||
}
|
||||
|
||||
private static void upload(TextMesh mesh, int start) {
|
||||
final int previous = GLStateManager.ctx().boundVBO;
|
||||
final ByteBuffer vertices = mesh.vertices;
|
||||
vertices.position(0);
|
||||
vertices.limit(mesh.quadCount * BatchingFontRenderer.QUAD_BYTES);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, buffer);
|
||||
GLStateManager.glBufferSubData(GL15.GL_ARRAY_BUFFER, (long) start * BatchingFontRenderer.QUAD_BYTES, vertices);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, previous);
|
||||
vertices.clear();
|
||||
}
|
||||
|
||||
private static void retireBuffer() {
|
||||
if (buffer == 0) return;
|
||||
if (retiredBufferCount == retiredBuffers.length) {
|
||||
retiredBuffers = Arrays.copyOf(retiredBuffers, retiredBufferCount * 2);
|
||||
retiredBufferFrames = Arrays.copyOf(retiredBufferFrames, retiredBufferCount * 2);
|
||||
}
|
||||
retiredBuffers[retiredBufferCount] = buffer;
|
||||
retiredBufferFrames[retiredBufferCount] = frame;
|
||||
retiredBufferCount++;
|
||||
buffer = 0;
|
||||
resident.clear();
|
||||
generation++;
|
||||
}
|
||||
|
||||
private static void deleteExpiredBuffers() {
|
||||
final long deletableUpTo = frame - RenderBackend.MAX_FRAMES_IN_FLIGHT;
|
||||
int kept = 0;
|
||||
for (int i = 0; i < retiredBufferCount; i++) {
|
||||
final int id = retiredBuffers[i];
|
||||
if (retiredBufferFrames[i] <= deletableUpTo) {
|
||||
BatchingFontRenderer.forgetVertexSource(id);
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
} else {
|
||||
retiredBuffers[kept] = id;
|
||||
retiredBufferFrames[kept] = retiredBufferFrames[i];
|
||||
kept++;
|
||||
}
|
||||
}
|
||||
retiredBufferCount = kept;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ColorMask;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PolygonState;
|
||||
@@ -28,23 +29,23 @@ final class TextDrawState {
|
||||
float offsetUnits;
|
||||
float alphaRef;
|
||||
|
||||
void captureLive() {
|
||||
depthTest = GLStateManager.getDepthTest().isEnabled();
|
||||
depthFunc = GLStateManager.getDepthState().getFunc();
|
||||
depthMask = GLStateManager.isEffectiveDepthMaskEnabled();
|
||||
final ColorMask colorMask = GLStateManager.getEffectiveColorMask(colorMaskScratch);
|
||||
void captureLive(GLContextState ctx) {
|
||||
depthTest = ctx.depthTest.isEnabled();
|
||||
depthFunc = ctx.depthState.getFunc();
|
||||
depthMask = ctx.depthState.isEffectiveMaskEnabled();
|
||||
final ColorMask colorMask = ctx.colorMask.readEffective(colorMaskScratch);
|
||||
colorRed = colorMask.red;
|
||||
colorGreen = colorMask.green;
|
||||
colorBlue = colorMask.blue;
|
||||
colorAlpha = colorMask.alpha;
|
||||
final PolygonState polygon = GLStateManager.getPolygonState();
|
||||
cull = GLStateManager.getCullState().isEnabled();
|
||||
final PolygonState polygon = ctx.polygonState;
|
||||
cull = ctx.cullState.isEnabled();
|
||||
cullFace = polygon.getCullFaceMode();
|
||||
frontFace = polygon.getFrontFace();
|
||||
polygonOffset = GLStateManager.getPolygonOffsetFillState().isEnabled();
|
||||
polygonOffset = ctx.polygonOffsetFillState.isEnabled();
|
||||
offsetFactor = polygon.getOffsetFactor();
|
||||
offsetUnits = polygon.getOffsetUnits();
|
||||
alphaRef = GLStateManager.getAlphaState().getReference();
|
||||
alphaRef = ctx.alphaState.getReference();
|
||||
}
|
||||
|
||||
boolean sameAs(TextDrawState other) {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.gtnewhorizons.angelica.client.font;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memRealloc;
|
||||
|
||||
public final class TextMesh {
|
||||
|
||||
private static final int INITIAL_COMMANDS = 8;
|
||||
|
||||
GlyphSettings settings;
|
||||
ByteBuffer vertices;
|
||||
long address;
|
||||
int quadCount;
|
||||
int[] commandStart = new int[INITIAL_COMMANDS];
|
||||
int[] commandCount = new int[INITIAL_COMMANDS];
|
||||
int[] commandTexture = new int[INITIAL_COMMANDS];
|
||||
int[] commandLayer = new int[INITIAL_COMMANDS];
|
||||
boolean[] commandUnicode = new boolean[INITIAL_COMMANDS];
|
||||
int commands;
|
||||
boolean captured;
|
||||
boolean animated;
|
||||
int regionStart = -1;
|
||||
int regionQuads;
|
||||
int regionGeneration;
|
||||
long lastUsedNanos;
|
||||
|
||||
public boolean isValid(GlyphSettings live) {
|
||||
return captured && !BatchingFontRenderer.recolorActive() && live.equals(settings);
|
||||
}
|
||||
|
||||
public boolean isAnimated() {
|
||||
return animated;
|
||||
}
|
||||
|
||||
void reserveVertexBytes(int bytes) {
|
||||
if (vertices == null) {
|
||||
vertices = memAlloc(Math.max(bytes, 1));
|
||||
} else if (vertices.capacity() < bytes) {
|
||||
vertices = memRealloc(vertices, Math.max(bytes, vertices.capacity() * 2));
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
address = memAddress0(vertices);
|
||||
}
|
||||
|
||||
public void free() {
|
||||
RetainedTextStore.release(this);
|
||||
memFree(vertices);
|
||||
vertices = null;
|
||||
address = 0L;
|
||||
captured = false;
|
||||
}
|
||||
|
||||
void addCommand(int start, int count, int texture, boolean unicode, int layer) {
|
||||
if (commands == commandStart.length) {
|
||||
final int n = commands * 2;
|
||||
commandStart = Arrays.copyOf(commandStart, n);
|
||||
commandCount = Arrays.copyOf(commandCount, n);
|
||||
commandTexture = Arrays.copyOf(commandTexture, n);
|
||||
commandLayer = Arrays.copyOf(commandLayer, n);
|
||||
commandUnicode = Arrays.copyOf(commandUnicode, n);
|
||||
}
|
||||
commandStart[commands] = start;
|
||||
commandCount[commands] = count;
|
||||
commandTexture[commands] = texture;
|
||||
commandLayer[commands] = layer;
|
||||
commandUnicode[commands] = unicode;
|
||||
commands++;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,13 @@ package com.gtnewhorizons.angelica.commands;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.debug.ChunkDebugMinimap;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugScreenEntryStatus;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugEntryDef;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugFactImpl;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugScreen;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugScreenEntries;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugScreenEntryList;
|
||||
import com.gtnewhorizons.angelica.debug.screen.entries.AngelicaEntries;
|
||||
import com.gtnewhorizons.angelica.debug.flyby.FlybyRoute;
|
||||
import com.gtnewhorizons.angelica.debug.flyby.FlybyRunner;
|
||||
import com.gtnewhorizons.angelica.debug.profiling.AsprofRecorder;
|
||||
@@ -13,7 +19,6 @@ import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.TracyBackend;
|
||||
import com.gtnewhorizons.angelica.rendering.IsbrhTestRenderer;
|
||||
import com.gtnewhorizons.angelica.rendering.RenderRecovery;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.command.CommandBase;
|
||||
@@ -38,7 +43,7 @@ public class AngelicaCommand extends CommandBase {
|
||||
final Map<String, String> help = new LinkedHashMap<>();
|
||||
if (SystemProperties.debugTooling()) {
|
||||
help.put("wireframe", helpLine("wireframe", "Toggle wireframe rendering"));
|
||||
help.put("fog", helpLine("fog", "Toggle fog debug on F3"));
|
||||
help.put("fog", helpLine("fog", "Toggle always-on fog debug info"));
|
||||
help.put("minimap", helpLine("minimap", "Toggle chunk debug overlay"));
|
||||
help.put("flyby", helpLine("flyby <" + FlybyRoute.ids() + ">", "Run a deterministic benchmark route"));
|
||||
help.put("profile", helpLine("profile <start|stop|status>", "Control async-profiler (JFR) recording"));
|
||||
@@ -154,19 +159,33 @@ public class AngelicaCommand extends CommandBase {
|
||||
}
|
||||
|
||||
private void handleFog(ICommandSender sender) {
|
||||
CeleritasDebugScreenHandler.showFogDebug = !CeleritasDebugScreenHandler.showFogDebug;
|
||||
final String f3State = CeleritasDebugScreenHandler.showFogDebug ? "ON" : "OFF";
|
||||
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Fog debug (F3): " + f3State));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " " + CeleritasDebugScreenHandler.getFogDebugString()));
|
||||
if (debugScreenUnavailable(sender)) return;
|
||||
final boolean on = toggleAlways(DebugScreenEntries.ANGELICA_FOG);
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Fog debug: " + (on ? "ON" : "OFF")));
|
||||
final StringBuilder fogText = new StringBuilder();
|
||||
AngelicaEntries.writeFog(new DebugFactImpl().intoPlain(fogText));
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " " + fogText));
|
||||
}
|
||||
|
||||
private void handleMinimap(ICommandSender sender) {
|
||||
ChunkDebugMinimap.toggle();
|
||||
final String state = ChunkDebugMinimap.isEnabled() ? "ON" : "OFF";
|
||||
if (debugScreenUnavailable(sender)) return;
|
||||
final String state = toggleAlways(DebugScreenEntries.ANGELICA_CHUNK_MINIMAP) ? "ON" : "OFF";
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Chunk debug minimap: " + state));
|
||||
}
|
||||
|
||||
private static boolean debugScreenUnavailable(ICommandSender sender) {
|
||||
if (DebugScreen.ENTRIES != null) return false;
|
||||
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.RED + "The debug screen failed to start; see the log"));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean toggleAlways(DebugEntryDef def) {
|
||||
final DebugScreenEntryList entries = DebugScreen.ENTRIES;
|
||||
final boolean on = entries.effectiveStatus(def) != DebugScreenEntryStatus.ALWAYS_ON;
|
||||
entries.overrideForSession(def, on ? DebugScreenEntryStatus.ALWAYS_ON : DebugScreenEntryStatus.NEVER);
|
||||
return on;
|
||||
}
|
||||
|
||||
private void handleCrashTest(ICommandSender sender, String[] args) {
|
||||
if (args.length >= 2 && "isbrh".equalsIgnoreCase(args[1])) {
|
||||
IsbrhTestRenderer.armCrashTest();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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;
|
||||
@@ -39,7 +40,7 @@ public class BlockError extends Block {
|
||||
@SubscribeEvent
|
||||
@SideOnly(Side.CLIENT)
|
||||
public void onTextureStitch(TextureStitchEvent.Pre event) {
|
||||
if (event.map.getTextureType() == 0) {
|
||||
if (event.map.getTextureType() == TextureAtlasType.BLOCK) {
|
||||
for (int i = 0; i < icons.length; i++) {
|
||||
icons[i] = event.map.registerIcon("angelica:error_block_" + i);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.gtnewhorizons.angelica.compat.debug;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugContext;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugEntryHandle;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugGroupHandle;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugScreenApi;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugScreenDisplayer;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugScreenEntry;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.world.World;
|
||||
import org.lwjgl.opengl.ContextCapabilities;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import vazkii.botania.client.fx.ParticleRenderDispatcher;
|
||||
import vazkii.botania.common.core.handler.ConfigHandler;
|
||||
import vazkii.botania.common.core.handler.ManaNetworkHandler;
|
||||
import vazkii.botania.common.lib.LibMisc;
|
||||
|
||||
public final class BotaniaDebugEntry implements DebugScreenEntry {
|
||||
|
||||
static final String GROUP_ID = "botania:botania";
|
||||
static final String ENTRY_ID = "botania:debug";
|
||||
|
||||
private static DebugEntryHandle handle;
|
||||
|
||||
private final DebugGroupHandle group;
|
||||
private final String version = displayVersion(runtimeVersion());
|
||||
private int collectors;
|
||||
private int pools;
|
||||
private String glVersion;
|
||||
private String glRenderer;
|
||||
private String glslVersion;
|
||||
|
||||
private BotaniaDebugEntry(DebugGroupHandle group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
static void register() {
|
||||
handle = DebugCompat.register(GROUP_ID, ENTRY_ID, "Botania", 0x00AA00, entry -> new BotaniaDebugEntry(DebugScreenApi.group(GROUP_ID)));
|
||||
}
|
||||
|
||||
public static boolean active() {
|
||||
return DebugCompat.active(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void display(DebugScreenDisplayer displayer, DebugContext context) {
|
||||
if (!ConfigHandler.showF3Debug) return;
|
||||
if (context.isSampleDue()) {
|
||||
final World world = context.world();
|
||||
collectors = ManaNetworkHandler.instance.getAllCollectorsInWorld(world).size();
|
||||
pools = ManaNetworkHandler.instance.getAllPoolsInWorld(world).size();
|
||||
}
|
||||
writeFacts(displayer, group, ParticleRenderDispatcher.sparkleFxCount, ParticleRenderDispatcher.fakeSparkleFxCount, ParticleRenderDispatcher.wispFxCount, ParticleRenderDispatcher.depthIgnoringWispFxCount, ParticleRenderDispatcher.lightningCount, collectors, pools, version);
|
||||
if (!GuiScreen.isCtrlKeyDown() || !GuiScreen.isShiftKeyDown()) {
|
||||
writeContextHint(displayer, group, Minecraft.isRunningOnMac);
|
||||
return;
|
||||
}
|
||||
if (glVersion == null) {
|
||||
glVersion = GLStateManager.glGetString(GL11.GL_VERSION);
|
||||
glRenderer = GLStateManager.glGetString(GL11.GL_RENDERER);
|
||||
glslVersion = GLStateManager.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION);
|
||||
}
|
||||
final ContextCapabilities caps = GLStateManager.capabilities;
|
||||
writeContext(displayer, group, ConfigHandler.useShaders, ConfigHandler.glSecondaryTextureUnit, glVersion, glRenderer, glslVersion, GLStateManager.MAX_TEXTURE_UNITS, caps.GL_ARB_multitexture, caps.GL_ARB_texture_non_power_of_two, caps.OpenGL13);
|
||||
}
|
||||
|
||||
private static String runtimeVersion() {
|
||||
try {
|
||||
return (String) LibMisc.class.getField("VERSION").get(null);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static String displayVersion(String version) {
|
||||
return version == null || version.contains("GRADLE") ? "N/A" : version;
|
||||
}
|
||||
|
||||
static void writeFacts(DebugScreenDisplayer displayer, DebugGroupHandle group, int sparkles, int fakeSparkles, int wisps, int depthIgnoringWisps, int lightning, int collectors, int pools, String version) {
|
||||
displayer.fact(group, "Sparkles").value(sparkles).text(" (").value(fakeSparkles).text(" fake)");
|
||||
displayer.fact(group, "Wisps").value(wisps).text(" (").value(depthIgnoringWisps).text(" depth-ignoring)");
|
||||
displayer.fact(group, "Lightning").value(lightning);
|
||||
displayer.fact(group, "Mana Network").value(collectors).text(" collectors, ").value(pools).text(" pools");
|
||||
displayer.fact(group, "Version").value(version);
|
||||
}
|
||||
|
||||
static void writeContextHint(DebugScreenDisplayer displayer, DebugGroupHandle group, boolean mac) {
|
||||
displayer.line(group).text(mac ? "SHIFT+CMD for context" : "SHIFT+CTRL for context");
|
||||
}
|
||||
|
||||
static void writeContext(DebugScreenDisplayer displayer, DebugGroupHandle group, boolean useShaders, int secondaryUnit, String glVersion, String glRenderer, String glslVersion, int maxTextureUnits, boolean multitexture, boolean nonPowerOfTwo, boolean openGl13) {
|
||||
displayer.line(group).text("Config Context");
|
||||
displayer.fact(group, "shaders.enabled").value(String.valueOf(useShaders));
|
||||
displayer.fact(group, "shaders.secondaryUnit").value(secondaryUnit);
|
||||
displayer.line(group).text("OpenGL Context");
|
||||
displayer.fact(group, "GL_VERSION").value(String.valueOf(glVersion));
|
||||
displayer.fact(group, "GL_RENDERER").value(String.valueOf(glRenderer));
|
||||
displayer.fact(group, "GL_SHADING_LANGUAGE_VERSION").value(String.valueOf(glslVersion));
|
||||
displayer.fact(group, "GL_MAX_TEXTURE_IMAGE_UNITS_ARB").value(maxTextureUnits);
|
||||
displayer.fact(group, "GL_ARB_multitexture").value(String.valueOf(multitexture));
|
||||
displayer.fact(group, "GL_ARB_texture_non_power_of_two").value(String.valueOf(nonPowerOfTwo));
|
||||
displayer.fact(group, "OpenGL13").value(String.valueOf(openGl13));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.gtnewhorizons.angelica.compat.debug;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugEntryCategory;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugEntryHandle;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugEntrySpec;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugEntrySpec.DebugEntryFactory;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugGroupHandle;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugGroupSpec;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugScreenApi;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugScreenEntryStatus;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugRegistry;
|
||||
import cpw.mods.fml.common.Loader;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public final class DebugCompat {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica");
|
||||
|
||||
private DebugCompat() {}
|
||||
|
||||
public static void registerMissing() {
|
||||
if (Loader.isModLoaded("Botania")) registerMissing("botania", BotaniaDebugEntry::register);
|
||||
if (Loader.isModLoaded("journeymap")) registerMissing("journeymap", JourneyMapDebugEntry::register);
|
||||
if (Loader.isModLoaded("hodgepodge")) registerMissing("hodgepodge", HodgepodgeDebugEntry::register);
|
||||
}
|
||||
|
||||
static void registerMissing(String namespace, Runnable register) {
|
||||
if (DebugRegistry.MAIN.hasNamespace(namespace)) return;
|
||||
try {
|
||||
register.run();
|
||||
} catch (RuntimeException | LinkageError e) {
|
||||
LOGGER.error("Debug screen compat entry for {} failed to register", namespace, e);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean active(DebugEntryHandle handle) {
|
||||
return handle != null && !handle.isFailed();
|
||||
}
|
||||
|
||||
static DebugEntryHandle register(String groupId, String entryId, String name, int accent, DebugEntryFactory factory) {
|
||||
DebugScreenApi.registerGroup(new DebugGroupSpec(groupId).title(name).accent(accent).side(DebugGroupHandle.LEFT));
|
||||
return DebugScreenApi.registerEntry(new DebugEntrySpec(entryId, DebugEntryCategory.SCREEN_TEXT)
|
||||
.name(name)
|
||||
.defaults(DebugScreenEntryStatus.IN_OVERLAY, DebugScreenEntryStatus.NEVER)
|
||||
.text(factory));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.gtnewhorizons.angelica.compat.debug;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugContext;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugEntryHandle;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugGroupHandle;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugScreenApi;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugScreenDisplayer;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugScreenEntry;
|
||||
import com.mitchej123.hodgepodge.config.DebugConfig;
|
||||
import com.mitchej123.hodgepodge.mixins.hooks.ChunkGenScheduler;
|
||||
import com.mitchej123.hodgepodge.mixins.hooks.ChunkGenStats;
|
||||
import net.minecraftforge.classloading.FMLForgePlugin;
|
||||
|
||||
public final class HodgepodgeDebugEntry implements DebugScreenEntry {
|
||||
|
||||
static final String GROUP_ID = "hodgepodge:chunkgen";
|
||||
static final String ENTRY_ID = "hodgepodge:chunkgen";
|
||||
|
||||
private static DebugEntryHandle handle;
|
||||
|
||||
private final DebugGroupHandle group;
|
||||
private boolean scheduled;
|
||||
private double budgetUsedMs;
|
||||
private double budgetAvailMs;
|
||||
private double terrainGenEmaMs;
|
||||
private double populateEmaMs;
|
||||
private int genQueue;
|
||||
private int popQueue;
|
||||
private int pipelineBacklog;
|
||||
private int terrainGenCount;
|
||||
private int popCount;
|
||||
private int blockedLoads;
|
||||
private int stuckCount;
|
||||
private int neighborFills;
|
||||
private double debtMs;
|
||||
|
||||
private HodgepodgeDebugEntry(DebugGroupHandle group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
static void register() {
|
||||
if (!DebugConfig.showChunkGenDebug && FMLForgePlugin.RUNTIME_DEOBF) return;
|
||||
handle = DebugCompat.register(GROUP_ID, ENTRY_ID, "Chunk Generation", 0xFFAA00, entry -> new HodgepodgeDebugEntry(DebugScreenApi.group(GROUP_ID)));
|
||||
}
|
||||
|
||||
public static boolean active() {
|
||||
return DebugCompat.active(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void display(DebugScreenDisplayer displayer, DebugContext context) {
|
||||
if (context.isSampleDue()) sample(context);
|
||||
if (!scheduled) return;
|
||||
writeFacts(displayer, group, budgetUsedMs, budgetAvailMs, terrainGenEmaMs, populateEmaMs, genQueue, popQueue, pipelineBacklog, terrainGenCount, popCount, blockedLoads, stuckCount, neighborFills, debtMs);
|
||||
}
|
||||
|
||||
private void sample(DebugContext context) {
|
||||
final ChunkGenScheduler scheduler = ChunkGenScheduler.getForDimension(context.player().dimension);
|
||||
scheduled = scheduler != null;
|
||||
if (scheduler == null) return;
|
||||
final ChunkGenStats stats = scheduler.getStats();
|
||||
budgetUsedMs = stats.getSnapBudgetUsedMs();
|
||||
budgetAvailMs = stats.getSnapBudgetAvailMs();
|
||||
terrainGenEmaMs = stats.getTerrainGenEmaMs();
|
||||
populateEmaMs = stats.getPopulateEmaMs();
|
||||
genQueue = stats.getSnapGenQueue();
|
||||
popQueue = stats.getSnapPopQueue();
|
||||
pipelineBacklog = stats.getSnapPipelineBacklog();
|
||||
terrainGenCount = stats.getSnapTerrainGenCount();
|
||||
popCount = stats.getSnapPopCount();
|
||||
blockedLoads = stats.getSnapBlockedLoads();
|
||||
stuckCount = stats.getSnapStuckCount();
|
||||
neighborFills = stats.getSnapNeighborFills();
|
||||
debtMs = stats.getDebtMs();
|
||||
}
|
||||
|
||||
static void writeFacts(DebugScreenDisplayer displayer, DebugGroupHandle group, double budgetUsedMs, double budgetAvailMs, double terrainGenEmaMs, double populateEmaMs, int genQueue, int popQueue, int pipelineBacklog, int terrainGenCount, int popCount, int blockedLoads, int stuckCount, int neighborFills, double debtMs) {
|
||||
displayer.fact(group, "Budget").value(budgetUsedMs, 1).text(" / ").value(budgetAvailMs, 1).text(" ms");
|
||||
displayer.fact(group, "Average").text("terrain ").value(terrainGenEmaMs, 1).text(" ms, populate ").value(populateEmaMs, 1).text(" ms");
|
||||
displayer.fact(group, "Queues").text("gen ").value(genQueue).text(", populate ").value(popQueue).text(", pipeline ").value(pipelineBacklog);
|
||||
displayer.fact(group, "Active").text("gen ").value(terrainGenCount).text(", populate ").value(popCount);
|
||||
displayer.fact(group, "Blocked").value(blockedLoads).text(" blocked, ").value(stuckCount).text(" stuck, ").value(neighborFills).text(" fills");
|
||||
displayer.fact(group, "Debt").value(debtMs, 1).text(" ms");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.gtnewhorizons.angelica.compat.debug;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugContext;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugEntryHandle;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugFactLines;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugGroupHandle;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugScreenApi;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugScreenDisplayer;
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugScreenEntry;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public final class JourneyMapDebugEntry implements DebugScreenEntry {
|
||||
|
||||
static final String GROUP_ID = "journeymap:journeymap";
|
||||
static final String ENTRY_ID = "journeymap:debug";
|
||||
|
||||
private static final String MAPPING_DISABLED_KEY = "jm.common.enable_mapping_false_text";
|
||||
private static final long REPORT_INTERVAL_NS = TimeUnit.SECONDS.toNanos(3);
|
||||
|
||||
private static DebugEntryHandle handle;
|
||||
|
||||
private final DebugGroupHandle group;
|
||||
private final DebugFactLines facts = new DebugFactLines();
|
||||
private MethodHandle stats;
|
||||
private MethodHandle mappingEnabled;
|
||||
private MethodHandle timerReport;
|
||||
private List<String> report = Collections.emptyList();
|
||||
private long reportAt;
|
||||
private boolean reported;
|
||||
|
||||
private JourneyMapDebugEntry(DebugGroupHandle group) {
|
||||
this.group = group;
|
||||
}
|
||||
|
||||
static void register() {
|
||||
handle = DebugCompat.register(GROUP_ID, ENTRY_ID, "JourneyMap", 0x55FFFF, entry -> new JourneyMapDebugEntry(DebugScreenApi.group(GROUP_ID)));
|
||||
}
|
||||
|
||||
public static boolean active() {
|
||||
return DebugCompat.active(handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void display(DebugScreenDisplayer displayer, DebugContext context) {
|
||||
if (context.isSampleDue()) sample(context.nanoTime());
|
||||
facts.emit(displayer, group);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void sample(long now) {
|
||||
try {
|
||||
if (stats == null) resolve();
|
||||
final boolean mapping = ((AtomicBoolean) mappingEnabled.invokeExact()).get();
|
||||
final boolean profiler = Minecraft.getMinecraft().gameSettings.showDebugProfilerChart;
|
||||
if (profiler && (!reported || now - reportAt > REPORT_INTERVAL_NS)) {
|
||||
report = (List<String>) timerReport.invokeExact("", "");
|
||||
reportAt = now;
|
||||
reported = true;
|
||||
}
|
||||
writeFacts(facts, mapping ? (String[]) stats.invokeExact() : null, mapping ? null : I18n.format(MAPPING_DISABLED_KEY), profiler ? report : null);
|
||||
} catch (RuntimeException | Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
throw new IllegalStateException("JourneyMap debug stats are unavailable", t);
|
||||
}
|
||||
}
|
||||
|
||||
private void resolve() throws ReflectiveOperationException {
|
||||
final MethodHandles.Lookup lookup = MethodHandles.publicLookup();
|
||||
final Class<?> properties = Class.forName("journeymap.client.properties.CoreProperties");
|
||||
final MethodHandle coreProperties = lookup.findStatic(Class.forName("journeymap.client.JourneymapClient"), "getCoreProperties", MethodType.methodType(properties));
|
||||
mappingEnabled = MethodHandles.filterReturnValue(coreProperties, lookup.findGetter(properties, "mappingEnabled", AtomicBoolean.class));
|
||||
timerReport = lookup.findStatic(Class.forName("journeymap.client.log.StatTimer"), "getReportByTotalTime", MethodType.methodType(List.class, String.class, String.class));
|
||||
stats = lookup.findStatic(Class.forName("journeymap.client.task.multi.MapPlayerTask"), "getDebugStats", MethodType.methodType(String[].class));
|
||||
}
|
||||
|
||||
static void writeFacts(DebugFactLines facts, String[] stats, String mappingDisabled, List<String> report) {
|
||||
facts.begin();
|
||||
if (mappingDisabled != null) facts.line(mappingDisabled);
|
||||
if (stats != null) {
|
||||
for (int i = 0; i < stats.length; i++) {
|
||||
final String line = stats[i];
|
||||
if (line != null) facts.split(line);
|
||||
}
|
||||
}
|
||||
if (report != null) {
|
||||
for (int i = 0, n = report.size(); i < n; i++) {
|
||||
final String line = report.get(i);
|
||||
if (line != null) facts.line(line);
|
||||
}
|
||||
}
|
||||
facts.end();
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@ public final class RenderLayer implements BlendingStateHolder {
|
||||
|
||||
private static Hook hookFor(TesrMaterial material, PassOverride pass, int glintSlot, boolean lightmap) {
|
||||
switch (material.special()) {
|
||||
case GLINT -> {
|
||||
case GLINT, ITEM_GLINT -> {
|
||||
return glintHook(glintSlot);
|
||||
}
|
||||
case BEACON_BEAM -> {
|
||||
|
||||
@@ -108,10 +108,6 @@ public class AngelicaConfig {
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean modernizeF3Screen;
|
||||
|
||||
@Config.Comment("Show block registry name and meta value in F3, similar to 1.8+. [From ArchaicFix]")
|
||||
@Config.DefaultBoolean(true)
|
||||
public static boolean showBlockDebugInfo;
|
||||
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.Comment("Hide downloading terrain screen. [From ArchaicFix]")
|
||||
public static boolean hideDownloadingTerrainScreen;
|
||||
@@ -167,6 +163,20 @@ 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
|
||||
@@ -192,6 +202,11 @@ 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;
|
||||
@@ -441,13 +456,14 @@ public class AngelicaConfig {
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean useVanillaChunkTracking;
|
||||
|
||||
@Config.Comment("Disables additional F3 information added by Angelica.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean disableF3Additions;
|
||||
|
||||
@Config.Comment("Shows developer counters (FFP, streaming, TESR, transfer) on the F3 screen. Toggle in game with F3+V.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean verboseF3;
|
||||
@Config.Comment({"Size of the F3 text.",
|
||||
"-1 = same as your GUI Scale (default, the size vanilla F3 uses)",
|
||||
"0 = Auto: half the largest size that fits the window",
|
||||
"1-16 = that size",
|
||||
"F3 is never drawn larger than your GUI Scale."})
|
||||
@Config.DefaultInt(-1)
|
||||
@Config.RangeInt(min = -1, max = 16)
|
||||
public static int debugGuiScale;
|
||||
|
||||
@Config.Comment("Replaces various FFP uploads with statically allocated VBO's.")
|
||||
@Config.DefaultBoolean(true)
|
||||
@@ -548,6 +564,10 @@ public class AngelicaConfig {
|
||||
return enableEntityBatching && enableCubeInstancing;
|
||||
}
|
||||
|
||||
public static boolean entityShadowReplayEnabled() {
|
||||
return enableEntityBatching && enableEntityShadowReplay;
|
||||
}
|
||||
|
||||
public static boolean sdlGpuConfigured() {
|
||||
return renderBackend == RenderBackendChoice.SDL_GPU;
|
||||
}
|
||||
|
||||
@@ -50,4 +50,13 @@ public class CompatConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean fixNTMSpace;
|
||||
|
||||
@Config.Comment({"Rules for F3 lines that other mods add without Angelica's F3 API, one rule per entry.",
|
||||
"A rule matches lines that start with the quoted text (color codes ignored), in the left or right column.",
|
||||
" drop left \"Text: \" hides the line",
|
||||
" fact left \"Text: \" group=<panel id> moves the line into that F3 panel; add name=\"Label\" to rename it",
|
||||
"Panel ids are listed under Panels in the F3+F6 screen; entry ids show when you hover an entry there.",
|
||||
"Add when=<entry id> to apply a rule only while that F3 entry is enabled."})
|
||||
@Config.DefaultStringList({})
|
||||
public static String[] debugLineRules;
|
||||
|
||||
}
|
||||
@@ -22,33 +22,48 @@
|
||||
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import com.gtnewhorizons.angelica.debug.render.DebugDrawScope;
|
||||
import com.gtnewhorizons.angelica.debug.render.DebugGeometry;
|
||||
import com.gtnewhorizons.angelica.debug.render.DebugProgram;
|
||||
import com.gtnewhorizons.angelica.debug.render.DebugPrograms;
|
||||
import com.gtnewhorizons.angelica.debug.render.NativeScratch;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.AngelicaRenderSectionManager;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.ChunkProviderClient;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.world.chunk.EmptyChunk;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Debug overlay that renders a minimap showing chunk loading and rendering state. Toggle via /angelica minimap
|
||||
* Debug overlay that renders a minimap showing chunk loading and rendering state.
|
||||
*/
|
||||
public class ChunkDebugMinimap {
|
||||
private static final ChunkDebugMinimap INSTANCE = new ChunkDebugMinimap();
|
||||
@Getter
|
||||
private static volatile boolean enabled = false;
|
||||
|
||||
private static final int RANGE = 32; // Chunks in each direction from player
|
||||
|
||||
public static synchronized void toggle() {
|
||||
if (enabled) disable();
|
||||
else enable();
|
||||
}
|
||||
private static final int FLAGS = VertexFlags.COLOR_BIT;
|
||||
private static final VertexFormat FORMAT = DefaultVertexFormat.ALL_FORMATS[FLAGS];
|
||||
private static final int STRIDE = FORMAT.getVertexSize();
|
||||
private static final int COLOR_OFFSET = DebugGeometry.offsetOf(FORMAT, VertexFormatElement.Usage.COLOR);
|
||||
private static final int CHUNKS = RANGE * 2 * RANGE * 2;
|
||||
private static final int ALPHA = (int) (0.8F * 255.0F);
|
||||
private static final int BYTES = CHUNKS * 4 * STRIDE;
|
||||
private static final ChunkDebugMinimap INSTANCE = new ChunkDebugMinimap();
|
||||
private static volatile boolean enabled = false;
|
||||
|
||||
private final DebugDrawScope scope = new DebugDrawScope();
|
||||
private final NativeScratch scratch = new NativeScratch(BYTES);
|
||||
private final Matrix4f mvp = new Matrix4f();
|
||||
private long cursor;
|
||||
|
||||
public static synchronized void enable() {
|
||||
if (enabled) return;
|
||||
@@ -60,6 +75,7 @@ public class ChunkDebugMinimap {
|
||||
if (!enabled) return;
|
||||
enabled = false;
|
||||
MinecraftForge.EVENT_BUS.unregister(INSTANCE);
|
||||
INSTANCE.scratch.free();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@@ -84,39 +100,31 @@ public class ChunkDebugMinimap {
|
||||
final int playerChunkX = ((int) Math.floor(pX)) >> 4;
|
||||
final int playerChunkZ = ((int) Math.floor(pZ)) >> 4;
|
||||
|
||||
// Save GL state
|
||||
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
|
||||
GLStateManager.glPushMatrix();
|
||||
try {
|
||||
// Setup rendering
|
||||
GLStateManager.glScaled(2, 2, 1);
|
||||
GLStateManager.glTranslated(32, 32, 0);
|
||||
scratch.ensure(BYTES);
|
||||
cursor = scratch.address();
|
||||
final ChunkProviderClient chunkProvider = (ChunkProviderClient) mc.theWorld.getChunkProvider();
|
||||
|
||||
GLStateManager.disableLighting();
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
tess.startDrawingQuads();
|
||||
|
||||
final ChunkProviderClient chunkProvider = (ChunkProviderClient) mc.theWorld.getChunkProvider();
|
||||
|
||||
for (int x = playerChunkX - RANGE; x < playerChunkX + RANGE; x++) {
|
||||
for (int z = playerChunkZ - RANGE; z < playerChunkZ + RANGE; z++) {
|
||||
drawChunk(tess, x, z, playerChunkX, playerChunkZ, manager, chunkProvider);
|
||||
}
|
||||
for (int x = playerChunkX - RANGE; x < playerChunkX + RANGE; x++) {
|
||||
for (int z = playerChunkZ - RANGE; z < playerChunkZ + RANGE; z++) {
|
||||
drawChunk(x, z, playerChunkX, playerChunkZ, manager, chunkProvider);
|
||||
}
|
||||
}
|
||||
|
||||
tess.draw();
|
||||
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), mvp).scale(2, 2, 1).translate(32, 32, 0);
|
||||
|
||||
scope.beginHud();
|
||||
try {
|
||||
final DebugProgram color = DebugPrograms.color();
|
||||
color.use();
|
||||
color.mvp(mvp);
|
||||
final int bytes = (int) (cursor - scratch.address());
|
||||
TessellatorStreamingDrawer.drawPacked(scratch.window(bytes), GL11.GL_QUADS, FLAGS, bytes / STRIDE);
|
||||
} finally {
|
||||
GLStateManager.glPopMatrix();
|
||||
GLStateManager.glPopAttrib();
|
||||
scope.end();
|
||||
}
|
||||
}
|
||||
|
||||
private void drawChunk(Tessellator tess, int chunkX, int chunkZ, int playerChunkX, int playerChunkZ, AngelicaRenderSectionManager manager, ChunkProviderClient chunkProvider) {
|
||||
private void drawChunk(int chunkX, int chunkZ, int playerChunkX, int playerChunkZ, AngelicaRenderSectionManager manager, ChunkProviderClient chunkProvider) {
|
||||
boolean initialized = false;
|
||||
float r = 0, g = 0, b = 0;
|
||||
|
||||
@@ -142,16 +150,23 @@ public class ChunkDebugMinimap {
|
||||
}
|
||||
}
|
||||
|
||||
tess.setColorRGBA_F(r, g, b, 0.8f);
|
||||
final int argb = ALPHA << 24 | (int) (r * 255) << 16 | (int) (g * 255) << 8 | (int) (b * 255);
|
||||
|
||||
final int xStart = chunkX - playerChunkX;
|
||||
final int zStart = chunkZ - playerChunkZ;
|
||||
final int xEnd = xStart + 1;
|
||||
final int zEnd = zStart + 1;
|
||||
|
||||
tess.addVertex(xStart, zStart, 0);
|
||||
tess.addVertex(xStart, zEnd, 0);
|
||||
tess.addVertex(xEnd, zEnd, 0);
|
||||
tess.addVertex(xEnd, zStart, 0);
|
||||
vertex(xStart, zStart, argb);
|
||||
vertex(xStart, zEnd, argb);
|
||||
vertex(xEnd, zEnd, argb);
|
||||
vertex(xEnd, zStart, argb);
|
||||
}
|
||||
|
||||
private void vertex(float x, float y, int argb) {
|
||||
final long p = cursor;
|
||||
DebugGeometry.putPosition(p, x, y, 0.0F);
|
||||
DebugGeometry.putColor(p + COLOR_OFFSET, argb);
|
||||
cursor = p + STRIDE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
public final class DebugF3Toggle {
|
||||
|
||||
private boolean armed;
|
||||
private boolean comboUsed;
|
||||
private boolean shift;
|
||||
private boolean alt;
|
||||
|
||||
public void arm(boolean shift, boolean alt) {
|
||||
armed = true;
|
||||
comboUsed = false;
|
||||
this.shift = shift;
|
||||
this.alt = alt;
|
||||
}
|
||||
|
||||
public void disarm() {
|
||||
armed = false;
|
||||
comboUsed = false;
|
||||
}
|
||||
|
||||
public boolean isArmed() {
|
||||
return armed;
|
||||
}
|
||||
|
||||
public boolean shift() {
|
||||
return shift;
|
||||
}
|
||||
|
||||
public boolean alt() {
|
||||
return alt;
|
||||
}
|
||||
|
||||
public void onKeyPress(int key) {
|
||||
if (!armed) return;
|
||||
switch (key) {
|
||||
case Keyboard.KEY_F3, Keyboard.KEY_LSHIFT, Keyboard.KEY_RSHIFT, Keyboard.KEY_LMENU, Keyboard.KEY_RMENU -> {}
|
||||
default -> comboUsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onF3Release() {
|
||||
final boolean toggle = armed && !comboUsed;
|
||||
disarm();
|
||||
return toggle;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,70 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.GTNHLib;
|
||||
import com.gtnewhorizon.gtnhlib.config.ConfigurationManager;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugEntryDef;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugOptionsScreen;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugScreen;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugScreenEntries;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IGameSettingsExt;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.common.gameevent.InputEvent;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.entity.RenderManager;
|
||||
import net.minecraft.util.StatCollector;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
/** F3+V toggles {@link AngelicaConfig#verboseF3} */
|
||||
public class DebugKeyHandler {
|
||||
|
||||
public static final DebugF3Toggle F3_TOGGLE = new DebugF3Toggle();
|
||||
|
||||
@SubscribeEvent
|
||||
public void onKey(InputEvent.KeyInputEvent event) {
|
||||
if (!Keyboard.getEventKeyState() || Keyboard.getEventKey() != Keyboard.KEY_V || !Keyboard.isKeyDown(Keyboard.KEY_F3)
|
||||
|| Minecraft.getMinecraft().currentScreen != null) {
|
||||
final int key = Keyboard.getEventKey();
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
|
||||
if (mc.currentScreen != null) {
|
||||
if (mc.currentScreen instanceof DebugOptionsScreen && key == Keyboard.KEY_F6 && Keyboard.getEventKeyState()
|
||||
&& !Keyboard.isRepeatEvent() && Keyboard.isKeyDown(Keyboard.KEY_F3)) {
|
||||
mc.displayGuiScreen(null);
|
||||
}
|
||||
F3_TOGGLE.disarm();
|
||||
return;
|
||||
}
|
||||
|
||||
AngelicaConfig.verboseF3 = !AngelicaConfig.verboseF3;
|
||||
ConfigurationManager.save(AngelicaConfig.class);
|
||||
CeleritasDebugScreenHandler.invalidate();
|
||||
GTNHLib.proxy.addDebugToChat(StatCollector.translateToLocal(AngelicaConfig.verboseF3 ? "angelica.debug.verbose.on" : "angelica.debug.verbose.off"));
|
||||
if (!Keyboard.getEventKeyState()) {
|
||||
if (key == Keyboard.KEY_F3 && F3_TOGGLE.onF3Release()) {
|
||||
mc.gameSettings.showDebugInfo = !mc.gameSettings.showDebugInfo;
|
||||
mc.gameSettings.showDebugProfilerChart = F3_TOGGLE.shift();
|
||||
((IGameSettingsExt) mc.gameSettings).angelica$setShowFpsGraph(F3_TOGGLE.alt());
|
||||
}
|
||||
if (key == Keyboard.KEY_F3) DebugScreen.syncOverlayVisible();
|
||||
return;
|
||||
}
|
||||
|
||||
F3_TOGGLE.onKeyPress(key);
|
||||
|
||||
if (!Keyboard.isKeyDown(Keyboard.KEY_F3)) {
|
||||
return;
|
||||
}
|
||||
if (!Keyboard.isRepeatEvent()) {
|
||||
switch (key) {
|
||||
case Keyboard.KEY_F6 -> DebugOptionsScreen.open(mc);
|
||||
case Keyboard.KEY_V -> toggleAndReport(DebugScreenEntries.ANGELICA_RENDERER_VERBOSE, "angelica.debug.verbose");
|
||||
case Keyboard.KEY_G -> toggleAndReport(DebugScreenEntries.CHUNK_BORDERS, "angelica.debug.chunk_borders");
|
||||
case Keyboard.KEY_B -> {
|
||||
if (DebugScreen.ENTRIES != null) DebugScreen.ENTRIES.toggleStatus(DebugScreenEntries.ENTITY_HITBOXES);
|
||||
}
|
||||
default -> {}
|
||||
}
|
||||
}
|
||||
if (key == Keyboard.KEY_B && DebugScreen.ENTRIES != null) {
|
||||
RenderManager.debugBoundingBox = DebugScreenEntries.ENTITY_HITBOXES.isEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
private static void toggleAndReport(DebugEntryDef def, String key) {
|
||||
if (DebugScreen.ENTRIES == null) return;
|
||||
final boolean on = DebugScreen.ENTRIES.toggleStatus(def);
|
||||
GTNHLib.proxy.addDebugToChat(StatCollector.translateToLocal(key + (on ? ".on" : ".off")));
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,44 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import com.gtnewhorizons.angelica.debug.render.DebugDrawScope;
|
||||
import com.gtnewhorizons.angelica.debug.render.DebugGeometry;
|
||||
import com.gtnewhorizons.angelica.debug.render.DebugPrograms;
|
||||
import com.gtnewhorizons.angelica.debug.render.DebugProgram;
|
||||
import com.gtnewhorizons.angelica.debug.render.NativeScratch;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.LineQuads;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraftforge.client.event.RenderGameOverlayEvent;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
public class F3Direction {
|
||||
private static final float AXIS_LENGTH = 10.0F;
|
||||
private static final float LINE_WIDTH = 2.0F;
|
||||
private static final int SEGMENTS = 3;
|
||||
private static final int LINE_VERTICES = SEGMENTS * 2;
|
||||
private static final int QUAD_VERTICES = SEGMENTS * 6;
|
||||
private static final int FLAGS = VertexFlags.COLOR_BIT;
|
||||
private static final VertexFormat FORMAT = DefaultVertexFormat.ALL_FORMATS[FLAGS];
|
||||
private static final VertexFormat QUAD_FORMAT = LineQuads.quadFormat(FORMAT);
|
||||
private static final int STRIDE = FORMAT.getVertexSize();
|
||||
private static final int COLOR_OFFSET = DebugGeometry.offsetOf(FORMAT, VertexFormatElement.Usage.COLOR);
|
||||
|
||||
private static final DebugDrawScope SCOPE = new DebugDrawScope();
|
||||
private static final Matrix4f MVP = new Matrix4f();
|
||||
private static int vao;
|
||||
|
||||
public static void renderWorldDirectionsEvent(Minecraft mc, RenderGameOverlayEvent.Pre event) {
|
||||
if (mc.gameSettings.showDebugInfo && mc.gameSettings.thirdPersonView == 0) {
|
||||
final ScaledResolution scaledresolution = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
final int width = scaledresolution.getScaledWidth();
|
||||
final int height = scaledresolution.getScaledHeight();
|
||||
if (mc.gameSettings.thirdPersonView == 0) {
|
||||
final int width = event.resolution.getScaledWidth();
|
||||
final int height = event.resolution.getScaledHeight();
|
||||
|
||||
GLStateManager.glPushMatrix();
|
||||
|
||||
@@ -30,33 +55,57 @@ public class F3Direction {
|
||||
GLStateManager.glPopMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
public static void renderWorldDirections() {
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glDepthMask(false);
|
||||
final Tessellator tessellator = Tessellator.instance;
|
||||
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), MVP);
|
||||
final ViewportState viewport = GLStateManager.getViewportState();
|
||||
|
||||
GLStateManager.glLineWidth(2.0F);
|
||||
tessellator.startDrawing(GL11.GL_LINES);
|
||||
SCOPE.beginHud();
|
||||
try {
|
||||
if (vao == 0) build();
|
||||
final DebugProgram lines = DebugPrograms.lines();
|
||||
lines.use();
|
||||
lines.mvp(MVP);
|
||||
lines.lineWidth(LINE_WIDTH);
|
||||
lines.viewport(viewport.width, viewport.height);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, QUAD_VERTICES);
|
||||
} finally {
|
||||
SCOPE.end();
|
||||
}
|
||||
}
|
||||
|
||||
//X
|
||||
tessellator.setColorRGBA_F(255, 0, 0, 1);
|
||||
tessellator.addVertex(0.0D, 0.0D, 0.0D);
|
||||
tessellator.addVertex(10, 0.0D, 0.0D);
|
||||
private static void build() {
|
||||
final int quadBytes = QUAD_VERTICES * QUAD_FORMAT.getVertexSize();
|
||||
final NativeScratch source = new NativeScratch(LINE_VERTICES * STRIDE);
|
||||
final NativeScratch expanded = new NativeScratch(quadBytes);
|
||||
try {
|
||||
source.ensure(LINE_VERTICES * STRIDE);
|
||||
expanded.ensure(quadBytes);
|
||||
long p = source.address();
|
||||
p = putAxis(p, AXIS_LENGTH, 0.0F, 0.0F, 0xFFFF0000);
|
||||
p = putAxis(p, 0.0F, 0.0F, AXIS_LENGTH, 0xFF0000FF);
|
||||
putAxis(p, 0.0F, AXIS_LENGTH, 0.0F, 0xFF00FF00);
|
||||
LineQuads.expandWithOtherEnd(source.address(), FORMAT, GL11.GL_LINES, LINE_VERTICES, expanded.address());
|
||||
|
||||
//Z
|
||||
tessellator.setColorRGBA_F(0, 0, 255, 1);
|
||||
tessellator.addVertex(0.0D, 0.0D, 0.0D);
|
||||
tessellator.addVertex(0.0D, 0.0D, 10);
|
||||
final int vbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, expanded.window(quadBytes), GL15.GL_STATIC_DRAW);
|
||||
vao = DebugGeometry.createVao(vbo, QUAD_FORMAT);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
} finally {
|
||||
source.free();
|
||||
expanded.free();
|
||||
}
|
||||
}
|
||||
|
||||
//Y
|
||||
tessellator.setColorRGBA_F(0, 255, 0, 1);
|
||||
tessellator.addVertex(0.0D, 0.0D, 0.0D);
|
||||
tessellator.addVertex(0.0D, 10, 0.0D);
|
||||
|
||||
tessellator.draw();
|
||||
|
||||
GLStateManager.glLineWidth(1.0F);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.enableTexture();
|
||||
private static long putAxis(long address, float x, float y, float z, int argb) {
|
||||
long p = address;
|
||||
DebugGeometry.putPosition(p, 0.0F, 0.0F, 0.0F);
|
||||
DebugGeometry.putColor(p + COLOR_OFFSET, argb);
|
||||
p += STRIDE;
|
||||
DebugGeometry.putPosition(p, x, y, z);
|
||||
DebugGeometry.putColor(p + COLOR_OFFSET, argb);
|
||||
return p + STRIDE;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,31 @@
|
||||
package com.gtnewhorizons.angelica.debug;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.shader.ShaderProgram;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import com.gtnewhorizons.angelica.debug.render.DebugDrawScope;
|
||||
import com.gtnewhorizons.angelica.debug.render.DebugGeometry;
|
||||
import com.gtnewhorizons.angelica.debug.render.DebugPrograms;
|
||||
import com.gtnewhorizons.angelica.debug.render.NativeScratch;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugNumbers;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderConstants;
|
||||
import org.joml.Matrix4f;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryStack.stackPush;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
|
||||
import static org.lwjgl.opengl.GL11.GL_FLOAT;
|
||||
import static org.lwjgl.opengl.GL11.GL_ONE_MINUS_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_SRC_ALPHA;
|
||||
import static org.lwjgl.opengl.GL11.GL_TRIANGLE_STRIP;
|
||||
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
|
||||
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
|
||||
@@ -29,14 +39,25 @@ public abstract class F3Graph {
|
||||
private static final int HEIGHT = 60;
|
||||
private static final int WIDTH = NUM_SAMPLES;
|
||||
private static final int FONT_COLOR = 0xFFE0E0E0;
|
||||
private static final int BACKGROUND_ARGB = 0x90505050;
|
||||
private static final int FOREGROUND_ARGB = 0xFFFFFFFF;
|
||||
private static final int COLOR_FLAGS = VertexFlags.COLOR_BIT;
|
||||
private static final int TEXTURED_FLAGS = VertexFlags.TEXTURE_BIT | VertexFlags.COLOR_BIT;
|
||||
private static final VertexFormat COLOR_FORMAT = DefaultVertexFormat.ALL_FORMATS[COLOR_FLAGS];
|
||||
private static final VertexFormat TEXTURED_FORMAT = DefaultVertexFormat.ALL_FORMATS[TEXTURED_FLAGS];
|
||||
private static final int COLOR_STRIDE = COLOR_FORMAT.getVertexSize();
|
||||
private static final int TEXTURED_STRIDE = TEXTURED_FORMAT.getVertexSize();
|
||||
private static final int TEXTURED_UV_OFFSET = DebugGeometry.offsetOf(TEXTURED_FORMAT, VertexFormatElement.Usage.PRIMARY_UV);
|
||||
private static final int TEXTURED_COLOR_OFFSET = DebugGeometry.offsetOf(TEXTURED_FORMAT, VertexFormatElement.Usage.COLOR);
|
||||
private static final int COLOR_COLOR_OFFSET = DebugGeometry.offsetOf(COLOR_FORMAT, VertexFormatElement.Usage.COLOR);
|
||||
private static final int QUAD_BYTES = 4 * Math.max(COLOR_STRIDE, TEXTURED_STRIDE);
|
||||
// Due to GLSL 120 limitations, it's just easier to use floats
|
||||
private final FloatBuffer sampleBuf = BufferUtils.createFloatBuffer(NUM_SAMPLES);
|
||||
private final long[] samples = new long[NUM_SAMPLES]; // long version for calculations
|
||||
// Circular buffer holding the last 240 samples, in nanoseconds
|
||||
private int samplesHead = 0; // one ahead of the position of the last sample
|
||||
private boolean initialized = false;
|
||||
private ShaderProgram shader;
|
||||
private int aPos;
|
||||
private int program;
|
||||
private int uFBWidth;
|
||||
private int uFBHeight;
|
||||
private int uScaleFactor;
|
||||
@@ -44,10 +65,20 @@ public abstract class F3Graph {
|
||||
private int uSamples;
|
||||
private int vertBuf;
|
||||
private int vao;
|
||||
private final DebugDrawScope scope = new DebugDrawScope();
|
||||
private final NativeScratch scratch = new NativeScratch(QUAD_BYTES);
|
||||
private final Matrix4f mvp = new Matrix4f();
|
||||
private final ResourceLocation texture;
|
||||
private final float pxPerNs;
|
||||
private final boolean left;
|
||||
private long sum;
|
||||
private final StringBuilder label = new StringBuilder(16);
|
||||
private String minString;
|
||||
private String avgString;
|
||||
private String maxString;
|
||||
private int labelTick = Integer.MIN_VALUE;
|
||||
private int avgWidth;
|
||||
private int maxWidth;
|
||||
|
||||
protected F3Graph(ResourceLocation texture, float pxPerNs, boolean left) {
|
||||
this.texture = texture;
|
||||
@@ -65,6 +96,10 @@ public abstract class F3Graph {
|
||||
samplesHead = (samplesHead + 1) % NUM_SAMPLES;
|
||||
}
|
||||
|
||||
public static int reservedHeight(FontRenderer fontRenderer) {
|
||||
return HEIGHT + BORDER * 2 + fontRenderer.FONT_HEIGHT;
|
||||
}
|
||||
|
||||
private int getVertX(ScaledResolution sres, int idx) {
|
||||
if (left) {
|
||||
return switch (idx) {
|
||||
@@ -92,23 +127,17 @@ public abstract class F3Graph {
|
||||
}
|
||||
|
||||
private void init() {
|
||||
shader = new ShaderProgram(
|
||||
"angelica",
|
||||
"shaders/debug_graph.vert.glsl",
|
||||
"shaders/debug_graph.frag.glsl");
|
||||
shader.use();
|
||||
final int handle = DebugPrograms.link("angelica:debug_graph", "angelica:debug_graph.vert.glsl", "angelica:debug_graph.frag.glsl", ShaderConstants.EMPTY, new String[]{"pos"}, new int[]{0});
|
||||
program = handle;
|
||||
GLStateManager.glUseProgram(handle);
|
||||
|
||||
// Attribute location is declared in shader via layout(location = 0)
|
||||
aPos = 0;
|
||||
|
||||
// Register uniforms
|
||||
uFBWidth = shader.getUniformLocation("fbWidth");
|
||||
uFBHeight = shader.getUniformLocation("fbHeight");
|
||||
uScaleFactor = shader.getUniformLocation("scaleFactor");
|
||||
uSamples = shader.getUniformLocation("samples");
|
||||
uHeadIdx = shader.getUniformLocation("headIdx");
|
||||
final int uPxPerNs = shader.getUniformLocation("pxPerNs");
|
||||
final int uLeft = shader.getUniformLocation("left");
|
||||
uFBWidth = GLStateManager.glGetUniformLocation(handle, "fbWidth");
|
||||
uFBHeight = GLStateManager.glGetUniformLocation(handle, "fbHeight");
|
||||
uScaleFactor = GLStateManager.glGetUniformLocation(handle, "scaleFactor");
|
||||
uSamples = GLStateManager.glGetUniformLocation(handle, "samples");
|
||||
uHeadIdx = GLStateManager.glGetUniformLocation(handle, "headIdx");
|
||||
final int uPxPerNs = GLStateManager.glGetUniformLocation(handle, "pxPerNs");
|
||||
final int uLeft = GLStateManager.glGetUniformLocation(handle, "left");
|
||||
|
||||
vao = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
@@ -131,33 +160,17 @@ public abstract class F3Graph {
|
||||
}
|
||||
|
||||
// Configure vertex attribute in the VAO
|
||||
GLStateManager.glVertexAttribPointer(aPos, VERT_FLOATS, GL_FLOAT, false, VERT_FLOATS * 4, 0);
|
||||
GLStateManager.glEnableVertexAttribArray(aPos);
|
||||
GLStateManager.glVertexAttribPointer(0, VERT_FLOATS, GL_FLOAT, false, VERT_FLOATS * 4, 0);
|
||||
GLStateManager.glEnableVertexAttribArray(0);
|
||||
|
||||
GLStateManager.glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
final ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
|
||||
// Load initial value for uniforms
|
||||
GLStateManager.glUniform1f(uFBWidth, mc.displayWidth);
|
||||
GLStateManager.glUniform1f(uFBHeight, mc.displayHeight);
|
||||
GLStateManager.glUniform1f(uScaleFactor, sr.getScaleFactor());
|
||||
GLStateManager.glUniform1i(uHeadIdx, samplesHead);
|
||||
GLStateManager.glUniform1(uSamples, sampleBuf);
|
||||
GLStateManager.glUniform1f(uPxPerNs, pxPerNs);
|
||||
GLStateManager.glUniform1i(uLeft, left ? 1 : 0); // this is how you load bool uniforms
|
||||
|
||||
ShaderProgram.clear();
|
||||
GLStateManager.glUniform1i(uLeft, left ? 1 : 0);
|
||||
}
|
||||
|
||||
public void render() {
|
||||
if (!initialized) {
|
||||
init();
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
public void render(ScaledResolution sr, int tick) {
|
||||
/*
|
||||
* We try to copy modern vanilla's tracker.
|
||||
* It is 240 (FRAME_TIMES) wide by 60 tall, including the 1px borders.
|
||||
@@ -171,88 +184,98 @@ public abstract class F3Graph {
|
||||
*/
|
||||
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
final ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
|
||||
// Setup GL state
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.enableAlphaTest();
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
GLStateManager.getProjectionMatrix().mul(GLStateManager.getModelViewMatrix(), mvp);
|
||||
|
||||
// Draw background
|
||||
GLStateManager.disableTexture();
|
||||
GLStateManager.glColor4f(0x50 / 255.0F, 0x50 / 255.0F, 0x50 / 255.0F, 0x90 / 255.0F);
|
||||
tess.startDrawingQuads();
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
tess.addVertex(getVertX(sr, i), getVertY(sr, i), 0);
|
||||
scope.beginHud();
|
||||
try {
|
||||
if (!initialized) {
|
||||
init();
|
||||
initialized = true;
|
||||
}
|
||||
drawGraph(mc, sr);
|
||||
} finally {
|
||||
scope.end();
|
||||
}
|
||||
tess.draw();
|
||||
GLStateManager.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GLStateManager.enableTexture();
|
||||
|
||||
|
||||
// Draw samples
|
||||
shader.use();
|
||||
// Draw text
|
||||
final FontRenderer fr = mc.fontRenderer;
|
||||
if (tick != labelTick) {
|
||||
labelTick = tick;
|
||||
updateLabels(fr);
|
||||
}
|
||||
final int top = sr.getScaledHeight() - reservedHeight(fr);
|
||||
final int scaledWidth = sr.getScaledWidth();
|
||||
final int minX;
|
||||
final int avgX;
|
||||
final int maxX;
|
||||
if (left) {
|
||||
minX = BORDER * 2;
|
||||
avgX = BORDER + WIDTH / 2 - avgWidth / 2;
|
||||
maxX = BORDER * 2 + WIDTH - maxWidth;
|
||||
} else {
|
||||
minX = scaledWidth - (BORDER * 2 + WIDTH);
|
||||
avgX = scaledWidth - (BORDER + WIDTH / 2 + avgWidth / 2);
|
||||
maxX = scaledWidth - (BORDER * 2 + maxWidth);
|
||||
}
|
||||
fr.drawString(minString, minX, top, FONT_COLOR, true);
|
||||
fr.drawString(avgString, avgX, top, FONT_COLOR, true);
|
||||
fr.drawString(maxString, maxX, top, FONT_COLOR, true);
|
||||
}
|
||||
|
||||
// Load uniforms
|
||||
private void drawGraph(Minecraft mc, ScaledResolution sr) {
|
||||
scratch.ensure(QUAD_BYTES);
|
||||
long p = scratch.address();
|
||||
for (int i = 0; i < 4; ++i, p += COLOR_STRIDE) {
|
||||
DebugGeometry.putPosition(p, getVertX(sr, i), getVertY(sr, i), 0.0F);
|
||||
DebugGeometry.putColor(p + COLOR_COLOR_OFFSET, BACKGROUND_ARGB);
|
||||
}
|
||||
final var color = DebugPrograms.color();
|
||||
color.use();
|
||||
color.mvp(mvp);
|
||||
TessellatorStreamingDrawer.drawPacked(scratch.window(4 * COLOR_STRIDE), GL11.GL_QUADS, COLOR_FLAGS, 4);
|
||||
|
||||
GLStateManager.glUseProgram(program);
|
||||
GLStateManager.glUniform1f(uFBWidth, mc.displayWidth);
|
||||
GLStateManager.glUniform1f(uFBHeight, mc.displayHeight);
|
||||
GLStateManager.glUniform1f(uScaleFactor, sr.getScaleFactor());
|
||||
GLStateManager.glUniform1i(uHeadIdx, samplesHead);
|
||||
GLStateManager.glUniform1(uSamples, sampleBuf);
|
||||
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
|
||||
// Left side graphs are inverted, so temporarily disable culling
|
||||
if (!left) GLStateManager.disableCull();
|
||||
|
||||
GLStateManager.glDrawArrays(GL_TRIANGLE_STRIP, 0, VERT_COUNT);
|
||||
|
||||
if (!left) GLStateManager.enableCull();
|
||||
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
|
||||
ShaderProgram.clear();
|
||||
|
||||
// Draw foreground
|
||||
|
||||
mc.getTextureManager().bindTexture(texture);
|
||||
tess.startDrawingQuads();
|
||||
tess.addVertexWithUV(getVertX(sr, 0), getVertY(sr, 0), 0.0D, 1, 0);
|
||||
tess.addVertexWithUV(getVertX(sr, 1), getVertY(sr, 1), 0.0D, 0, 0);
|
||||
tess.addVertexWithUV(getVertX(sr, 2), getVertY(sr, 2), 0.0D, 0, 1);
|
||||
tess.addVertexWithUV(getVertX(sr, 3), getVertY(sr, 3), 0.0D, 1, 1);
|
||||
tess.draw();
|
||||
p = scratch.address();
|
||||
for (int i = 0; i < 4; ++i, p += TEXTURED_STRIDE) {
|
||||
DebugGeometry.putPosition(p, getVertX(sr, i), getVertY(sr, i), 0.0F);
|
||||
memPutFloat(p + TEXTURED_UV_OFFSET, (i == 0 || i == 3) ? 1.0F : 0.0F);
|
||||
memPutFloat(p + TEXTURED_UV_OFFSET + 4, i >= 2 ? 1.0F : 0.0F);
|
||||
DebugGeometry.putColor(p + TEXTURED_COLOR_OFFSET, FOREGROUND_ARGB);
|
||||
}
|
||||
final var textured = DebugPrograms.textured();
|
||||
textured.use();
|
||||
textured.mvp(mvp);
|
||||
TessellatorStreamingDrawer.drawPacked(scratch.window(4 * TEXTURED_STRIDE), GL11.GL_QUADS, TEXTURED_FLAGS, 4);
|
||||
}
|
||||
|
||||
// Reset GL state
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.disableAlphaTest();
|
||||
GLStateManager.disableBlend();
|
||||
|
||||
// Draw text
|
||||
// Ensure running counters are reset on first sample
|
||||
private void updateLabels(FontRenderer fr) {
|
||||
long min = Long.MAX_VALUE;
|
||||
long max = Long.MIN_VALUE;
|
||||
for (int i = 0; i < NUM_SAMPLES; ++i) {
|
||||
min = Math.min(min, samples[i]);
|
||||
max = Math.max(max, samples[i]);
|
||||
}
|
||||
String minStr = String.format("%.1f ms min", min / 1_000_000D);
|
||||
String avgStr = String.format("%.1f ms avg", sum / 1_000_000 / (double) NUM_SAMPLES);
|
||||
String maxStr = String.format("%.1f ms max", max / 1_000_000D);
|
||||
final FontRenderer fr = mc.fontRenderer;
|
||||
final int top = sr.getScaledHeight() - HEIGHT - BORDER * 2 - fr.FONT_HEIGHT;
|
||||
final int scaledWidth = sr.getScaledWidth();
|
||||
minString = label(min / 1_000_000D, " ms min");
|
||||
avgString = label(sum / 1_000_000 / (double) NUM_SAMPLES, " ms avg");
|
||||
maxString = label(max / 1_000_000D, " ms max");
|
||||
avgWidth = fr.getStringWidth(avgString);
|
||||
maxWidth = fr.getStringWidth(maxString);
|
||||
}
|
||||
|
||||
if (left) {
|
||||
fr.drawString(minStr, BORDER * 2, top, FONT_COLOR, true);
|
||||
fr.drawString(avgStr, BORDER + WIDTH / 2 - fr.getStringWidth(avgStr) / 2, top, FONT_COLOR, true);
|
||||
fr.drawString(maxStr, BORDER * 2 + WIDTH - fr.getStringWidth(maxStr), top, FONT_COLOR, true);
|
||||
} else {
|
||||
fr.drawString(minStr, scaledWidth - (BORDER * 2 + WIDTH), top, FONT_COLOR, true);
|
||||
fr.drawString(avgStr, scaledWidth - (BORDER + WIDTH / 2 + fr.getStringWidth(avgStr) / 2), top, FONT_COLOR, true);
|
||||
fr.drawString(maxStr, scaledWidth - (BORDER * 2 + fr.getStringWidth(maxStr)), top, FONT_COLOR, true);
|
||||
}
|
||||
private String label(double millis, String suffix) {
|
||||
label.setLength(0);
|
||||
DebugNumbers.appendFixed(label, millis, 1);
|
||||
return label.append(suffix).toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.gtnewhorizons.angelica.debug.flyby;
|
||||
|
||||
import com.gtnewhorizons.angelica.api.debug.DebugScreenEntryStatus;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugEntryDef;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugRegistry;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugScreen;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugScreenEntryList;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugScreenProfile;
|
||||
import com.gtnewhorizons.angelica.mixins.interfaces.IGameSettingsExt;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.gtnewhorizons.angelica.config.SystemProperties.FLYBY_DEBUG_ENTRIES;
|
||||
import static com.gtnewhorizons.angelica.config.SystemProperties.FLYBY_DEBUG_FLAT;
|
||||
import static com.gtnewhorizons.angelica.config.SystemProperties.FLYBY_DEBUG_HUD;
|
||||
import static com.gtnewhorizons.angelica.config.SystemProperties.FLYBY_DEBUG_PROFILE;
|
||||
import static com.gtnewhorizons.angelica.config.SystemProperties.FLYBY_FPS_GRAPH;
|
||||
import static com.gtnewhorizons.angelica.config.SystemProperties.FLYBY_HUD_CACHING;
|
||||
|
||||
final class FlybyDebugOverrides {
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica/Flyby");
|
||||
|
||||
record Pair(String id, DebugScreenEntryStatus status) {}
|
||||
|
||||
private Boolean hudSaved;
|
||||
private Boolean flatSaved;
|
||||
private Boolean cachingSaved;
|
||||
private Boolean fpsGraphSaved;
|
||||
private boolean sessionOverridden;
|
||||
|
||||
static List<Pair> parseEntries(String raw) {
|
||||
final List<Pair> pairs = new ArrayList<>();
|
||||
if (raw == null) return pairs;
|
||||
for (final String part : raw.split(",")) {
|
||||
final String pair = part.trim();
|
||||
if (pair.isEmpty()) continue;
|
||||
final int eq = pair.lastIndexOf('=');
|
||||
if (eq <= 0 || eq == pair.length() - 1) {
|
||||
LOGGER.error("Flyby debugEntries: malformed pair '{}', expected id=status", pair);
|
||||
continue;
|
||||
}
|
||||
final String id = pair.substring(0, eq).trim();
|
||||
final String name = pair.substring(eq + 1).trim();
|
||||
final DebugScreenEntryStatus status = DebugScreenEntryStatus.bySerializedName(name);
|
||||
if (id.isEmpty() || status == null) {
|
||||
LOGGER.error("Flyby debugEntries: bad {} '{}' in '{}'", id.isEmpty() ? "id" : "status", id.isEmpty() ? id : name, pair);
|
||||
continue;
|
||||
}
|
||||
pairs.add(new Pair(id, status));
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
void apply(Minecraft mc) {
|
||||
if (FLYBY_DEBUG_HUD != null && this.hudSaved == null) {
|
||||
this.hudSaved = mc.gameSettings.showDebugInfo;
|
||||
mc.gameSettings.showDebugInfo = FLYBY_DEBUG_HUD;
|
||||
}
|
||||
if (FLYBY_DEBUG_FLAT != null && this.flatSaved == null) {
|
||||
this.flatSaved = AngelicaConfig.modernizeF3Screen;
|
||||
AngelicaConfig.modernizeF3Screen = !FLYBY_DEBUG_FLAT;
|
||||
}
|
||||
if (FLYBY_HUD_CACHING != null && this.cachingSaved == null) {
|
||||
this.cachingSaved = AngelicaConfig.hudCachingActive;
|
||||
AngelicaConfig.hudCachingActive = FLYBY_HUD_CACHING;
|
||||
}
|
||||
if (FLYBY_FPS_GRAPH != null && this.fpsGraphSaved == null) {
|
||||
final IGameSettingsExt settings = (IGameSettingsExt) mc.gameSettings;
|
||||
this.fpsGraphSaved = settings.angelica$showFpsGraph();
|
||||
settings.angelica$setShowFpsGraph(FLYBY_FPS_GRAPH);
|
||||
}
|
||||
this.applySession();
|
||||
}
|
||||
|
||||
private void applySession() {
|
||||
final boolean wantProfile = !FLYBY_DEBUG_PROFILE.isEmpty();
|
||||
final boolean wantEntries = !FLYBY_DEBUG_ENTRIES.isEmpty();
|
||||
if (!wantProfile && !wantEntries) return;
|
||||
|
||||
final DebugScreenEntryList entries = DebugScreen.ENTRIES;
|
||||
if (entries == null) {
|
||||
LOGGER.error("Flyby: debug entry list is not available, ignoring debugProfile and debugEntries");
|
||||
return;
|
||||
}
|
||||
final DebugRegistry registry = DebugRegistry.MAIN;
|
||||
this.sessionOverridden = true;
|
||||
|
||||
if (wantProfile) {
|
||||
final DebugScreenProfile profile = DebugScreenProfile.bySerializedName(FLYBY_DEBUG_PROFILE);
|
||||
if (profile == null) {
|
||||
LOGGER.error("Flyby: unknown debugProfile '{}', expected default or performance", FLYBY_DEBUG_PROFILE);
|
||||
} else {
|
||||
for (int i = 0; i < registry.entryCount(); i++) {
|
||||
final DebugEntryDef def = registry.entryAt(i);
|
||||
entries.overrideForSession(def, def.profileStatus(profile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final Pair pair : parseEntries(FLYBY_DEBUG_ENTRIES)) {
|
||||
final DebugEntryDef def = registry.entryById(pair.id());
|
||||
if (def == null) {
|
||||
LOGGER.error("Flyby debugEntries: unknown entry id '{}'", pair.id());
|
||||
continue;
|
||||
}
|
||||
entries.overrideForSession(def, pair.status());
|
||||
}
|
||||
}
|
||||
|
||||
void restore(Minecraft mc) {
|
||||
if (this.hudSaved != null) {
|
||||
mc.gameSettings.showDebugInfo = this.hudSaved;
|
||||
this.hudSaved = null;
|
||||
}
|
||||
if (this.flatSaved != null) {
|
||||
AngelicaConfig.modernizeF3Screen = this.flatSaved;
|
||||
this.flatSaved = null;
|
||||
}
|
||||
if (this.cachingSaved != null) {
|
||||
AngelicaConfig.hudCachingActive = this.cachingSaved;
|
||||
this.cachingSaved = null;
|
||||
}
|
||||
if (this.fpsGraphSaved != null) {
|
||||
((IGameSettingsExt) mc.gameSettings).angelica$setShowFpsGraph(this.fpsGraphSaved);
|
||||
this.fpsGraphSaved = null;
|
||||
}
|
||||
if (this.sessionOverridden) {
|
||||
final DebugScreenEntryList entries = DebugScreen.ENTRIES;
|
||||
if (entries != null) entries.clearSessionOverrides();
|
||||
this.sessionOverridden = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.config.SystemProperties.FlybyPacing;
|
||||
import com.gtnewhorizons.angelica.debug.profiling.AsprofRecorder;
|
||||
import com.gtnewhorizons.angelica.debug.profiling.AsprofRecorder.StopResult;
|
||||
import com.gtnewhorizons.angelica.debug.profiling.AsprofRecorder.StopStatus;
|
||||
import com.gtnewhorizons.angelica.debug.screen.DebugScreenOverlay;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
@@ -14,6 +15,7 @@ import com.gtnewhorizons.angelica.rendering.FramePacer;
|
||||
import com.gtnewhorizons.angelica.rendering.IsbrhTestRenderer;
|
||||
import com.sun.management.OperatingSystemMXBean;
|
||||
import com.sun.management.ThreadMXBean;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.common.gameevent.TickEvent;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -92,8 +94,7 @@ public final class FlybyRunner {
|
||||
private boolean pauseOnLostFocusSaved;
|
||||
private boolean pauseOnLostFocusOverridden;
|
||||
private boolean vsyncOverridden;
|
||||
private boolean showDebugInfoSaved;
|
||||
private boolean showDebugInfoOverridden;
|
||||
private final FlybyDebugOverrides debugOverrides = new FlybyDebugOverrides();
|
||||
private int cameraSaved;
|
||||
private boolean cameraOverridden;
|
||||
private int shotEvery;
|
||||
@@ -180,8 +181,8 @@ public final class FlybyRunner {
|
||||
this.fixedOrigin = origin;
|
||||
this.waitForTracy = SystemProperties.FLYBY_WAIT_FOR_TRACY;
|
||||
if (this.waitForTracy && !Tracy.ENABLED) {
|
||||
LOGGER.warn("Flyby: waitForTracy requested but Tracy is disabled in this build");
|
||||
this.waitForTracy = false;
|
||||
LOGGER.error("Flyby: a Tracy capture was requested but Tracy is not available (native library missing or init failed, see the Tracy log lines above); exiting instead of running unprofiled");
|
||||
FMLCommonHandler.instance().exitJava(1, false);
|
||||
}
|
||||
this.waitForFocus = SystemProperties.FLYBY_WAIT_FOR_FOCUS;
|
||||
this.exitWhenDone = SystemProperties.FLYBY_EXIT_WHEN_DONE;
|
||||
@@ -339,6 +340,7 @@ public final class FlybyRunner {
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
final IChatComponent result = ScreenShotHelper.saveScreenshot(mc.mcDataDir, "flyby-shot-" + this.shot + ".png", mc.displayWidth, mc.displayHeight, mc.getFramebuffer());
|
||||
LOGGER.info("Flyby screenshot {}", result.getUnformattedText());
|
||||
if (SystemProperties.FLYBY_DEBUG_DUMP) DebugScreenOverlay.INSTANCE.describe(LOGGER, this.shot);
|
||||
this.shot++;
|
||||
this.nextShot += this.shotEvery;
|
||||
}
|
||||
@@ -401,11 +403,7 @@ public final class FlybyRunner {
|
||||
|
||||
this.buildPath();
|
||||
this.suppressPacing();
|
||||
if (SystemProperties.FLYBY_DEBUG_HUD && !this.showDebugInfoOverridden) {
|
||||
this.showDebugInfoSaved = mc.gameSettings.showDebugInfo;
|
||||
this.showDebugInfoOverridden = true;
|
||||
mc.gameSettings.showDebugInfo = true;
|
||||
}
|
||||
this.debugOverrides.apply(mc);
|
||||
if (!this.cameraOverridden) {
|
||||
this.cameraSaved = mc.gameSettings.thirdPersonView;
|
||||
this.cameraOverridden = true;
|
||||
@@ -682,6 +680,7 @@ public final class FlybyRunner {
|
||||
ThreadedFileIOBase.threadedIOInstance.waitForFinish();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
LOGGER.error("Flyby: interrupted while draining chunk IO before discarding changes", e);
|
||||
}
|
||||
|
||||
worldChangesDiscarded = true;
|
||||
@@ -878,7 +877,7 @@ public final class FlybyRunner {
|
||||
|
||||
this.returnToOrigin(player);
|
||||
this.restorePauseOnLostFocus(mc);
|
||||
this.restoreShowDebugInfo(mc);
|
||||
this.debugOverrides.restore(mc);
|
||||
this.restoreCamera(mc);
|
||||
this.armed = false;
|
||||
sceneGuarded = false;
|
||||
@@ -906,12 +905,6 @@ public final class FlybyRunner {
|
||||
this.pauseOnLostFocusOverridden = false;
|
||||
}
|
||||
|
||||
private void restoreShowDebugInfo(Minecraft mc) {
|
||||
if (!this.showDebugInfoOverridden) return;
|
||||
mc.gameSettings.showDebugInfo = this.showDebugInfoSaved;
|
||||
this.showDebugInfoOverridden = false;
|
||||
}
|
||||
|
||||
private void restoreCamera(Minecraft mc) {
|
||||
if (!this.cameraOverridden) return;
|
||||
mc.gameSettings.thirdPersonView = this.cameraSaved;
|
||||
@@ -1067,7 +1060,7 @@ public final class FlybyRunner {
|
||||
FramePacer.endStats();
|
||||
final Minecraft mc = Minecraft.getMinecraft();
|
||||
this.restorePauseOnLostFocus(mc);
|
||||
this.restoreShowDebugInfo(mc);
|
||||
this.debugOverrides.restore(mc);
|
||||
this.restoreCamera(mc);
|
||||
this.armed = false;
|
||||
this.stopRecording(mc);
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.gtnewhorizons.angelica.debug.render;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
public final class DebugDrawScope {
|
||||
|
||||
private static final StateSet SET = StateSet.forMask(GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_ENABLE_BIT | GL11.GL_POLYGON_BIT | GL11.GL_SCISSOR_BIT | GL11.GL_TEXTURE_BIT | GL11.GL_LINE_BIT);
|
||||
|
||||
private int depth;
|
||||
private int program;
|
||||
private int vao;
|
||||
private int vbo;
|
||||
|
||||
public void beginHud() {
|
||||
capture();
|
||||
GLStateManager.disableDepthTest();
|
||||
common();
|
||||
}
|
||||
|
||||
public void beginWorld() {
|
||||
capture();
|
||||
GLStateManager.enableDepthTest();
|
||||
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
|
||||
GLStateManager.glCullFace(GL11.GL_BACK);
|
||||
GLStateManager.glFrontFace(GL11.GL_CCW);
|
||||
common();
|
||||
}
|
||||
|
||||
private void capture() {
|
||||
depth = GLStateManager.pushState(SET);
|
||||
program = GLStateManager.getActiveProgram();
|
||||
vao = GLStateManager.getBoundVAO();
|
||||
vbo = GLStateManager.getBoundVBO();
|
||||
}
|
||||
|
||||
public void end() {
|
||||
GLStateManager.glUseProgram(program);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GLStateManager.popStateTo(depth);
|
||||
}
|
||||
|
||||
private static void common() {
|
||||
GLStateManager.glDepthMask(false);
|
||||
GLStateManager.enableBlend();
|
||||
GLStateManager.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
|
||||
GLStateManager.disableScissorTest();
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glLineWidth(1.0F);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.gtnewhorizons.angelica.debug.render;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutByte;
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
|
||||
|
||||
public final class DebugGeometry {
|
||||
|
||||
private DebugGeometry() {}
|
||||
|
||||
public static int createVao(int vbo, VertexFormat format) {
|
||||
final int vao = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
format.setupBufferState(0L);
|
||||
return vao;
|
||||
}
|
||||
|
||||
public static int offsetOf(VertexFormat format, VertexFormatElement.Usage usage) {
|
||||
int offset = 0;
|
||||
for (final VertexFormatElement element : format.elementsArray) {
|
||||
if (element.getUsage() == usage) return offset;
|
||||
offset += element.getByteSize();
|
||||
}
|
||||
throw new IllegalArgumentException("Vertex format has no " + usage);
|
||||
}
|
||||
|
||||
public static void putColor(long address, int argb) {
|
||||
memPutByte(address, (byte) (argb >> 16));
|
||||
memPutByte(address + 1, (byte) (argb >> 8));
|
||||
memPutByte(address + 2, (byte) argb);
|
||||
memPutByte(address + 3, (byte) (argb >>> 24));
|
||||
}
|
||||
|
||||
public static void putPosition(long address, float x, float y, float z) {
|
||||
memPutFloat(address, x);
|
||||
memPutFloat(address + 4, y);
|
||||
memPutFloat(address + 8, z);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 329 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user