Compare commits

...
20 Commits
Author SHA1 Message Date
Jason Mitchell a019b7eed8 Update PR template to be in line with the GTNH org 2026-10-25 19:35:59 -07:00
Eclipse-Sol 1772e9eae2 Mipmap stuff again (#2300) 2026-10-25 19:25:54 -07:00
Eclipse-Sol 17a2ed7f37 Initial commit for gui-item-supersampling (#2299) 2026-10-25 13:10:15 -07:00
madoha 34ed1d101b Update Japanese Localizations (#2297) 2026-10-24 23:11:39 -07:00
KolyaandJason Mitchell 7cc9cea337 Cache the tile entity lightmap lookup for one tick (#2296)
Co-authored-by: Jason Mitchell <[email protected]>
2026-10-24 23:10:04 -07:00
Jason Mitchell 6c5e146f96 Fix GL core tests that depended on test order and JIT timing (#2298) 2026-10-24 22:32:13 -07:00
Kolya ea50e0a0b6 Bunch of rendering fixes (#2295) 2026-10-24 19:24:57 -07:00
Eclipse-Sol 713509a87b Entity Shadow Replay (#2294) 2026-10-24 16:21:33 -07:00
Eclipse-Sol 102c6cad86 Initial commit for glsm-exit-state-capture (#2292) 2026-10-24 15:58:17 -07:00
Eclipse-Sol 8af9340be6 Initial commit for shadow-pass-camera (#2291) 2026-10-22 20:38:28 -07:00
Eclipse-Sol fc8d3e8b89 Keep armor and held-item glint materials separate (#2289) 2026-10-22 19:29:22 -07:00
Jason Mitchell e84c8c8d7a Stop stalls on some GPUs when closing the questbook (#2286) 2026-10-22 00:24:31 -07:00
Eclipse-Sol 73d61d385c initial commit for gl-fences (#2285) 2026-10-21 20:12:01 -07:00
Wackypocket 69124e3f2f Stop capping mailbox present mode to the refresh rate (#2279) 2026-10-21 19:47:18 -07:00
VortexSo4andVortexSo4 98703a11be Read only the mcmeta in the interpolated icon check (#2284)
Co-authored-by: VortexSo4 <[email protected]>
2026-10-21 17:32:34 -07:00
brandyn 67ca46a220 why min 16 (#2278) 2026-10-21 17:25:35 -07:00
Jason Mitchell a037a96fa4 Update to Celeritas 2.5.13-GTNH (#2283) 2026-10-21 15:36:58 -07:00
Jason Mitchell ed260a34c4 Fix tests & SDL-GPU framebuffers losing attachments on resize or texture delete (#2282) 2026-10-21 15:12:32 -07:00
Jason Mitchell e98c927a93 Reduce test duplication (#2276) 2026-10-19 00:43:29 -07:00
Jason Mitchell b446968e66 Instance animated TESRs and let mods register shaders for batched TESRs (#2275) 2026-10-18 23:59:55 -07:00
326 changed files with 16948 additions and 6917 deletions

No files matched your search

+8 -8
View File
@@ -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
+7
View File
@@ -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")
}
+1 -1
View File
@@ -64,7 +64,7 @@ DependencyHandlerScope.of(dependencies).apply {
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BuildCraft:7.1.63:dev")
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.55-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenComputers:1.12.64-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Mobs-Info:0.5.18-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:IguanaTweaksTConstruct:2.7.10:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenBlocks:1.12.18-GTNH:dev")
+15
View File
@@ -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) ->
@@ -1,5 +1,7 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.FragmentKey;
import com.gtnewhorizons.angelica.glsm.ffp.Uniforms;
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.LightStateStack;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
@@ -66,7 +68,13 @@ public class CompatUniformManager {
static final int LOC_IRIS_MODELVIEW_INVERSE = LOC_MAT_BASE + MAT_FIELDS; // 48
static final int LOC_IRIS_PROJECTION_INVERSE = LOC_IRIS_MODELVIEW_INVERSE + 1; // 49
static final int LOC_COUNT = LOC_IRIS_PROJECTION_INVERSE + 1; // 50
static final int LOC_FOG_MODE = LOC_IRIS_PROJECTION_INVERSE + 1; // 50
static final int LOC_FOG_PARAMS = LOC_FOG_MODE + 1; // 51
static final int LOC_FOG_DISTANCE_MODE = LOC_FOG_PARAMS + 1; // 52
static final int LOC_MODELVIEW_PROJECTION = LOC_FOG_DISTANCE_MODE + 1; // 53
static final int LOC_COUNT = LOC_MODELVIEW_PROJECTION + 1; // 54
private static final String[] LIGHT_FIELD_NAMES = {
"ambient", "diffuse", "specular", "position", "halfVector",
@@ -84,6 +92,7 @@ public class CompatUniformManager {
UNIFORM_NAMES[LOC_MODELVIEW] = "angelica_ModelViewMatrix";
UNIFORM_NAMES[LOC_MODELVIEW_INVERSE] = "angelica_ModelViewMatrixInverse";
UNIFORM_NAMES[LOC_PROJECTION] = "angelica_ProjectionMatrix";
UNIFORM_NAMES[LOC_MODELVIEW_PROJECTION] = "angelica_ModelViewProjectionMatrix";
UNIFORM_NAMES[LOC_PROJECTION_INVERSE] = "angelica_ProjectionMatrixInverse";
UNIFORM_NAMES[LOC_NORMAL] = "angelica_NormalMatrix";
UNIFORM_NAMES[LOC_LIGHTMAP_TEXTURE_MATRIX] = "angelica_LightmapTextureMatrix";
@@ -98,6 +107,9 @@ public class CompatUniformManager {
UNIFORM_NAMES[LOC_IRIS_TEXTURE_MATRIX] = "iris_TextureMatrix";
UNIFORM_NAMES[LOC_IRIS_MODELVIEW_INVERSE] = "iris_ModelViewMatrixInverse";
UNIFORM_NAMES[LOC_IRIS_PROJECTION_INVERSE] = "iris_ProjectionMatrixInverse";
UNIFORM_NAMES[LOC_FOG_MODE] = "angelica_FogMode";
UNIFORM_NAMES[LOC_FOG_PARAMS] = "angelica_FogParams";
UNIFORM_NAMES[LOC_FOG_DISTANCE_MODE] = "angelica_FogDistanceMode";
UNIFORM_NAMES[LOC_ALPHA_TEST_REF] = "angelica_currentAlphaTest";
UNIFORM_NAMES[LOC_SCENE_COLOR] = "angelica_SceneColor";
UNIFORM_NAMES[LOC_CLIP_PLANES] = "angelica_ClipPlane[0]";
@@ -120,7 +132,14 @@ public class CompatUniformManager {
GLContextState owner;
int lastMvGen = -1, lastMvLinearGen = -1, lastProjGen = -1, lastTexMatGen = -1;
int lastFragmentGen = -1, lastLightingGen = -1, lastClipPlaneGen = -1;
ProgramUniforms(int[] locs) { this.locs = locs; }
int lastFogMode = -1, lastFogDistanceMode = -1;
final boolean lights;
ProgramUniforms(int[] locs) {
this.locs = locs;
boolean any = false;
for (int i = LOC_LIGHT_BASE; i < LOC_MAT_BASE; i++) any |= locs[i] != -1;
this.lights = any;
}
}
// Reusable NIO buffers for upload
@@ -186,6 +205,7 @@ public class CompatUniformManager {
pu.owner = glCtx;
pu.lastMvGen = pu.lastMvLinearGen = pu.lastProjGen = pu.lastTexMatGen = -1;
pu.lastFragmentGen = pu.lastLightingGen = pu.lastClipPlaneGen = -1;
pu.lastFogMode = pu.lastFogDistanceMode = -1;
}
// Matrix uniforms — skip if this program's storage already holds the current generation
@@ -212,12 +232,15 @@ public class CompatUniformManager {
uploadFragmentUniforms(locs);
}
pu.lastFogMode = uploadIfChanged(locs[LOC_FOG_MODE], FragmentKey.activeFogMode(glCtx), pu.lastFogMode);
pu.lastFogDistanceMode = uploadIfChanged(locs[LOC_FOG_DISTANCE_MODE], glCtx.fogState.getFogDistanceMode(), pu.lastFogDistanceMode);
// Lighting-derived uniforms (scene color, light sources, material)
final int litGen = glCtx.lightingGeneration;
if (litGen != pu.lastLightingGen) {
pu.lastLightingGen = litGen;
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
uploadLightSources(locs);
if (pu.lights) uploadLightSources(locs);
uploadMaterial(locs);
}
@@ -262,6 +285,11 @@ public class CompatUniformManager {
}
}
if ((mvChanged || projChanged) && locs[LOC_MODELVIEW_PROJECTION] != -1) {
Uniforms.modelViewProjection(proj, mv, scratchMatrix).get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_PROJECTION], false, mat4Buf);
}
if (mvLinearChanged) {
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
@@ -324,6 +352,11 @@ public class CompatUniformManager {
}
}
private static int uploadIfChanged(int location, int value, int last) {
if (location != -1 && value != last) RENDER_BACKEND.uniform1i(location, value);
return value;
}
private static void uploadFragmentUniforms(int[] locs) {
// Fog uniforms
FogStateStack fog = GLStateManager.getFogState();
@@ -336,6 +369,9 @@ public class CompatUniformManager {
if (locs[LOC_FOG_END] != -1) {
RENDER_BACKEND.uniform1f(locs[LOC_FOG_END], fog.getEnd());
}
if (locs[LOC_FOG_PARAMS] != -1) {
RENDER_BACKEND.uniform4f(locs[LOC_FOG_PARAMS], fog.linearScale(), fog.linearBias(), fog.expScale(), fog.exp2Scale());
}
if (locs[LOC_FOG_COLOR] != -1) {
vec4Buf.clear();
vec4Buf.put((float) fog.getFogColor().x).put((float) fog.getFogColor().y).put((float) fog.getFogColor().z).put(fog.getFogAlpha());
@@ -372,8 +408,6 @@ public class CompatUniformManager {
}
private static void uploadLightSources(int[] locs) {
// Early exit: if the first light field isn't present, no lighting uniforms exist
if (locs[LOC_LIGHT_BASE] == -1) return;
final LightStateStack[] lights = GLStateManager.getLightDataStates();
for (int li = 0; li < 2; li++) {
final int base = LOC_LIGHT_BASE + li * LIGHT_FIELDS;
@@ -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);
}
}
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import com.gtnewhorizons.angelica.glsm.stacks.AlphaStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
@@ -58,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<>();
@@ -257,6 +259,7 @@ public class GLContextState {
public boolean vanillaBlendEnabledBefore;
public int drawFramebuffer = 0;
public int readFramebuffer = 0;
public final Int2ObjectOpenHashMap<int[]> framebufferTextures = new Int2ObjectOpenHashMap<>();
public int texGenGeneration;
public int defaultVAO;
public final VAOManager vaos = new VAOManager();
@@ -272,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
@@ -46,6 +46,8 @@ public class RenderSystem {
private static boolean supportsMultiDrawIndirect;
private static boolean supportsBufferStorage;
private static boolean supportsClearTexture;
private static boolean supportsStencilTexturing;
private static boolean supportsTextureStorage;
private static boolean supportsTesselation;
private static boolean supportsSamplerObjects;
private static boolean supportsProgramBinary;
@@ -128,6 +130,8 @@ public class RenderSystem {
supportsMultiDrawIndirect = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_multi_draw_indirect;
supportsBufferStorage = !isGLES && (GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_buffer_storage);
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
supportsStencilTexturing = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_stencil_texturing;
supportsTextureStorage = isGLES ? glesVersion >= 300 : GLStateManager.capabilities.OpenGL42 || GLStateManager.capabilities.GL_ARB_texture_storage;
hasClipCullDistance = !isGLES || hasExtension("GL_EXT_clip_cull_distance");
// Cache maximum image units
@@ -154,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);
}
@@ -194,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();
@@ -216,6 +223,7 @@ public class RenderSystem {
}
public static void uniformMatrix4fv(int location, boolean transpose, FloatBuffer matrix) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.uniformMatrix4(location, transpose, matrix);
}
@@ -224,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();
@@ -286,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();
@@ -293,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();
@@ -300,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();
@@ -307,40 +335,48 @@ 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) {
RENDER_BACKEND.bindTexture(target, texture);
GLStateManager.rejectExitCapture();
GLStateManager.glBindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage1D(target, level, internalformat, width, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
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) {
RENDER_BACKEND.bindTexture(target, texture);
GLStateManager.rejectExitCapture();
GLStateManager.glBindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
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();
@@ -351,31 +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) {
TextureInfoCache.INSTANCE.onWrittenOutsideUploads(texture);
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);
}
@@ -459,23 +501,34 @@ public class RenderSystem {
return supportsClearTexture;
}
public static boolean supportsStencilTexturing() {
return supportsStencilTexturing;
}
public static boolean supportsTextureStorage() {
return supportsTextureStorage;
}
public static int getMaxSSBOBindings() {
return maxSSBOBindings;
}
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);
}
@@ -484,6 +537,7 @@ public class RenderSystem {
}
public static void memoryBarrier(int barriers) {
GLStateManager.rejectExitCapture();
if (supportsCompute) {
RENDER_BACKEND.memoryBarrier(barriers);
}
@@ -506,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);
}
@@ -531,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 {
@@ -541,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);
}
@@ -562,6 +621,7 @@ public class RenderSystem {
}
public static int getIndexedBufferBinding(int target, int index) {
GLStateManager.rejectExitCapture();
return GLStateManager.getIndexedBufferBinding(target, index);
}
@@ -593,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);
}
@@ -652,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
@@ -609,6 +609,21 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL30.glFramebufferTexture2D(target, attachment, textarget, texture, level);
}
@Override
public void framebufferTexture1D(int target, int attachment, int textarget, int texture, int level) {
GL30.glFramebufferTexture1D(target, attachment, textarget, texture, level);
}
@Override
public void framebufferTexture3D(int target, int attachment, int textarget, int texture, int level, int zoffset) {
GL30.glFramebufferTexture3D(target, attachment, textarget, texture, level, zoffset);
}
@Override
public void framebufferTextureLayer(int target, int attachment, int texture, int level, int layer) {
GL30.glFramebufferTextureLayer(target, attachment, texture, level, layer);
}
@Override
public void framebufferTexture(int target, int attachment, int texture, int level) {
GL32.glFramebufferTexture(target, attachment, texture, level);
@@ -99,7 +99,7 @@ public abstract class RenderBackend {
static VSyncMode resolveTearFreeMode(VSyncMode preferred, Predicate<VSyncMode> supported) {
if (preferred != null && preferred.tearFree() && supported.test(preferred)) return preferred;
return supported.test(VSyncMode.MAILBOX) ? VSyncMode.MAILBOX : VSyncMode.ON;
return VSyncMode.ON;
}
protected abstract VSyncMode applyVSyncMode(VSyncMode preferred);
@@ -457,6 +457,9 @@ public abstract class RenderBackend {
public abstract void deleteFramebuffers(int framebuffer);
public abstract void bindFramebuffer(int target, int framebuffer);
public abstract void framebufferTexture2D(int target, int attachment, int textarget, int texture, int level);
public abstract void framebufferTexture1D(int target, int attachment, int textarget, int texture, int level);
public abstract void framebufferTexture3D(int target, int attachment, int textarget, int texture, int level, int zoffset);
public abstract void framebufferTextureLayer(int target, int attachment, int texture, int level, int layer);
public abstract void framebufferTexture(int target, int attachment, int texture, int level);
public abstract int genRenderbuffers();
public abstract void deleteRenderbuffers(int renderbuffer);
@@ -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) {
}
@@ -128,7 +128,7 @@ public class DSAARB extends DSAUnsupported {
@Override
public void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
RENDER_BACKEND.namedFramebufferTexture(fb, attachment, texture, levels);
GLStateManager.glNamedFramebufferTexture(fb, attachment, texture, levels);
}
@Override
@@ -139,7 +139,6 @@ public class DSAARB extends DSAUnsupported {
@Override
public int createTexture(int target) {
GLStateManager.requireContext("glCreateTextures");
return RENDER_BACKEND.createTextures(target);
return GLStateManager.glCreateTextures(target);
}
}
@@ -2,13 +2,12 @@ package com.gtnewhorizons.angelica.glsm.dsa;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.stacks.TextureBindingStack;
import com.gtnewhorizons.angelica.glsm.texture.TextureTargets;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
@@ -153,7 +152,7 @@ public class DSAUnsupported implements DSAAccess {
@Override
public void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
GLStateManager.glBindFramebuffer(fbtarget, fb);
RENDER_BACKEND.framebufferTexture2D(fbtarget, attachment, target, texture, levels);
GLStateManager.glFramebufferTexture2D(fbtarget, attachment, target, texture, levels);
}
@Override
@@ -166,23 +165,12 @@ public class DSAUnsupported implements DSAAccess {
@Override
public int createTexture(int target) {
final int texture = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
GLStateManager.glBindTexture(target, texture);
return texture;
}
private static int bindingQuery(int target) {
return switch (target) {
case GL11.GL_TEXTURE_1D -> GL11.GL_TEXTURE_BINDING_1D;
case GL12.GL_TEXTURE_3D -> GL12.GL_TEXTURE_BINDING_3D;
case GL13.GL_TEXTURE_CUBE_MAP -> GL13.GL_TEXTURE_BINDING_CUBE_MAP;
case GL30.GL_TEXTURE_2D_ARRAY -> GL30.GL_TEXTURE_BINDING_2D_ARRAY;
case GL31.GL_TEXTURE_RECTANGLE -> GL31.GL_TEXTURE_BINDING_RECTANGLE;
default -> GL11.GL_TEXTURE_BINDING_2D;
};
}
private static int previousBinding(int target) {
return target == GL11.GL_TEXTURE_2D ? previous2D() : RENDER_BACKEND.getInteger(bindingQuery(target));
return target == GL11.GL_TEXTURE_2D ? previous2D() : RENDER_BACKEND.getInteger(TextureTargets.bindingQuery(target));
}
private static int previous2D() {
@@ -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");
@@ -0,0 +1,32 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.states.FogState;
public final class FfpGlsl {
private FfpGlsl() {}
// Mesa ffvertex_prog.c build_fog(): FDM_EYE_RADIAL/EYE_PLANE/EYE_PLANE_ABS
public static String fogDistance(int mode, String eye) {
return switch (mode) {
case FogState.DISTANCE_EYE_RADIAL -> "length(" + eye + ".xyz)";
case FogState.DISTANCE_EYE_PLANE -> eye + ".z";
default -> "abs(" + eye + ".z)";
};
}
public static void appendFogFactor(StringBuilder sb, String indent, int fogMode, String coord, String params) {
switch (fogMode) {
case FragmentKey.FOG_LINEAR -> sb.append(indent).append("f = ").append(coord).append(" * ").append(params).append(".x + ").append(params).append(".y;\n");
case FragmentKey.FOG_EXP -> sb.append(indent).append("f = exp2(-(").append(coord).append(" * ").append(params).append(".z));\n");
case FragmentKey.FOG_EXP2 -> {
sb.append(indent).append("float fogTmp = ").append(coord).append(" * ").append(params).append(".w;\n");
sb.append(indent).append("f = exp2(-(fogTmp * fogTmp));\n");
}
}
}
public static String quantizeAlpha(String alpha) {
return "round(" + alpha + " * 255.0)";
}
}
@@ -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;
@@ -65,6 +66,16 @@ public final class FragmentKey {
public static final int FOG_EXP = 2;
public static final int FOG_EXP2 = 3;
public static int activeFogMode(GLContextState glCtx) {
if (!glCtx.fogMode.isEnabled()) return FOG_NONE;
return switch (glCtx.fogState.getFogMode()) {
case GL11.GL_LINEAR -> FOG_LINEAR;
case GL11.GL_EXP -> FOG_EXP;
case GL11.GL_EXP2 -> FOG_EXP2;
default -> FOG_NONE;
};
}
private static final int GLOBAL_BITS = 14;
private static final int BIT_COMBINED_GLINT = 61;
private static final int BIT_GLINT_REPLACE_ALPHA = 62;
@@ -72,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
@@ -117,15 +128,7 @@ public final class FragmentKey {
if (CombinedGlint.replacesAlpha()) global |= 1L << BIT_GLINT_REPLACE_ALPHA;
// Fog
if (glCtx.fogMode.isEnabled()) {
final int fogMode = switch (glCtx.fogState.getFogMode()) {
case GL11.GL_LINEAR -> FOG_LINEAR;
case GL11.GL_EXP -> FOG_EXP;
case GL11.GL_EXP2 -> FOG_EXP2;
default -> FOG_NONE;
};
global |= (long) fogMode;
}
global |= activeFogMode(glCtx);
// Alpha test
if (glCtx.alphaTest.isEnabled()) {
@@ -170,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;
@@ -254,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; }
@@ -398,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");
}
}
@@ -305,7 +310,7 @@ public final class FragmentShaderGenerator {
final int func = FragmentKey.decodeAlphaFunc(key.alphaTestFunc());
sb.append(" float qa = round(color.a * 255.0);\n");
sb.append(" float qa = ").append(FfpGlsl.quantizeAlpha("color.a")).append(";\n");
switch (func) {
case GL11.GL_NEVER ->
sb.append(" discard;\n");
@@ -335,18 +340,7 @@ public final class FragmentShaderGenerator {
sb.append(" // Fog\n");
sb.append(" float f;\n");
switch (fogMode) {
case FragmentKey.FOG_LINEAR ->
sb.append(" f = v_FogCoord * u_FogParams.x + u_FogParams.y;\n");
case FragmentKey.FOG_EXP ->
sb.append(" f = exp2(-(v_FogCoord * u_FogParams.z));\n");
case FragmentKey.FOG_EXP2 -> {
sb.append(" float fogTmp = v_FogCoord * u_FogParams.w;\n");
sb.append(" f = exp2(-(fogTmp * fogTmp));\n");
}
}
FfpGlsl.appendFogFactor(sb, " ", fogMode, "v_FogCoord", "u_FogParams");
sb.append(" f = clamp(f, 0.0, 1.0);\n");
sb.append(" color.rgb = mix(u_FogColor.rgb, color.rgb, f);\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;
@@ -80,9 +82,6 @@ public class Uniforms {
private final Vector3f tempVec3 = new Vector3f();
private final FloatBuffer clipPlaneBuf = memAllocFloat(32); // 8 planes * vec4
private static final double LN2 = java.lang.Math.log(2.0);
private static final double SQRT_LN2 = java.lang.Math.sqrt(LN2);
public void upload(GLContextState glCtx) {
boolean dirty = false;
contentChanged = false;
@@ -173,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) {
@@ -233,8 +241,11 @@ public class Uniforms {
if (projChanged) {
putMat4(FFPUniformBlock.PROJECTION_MATRIX, proj);
}
proj.mul(mv, mvpMatrix);
putMat4(FFPUniformBlock.MVP_MATRIX, mvpMatrix);
putMat4(FFPUniformBlock.MVP_MATRIX, modelViewProjection(proj, mv, mvpMatrix));
}
public static Matrix4f modelViewProjection(Matrix4f proj, Matrix4f mv, Matrix4f dest) {
return proj.mul(mv, dest);
}
private void stageNormalMatrix(GLContextState glCtx) {
@@ -343,17 +354,8 @@ public class Uniforms {
final var secondary = glCtx.secondaryColor;
putVec3(FFPUniformBlock.SECONDARY_COLOR, secondary.getRed(), secondary.getGreen(), secondary.getBlue());
// Mesa STATE_FOG_PARAMS_OPTIMIZED
final FogState fog = glCtx.fogState;
final float start = fog.getStart();
final float end = fog.getEnd();
final float density = fog.getDensity();
final float range = end - start;
putVec4(FFPUniformBlock.FOG_PARAMS,
range != 0.0f ? -1.0f / range : 0.0f,
range != 0.0f ? end / range : 1.0f,
(float) (density / LN2),
(float) (density / SQRT_LN2));
putVec4(FFPUniformBlock.FOG_PARAMS, fog.linearScale(), fog.linearBias(), fog.expScale(), fog.exp2Scale());
putVec4(FFPUniformBlock.FOG_COLOR,
(float) fog.getFogColor().x, (float) fog.getFogColor().y, (float) fog.getFogColor().z,
fog.getFogAlpha());
@@ -420,6 +422,7 @@ public class Uniforms {
if (ring != null) {
ring.endFrame();
}
invalidateBinding();
lastFrameBlockWrites = blockWrites;
lastFrameBlockSkips = blockSkips;
lastFrameStagedMatrices = stagedMatrices;
@@ -100,9 +100,9 @@ public final class VertexShaderGenerator {
private static void emitOutputs(StringBuilder sb, VertexKey key) {
sb.append("// Outputs\n");
sb.append("out vec4 v_Color;\n");
sb.append("invariant gl_Position;\n");
if (key.instancing().hasInstanceHead()) {
sb.append("out vec4 v_Overlay;\n");
sb.append("invariant gl_Position;\n");
}
if (key.lineStipple()) {
sb.append("flat out vec2 v_LineStart;\n");
@@ -414,20 +414,11 @@ public final class VertexShaderGenerator {
private static void emitFogDistance(StringBuilder sb, VertexKey key) {
sb.append(" // Fog distance\n");
final String own = fogDistance(key, "eyePos");
final String fog = key.lineQuads() ? acrossLine(own, fogDistance(key, "lineEyeOther")) : own;
final String own = FfpGlsl.fogDistance(key.fogDistanceMode(), "eyePos");
final String fog = key.lineQuads() ? acrossLine(own, FfpGlsl.fogDistance(key.fogDistanceMode(), "lineEyeOther")) : own;
sb.append(" v_FogCoord = ").append(fog).append(";\n");
}
// Mesa ffvertex_prog.c build_fog(): FDM_EYE_RADIAL/EYE_PLANE/EYE_PLANE_ABS
private static String fogDistance(VertexKey key, String eye) {
return switch (key.fogDistanceMode()) {
case 0 -> "length(" + eye + ".xyz)"; // FDM_EYE_RADIAL: Euclidean distance
case 1 -> eye + ".z"; // FDM_EYE_PLANE: raw Z (can be negative, clamped by fog factor)
default -> "abs(" + eye + ".z)"; // FDM_EYE_PLANE_ABS: absolute Z
};
}
private static void emitClipDistances(StringBuilder sb, VertexKey key) {
sb.append(" // Clip distances\n");
for (int i = 0; i < 8; i++) {
@@ -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);
}
@@ -26,6 +26,7 @@ public final class FrameHooks {
}
public static void frameEnd() {
GLStateManager.onFrameEnd();
popFrameGroup();
RENDER_BACKEND.onFrameEnd();
}
@@ -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) {
@@ -167,6 +167,14 @@ public class GLSMRedirector {
private static final String EXTTimerQuery = "org/lwjgl/opengl/EXTTimerQuery";
private static final String ARBSamplerObjects = "org/lwjgl/opengl/ARBSamplerObjects";
private static final String ARBMultiBind = "org/lwjgl/opengl/ARBMultiBind";
private static final String ARBDirectStateAccess = "org/lwjgl/opengl/ARBDirectStateAccess";
private static final String ARBGeometryShader4 = "org/lwjgl/opengl/ARBGeometryShader4";
private static final String EXTGeometryShader4 = "org/lwjgl/opengl/EXTGeometryShader4";
private static final String NVGeometryProgram4 = "org/lwjgl/opengl/NVGeometryProgram4";
private static final String EXTTextureArray = "org/lwjgl/opengl/EXTTextureArray";
private static final String EXTDirectStateAccess = "org/lwjgl/opengl/EXTDirectStateAccess";
private static final String ARBShaderImageLoadStore = "org/lwjgl/opengl/ARBShaderImageLoadStore";
private static final String EXTShaderImageLoadStore = "org/lwjgl/opengl/EXTShaderImageLoadStore";
// Redirect VAO related calls from NHLib
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
@@ -513,7 +521,10 @@ public class GLSMRedirector {
.add("glDeleteFramebuffers")
.add("glGenFramebuffers")
.add("glCheckFramebufferStatus")
.add("glFramebufferTexture1D")
.add("glFramebufferTexture2D")
.add("glFramebufferTexture3D")
.add("glFramebufferTextureLayer")
.add("glVertexAttribIPointer")
.add("glGenerateMipmap")
.add("glGetFramebufferAttachmentParameteri")
@@ -567,11 +578,18 @@ public class GLSMRedirector {
final var gl44 = RedirectMap.newMap()
.add("glBufferStorage")
.add("glBindSamplers")
.add("glClearTexImage");
.add("glClearTexImage")
.add("glBindTextures")
.add("glBindImageTextures");
final var gl45 = RedirectMap.newMap()
.add("glCreateBuffers")
.add("glNamedBufferData")
.add("glNamedBufferSubData");
.add("glNamedBufferSubData")
.add("glNamedFramebufferTexture")
.add("glNamedFramebufferTextureLayer")
.add("glNamedFramebufferRenderbuffer")
.add("glCreateTextures")
.add("glBindTextureUnit");
final var gl46 = RedirectMap.newMap()
.add("glPolygonOffsetClamp")
.add("glMultiDrawArraysIndirectCount")
@@ -630,7 +648,10 @@ public class GLSMRedirector {
.add("glDeleteFramebuffers")
.add("glGenFramebuffers")
.add("glCheckFramebufferStatus")
.add("glFramebufferTexture1D")
.add("glFramebufferTexture2D")
.add("glFramebufferTexture3D")
.add("glFramebufferTextureLayer")
.add("glBindRenderbuffer")
.add("glDeleteRenderbuffers")
.add("glGenRenderbuffers")
@@ -643,7 +664,9 @@ public class GLSMRedirector {
.add("glDeleteFramebuffersEXT", "glDeleteFramebuffers")
.add("glGenFramebuffersEXT", "glGenFramebuffers")
.add("glCheckFramebufferStatusEXT", "glCheckFramebufferStatus")
.add("glFramebufferTexture1DEXT", "glFramebufferTexture1D")
.add("glFramebufferTexture2DEXT", "glFramebufferTexture2D")
.add("glFramebufferTexture3DEXT", "glFramebufferTexture3D")
.add("glBindRenderbufferEXT", "glBindRenderbuffer")
.add("glDeleteRenderbuffersEXT", "glDeleteRenderbuffers")
.add("glGenRenderbuffersEXT", "glGenRenderbuffers")
@@ -793,6 +816,45 @@ public class GLSMRedirector {
);
methodRedirects.put(ARBMultiBind, RedirectMap.newMap()
.add("glBindSamplers")
.add("glBindTextures")
.add("glBindImageTextures")
);
methodRedirects.put(ARBDirectStateAccess, RedirectMap.newMap()
.add("glNamedFramebufferTexture")
.add("glNamedFramebufferTextureLayer")
.add("glNamedFramebufferRenderbuffer")
.add("glCreateTextures")
.add("glBindTextureUnit")
);
methodRedirects.put(ARBGeometryShader4, RedirectMap.newMap()
.add("glFramebufferTextureARB", "glFramebufferTexture")
.add("glFramebufferTextureLayerARB", "glFramebufferTextureLayer")
.add("glFramebufferTextureFaceARB", "glFramebufferTextureFace")
);
final var geometryShader4 = RedirectMap.newMap()
.add("glFramebufferTextureEXT", "glFramebufferTexture")
.add("glFramebufferTextureLayerEXT", "glFramebufferTextureLayer")
.add("glFramebufferTextureFaceEXT", "glFramebufferTextureFace");
methodRedirects.put(EXTGeometryShader4, geometryShader4);
methodRedirects.put(NVGeometryProgram4, geometryShader4);
methodRedirects.put(EXTTextureArray, RedirectMap.newMap()
.add("glFramebufferTextureLayerEXT", "glFramebufferTextureLayer")
);
methodRedirects.put(EXTDirectStateAccess, RedirectMap.newMap()
.add("glNamedFramebufferTexture1DEXT")
.add("glNamedFramebufferTexture2DEXT")
.add("glNamedFramebufferTexture3DEXT")
.add("glNamedFramebufferTextureEXT")
.add("glNamedFramebufferTextureLayerEXT", "glNamedFramebufferTextureLayer")
.add("glNamedFramebufferTextureFaceEXT")
.add("glNamedFramebufferRenderbufferEXT", "glNamedFramebufferRenderbuffer")
.add("glBindMultiTextureEXT")
);
methodRedirects.put(ARBShaderImageLoadStore, RedirectMap.newMap()
.add("glBindImageTexture")
);
methodRedirects.put(EXTShaderImageLoadStore, RedirectMap.newMap()
.add("glBindImageTextureEXT", "glBindImageTexture")
);
// APPLE
@@ -96,6 +96,7 @@ public class BooleanStateStack extends BooleanState implements CowStateStack<Boo
@Override
public void setEnabled(boolean enabled) {
final GLContextState o = cow.owner;
GLStateManager.recordExitCapability(o, glCap, -1);
if (o != null && o.attribDepth > 0
&& (stateUnknown || !GLStateManager.isCachingEnabled() || enabled != vanillaValue())) {
beforeModify();
@@ -24,6 +24,7 @@ public final class TextureUnitBooleanStateStack extends BooleanStateStack {
@Override
public void setEnabled(boolean enabled) {
GLStateManager.recordExitCapability(cowDepths().owner, glCap, unitIndex);
if (!GLStateManager.isCachingEnabled() || enabled != this.enabled) {
GLStateManager.beforeUncapturedTextureUnitChange(glCap, unitIndex);
beforeModify();
@@ -9,6 +9,13 @@ import java.nio.FloatBuffer;
@Getter @Setter
public class FogState implements ISettableState<FogState> {
public static final int DISTANCE_EYE_RADIAL = 0;
public static final int DISTANCE_EYE_PLANE = 1;
public static final int DISTANCE_EYE_PLANE_ABS = 2;
private static final double LN2 = Math.log(2.0);
private static final double SQRT_LN2 = Math.sqrt(LN2);
protected int fogMode = GL11.GL_EXP;
protected final Vector3d fogColor = new Vector3d(0.0F, 0.0F, 0.0F);
protected float fogAlpha = 1.0F;
@@ -16,8 +23,26 @@ public class FogState implements ISettableState<FogState> {
protected float density = 1.0F;
protected float start;
protected float end = 1.0F;
/** Fog distance computation mode: 0=EYE_RADIAL, 1=EYE_PLANE, 2=EYE_PLANE_ABS */
protected int fogDistanceMode = 2;
protected int fogDistanceMode = DISTANCE_EYE_PLANE_ABS;
// Mesa STATE_FOG_PARAMS_OPTIMIZED
public float linearScale() {
final float range = end - start;
return range != 0.0f ? -1.0f / range : 0.0f;
}
public float linearBias() {
final float range = end - start;
return range != 0.0f ? end / range : 1.0f;
}
public float expScale() {
return (float) (density / LN2);
}
public float exp2Scale() {
return (float) (density / SQRT_LN2);
}
@Override
public FogState set(FogState state) {
@@ -1,14 +1,16 @@
package com.gtnewhorizons.angelica.glsm.states;
import lombok.Getter;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
public class ImageUnitBinding implements ISettableState<ImageUnitBinding> {
@Getter protected int texture;
@Getter protected int level;
@Getter protected boolean layered;
@Getter protected int layer;
@Getter protected int access;
@Getter protected int format;
@Getter protected int access = GL15.GL_READ_ONLY;
@Getter protected int format = GL30.GL_R8;
public void setBinding(int texture, int level, boolean layered, int layer, int access, int format) {
this.texture = texture;
@@ -19,6 +21,10 @@ public class ImageUnitBinding implements ISettableState<ImageUnitBinding> {
this.format = format;
}
public void reset() {
setBinding(0, 0, false, 0, GL15.GL_READ_ONLY, GL30.GL_R8);
}
@Override
public ImageUnitBinding set(ImageUnitBinding state) {
this.texture = state.texture;
@@ -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() {
@@ -25,6 +25,7 @@ final class GlStreamingRing {
private static final long SYNC_WAIT_SLICE_NANOS = 10_000_000L;
private static final long SYNC_WAIT_TOTAL_NANOS = 5_000_000_000L;
private static long totalFencesIssued;
private final FenceQueue fences = new FenceQueue();
private int writePos;
@@ -68,6 +69,9 @@ final class GlStreamingRing {
/** Byte offset of a reserved range, or -1 when it can never be served. */
long reserve(int bytes, int alignment) {
if (bytes > capacity) return -1;
if (pendingBytes >= capacity / 4) {
fence();
}
if (remaining < capacity / 2) {
reclaim();
}
@@ -82,9 +86,6 @@ final class GlStreamingRing {
writePos = offset + bytes;
remaining -= waste + bytes;
pendingBytes += waste + bytes;
if (pendingBytes >= capacity / 4) {
fence();
}
return offset;
}
@@ -104,6 +105,7 @@ final class GlStreamingRing {
fences.enqueue(GLStateManager.glFenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0), pendingBytes);
pendingBytes = 0;
fencesIssued++;
totalFencesIssued++;
}
}
@@ -154,6 +156,7 @@ final class GlStreamingRing {
int remaining() { return remaining; }
int wraps() { return wraps; }
int fencesIssued() { return fencesIssued; }
static long totalFencesIssued() { return totalFencesIssued; }
int forcedReclaims() { return forcedReclaims; }
void destroy() {
@@ -45,12 +45,14 @@ public final class PersistentStreamingBuffer {
public int getBufferId() { return ring.bufferId(); }
public int getCapacity() { return ring.capacity(); }
public int getRemaining() { return ring.remaining(); }
public int getFencesIssued() { return ring.fencesIssued(); }
public int getForcedReclaims() { return ring.forcedReclaims(); }
public int getWraps() { return ring.wraps(); }
public static long getFencesIssuedByAllRings() { return GlStreamingRing.totalFencesIssued(); }
ByteBuffer getMappedBuffer() { return ring.mapped(); }
public void postDraw() {
public void endFrame() {
ring.endFrame();
}
@@ -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) {
@@ -330,7 +331,7 @@ public class TessellatorStreamingDrawer {
public static void endFrame() {
if (persistentBuffer != null) {
persistentBuffer.postDraw();
persistentBuffer.endFrame();
}
}
@@ -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;
@@ -27,4 +27,20 @@ public final class TextureTargets {
default -> false;
};
}
public static int bindingQuery(int target) {
return switch (target) {
case GL11.GL_TEXTURE_1D -> GL11.GL_TEXTURE_BINDING_1D;
case GL12.GL_TEXTURE_3D -> GL12.GL_TEXTURE_BINDING_3D;
case GL13.GL_TEXTURE_CUBE_MAP -> GL13.GL_TEXTURE_BINDING_CUBE_MAP;
case GL30.GL_TEXTURE_1D_ARRAY -> GL30.GL_TEXTURE_BINDING_1D_ARRAY;
case GL30.GL_TEXTURE_2D_ARRAY -> GL30.GL_TEXTURE_BINDING_2D_ARRAY;
case GL31.GL_TEXTURE_RECTANGLE -> GL31.GL_TEXTURE_BINDING_RECTANGLE;
case GL31.GL_TEXTURE_BUFFER -> GL31.GL_TEXTURE_BINDING_BUFFER;
case GL32.GL_TEXTURE_2D_MULTISAMPLE -> GL32.GL_TEXTURE_BINDING_2D_MULTISAMPLE;
case GL32.GL_TEXTURE_2D_MULTISAMPLE_ARRAY -> GL32.GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY;
case GL40.GL_TEXTURE_CUBE_MAP_ARRAY -> GL40.GL_TEXTURE_BINDING_CUBE_MAP_ARRAY;
default -> GL11.GL_TEXTURE_BINDING_2D;
};
}
}
@@ -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);
}
@@ -7,7 +7,6 @@ import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.nio.file.Path;
import java.util.stream.Stream;
@@ -24,8 +23,11 @@ class CompatShaderTransformerTest {
return values;
}
private static void assertRewrite(boolean fragment, String src, String[] present, String[] absent) {
final String result = CompatShaderTransformer.transform(src, fragment);
private static String compact(String s) {
return s.replaceAll("\\s+", "");
}
private static void assertNeedles(String result, String[] present, String[] absent) {
for (final String s : present) {
assertTrue(result.contains(s), () -> "expected '" + s + "' in\n\n" + result);
}
@@ -34,7 +36,7 @@ class CompatShaderTransformerTest {
}
}
static Stream<Arguments> matrixBuiltins() {
static Stream<Arguments> rewrites() {
return Stream.of(
Arguments.of("modelview and projection matrices", false, """
#version 120
@@ -78,17 +80,7 @@ class CompatShaderTransformerTest {
vec4 color = texture2D(tex, vec2(0.0));
gl_FragColor = color;
}
""", strings("angelica_ModelViewMatrix", "angelica_ProjectionMatrix", "angelica_NormalMatrix", "#version 330 core"), strings("gl_ModelViewMatrix", "gl_ProjectionMatrix", "gl_NormalMatrix", "texture2D", "gl_FragColor")));
}
@ParameterizedTest(name = "{0}")
@MethodSource("matrixBuiltins")
void matrixBuiltinsAreRewritten(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
static Stream<Arguments> textureFunctions() {
return Stream.of(
""", strings("angelica_ModelViewMatrix", "angelica_ProjectionMatrix", "angelica_NormalMatrix", "#version 330 core"), strings("gl_ModelViewMatrix", "gl_ProjectionMatrix", "gl_NormalMatrix", "texture2D", "gl_FragColor")),
Arguments.of("texture2D", false, """
#version 120
uniform sampler2D tex;
@@ -111,33 +103,7 @@ class CompatShaderTransformerTest {
vec4 v = texelFetch3D(vol, ivec3(0), 0);
gl_FragColor = v;
}
""", strings("texelFetch"), strings("texelFetch3D")));
}
@ParameterizedTest(name = "{0}")
@MethodSource("textureFunctions")
void legacyTextureFunctionsAreRenamed(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
@Test
void testFragColorHandling() {
String src = """
#version 120
void main() {
gl_FragColor = vec4(1.0);
}
""";
String result = CompatShaderTransformer.transform(src, true);
assertFalse(result.contains("gl_FragColor"));
// Should have layout-qualified output declaration
assertTrue(result.contains("angelica_FragData0"));
assertTrue(result.contains("layout"));
}
static Stream<Arguments> versionDirectives() {
return Stream.of(
""", strings("texelFetch"), strings("texelFetch3D")),
Arguments.of("110 becomes 330 core", true, """
#version 110
void main() {
@@ -149,51 +115,7 @@ class CompatShaderTransformerTest {
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("#version 330 core"), strings("compatibility")));
}
@ParameterizedTest(name = "{0}")
@MethodSource("versionDirectives")
void versionIsUpgradedToCore(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
@Test
void testPassthroughUnchangedShader() {
String src = """
#version 330 core
in vec3 position;
uniform mat4 mvp;
void main() {
gl_Position = mvp * vec4(position, 1.0);
}
""";
String result = CompatShaderTransformer.transform(src, false);
// Source should be returned as-is (passthrough)
assertEquals(src, result);
}
@Test
void testFogStructAccess() {
String src = """
#version 120
void main() {
vec4 c = gl_Fog.color;
float d = gl_Fog.density;
}
""";
String result = CompatShaderTransformer.transform(src, true);
assertTrue(result.contains("angelica_Fog"), "gl_Fog should be renamed to angelica_Fog");
assertFalse(result.contains("gl_Fog"), "gl_Fog should not remain in output");
assertTrue(result.contains("angelica_FogParameters"), "Fog struct should be injected");
assertTrue(result.contains("uniform float angelica_FogDensity"), "Fog density uniform should be injected");
assertTrue(result.contains("uniform vec4 angelica_FogColor"), "Fog color uniform should be injected");
}
static Stream<Arguments> fogFragCoord() {
return Stream.of(
""", strings("#version 330 core"), strings("compatibility")),
Arguments.of("vertex shader declares an out", false, """
#version 120
void main() {
@@ -206,80 +128,7 @@ class CompatShaderTransformerTest {
float f = gl_FogFragCoord;
gl_FragColor = vec4(f);
}
""", strings("in float angelica_FogFragCoord"), strings("gl_FogFragCoord")));
}
@ParameterizedTest(name = "{0}")
@MethodSource("fogFragCoord")
void fogFragCoordBecomesAVarying(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
@Test
void testGlFrontColorVertex() {
String src = """
#version 120
void main() {
gl_FrontColor = vec4(1.0, 0.0, 0.0, 1.0);
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("vec4 angelica_FrontColor"), "gl_FrontColor should become angelica_FrontColor out varying");
assertFalse(result.contains("gl_FrontColor"), "gl_FrontColor should not remain");
}
@Test
void testReservedWordTextureAsVariable() {
// Shader declares 'texture' as a sampler variable - this is valid in GLSL 120
// but 'texture' is a reserved word in GLSL 330+, causing parse failures without pre-rename
String src = """
#version 120
uniform sampler2D texture;
void main() {
gl_FragColor = texture2D(texture, vec2(0.5));
}
""";
// Should not throw - the pre-parse replaceTexture handles this
String result = CompatShaderTransformer.transform(src, true);
assertNotNull(result);
assertTrue(result.contains("#version 330 core"));
// The variable named 'texture' should have been renamed to gtexture (matching CommonTransformer convention)
assertTrue(result.contains("gtexture"), "Variable 'texture' should be renamed to gtexture");
}
@Test
void testCacheHit() {
String src = """
#version 120
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result1 = CompatShaderTransformer.transform(src, false);
String result2 = CompatShaderTransformer.transform(src, false);
assertSame(result1, result2, "Second call should return cached instance (same reference)");
}
@Test
void testAlphaTestDiscardInjected() {
String src = """
#version 120
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5);
}
""";
String result = CompatShaderTransformer.transform(src, true);
assertTrue(result.contains("uniform float angelica_currentAlphaTest"), "Alpha test uniform should be injected");
assertTrue(result.contains("angelica_FragData0 . a <= angelica_currentAlphaTest") || result.contains("angelica_FragData0.a <= angelica_currentAlphaTest"),
"Alpha test discard should be appended to main()");
}
static Stream<Arguments> vertexAttributes() {
return Stream.of(
""", strings("in float angelica_FogFragCoord"), strings("gl_FogFragCoord")),
Arguments.of("gl_Vertex at location 0", false, """
#version 120
void main() {
@@ -314,17 +163,7 @@ class CompatShaderTransformerTest {
vec3 n = gl_NormalMatrix * gl_Normal;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("in vec3 angelica_Normal", "location = 4"), strings("gl_Normal")));
}
@ParameterizedTest(name = "{0}")
@MethodSource("vertexAttributes")
void vertexAttributeBuiltinsGetFixedLocations(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
static Stream<Arguments> texCoordVaryings() {
return Stream.of(
""", strings("in vec3 angelica_Normal", "location = 4"), strings("gl_Normal")),
Arguments.of("vertex shader declares an out", false, """
#version 120
void main() {
@@ -346,144 +185,280 @@ class CompatShaderTransformerTest {
gl_TexCoord[1] = gl_MultiTexCoord1;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("out vec4 angelica_TexCoord0", "out vec4 angelica_TexCoord1"), strings("gl_TexCoord")));
""", strings("out vec4 angelica_TexCoord0", "out vec4 angelica_TexCoord1"), strings("gl_TexCoord")),
Arguments.of("gl_FragColor becomes a layout-qualified output", true, """
#version 120
void main() {
gl_FragColor = vec4(1.0);
}
""", strings("angelica_FragData0", "layout"), strings("gl_FragColor")),
Arguments.of("gl_Fog struct access is renamed and its uniforms injected", true, """
#version 120
void main() {
vec4 c = gl_Fog.color;
float d = gl_Fog.density;
}
""", strings("angelica_Fog", "angelica_FogParameters", "uniform float angelica_FogDensity", "uniform vec4 angelica_FogColor"), strings("gl_Fog")),
Arguments.of("gl_FrontColor becomes an out varying", false, """
#version 120
void main() {
gl_FrontColor = vec4(1.0, 0.0, 0.0, 1.0);
}
""", strings("vec4 angelica_FrontColor"), strings("gl_FrontColor")),
Arguments.of("texture as a variable name is renamed to gtexture", true, """
#version 120
uniform sampler2D texture;
void main() {
gl_FragColor = texture2D(texture, vec2(0.5));
}
""", strings("#version 330 core", "gtexture"), strings()),
Arguments.of("vertex gl_Color is the color attribute", false, """
#version 120
void main() {
gl_FrontColor = gl_Color;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("angelica_Color", "angelica_FrontColor"), strings("gl_Color", "gl_FrontColor")),
Arguments.of("fragment gl_Color is the interpolated front color", true, """
#version 120
void main() {
gl_FragColor = gl_Color;
}
""", strings("angelica_FrontColor"), strings("gl_Color")),
Arguments.of("#define survives the transformation", true, """
#version 120
#define M_PI 3.1415926535897932384626433832795
void main() {
float x = 2.0 * M_PI;
gl_FragColor = vec4(x);
}
""", strings("#define M_PI 3.1415926535897932384626433832795"), strings()),
Arguments.of("gl_FrontLightModelProduct.sceneColor becomes a uniform", false, """
#version 120
void main() {
gl_FrontColor = gl_FrontLightModelProduct.sceneColor;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("uniform vec4 angelica_SceneColor"), strings("gl_FrontLightModelProduct")),
Arguments.of("version fixup does not eat the next line", false, "#version 330\nsomeword\n", strings("someword", "#version 330 core"), strings()),
Arguments.of("modern shader without core keyword keeps its content", false, """
#version 430
in vec3 Position;
uniform mat4 ModelViewMat;
uniform mat4 ProjMat;
out vec4 vertexColor;
out float fogDistance;
void main() {
vec4 finalPos = vec4(Position, 1.0);
vec4 modelPos = ModelViewMat * finalPos;
gl_Position = ProjMat * modelPos;
fogDistance = length(modelPos.xz);
vertexColor = vec4(1.0);
}
""", strings("#version 430 core", "in vec3 Position", "ModelViewMat", "ProjMat", "fogDistance"), strings()),
Arguments.of("vertex attribute and varying become in and out", false, """
#version 120
attribute float a_Custom;
varying vec2 v_TexCoord;
void main() {
v_TexCoord = vec2(a_Custom);
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("in float a_Custom", "out vec2 v_TexCoord"), strings("attribute", "varying")),
Arguments.of("fragment varying becomes in", true, """
#version 120
varying vec2 v_TexCoord;
void main() {
gl_FragColor = vec4(v_TexCoord, 0.0, 1.0);
}
""", strings("in vec2 v_TexCoord"), strings("varying")));
}
@ParameterizedTest(name = "{0}")
@MethodSource("texCoordVaryings")
void texCoordBecomesPerIndexVaryings(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
@MethodSource
void rewrites(String name, boolean fragment, String src, String[] present, String[] absent) {
assertNeedles(CompatShaderTransformer.transform(src, fragment), present, absent);
}
@Test
void testGlColorVertexVsFragmentDifferentTreatment() {
// Vertex: gl_Color = vertex attribute -> angelica_Color
// Fragment: gl_Color = interpolated gl_FrontColor -> angelica_FrontColor
String vertSrc = """
#version 120
void main() {
gl_FrontColor = gl_Color;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String fragSrc = """
#version 120
void main() {
gl_FragColor = gl_Color;
}
""";
String vertResult = CompatShaderTransformer.transform(vertSrc, false);
String fragResult = CompatShaderTransformer.transform(fragSrc, true);
// Vertex shader: gl_Color -> angelica_Color (attribute), gl_FrontColor -> angelica_FrontColor (out)
assertTrue(vertResult.contains("angelica_Color"), "vertex gl_Color -> angelica_Color");
assertTrue(vertResult.contains("angelica_FrontColor"), "vertex gl_FrontColor -> angelica_FrontColor");
assertFalse(vertResult.contains("gl_Color"), "no gl_Color should remain in vertex");
assertFalse(vertResult.contains("gl_FrontColor"), "no gl_FrontColor should remain");
// Fragment shader: gl_Color -> angelica_FrontColor (in varying)
assertTrue(fragResult.contains("angelica_FrontColor"), "fragment gl_Color -> angelica_FrontColor");
assertFalse(fragResult.contains("gl_Color"), "fragment should NOT have gl_Color");
}
@Test
void testDefinePreservedDuringTransformation() {
void testAlphaTestDiscardInjected() {
String src = """
#version 120
#define M_PI 3.1415926535897932384626433832795
void main() {
float x = 2.0 * M_PI;
gl_FragColor = vec4(x);
gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5);
}
""";
String result = CompatShaderTransformer.transform(src, true);
assertTrue(result.contains("#define M_PI 3.1415926535897932384626433832795"),
"#define should be preserved through transformation\n\n" + result);
assertTrue(result.contains("uniform float angelica_currentAlphaTest"), "Alpha test uniform should be injected");
assertTrue(result.contains("angelica_FragData0 . a <= angelica_currentAlphaTest") || result.contains("angelica_FragData0.a <= angelica_currentAlphaTest"),
"Alpha test discard should be appended to main()");
}
static Stream<Arguments> inputLocations() {
return Stream.of(
Arguments.of("custom position attribute is pinned to location zero", """
#version 120
attribute vec4 a_position;
uniform mat4 u_projMat;
uniform vec2 u_outSize;
varying vec2 v_texCoord;
void main(){
gl_Position = u_projMat * vec4(a_position.xy, 0.0, 1.0);
v_texCoord = a_position.xy / u_outSize;
}
""", strings("0:vec4 a_position"), strings(), strings("#version 330 core"), strings()),
Arguments.of("location-less inputs get sequential locations", """
#version 120
attribute vec4 a_pos;
attribute vec2 a_uv;
varying vec2 v_uv;
void main() {
v_uv = a_uv;
gl_Position = vec4(a_pos.xy, 0.0, 1.0);
}
""", strings("0:vec4 a_pos", "1:vec2 a_uv"), strings(), strings(), strings()),
Arguments.of("a custom input is placed around gl_Vertex", """
#version 120
attribute vec4 a_foo;
void main() {
gl_Position = ftransform() + a_foo;
}
""", strings("0:vec4 angelica_Vertex", "1:vec4 a_foo"), strings(), strings(), strings()),
Arguments.of("a custom input skips the FFP slots", """
#version 120
attribute vec4 a_foo;
void main() {
gl_Position = vec4(a_foo.xy, 0.0, 1.0) + gl_Color;
}
""", strings("0:vec4 a_foo", "1:vec4 angelica_Color"), strings(), strings(), strings()),
Arguments.of("an explicit low location is reserved", """
#version 120
layout(location = 0) in vec4 a_x;
attribute vec4 a_y;
void main() {
gl_Position = a_x + a_y;
}
""", strings("0:vec4 a_x", "1:vec4 a_y"), strings(), strings(), strings()),
Arguments.of("a 4-slot matrix input is sorted first", """
#version 120
attribute vec4 a_a;
attribute mat4 a_m;
void main() {
gl_Position = a_m * a_a;
}
""", strings("0:mat4 a_m", "4:vec4 a_a"), strings(), strings(), strings()),
Arguments.of("an input named location is still pinned", """
#version 120
attribute vec4 a_location;
void main() {
gl_Position = vec4(a_location.xy, 0.0, 1.0);
}
""", strings("0:vec4 a_location"), strings(), strings(), strings()),
Arguments.of("an input with an explicit layout is left alone", """
#version 120
layout(location = 3) in vec4 a_data;
attribute vec4 a_position;
varying vec2 v_uv;
void main() {
v_uv = a_data.xy;
gl_Position = vec4(a_position.xy, 0.0, 1.0);
}
""", strings("0:vec4 a_position", "3:vec4 a_data"), strings("0:vec4 a_data"), strings(), strings()),
Arguments.of("a gl_Vertex shader does not pin a custom attribute", """
#version 120
attribute float activelights;
void main() {
gl_Position = ftransform();
float x = activelights;
}
""", strings("0:vec4 angelica_Vertex"), strings("0:float activelights"), strings(), strings()),
Arguments.of("array on the declarator round-trips", """
#version 120
attribute vec4 positions[2];
attribute vec4 a_tail;
void main() {
gl_Position = positions[0] + positions[1] + a_tail;
}
""", strings("0:vec4 positions[2]", "2:vec4 a_tail"), strings(), strings(), strings()),
Arguments.of("array on the type round-trips", """
#version 120
attribute vec4[2] positions;
attribute vec4 a_tail;
void main() {
gl_Position = positions[0] + positions[1] + a_tail;
}
""", strings("0:vec4 positions[2]", "2:vec4 a_tail"), strings(), strings(), strings()),
Arguments.of("a multi-declarator is split into separate locations", """
#version 120
attribute vec4 a, b;
void main() {
gl_Position = a + b;
}
""", strings("0:vec4 a", "1:vec4 b"), strings(), strings("#version 330 core"), strings("attribute")),
Arguments.of("a 2-slot array declarator sorts before a 1-slot one", """
#version 120
attribute vec4 a, b[2];
void main() {
gl_Position = a + b[0] + b[1];
}
""", strings("0:vec4 b[2]", "2:vec4 a"), strings(), strings(), strings("attribute")),
Arguments.of("a hex explicit location is preserved and reserved", """
#version 120
layout(location = 0x1) in vec4 a_x;
attribute vec4 a_y;
void main() {
gl_Position = a_x + a_y;
}
""", strings("0x1:vec4 a_x", "0:vec4 a_y"), strings("1:vec4 a_y"), strings(), strings()));
}
@ParameterizedTest(name = "{0}")
@MethodSource
void inputLocations(String name, String src, String[] at, String[] notAt, String[] present, String[] absent) {
final String result = CompatShaderTransformer.transform(src, false);
final String c = compact(result);
for (final String slot : at) {
assertTrue(c.contains(locationDecl(slot)), () -> slot + " expected\n\n" + result);
}
for (final String slot : notAt) {
assertFalse(c.contains(locationDecl(slot)), () -> slot + " must not be assigned\n\n" + result);
}
assertNeedles(result, present, absent);
}
private static String locationDecl(String slot) {
final int colon = slot.indexOf(':');
return compact("layout(location=" + slot.substring(0, colon) + ")in" + slot.substring(colon + 1));
}
@Test
void testGlFrontLightModelProductSceneColor() {
void testPassthroughUnchangedShader() {
String src = """
#version 330 core
in vec3 position;
uniform mat4 mvp;
void main() {
gl_Position = mvp * vec4(position, 1.0);
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertEquals(src, result);
}
@Test
void testCacheHit() {
String src = """
#version 120
void main() {
gl_FrontColor = gl_FrontLightModelProduct.sceneColor;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_FrontLightModelProduct"), "gl_FrontLightModelProduct should be replaced");
assertTrue(result.contains("uniform vec4 angelica_SceneColor"), "angelica_SceneColor uniform should be injected");
}
@Test
void testVersionPatternDoesNotEatNextLine() {
String src = "#version 330\nsomeword\n";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("someword"), "Next-line word must not be eaten by version fixup");
assertTrue(result.contains("#version 330 core"), "Version should be upgraded to core");
}
@Test
void testModernShaderWithoutCoreKeyword() {
String src = """
#version 430
in vec3 Position;
uniform mat4 ModelViewMat;
uniform mat4 ProjMat;
out vec4 vertexColor;
out float fogDistance;
void main() {
vec4 finalPos = vec4(Position, 1.0);
vec4 modelPos = ModelViewMat * finalPos;
gl_Position = ProjMat * modelPos;
fogDistance = length(modelPos.xz);
vertexColor = vec4(1.0);
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("#version 430 core"), "Version should get 'core' profile added");
assertTrue(result.contains("in vec3 Position"), "'in' keyword must not be eaten");
assertTrue(result.contains("ModelViewMat"), "Shader content must be preserved");
assertTrue(result.contains("ProjMat"), "Shader content must be preserved");
assertTrue(result.contains("fogDistance"), "Shader content must be preserved");
}
@Test
void testLegacyQualifierConversion() {
String vertSrc = """
#version 120
attribute float a_Custom;
varying vec2 v_TexCoord;
void main() {
v_TexCoord = vec2(a_Custom);
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String fragSrc = """
#version 120
varying vec2 v_TexCoord;
void main() {
gl_FragColor = vec4(v_TexCoord, 0.0, 1.0);
}
""";
String vertResult = CompatShaderTransformer.transform(vertSrc, false);
String fragResult = CompatShaderTransformer.transform(fragSrc, true);
// attribute -> in, varying -> out (vertex)
assertFalse(vertResult.contains("attribute"), "no 'attribute' keyword in core profile");
assertFalse(vertResult.contains("varying"), "no 'varying' keyword in vertex");
assertTrue(vertResult.contains("in float a_Custom"), "attribute -> in");
assertTrue(vertResult.contains("out vec2 v_TexCoord"), "varying -> out in vertex");
// varying -> in (fragment)
assertFalse(fragResult.contains("varying"), "no 'varying' keyword in fragment");
assertTrue(fragResult.contains("in vec2 v_TexCoord"), "varying -> in in fragment");
String result1 = CompatShaderTransformer.transform(src, false);
String result2 = CompatShaderTransformer.transform(src, false);
assertSame(result1, result2, "Second call should return cached instance (same reference)");
}
@Test
@@ -525,140 +500,6 @@ class CompatShaderTransformerTest {
assertTrue(result.contains("angelica_renamed_new"), "'new' should be renamed to angelica_renamed_new\n\n" + result);
}
private static String compact(String s) {
return s.replaceAll("\\s+", "");
}
private static String core(String src) {
return compact(CompatShaderTransformer.transform(src, false));
}
private static void assertLoc(String c, int loc, String decl) {
assertTrue(c.contains(compact("layout(location=" + loc + ")in" + decl)), decl + " expected at location " + loc + "\n\n" + c);
}
private static void assertNoLoc(String c, int loc, String decl) {
assertFalse(c.contains(compact("layout(location=" + loc + ")in" + decl)), decl + " must not be at location " + loc + "\n\n" + c);
}
@Test
void testCustomPositionAttributePinnedToLocationZero() {
// Betweenlands blshader fix
String src = """
#version 120
attribute vec4 a_position;
uniform mat4 u_projMat;
uniform vec2 u_outSize;
varying vec2 v_texCoord;
void main(){
gl_Position = u_projMat * vec4(a_position.xy, 0.0, 1.0);
v_texCoord = a_position.xy / u_outSize;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertLoc(compact(result), 0, "vec4 a_position");
assertTrue(result.contains("#version 330 core"), result);
}
@Test
void testMultipleLocationlessInputsGetSequentialLocations() {
String c = core("""
#version 120
attribute vec4 a_pos;
attribute vec2 a_uv;
varying vec2 v_uv;
void main() {
v_uv = a_uv;
gl_Position = vec4(a_pos.xy, 0.0, 1.0);
}
""");
assertLoc(c, 0, "vec4 a_pos");
assertLoc(c, 1, "vec2 a_uv");
}
@Test
void testCustomInputReservesAroundGlVertex() {
String c = core("""
#version 120
attribute vec4 a_foo;
void main() {
gl_Position = ftransform() + a_foo;
}
""");
assertLoc(c, 0, "vec4 angelica_Vertex");
assertLoc(c, 1, "vec4 a_foo");
}
@Test
void testCustomInputSkipsFfpSlots() {
String c = core("""
#version 120
attribute vec4 a_foo;
void main() {
gl_Position = vec4(a_foo.xy, 0.0, 1.0) + gl_Color;
}
""");
assertLoc(c, 0, "vec4 a_foo");
assertLoc(c, 1, "vec4 angelica_Color");
}
@Test
void testExplicitLowLocationReserved() {
String c = core("""
#version 120
layout(location = 0) in vec4 a_x;
attribute vec4 a_y;
void main() {
gl_Position = a_x + a_y;
}
""");
assertLoc(c, 0, "vec4 a_x");
assertLoc(c, 1, "vec4 a_y");
}
@Test
void testMatrixInputSortedFirst() {
String c = core("""
#version 120
attribute vec4 a_a;
attribute mat4 a_m;
void main() {
gl_Position = a_m * a_a;
}
""");
assertLoc(c, 0, "mat4 a_m"); // 4-slot matrix sorted first
assertLoc(c, 4, "vec4 a_a");
}
@Test
void testInputNamedLocationStillPinned() {
assertLoc(core("""
#version 120
attribute vec4 a_location;
void main() {
gl_Position = vec4(a_location.xy, 0.0, 1.0);
}
"""), 0, "vec4 a_location");
}
@Test
void testInputWithExplicitLayoutLeftAlone() {
String c = core("""
#version 120
layout(location = 3) in vec4 a_data;
attribute vec4 a_position;
varying vec2 v_uv;
void main() {
v_uv = a_data.xy;
gl_Position = vec4(a_position.xy, 0.0, 1.0);
}
""");
assertLoc(c, 0, "vec4 a_position"); // sole location-less input
assertLoc(c, 3, "vec4 a_data"); // explicit location preserved
assertNoLoc(c, 0, "vec4 a_data");
}
@Test
void testPinnedShaderRemainsWellFormed() {
String src = """
@@ -673,8 +514,8 @@ class CompatShaderTransformerTest {
String result = CompatShaderTransformer.transform(src, false);
String c = compact(result);
assertLoc(c, 0, "vec4 a_position");
assertLoc(c, 1, "vec4 angelica_Color");
assertTrue(c.contains(locationDecl("0:vec4 a_position")), result);
assertTrue(c.contains(locationDecl("1:vec4 angelica_Color")), result);
assertTrue(c.contains("uniformmat4angelica_ModelViewMatrix"), "matrix uniform injected\n\n" + result);
assertFalse(c.contains("gl_ModelViewMatrix"), "builtin rewritten (not fallback)\n\n" + result);
@@ -684,82 +525,6 @@ class CompatShaderTransformerTest {
assertTrue(result.indexOf("a_position") < mainIdx, "pinned input before main\n\n" + result);
}
@Test
void testGlVertexShaderDoesNotPinCustomAttribute() {
String c = core("""
#version 120
attribute float activelights;
void main() {
gl_Position = ftransform();
float x = activelights;
}
""");
assertLoc(c, 0, "vec4 angelica_Vertex");
assertNoLoc(c, 0, "float activelights");
}
@ParameterizedTest // array on the declarator (vec4 a[2]) and on the type (vec4[2] a) must both round-trip
@ValueSource(strings = {"attribute vec4 positions[2];", "attribute vec4[2] positions;"})
void testArrayAttributePreservedEitherSyntax(String decl) {
String c = core("""
#version 120
%s
attribute vec4 a_tail;
void main() {
gl_Position = positions[0] + positions[1] + a_tail;
}
""".formatted(decl));
assertLoc(c, 0, "vec4 positions[2]"); // 2-slot array pinned to 0..1
assertLoc(c, 2, "vec4 a_tail");
}
@Test
void testMultiDeclaratorSplitIntoSeparateLocations() {
String result = CompatShaderTransformer.transform("""
#version 120
attribute vec4 a, b;
void main() {
gl_Position = a + b;
}
""", false);
String c = compact(result);
assertLoc(c, 0, "vec4 a");
assertLoc(c, 1, "vec4 b");
assertFalse(result.matches("(?s).*\\battribute\\b.*"), "no deprecated 'attribute' keyword may survive\n\n" + result);
assertTrue(result.contains("#version 330 core"), result);
}
@Test
void testMultiDeclaratorWithMixedArray() {
String result = CompatShaderTransformer.transform("""
#version 120
attribute vec4 a, b[2];
void main() {
gl_Position = a + b[0] + b[1];
}
""", false);
String c = compact(result);
// b takes 2 slots; sorted before the 1-slot a, so b -> 0..1, a -> 2.
assertLoc(c, 0, "vec4 b[2]");
assertLoc(c, 2, "vec4 a");
assertFalse(result.matches("(?s).*\\battribute\\b.*"), "no deprecated 'attribute' keyword may survive\n\n" + result);
}
@Test
void testHexExplicitLocationReserved() {
String c = core("""
#version 120
layout(location = 0x1) in vec4 a_x;
attribute vec4 a_y;
void main() {
gl_Position = a_x + a_y;
}
""");
assertTrue(c.contains("layout(location=0x1)invec4a_x"), "hex explicit location preserved\n\n" + c);
assertNoLoc(c, 1, "vec4 a_y"); // must not collide with the hex-reserved slot
assertLoc(c, 0, "vec4 a_y");
}
@Test
void diskCacheHitReplacesTransform(@TempDir Path dir) {
final String src = """
@@ -99,22 +99,6 @@ class DisplayListCompilationParityTest {
GLStateManager.glLoadIdentity();
}
@ParameterizedTest
@EnumSource(Caller.class)
void compile_doesNotChangeState(Caller c) {
testList = GL11.glGenLists(1);
c.newList(testList, GL11.GL_COMPILE);
c.enable(GL11.GL_BLEND);
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND), "During GL_COMPILE: state unchanged");
c.endList();
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND), "After GL_COMPILE: state unchanged");
c.callList(testList);
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND), "After playback: state changed");
}
@ParameterizedTest
@EnumSource(Caller.class)
void compileAndExecute_changesStateImmediately(Caller c) {
@@ -186,6 +170,9 @@ class DisplayListCompilationParityTest {
c.enable(GL11.GL_BLEND);
c.enable(GL11.GL_DEPTH_TEST);
c.enable(GL11.GL_CULL_FACE);
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND), "During GL_COMPILE: BLEND unchanged");
c.endList();
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND), "After GL_COMPILE: BLEND unchanged");
@@ -197,6 +197,7 @@ class DisplayListMatrixRestorationTest extends DisplayListTestFixture {
GLStateManager.glCallList(list);
String context = "compileMode=" + compileMode;
assertEquals(GL13.GL_TEXTURE0, GLStateManager.glGetInteger(GL13.GL_ACTIVE_TEXTURE), context);
assertEquals(GL13.GL_TEXTURE0, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE), context);
assertEquals(3f, translation(GL11.GL_MODELVIEW_MATRIX), context);
assertEquals(0f, translation(GL11.GL_TEXTURE_MATRIX), context);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
@@ -55,36 +55,7 @@ class FeedbackManagerTest {
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
// Set up identity matrices and a viewport
GLStateManager.getModelViewMatrix().identity();
GLStateManager.getProjectionMatrix().identity();
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
// Upload 3 vertices to a VBO and draw
final int vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
final float[] verts = { 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f, 0f };
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(9);
vertBuf.put(verts).flip();
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
FeedbackManager.processDrawArrays(GL11.GL_TRIANGLES, 0, 3);
final int result = FeedbackManager.glRenderMode(GL11.GL_RENDER);
assertEquals(-1, result, "Overflow should return -1");
// Cleanup
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteVertexArrays(vao);
assertEquals(-1, feedbackDraw(GL11.GL_TRIANGLES, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f, 0f), "Overflow should return -1");
}
@Test
@@ -122,37 +93,13 @@ class FeedbackManagerTest {
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
GLStateManager.getModelViewMatrix().identity();
GLStateManager.getProjectionMatrix().identity();
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
final int vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(verts.length);
vertBuf.put(verts).flip();
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
FeedbackManager.processDrawArrays(mode, 0, verts.length / 3);
final int count = FeedbackManager.glRenderMode(GL11.GL_RENDER);
final int count = feedbackDraw(mode, verts);
// GL_2D: POLYGON_TOKEN, vertex count, then x, y per vertex
assertEquals(expected.length, count, name + " float count");
for (int i = 0; i < expected.length; i++) {
assertEquals(expected[i], buf.get(i), 0.01f, name + " feedback value " + i);
}
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteVertexArrays(vao);
}
@Test
@@ -161,28 +108,7 @@ class FeedbackManagerTest {
FeedbackManager.glFeedbackBuffer(GL11.GL_3D, buf);
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
GLStateManager.getModelViewMatrix().identity();
GLStateManager.getProjectionMatrix().identity();
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
final int vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
// Single point at NDC origin (0,0,0)
final float[] verts = { 0f, 0f, 0f };
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(3);
vertBuf.put(verts).flip();
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
FeedbackManager.processDrawArrays(GL11.GL_POINTS, 0, 1);
final int count = FeedbackManager.glRenderMode(GL11.GL_RENDER);
final int count = feedbackDraw(GL11.GL_POINTS, 0f, 0f, 0f);
// GL_3D point: POINT_TOKEN, x, y, z = 4 floats
assertEquals(4, count);
@@ -191,11 +117,31 @@ class FeedbackManagerTest {
assertEquals(400.0f, buf.get(1), 0.01f);
assertEquals(300.0f, buf.get(2), 0.01f);
assertEquals(0.5f, buf.get(3), 0.01f);
}
// Cleanup
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteVertexArrays(vao);
private static int feedbackDraw(int mode, float... verts) {
GLStateManager.getModelViewMatrix().identity();
GLStateManager.getProjectionMatrix().identity();
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
final int vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(verts.length);
vertBuf.put(verts).flip();
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuf, GL15.GL_STATIC_DRAW);
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
try {
FeedbackManager.processDrawArrays(mode, 0, verts.length / 3);
return FeedbackManager.glRenderMode(GL11.GL_RENDER);
} finally {
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteVertexArrays(vao);
}
}
}
@@ -1,12 +1,17 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
class GLESFormatRemapTest {
@@ -26,82 +31,45 @@ class GLESFormatRemapTest {
assertEquals(GL11.GL_RGBA4, remapDesktop(GL11.GL_ALPHA4, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE).internalFormat());
}
@Test
void rgb16AndRgba16_remapToHalfFloat_onGles() {
final var r1 = remap(GL11.GL_RGB16, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE);
assertEquals(GL30.GL_RGB16F, r1.internalFormat());
assertEquals(GL30.GL_HALF_FLOAT, r1.type());
final var r2 = remap(GL11.GL_RGBA16, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
assertEquals(GL30.GL_RGBA16F, r2.internalFormat());
assertEquals(GL30.GL_HALF_FLOAT, r2.type());
static Stream<Arguments> remappedType() {
return Stream.of(
Arguments.of("R16F gets half float", true, GL30.GL_R16F, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, GL30.GL_HALF_FLOAT),
Arguments.of("RG16F gets half float", true, GL30.GL_RG16F, GL30.GL_RG, GL11.GL_UNSIGNED_BYTE, GL30.GL_HALF_FLOAT),
Arguments.of("RGB16F gets half float", true, GL30.GL_RGB16F, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, GL30.GL_HALF_FLOAT),
Arguments.of("RGBA16F gets half float", true, GL30.GL_RGBA16F, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, GL30.GL_HALF_FLOAT),
Arguments.of("RGBA32F gets float", true, GL30.GL_RGBA32F, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, GL11.GL_FLOAT),
Arguments.of("RGB32F gets float", true, GL30.GL_RGB32F, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, GL11.GL_FLOAT),
Arguments.of("RGBA8_SNORM gets byte", true, GL31.GL_RGBA8_SNORM, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, GL11.GL_BYTE),
Arguments.of("RGB8_SNORM gets byte", true, GL31.GL_RGB8_SNORM, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, GL11.GL_BYTE),
Arguments.of("RGB10_A2 gets packed type", true, GL11.GL_RGB10_A2, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, GL12.GL_UNSIGNED_INT_2_10_10_10_REV),
Arguments.of("DEPTH_COMPONENT24 gets unsigned int", true, GL14.GL_DEPTH_COMPONENT24, GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_BYTE, GL11.GL_UNSIGNED_INT),
Arguments.of("DEPTH_COMPONENT32F gets float", true, GL30.GL_DEPTH_COMPONENT32F, GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_BYTE, GL11.GL_FLOAT),
Arguments.of("DEPTH24_STENCIL8 gets 24_8", true, GL30.GL_DEPTH24_STENCIL8, GL30.GL_DEPTH_STENCIL, GL11.GL_UNSIGNED_BYTE, GL30.GL_UNSIGNED_INT_24_8),
Arguments.of("already valid type is left alone", true, GL30.GL_RGBA16F, GL11.GL_RGBA, GL30.GL_HALF_FLOAT, GL30.GL_HALF_FLOAT),
Arguments.of("packed ARGB RGBA remaps to unsigned byte on GLES", true, GL11.GL_RGBA8, GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, GL11.GL_UNSIGNED_BYTE),
Arguments.of("packed ARGB BGRA remaps to unsigned byte on GLES", true, GL11.GL_RGBA8, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, GL11.GL_UNSIGNED_BYTE),
Arguments.of("packed ARGB untouched on desktop", false, GL11.GL_RGBA8, GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, GL12.GL_UNSIGNED_INT_8_8_8_8_REV));
}
@Test
void rgb16AndRgba16_passThroughUntouched_onDesktop() {
final var r = remapDesktop(GL11.GL_RGB16, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE);
assertEquals(GL11.GL_RGB16, r.internalFormat());
assertEquals(GL11.GL_UNSIGNED_BYTE, r.type());
@ParameterizedTest(name = "{0}")
@MethodSource
void remappedType(String label, boolean gles, int internal, int format, int type, int expectedType) {
assertEquals(expectedType, GLESFormatRemap.apply(internal, format, type, gles).type());
}
@Test
void halfFloatInternalFormats_getHalfFloatType() {
assertEquals(GL30.GL_HALF_FLOAT, remap(GL30.GL_R16F, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE).type());
assertEquals(GL30.GL_HALF_FLOAT, remap(GL30.GL_RG16F, GL30.GL_RG, GL11.GL_UNSIGNED_BYTE).type());
assertEquals(GL30.GL_HALF_FLOAT, remap(GL30.GL_RGB16F, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE).type());
assertEquals(GL30.GL_HALF_FLOAT, remap(GL30.GL_RGBA16F, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE).type());
static Stream<Arguments> remappedFormatAndType() {
return Stream.of(
Arguments.of("RGB16 remaps to half float on GLES", true, GL11.GL_RGB16, GL11.GL_RGB, GL30.GL_RGB16F, GL30.GL_HALF_FLOAT),
Arguments.of("RGBA16 remaps to half float on GLES", true, GL11.GL_RGBA16, GL11.GL_RGBA, GL30.GL_RGBA16F, GL30.GL_HALF_FLOAT),
Arguments.of("RGB16 passes through untouched on desktop", false, GL11.GL_RGB16, GL11.GL_RGB, GL11.GL_RGB16, GL11.GL_UNSIGNED_BYTE),
Arguments.of("R11F_G11F_B10F keeps its format and gets half float", true, GL30.GL_R11F_G11F_B10F, GL11.GL_RGB, GL30.GL_R11F_G11F_B10F, GL30.GL_HALF_FLOAT));
}
@Test
void floatInternalFormats_getFloatType() {
assertEquals(GL11.GL_FLOAT, remap(GL30.GL_RGBA32F, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE).type());
assertEquals(GL11.GL_FLOAT, remap(GL30.GL_RGB32F, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE).type());
}
@Test
void snormInternalFormats_getByteType() {
assertEquals(GL11.GL_BYTE, remap(GL31.GL_RGBA8_SNORM, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE).type());
assertEquals(GL11.GL_BYTE, remap(GL31.GL_RGB8_SNORM, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE).type());
}
@Test
void r11fG11fB10f_getsHalfFloatType() {
final var r = remap(GL30.GL_R11F_G11F_B10F, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE);
assertEquals(GL30.GL_R11F_G11F_B10F, r.internalFormat()); // not remapped
assertEquals(GL30.GL_HALF_FLOAT, r.type());
}
@Test
void rgb10A2_getsPackedType() {
final var r = remap(GL11.GL_RGB10_A2, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
assertEquals(GL12.GL_UNSIGNED_INT_2_10_10_10_REV, r.type());
}
@Test
void depthFormats_getMatchingType() {
assertEquals(GL11.GL_UNSIGNED_INT, remap(GL14.GL_DEPTH_COMPONENT24, GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_BYTE).type());
assertEquals(GL11.GL_FLOAT, remap(GL30.GL_DEPTH_COMPONENT32F, GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_BYTE).type());
assertEquals(GL30.GL_UNSIGNED_INT_24_8, remap(GL30.GL_DEPTH24_STENCIL8, GL30.GL_DEPTH_STENCIL, GL11.GL_UNSIGNED_BYTE).type());
}
@Test
void alreadyValidType_isLeftAlone() {
final var r = remap(GL30.GL_RGBA16F, GL11.GL_RGBA, GL30.GL_HALF_FLOAT);
assertEquals(GL30.GL_HALF_FLOAT, r.type());
}
@Test
void packedArgb_remapsToUnsignedByte_onGles() {
final var r = remap(GL11.GL_RGBA8, GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV);
assertEquals(GL11.GL_UNSIGNED_BYTE, r.type());
final var r2 = remap(GL11.GL_RGBA8, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV);
assertEquals(GL11.GL_UNSIGNED_BYTE, r2.type());
}
@Test
void packedArgb_untouched_onDesktop() {
final var r = remapDesktop(GL11.GL_RGBA8, GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV);
assertEquals(GL12.GL_UNSIGNED_INT_8_8_8_8_REV, r.type());
@ParameterizedTest(name = "{0}")
@MethodSource
void remappedFormatAndType(String label, boolean gles, int internal, int format, int expectedInternal, int expectedType) {
final GLESFormatRemap.Result r = GLESFormatRemap.apply(internal, format, GL11.GL_UNSIGNED_BYTE, gles);
assertEquals(expectedInternal, r.internalFormat());
assertEquals(expectedType, r.type());
}
}
@@ -14,6 +14,7 @@ import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL33;
import java.nio.FloatBuffer;
@@ -502,6 +503,7 @@ public class GLSM_ClientArrays_GLTest {
assertEquals(2, GLStateManager.glGetInteger(GL11.GL_VERTEX_ARRAY_SIZE), "array state is not restored once its array buffer is gone");
assertEquals(vbo2, GLStateManager.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING));
assertEquals(vbo2, GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING));
assertEquals(GL13.GL_TEXTURE0, GLStateManager.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE));
} finally {
drainClientAttribStack();
@@ -524,6 +526,7 @@ public class GLSM_ClientArrays_GLTest {
GLStateManager.glBindVertexArray(other);
GLStateManager.glCallList(list);
assertEquals(other, GLStateManager.getBoundVAO(), "replaying the list must not rebind the vertex array that was bound while compiling");
assertEquals(other, GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING), "driver vertex array unchanged by replay");
} finally {
drainClientAttribStack();
GLStateManager.glDeleteLists(list, 1);
@@ -93,19 +93,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
}
}
@Test
void secondaryColorIsCacheOnly() {
try {
// Calling through to the driver here aborts the JVM in a core profile - the color sum is emulated by the FFP shaders
GLStateManager.glSecondaryColor3f(0.25F, 0.5F, 0.75F);
assertNoGlError("glSecondaryColor3f");
assertSecondaryColor(0.25F, 0.5F, 0.75F);
} finally {
GLStateManager.glSecondaryColor3f(0.0F, 0.0F, 0.0F);
}
}
@Test
void secondaryColorIsRestoredByPopAttrib() {
try {
@@ -1,9 +1,20 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
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.GL14;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL33;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@@ -11,6 +22,10 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
public class GLSM_DeferredTextureDelete_GLTest {
private static final int RELEASE_DELAY_FRAMES = Reflect.getStatic(GLStateManager.class, "RELEASE_DELAY_FRAMES");
private static final int SIZE = 4;
private static final int LEVELS = 3;
private static int immutableTexture() {
assumeTrue(GLStateManager.capabilities.OpenGL42, "no glTexStorage2D on this context");
final int tex = GLStateManager.glGenTextures();
@@ -20,6 +35,292 @@ public class GLSM_DeferredTextureDelete_GLTest {
return tex;
}
private static int mipmappedTexture() {
final int tex = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
for (int level = 0; level < LEVELS; level++) {
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA8, SIZE >> level, SIZE >> level, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
}
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
return tex;
}
private static int levelWidth(int tex, int level) {
final int previous = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, tex);
final int width = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_WIDTH);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, previous);
return width;
}
private static int cachedBinding(int unit) {
return GLStateManager.getTextures().getTextureUnitBindings(unit).getBinding();
}
private static void framesPass(int frames) {
for (int i = 0; i < frames; i++) GLStateManager.onFrameEnd();
}
enum Attachment {
NONE {
@Override int attach(int tex) { return 0; }
},
FRAMEBUFFER {
@Override int attach(int tex) {
final int fbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, tex, 0);
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
return fbo;
}
},
NAMED_EXT {
@Override int attach(int tex) {
final int fbo = GLStateManager.glGenFramebuffers();
GLStateManager.glNamedFramebufferTexture2DEXT(fbo, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, tex, 0);
assertEquals(0, GL11.glGetInteger(GL30.GL_DRAW_FRAMEBUFFER_BINDING), "named attach must restore the draw binding");
return fbo;
}
};
abstract int attach(int tex);
}
@ParameterizedTest
@EnumSource(Attachment.class)
void storageIsReleasedOnlyAfterTheDelayFramesUnattached(Attachment attachment) {
framesPass(RELEASE_DELAY_FRAMES);
final int before = GLStateManager.deferredTexturesAwaitingRelease();
final int tex = mipmappedTexture();
final int fbo = attachment.attach(tex);
GLStateManager.glDeleteTextures(tex);
assertEquals(before + 1, GLStateManager.deferredTexturesAwaitingRelease());
framesPass(RELEASE_DELAY_FRAMES - 1);
assertEquals(SIZE, levelWidth(tex, 0), "storage must outlive the delete by the delay frames");
if (fbo != 0) {
framesPass(RELEASE_DELAY_FRAMES);
assertEquals(SIZE, levelWidth(tex, 0), "an attached texture keeps its storage");
assertEquals(before + 1, GLStateManager.deferredTexturesAwaitingRelease());
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glDeleteTextures(GLStateManager.glGenTextures());
assertEquals(tex, GL30.glGetFramebufferAttachmentParameteri(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME), "a flush must not delete an attached texture");
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glDeleteFramebuffers(BufferUtils.createIntBuffer(1).put(fbo).flip());
framesPass(RELEASE_DELAY_FRAMES - 1);
assertEquals(SIZE, levelWidth(tex, 0), "storage must outlive the detach by the delay frames");
}
framesPass(1);
for (int level = 0; level < LEVELS; level++) assertEquals(0, levelWidth(tex, level), "level " + level);
assertEquals(before, GLStateManager.deferredTexturesAwaitingRelease(), "released storage leaves nothing awaiting release");
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
enum Rejection { ATTACH, BIND_TEXTURE_UNIT }
@ParameterizedTest
@EnumSource(Rejection.class)
void deletedNamesAreRejected(Rejection rejection) {
final int tex = mipmappedTexture();
final int other = mipmappedTexture();
GLStateManager.glDeleteTextures(tex);
try {
switch (rejection) {
case ATTACH -> {
final int fbo = GLStateManager.glGenFramebuffers();
try {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, tex, 0);
assertEquals(GL11.GL_NONE, GL30.glGetFramebufferAttachmentParameteri(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE));
} finally {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glDeleteFramebuffers(fbo);
}
}
case BIND_TEXTURE_UNIT -> {
GLStateManager.glBindTextureUnit(2, other);
GLStateManager.glBindTextureUnit(2, tex);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
assertEquals(other, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "driver binding");
assertEquals(other, GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "cached binding");
}
}
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTextureUnit(2, 0);
GLStateManager.glDeleteTextures(other);
}
}
@Test
void bindTexturesBindsEachTextureToItsTarget() {
final int t2d = mipmappedTexture();
final int t3d = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, t3d);
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, 0);
try {
GLStateManager.glBindTextures(1, new int[] {t2d, t3d});
assertEquals(GL13.GL_TEXTURE0, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE), "active unit unchanged");
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
assertEquals(t2d, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "unit 1 driver binding");
assertEquals(t2d, GLStateManager.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "unit 1 cached binding");
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
assertEquals(t3d, GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D), "unit 2 driver binding");
assertEquals(t3d, cachedBinding(2), "unit 2 cached binding");
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final int unit0 = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
GLStateManager.glBindTextures(GLStateManager.MAX_TEXTURE_UNITS - 1, new int[] {t3d, t2d});
assertEquals(unit0, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "an out-of-range multi-bind must not land on the active unit");
GLStateManager.glDeleteTextures(t2d);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
assertEquals(0, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "delete unbinds a multi-bound texture");
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
} finally {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTextures(1, new int[] {0, 0});
GLStateManager.glDeleteTextures(t3d);
}
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void dsaCreated3DTextureIsBoundAs3D() {
final int tex = RenderSystem.createTexture(GL12.GL_TEXTURE_3D);
final int other = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, other);
RenderSystem.texImage3D(tex, GL12.GL_TEXTURE_3D, 0, GL11.GL_RGBA8, SIZE, SIZE, SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
assertEquals(tex, GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D), "texImage3D binds the texture as 3D");
assertEquals(tex, cachedBinding(0), "cached 3D binding");
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, 0);
GLStateManager.glDeleteTextures(tex);
GLStateManager.glDeleteTextures(other);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void createdNon2DTextureIsNotReleasedAs2D() {
assumeTrue(GLStateManager.capabilities.OpenGL45, "no glCreateTextures on this context");
final int tex = GLStateManager.glCreateTextures(GL12.GL_TEXTURE_3D);
GLStateManager.glDeleteTextures(tex);
framesPass(RELEASE_DELAY_FRAMES);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void deletingDetachesOnlyFromBoundFramebuffers() {
final int tex = mipmappedTexture();
final int unbound = Attachment.FRAMEBUFFER.attach(tex);
final int bound = Attachment.FRAMEBUFFER.attach(tex);
try {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, bound);
GLStateManager.glDeleteTextures(tex);
assertEquals(GL11.GL_NONE, GL30.glGetFramebufferAttachmentParameteri(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE));
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, unbound);
assertEquals(tex, GL30.glGetFramebufferAttachmentParameteri(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME));
} finally {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glDeleteFramebuffers(bound);
GLStateManager.glDeleteFramebuffers(unbound);
}
}
enum Rescue { PENDING, RELEASED, ATTACHED }
@ParameterizedTest
@EnumSource(Rescue.class)
void rescuedTextureIsEmptyWithDefaultParameters(Rescue rescue) {
final int tex = mipmappedTexture();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
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);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_BASE_LEVEL, 1);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 2);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL33.GL_TEXTURE_SWIZZLE_R, GL11.GL_GREEN);
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 1.5f);
GLStateManager.glTexParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_BORDER_COLOR, (FloatBuffer) BufferUtils.createFloatBuffer(4).put(new float[] {1, 1, 1, 1}).flip());
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
final int fbo = rescue == Rescue.ATTACHED ? Attachment.FRAMEBUFFER.attach(tex) : 0;
GLStateManager.glDeleteTextures(tex);
if (rescue != Rescue.PENDING) framesPass(RELEASE_DELAY_FRAMES);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
try {
assertEquals(rescue == Rescue.ATTACHED ? SIZE : 0, levelWidth(tex, 0), "only an attached texture keeps its storage");
final int[][] defaults = {
{GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST_MIPMAP_LINEAR},
{GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR},
{GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT},
{GL12.GL_TEXTURE_BASE_LEVEL, 0},
{GL12.GL_TEXTURE_MAX_LEVEL, 1000},
{GL33.GL_TEXTURE_SWIZZLE_R, GL11.GL_RED},
};
for (int[] pair : defaults) {
assertEquals(pair[1], GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, pair[0]), "driver pname 0x" + Integer.toHexString(pair[0]));
assertEquals(pair[1], GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, pair[0]), "cached pname 0x" + Integer.toHexString(pair[0]));
}
assertEquals(0.0f, GL11.glGetTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS));
final FloatBuffer border = BufferUtils.createFloatBuffer(16);
GL11.glGetTexParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_BORDER_COLOR, border);
assertEquals(0.0f, border.get(0) + border.get(1) + border.get(2) + border.get(3), "border color");
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glDeleteTextures(tex);
if (fbo != 0) GLStateManager.glDeleteFramebuffers(fbo);
}
}
@Test
void rescuingWithADifferentTargetLeavesTheBoundTextureAlone() {
final int t3d = GLStateManager.glGenTextures();
final int tex = mipmappedTexture();
try {
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, t3d);
GLStateManager.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLStateManager.glDeleteTextures(tex);
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, tex);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "GLSM drops the bind");
assertEquals(t3d, cachedBinding(0), "cached 3D binding");
assertEquals(GL11.GL_LINEAR, GL11.glGetTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MIN_FILTER));
framesPass(RELEASE_DELAY_FRAMES);
assertEquals(t3d, GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D), "the release must not unbind the 3D texture");
assertEquals(0, levelWidth(tex, 0), "the mismatched bind must leave the texture deferred");
} finally {
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, 0);
GLStateManager.glDeleteTextures(t3d);
}
}
@Test
void deletingAStaleNameKeepsTheBoundTexture() {
final int live = mipmappedTexture();
final int stale = GLStateManager.glGenTextures();
try {
GLStateManager.glDeleteTextures(stale);
GLStateManager.glDeleteTextures(GLStateManager.glGenTextures());
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, live);
GLStateManager.glDeleteTextures(stale);
framesPass(RELEASE_DELAY_FRAMES);
assertEquals(SIZE, levelWidth(live, 0), "a stale delete must not empty the bound texture");
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glDeleteTextures(live);
}
}
@Test
void deletingAnImmutableTextureRaisesNoError() {
final int tex = immutableTexture();
@@ -30,33 +331,40 @@ public class GLSM_DeferredTextureDelete_GLTest {
}
@Test
void deletingARescuedImmutableTextureRaisesNoError() {
void rescuedImmutableTextureResetsParametersAndDeletesAgain() {
final int tex = immutableTexture();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glDeleteTextures(tex);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
assertEquals(GL11.GL_NEAREST_MIPMAP_LINEAR, GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER));
GLStateManager.glDeleteTextures(tex);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void deletingA2DTextureWhileA3DTextureIsBoundLeavesNothingBoundTo2D() {
@ParameterizedTest
@ValueSource(booleans = {true, false})
void deletingA2DTextureWhileA3DTextureIsBound(boolean deletedIsHidden2D) {
final int t2d = GLStateManager.glGenTextures();
final int t3d = GLStateManager.glGenTextures();
final int other = GLStateManager.glGenTextures();
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, t2d);
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, t3d);
GLStateManager.glDeleteTextures(t2d);
GLStateManager.glDeleteTextures(deletedIsHidden2D ? t2d : other);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
assertEquals(0, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "the deleted texture must not stay bound");
assertEquals(deletedIsHidden2D ? 0 : t2d, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "only the deleted texture leaves the hidden 2D binding");
assertEquals(t3d, GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D));
} finally {
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, 0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glDeleteTextures(t3d);
GLStateManager.glDeleteTextures(deletedIsHidden2D ? other : t2d);
}
}
}
@@ -6,6 +6,8 @@ import com.gtnewhorizons.angelica.glsm.recording.DisplayListVBO;
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.HashSet;
import java.util.Set;
@@ -39,26 +41,18 @@ class GLSM_DisplayList_Batching_Test {
// ==================== Pure Unit Tests (No GL Context) ====================
@Test
void testConsecutiveDrawsWithSameFormatShareSingleVBO() {
@ParameterizedTest(name = "{0} draws")
@ValueSource(ints = { 3, 100 })
void testConsecutiveDrawsWithSameFormatShareSingleVBO(int draws) {
CompiledDisplayList compiled = createDisplayList(() -> {
createSimpleDraw();
createSimpleDraw();
createSimpleDraw();
for (int i = 0; i < draws; i++) {
createSimpleDraw();
}
});
// Verify: 1 VBO for all draws (format-based batching)
assertEquals(1, getVBOCount(compiled),
"3 draws with same format should produce 1 shared VBO");
// Verify: 1 DrawRangeCmd covering all geometry
Int2IntMap counts = compiled.getCommandCounts();
int drawCount = counts.getOrDefault(GLCommand.DRAW_RANGE, 0);
assertEquals(1, drawCount,
"3 consecutive draws with same format and transform should produce 1 DrawRangeCmd");
assertEquals(1, getVBOCount(compiled), draws + " draws with same format should produce 1 shared VBO");
assertEquals(1, compiled.getCommandCounts().getOrDefault(GLCommand.DRAW_RANGE, 0),
draws + " consecutive draws with same format and transform should produce 1 DrawRangeCmd");
}
@Test
@@ -101,23 +95,6 @@ class GLSM_DisplayList_Batching_Test {
assertEquals(1, multMatrixCount, "Should have 1 Translate at the end (the transforms in the draws are prebaked)");
}
@Test
void testManyDrawsSameFormatProduceSingleVBO() {
CompiledDisplayList compiled = createDisplayList(() -> {
for (int i = 0; i < 100; i++) {
createSimpleDraw();
}
});
// Verify: 1 shared VBO
assertEquals(1, getVBOCount(compiled), "100 draws with same format should produce 1 shared VBO");
Int2IntMap counts = compiled.getCommandCounts();
int drawCount = counts.getOrDefault(GLCommand.DRAW_RANGE, 0);
assertEquals(1, drawCount,
"100 consecutive draws with same format and transform should produce 1 DrawRangeCmd");
}
@Test
void testCancellingTransformsProduceSeparateDrawRangeCmds() {
@@ -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");
}
}
@@ -62,12 +62,7 @@ class GLSM_DisplayList_FormatOptimization_Test {
GLStateManager.glNewList(displayList, GL11.GL_COMPILE);
// First draw: position only
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glVertex3f(0, 0, 0);
GLStateManager.glVertex3f(1, 0, 0);
GLStateManager.glVertex3f(1, 1, 0);
GLStateManager.glVertex3f(0, 1, 0);
GLStateManager.glEnd();
DisplayListTestHelper.immediateQuad(1.0f);
// Second draw: position + color
GLStateManager.glBegin(GL11.GL_QUADS);
@@ -3,10 +3,12 @@ package com.gtnewhorizons.angelica.glsm;
import org.joml.Matrix4f;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -55,71 +57,42 @@ class GLSM_DisplayList_Nesting_Test {
GLStateManager.glCallList(childList); // Should render child at (10,0,0)
GLStateManager.glEndList();
// Execute parent list
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(parentList);
// Check final matrix state - should have parent's translate applied
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
// Expected: translate(10, 0, 0)
Matrix4f expected = new Matrix4f().translate(10.0f, 0.0f, 0.0f);
FloatBuffer expectedBuf = BufferUtils.createFloatBuffer(16);
expected.get(expectedBuf);
// Compare buffers directly (both are column-major OpenGL format)
for (int i = 0; i < 16; i++) {
assertEquals(expectedBuf.get(i), actualBuf.get(i), 0.0001f,
String.format("Matrix[%d] mismatch", i));
}
GLStateManager.glPopMatrix();
DisplayListTestHelper.assertModelviewAfterCall(parentList, new Matrix4f().translate(10.0f, 0.0f, 0.0f), "parent transform applies to the child");
}
@Test
void testNestedDisplayListWithBothTransforms() {
// Create child list: translate and draw
childList = GL11.glGenLists(1);
GLStateManager.glNewList(childList, GL11.GL_COMPILE);
GLStateManager.glTranslatef(1.0f, 0.0f, 0.0f); // Child transform
GL11.glBegin(GL11.GL_QUADS);
GL11.glVertex3f(0, 0, 0);
GL11.glVertex3f(1, 0, 0);
GL11.glVertex3f(1, 1, 0);
GL11.glVertex3f(0, 1, 0);
GL11.glEnd();
GLStateManager.glEndList();
static Stream<Arguments> nestedTranslationsCompose() {
return Stream.of(
Arguments.of(new float[] { 10.0f, 1.0f }, 11.0f),
Arguments.of(new float[] { 100.0f, 10.0f, 1.0f }, 111.0f));
}
// Create parent list: translate then call child
parentList = GL11.glGenLists(1);
GLStateManager.glNewList(parentList, GL11.GL_COMPILE);
GLStateManager.glTranslatef(10.0f, 0.0f, 0.0f); // Parent transform
GLStateManager.glCallList(childList); // Child adds (1,0,0) to parent's (10,0,0)
GLStateManager.glEndList();
// Execute parent list
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(parentList);
// Check final matrix state - should be translate(10,0,0) * translate(1,0,0) = translate(11,0,0)
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
// Expected: translate(11, 0, 0) = parent(10) + child(1)
Matrix4f expected = new Matrix4f().translate(11.0f, 0.0f, 0.0f);
FloatBuffer expectedBuf = BufferUtils.createFloatBuffer(16);
expected.get(expectedBuf);
// Compare buffers directly
for (int i = 0; i < 16; i++) {
assertEquals(expectedBuf.get(i), actualBuf.get(i), 0.0001f,
String.format("Matrix[%d] mismatch", i));
@ParameterizedTest(name = "{0} composes to {1}")
@MethodSource
void nestedTranslationsCompose(float[] chain, float expectedX) {
final int[] lists = new int[chain.length];
try {
for (int depth = chain.length - 1; depth >= 0; depth--) {
lists[depth] = GL11.glGenLists(1);
GLStateManager.glNewList(lists[depth], GL11.GL_COMPILE);
GLStateManager.glTranslatef(chain[depth], 0.0f, 0.0f);
if (depth == chain.length - 1) {
GL11.glBegin(GL11.GL_QUADS);
GL11.glVertex3f(0, 0, 0);
GL11.glVertex3f(1, 0, 0);
GL11.glVertex3f(1, 1, 0);
GL11.glVertex3f(0, 1, 0);
GL11.glEnd();
} else {
GLStateManager.glCallList(lists[depth + 1]);
}
GLStateManager.glEndList();
}
DisplayListTestHelper.assertModelviewAfterCall(lists[0], new Matrix4f().translate(expectedX, 0.0f, 0.0f), "nested translations");
} finally {
for (int list : lists) {
if (list > 0) GLStateManager.glDeleteLists(list, 1);
}
}
GLStateManager.glPopMatrix();
}
@Test
@@ -145,79 +118,7 @@ class GLSM_DisplayList_Nesting_Test {
GLStateManager.glCallList(childList); // Child uses push/pop
GLStateManager.glEndList();
// Execute parent list
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(parentList);
// Check final matrix state - should still be translate(10,0,0) because child did push/pop
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
// Expected: translate(10, 0, 0) only (child's transform was popped)
Matrix4f expected = new Matrix4f().translate(10.0f, 0.0f, 0.0f);
FloatBuffer expectedBuf = BufferUtils.createFloatBuffer(16);
expected.get(expectedBuf);
// Compare buffers directly
for (int i = 0; i < 16; i++) {
assertEquals(expectedBuf.get(i), actualBuf.get(i), 0.0001f,
String.format("Matrix[%d] mismatch", i));
}
GLStateManager.glPopMatrix();
}
@Test
void testDeepNesting() {
// Create deeply nested lists: grandchild -> child -> parent
int grandchildList = GL11.glGenLists(1);
GLStateManager.glNewList(grandchildList, GL11.GL_COMPILE);
GLStateManager.glTranslatef(1.0f, 0.0f, 0.0f); // Grandchild: +1
GL11.glBegin(GL11.GL_QUADS);
GL11.glVertex3f(0, 0, 0);
GL11.glVertex3f(1, 0, 0);
GL11.glVertex3f(1, 1, 0);
GL11.glVertex3f(0, 1, 0);
GL11.glEnd();
GLStateManager.glEndList();
childList = GL11.glGenLists(1);
GLStateManager.glNewList(childList, GL11.GL_COMPILE);
GLStateManager.glTranslatef(10.0f, 0.0f, 0.0f); // Child: +10
GLStateManager.glCallList(grandchildList); // Grandchild: +1
GLStateManager.glEndList();
parentList = GL11.glGenLists(1);
GLStateManager.glNewList(parentList, GL11.GL_COMPILE);
GLStateManager.glTranslatef(100.0f, 0.0f, 0.0f); // Parent: +100
GLStateManager.glCallList(childList); // Child: +10, Grandchild: +1
GLStateManager.glEndList();
// Execute parent list
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(parentList);
// Check final matrix state - should be 100 + 10 + 1 = 111
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
// Expected: translate(111, 0, 0)
Matrix4f expected = new Matrix4f().translate(111.0f, 0.0f, 0.0f);
FloatBuffer expectedBuf = BufferUtils.createFloatBuffer(16);
expected.get(expectedBuf);
// Compare buffers directly
for (int i = 0; i < 16; i++) {
assertEquals(expectedBuf.get(i), actualBuf.get(i), 0.0001f,
String.format("Matrix[%d] mismatch", i));
}
GLStateManager.glPopMatrix();
// Cleanup grandchild
GLStateManager.glDeleteLists(grandchildList, 1);
DisplayListTestHelper.assertModelviewAfterCall(parentList, new Matrix4f().translate(10.0f, 0.0f, 0.0f), "child push/pop leaves only the parent transform");
}
@Test
@@ -27,18 +27,21 @@ class GLSM_DisplayList_RawTessellatorTransform_Test {
GLStateManager.glLoadIdentity();
}
@Test
void pendingTransformIsEmittedBeforeARawDraw() {
private CompiledDisplayList compileHalfScaled(Runnable body) {
testList = GLStateManager.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
GLStateManager.glScalef(0.5f, 0.5f, 0.5f);
rawQuad(1.0f);
body.run();
GLStateManager.glEndList();
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(testList);
assertNotNull(compiled);
return compiled;
}
@Test
void pendingTransformIsEmittedBeforeARawDraw() {
final CompiledDisplayList compiled = compileHalfScaled(() -> rawQuad(1.0f));
final IntList opcodes = compiled.getCommandOpcodes();
final int scaleIndex = opcodes.indexOf(GLCommand.SCALE);
@@ -52,16 +55,7 @@ class GLSM_DisplayList_RawTessellatorTransform_Test {
@Test
void pendingTransformIsNotEmittedBeforeABakedDraw() {
testList = GLStateManager.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
GLStateManager.glScalef(0.5f, 0.5f, 0.5f);
immediateQuad(1.0f);
GLStateManager.glEndList();
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(testList);
assertNotNull(compiled);
final CompiledDisplayList compiled = compileHalfScaled(() -> immediateQuad(1.0f));
final IntList opcodes = compiled.getCommandOpcodes();
final int scaleIndex = opcodes.indexOf(GLCommand.SCALE);
@@ -73,17 +67,10 @@ class GLSM_DisplayList_RawTessellatorTransform_Test {
@Test
void aRawDrawAfterABakedDrawStillGetsTheTransform() {
testList = GLStateManager.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
GLStateManager.glScalef(0.5f, 0.5f, 0.5f);
immediateQuad(1.0f);
rawQuad(1.0f);
GLStateManager.glEndList();
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(testList);
assertNotNull(compiled);
final CompiledDisplayList compiled = compileHalfScaled(() -> {
immediateQuad(1.0f);
rawQuad(1.0f);
});
final IntList opcodes = compiled.getCommandOpcodes();
final int scaleIndex = opcodes.indexOf(GLCommand.SCALE);
@@ -97,23 +84,17 @@ class GLSM_DisplayList_RawTessellatorTransform_Test {
@Test
void aRawDrawDoesNotFlushWhileBakedGeometryIsPending() {
testList = GLStateManager.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
final CompiledDisplayList compiled = compileHalfScaled(() -> {
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glVertex3f(0.0f, 0.0f, 0.0f);
GLStateManager.glVertex3f(1.0f, 0.0f, 0.0f);
GLStateManager.glVertex3f(1.0f, 1.0f, 0.0f);
GLStateManager.glVertex3f(0.0f, 1.0f, 0.0f);
GLStateManager.glScalef(0.5f, 0.5f, 0.5f);
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glVertex3f(0.0f, 0.0f, 0.0f);
GLStateManager.glVertex3f(1.0f, 0.0f, 0.0f);
GLStateManager.glVertex3f(1.0f, 1.0f, 0.0f);
GLStateManager.glVertex3f(0.0f, 1.0f, 0.0f);
rawQuad(1.0f);
rawQuad(1.0f);
GLStateManager.glEnd();
GLStateManager.glEndList();
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(testList);
assertNotNull(compiled);
GLStateManager.glEnd();
});
final IntList opcodes = compiled.getCommandOpcodes();
final int scaleIndex = opcodes.indexOf(GLCommand.SCALE);
@@ -48,6 +48,20 @@ public class GLSM_DisplayList_StateSet_GLTest {
return id;
}
private int recordCutoutBracket(int mode) {
final int list = newList();
GLStateManager.glNewList(list, mode);
final int d = GLStateManager.pushState(StateSet.CUTOUT);
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.enableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
return list;
}
private static Int2IntMap commandCounts(int listId) {
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(listId);
assertNotNull(compiled, "compiled display list must exist for id " + listId);
@@ -65,16 +79,7 @@ public class GLSM_DisplayList_StateSet_GLTest {
final int unit0 = GLStateManager.getActiveTextureUnit();
final int depth0 = GLStateManager.getAttribDepth();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
final int d = GLStateManager.pushState(StateSet.CUTOUT);
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.enableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
final int list = recordCutoutBracket(GL11.GL_COMPILE);
verifyIsEnabled(GL11.GL_ALPHA_TEST, true, "cache alpha test enable unchanged after COMPILE-only bracket");
assertEquals(func0, GLStateManager.getAlphaState().getFunction(), "cache alpha func unchanged after COMPILE-only bracket");
@@ -86,16 +91,7 @@ public class GLSM_DisplayList_StateSet_GLTest {
@Test
void compiledBracketReplayRestoresState() {
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
final int d = GLStateManager.pushState(StateSet.CUTOUT);
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.enableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
final int list = recordCutoutBracket(GL11.GL_COMPILE);
GLStateManager.glAlphaFunc(GL11.GL_LESS, 0.9f);
GLStateManager.disableAlphaTest();
@@ -119,21 +115,13 @@ public class GLSM_DisplayList_StateSet_GLTest {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final int depth0 = GLStateManager.getAttribDepth();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
final int d = GLStateManager.pushState(StateSet.CUTOUT);
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.enableTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.popStateTo(d);
GLStateManager.glEndList();
final int list = recordCutoutBracket(GL11.GL_COMPILE_AND_EXECUTE);
assertEquals(GL11.GL_LESS, GLStateManager.getAlphaState().getFunction(), "cache alpha func restored after GL_COMPILE_AND_EXECUTE bracket");
assertEquals(0.9f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref restored after GL_COMPILE_AND_EXECUTE bracket");
verifyIsEnabled(GL11.GL_ALPHA_TEST, false, "cache alpha test restored disabled after GL_COMPILE_AND_EXECUTE bracket");
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored after GL_COMPILE_AND_EXECUTE bracket");
assertEquals(GL13.GL_TEXTURE0, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE), "driver active unit restored after GL_COMPILE_AND_EXECUTE bracket");
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after balanced GL_COMPILE_AND_EXECUTE bracket");
GLStateManager.glCallList(list);
@@ -143,6 +131,7 @@ public class GLSM_DisplayList_StateSet_GLTest {
assertEquals(0.9f, GLStateManager.getAlphaState().getReference(), 0.0001f, "cache alpha ref restored after replay");
verifyIsEnabled(GL11.GL_ALPHA_TEST, false, "cache alpha test restored disabled after replay");
assertEquals(0, GLStateManager.getActiveTextureUnit(), "cache active unit restored after replay");
assertEquals(GL13.GL_TEXTURE0, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE), "driver active unit restored after replay");
assertEquals(depth0, GLStateManager.getAttribDepth(), "live attrib depth unchanged after replay");
}
@@ -10,6 +10,8 @@ import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizons.angelica.glsm.DisplayListTestHelper.assertModelviewAfterCall;
import static com.gtnewhorizons.angelica.glsm.DisplayListTestHelper.modelviewAfterCall;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -91,19 +93,7 @@ class GLSM_DisplayList_Stress_Test {
GLStateManager.glEndList();
// Execute and verify final transform
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(list);
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
// All push/pop pairs cancel out, only final translate remains
assertEquals(42.0f, actualBuf.get(12), 0.0001f,
"After many push/pop pairs, only final transform should remain");
GLStateManager.glPopMatrix();
assertEquals(42.0f, modelviewAfterCall(list).get(12), 0.0001f, "After many push/pop pairs, only final transform should remain");
}
@Test
@@ -148,25 +138,7 @@ class GLSM_DisplayList_Stress_Test {
GLStateManager.glEndList();
}
// Execute the outermost list
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(chain[chainLength - 1]);
// Final matrix should have translation = chainLength (each list adds 1)
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
Matrix4f expected = new Matrix4f().translate(chainLength, 0.0f, 0.0f);
FloatBuffer expectedBuf = BufferUtils.createFloatBuffer(16);
expected.get(expectedBuf);
for (int i = 0; i < 16; i++) {
assertEquals(expectedBuf.get(i), actualBuf.get(i), 0.0001f,
String.format("Matrix[%d] mismatch for chain of %d lists", i, chainLength));
}
GLStateManager.glPopMatrix();
assertModelviewAfterCall(chain[chainLength - 1], new Matrix4f().translate(chainLength, 0.0f, 0.0f), "chain of " + chainLength + " lists");
}
@Test
@@ -192,19 +164,7 @@ class GLSM_DisplayList_Stress_Test {
GLStateManager.glEndList();
// Execute and verify
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(list);
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
// All pushes and pops cancel out, only final 99 translate remains
assertEquals(99.0f, actualBuf.get(12), 0.0001f,
"After balanced push/pop, only final transform should remain");
GLStateManager.glPopMatrix();
assertEquals(99.0f, modelviewAfterCall(list).get(12), 0.0001f, "After balanced push/pop, only final transform should remain");
}
@Test
@@ -224,17 +184,7 @@ class GLSM_DisplayList_Stress_Test {
// Rapidly switch between calling different lists
for (int iteration = 0; iteration < 100; iteration++) {
for (int i = 0; i < numLists; i++) {
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(lists[i]);
// Verify correct transform was applied
FloatBuffer buf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, buf);
assertEquals(i + 1, buf.get(12), 0.0001f,
"List " + i + " should translate by " + (i + 1));
GLStateManager.glPopMatrix();
assertEquals(i + 1, modelviewAfterCall(lists[i]).get(12), 0.0001f, "List " + i + " should translate by " + (i + 1));
}
}
}
@@ -249,32 +199,14 @@ class GLSM_DisplayList_Stress_Test {
GLStateManager.glTranslatef(5.0f, 0.0f, 0.0f);
GLStateManager.glEndList();
// Verify first compilation
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(list);
FloatBuffer buf1 = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, buf1);
assertEquals(5.0f, buf1.get(12), 0.0001f, "First compilation should translate by 5");
GLStateManager.glPopMatrix();
assertEquals(5.0f, modelviewAfterCall(list).get(12), 0.0001f, "First compilation should translate by 5");
// Recompile with different transform
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTranslatef(10.0f, 0.0f, 0.0f);
GLStateManager.glEndList();
// Verify recompilation
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(list);
FloatBuffer buf2 = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, buf2);
assertEquals(10.0f, buf2.get(12), 0.0001f, "Recompilation should translate by 10");
GLStateManager.glPopMatrix();
assertEquals(10.0f, modelviewAfterCall(list).get(12), 0.0001f, "Recompilation should translate by 10");
}
@Test
@@ -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) {}
}
}
@@ -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());
}
}
@@ -5,6 +5,7 @@ import org.joml.Vector4f;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
@@ -84,61 +85,24 @@ public class GLSM_GLU_UnitTest {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
@Test
void gluPickMatrix_matchesManual() {
@ParameterizedTest(name = "{0}")
@CsvSource({
"gluPickMatrix center, 0, 0, 800, 600, 400, 300, 5",
"gluPickMatrix off-center viewport, 100, 50, 640, 480, 200, 150, 10",
})
void gluPickMatrix_matchesManual(String label, int vx, int vy, int vw, int vh, float x, float y, float delta) {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
IntBuffer viewport = BufferUtils.createIntBuffer(4);
viewport.put(0, 0); // x
viewport.put(1, 0); // y
viewport.put(2, 800); // width
viewport.put(3, 600); // height
viewport.put(0, vx).put(1, vy).put(2, vw).put(3, vh);
float x = 400.0f, y = 300.0f, deltaX = 5.0f, deltaY = 5.0f;
GLStateManager.gluPickMatrix(x, y, deltaX, deltaY, viewport);
// Reproduce the expected result: translate then scale
Matrix4f expected = new Matrix4f()
.translate(
(800 - 2 * (x - 0)) / deltaX,
(600 - 2 * (y - 0)) / deltaY,
0.0f)
.scale(
800.0f / deltaX,
600.0f / deltaY,
1.0f);
assertMatrixEquals(expected, getProjectionMatrix(), "gluPickMatrix center");
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
@Test
void gluPickMatrix_offCenter() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
IntBuffer viewport = BufferUtils.createIntBuffer(4);
viewport.put(0, 100);
viewport.put(1, 50);
viewport.put(2, 640);
viewport.put(3, 480);
float x = 200.0f, y = 150.0f, deltaX = 10.0f, deltaY = 10.0f;
GLStateManager.gluPickMatrix(x, y, deltaX, deltaY, viewport);
GLStateManager.gluPickMatrix(x, y, delta, delta, viewport);
Matrix4f expected = new Matrix4f()
.translate(
(640 - 2 * (x - 100)) / deltaX,
(480 - 2 * (y - 50)) / deltaY,
0.0f)
.scale(
640.0f / deltaX,
480.0f / deltaY,
1.0f);
assertMatrixEquals(expected, getProjectionMatrix(), "gluPickMatrix off-center viewport");
.translate((vw - 2 * (x - vx)) / delta, (vh - 2 * (y - vy)) / delta, 0.0f)
.scale(vw / delta, vh / delta, 1.0f);
assertMatrixEquals(expected, getProjectionMatrix(), label);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
@@ -178,38 +142,21 @@ public class GLSM_GLU_UnitTest {
assertEquals(expected, GLStateManager.gluErrorString(code));
}
@Test
void gluUnProject_identity() {
FloatBuffer model = BufferUtils.createFloatBuffer(16);
FloatBuffer proj = BufferUtils.createFloatBuffer(16);
new Matrix4f().get(model);
new Matrix4f().get(proj);
IntBuffer viewport = BufferUtils.createIntBuffer(4);
viewport.put(0, 0).put(1, 0).put(2, 800).put(3, 600);
FloatBuffer result = BufferUtils.createFloatBuffer(3);
boolean ok = GLStateManager.gluUnProject(400.0f, 300.0f, 0.5f, model, proj, viewport, result);
assertTrue(ok, "gluUnProject should succeed");
assertEquals(0.0f, result.get(0), 0.001f, "X");
assertEquals(0.0f, result.get(1), 0.001f, "Y");
assertEquals(0.0f, result.get(2), 0.001f, "Z");
static Stream<Arguments> gluUnProject_roundTrips() {
return Stream.of(
Arguments.of("identity", new Matrix4f(), new Matrix4f(), 0.001f),
Arguments.of("perspective with lookAt", new Matrix4f().perspective((float) Math.toRadians(60.0f), 800.0f / 600.0f, 0.1f, 100.0f),
new Matrix4f().lookAt(0, 0, 5, 0, 0, 0, 0, 1, 0), 0.01f));
}
@Test
void gluUnProject_withPerspective() {
Matrix4f proj = new Matrix4f().perspective((float) Math.toRadians(60.0f), 800.0f / 600.0f, 0.1f, 100.0f);
Matrix4f model = new Matrix4f().lookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);
@ParameterizedTest(name = "{0}")
@MethodSource
void gluUnProject_roundTrips(String label, Matrix4f proj, Matrix4f model, float tolerance) {
Vector4f worldPt = new Vector4f(0, 0, 0, 1);
Matrix4f mvp = new Matrix4f(proj).mul(model);
mvp.transform(worldPt);
float ndcX = worldPt.x / worldPt.w;
float ndcY = worldPt.y / worldPt.w;
float ndcZ = worldPt.z / worldPt.w;
float winX = (ndcX * 0.5f + 0.5f) * 800.0f;
float winY = (ndcY * 0.5f + 0.5f) * 600.0f;
float winZ = ndcZ * 0.5f + 0.5f;
new Matrix4f(proj).mul(model).transform(worldPt);
float winX = (worldPt.x / worldPt.w * 0.5f + 0.5f) * 800.0f;
float winY = (worldPt.y / worldPt.w * 0.5f + 0.5f) * 600.0f;
float winZ = worldPt.z / worldPt.w * 0.5f + 0.5f;
FloatBuffer modelBuf = BufferUtils.createFloatBuffer(16);
FloatBuffer projBuf = BufferUtils.createFloatBuffer(16);
@@ -220,11 +167,10 @@ public class GLSM_GLU_UnitTest {
viewport.put(0, 0).put(1, 0).put(2, 800).put(3, 600);
FloatBuffer result = BufferUtils.createFloatBuffer(3);
boolean ok = GLStateManager.gluUnProject(winX, winY, winZ, modelBuf, projBuf, viewport, result);
assertTrue(ok, "gluUnProject should succeed");
assertEquals(0.0f, result.get(0), 0.01f, "X should unproject back to 0");
assertEquals(0.0f, result.get(1), 0.01f, "Y should unproject back to 0");
assertEquals(0.0f, result.get(2), 0.01f, "Z should unproject back to 0");
assertTrue(GLStateManager.gluUnProject(winX, winY, winZ, modelBuf, projBuf, viewport, result), "gluUnProject should succeed");
assertEquals(0.0f, result.get(0), tolerance, "X should unproject back to 0");
assertEquals(0.0f, result.get(1), tolerance, "Y should unproject back to 0");
assertEquals(0.0f, result.get(2), tolerance, "Z should unproject back to 0");
}
@Test
@@ -261,6 +207,8 @@ public class GLSM_GLU_UnitTest {
int result = GLStateManager.gluBuild2DMipmaps(GL11.GL_TEXTURE_2D, GL11.GL_RGBA, 4, 4, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
assertEquals(0, result, "gluBuild2DMipmaps should return 0 on success");
assertEquals(4, GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_WIDTH));
assertEquals(1, GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 2, GL11.GL_TEXTURE_WIDTH));
GL11.glDeleteTextures(texId);
}
@@ -83,13 +83,24 @@ public class GLSM_ImageUnitBindingCache_UnitTest {
@Test
void deletingTheBoundTextureClearsTheImageUnit() {
final boolean driverHasImages = GLStateManager.capabilities != null && GLStateManager.capabilities.OpenGL42;
final int tex = GL11.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
try {
GLStateManager.glBindImageTexture(1, tex, 0, true, 0, GL15.GL_READ_WRITE, GL30.GL_R32UI);
assertEquals(tex, GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_NAME, 1));
if (driverHasImages) assertEquals(tex, GL30.glGetInteger(GL42.GL_IMAGE_BINDING_NAME, 1));
GLStateManager.glDeleteTextures(tex);
assertEquals(0, GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_NAME, 1), "GL unbinds a deleted texture from every image unit");
if (driverHasImages) assertEquals(0, GL30.glGetInteger(GL42.GL_IMAGE_BINDING_NAME, 1), "driver unbinds a deleted texture from every image unit");
assertEquals(GL15.GL_READ_ONLY, GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_ACCESS, 1));
assertEquals(GL30.GL_R8, GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_FORMAT, 1));
if (driverHasImages) {
assertEquals(GL30.glGetInteger(GL42.GL_IMAGE_BINDING_ACCESS, 1), GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_ACCESS, 1));
assertEquals(GL30.glGetInteger(GL42.GL_IMAGE_BINDING_FORMAT, 1), GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_FORMAT, 1));
}
} finally {
unbind(1);
}
@@ -151,9 +162,29 @@ public class GLSM_ImageUnitBindingCache_UnitTest {
assertEquals(GL30.glGetInteger(GL42.GL_IMAGE_BINDING_NAME, 1), GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_NAME, 1));
assertEquals(GL30.glGetInteger(GL42.GL_IMAGE_BINDING_ACCESS, 1), GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_ACCESS, 1));
assertEquals(GL30.glGetInteger(GL42.GL_IMAGE_BINDING_FORMAT, 1), GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_FORMAT, 1));
assertEquals(GL30.glGetInteger(GL42.GL_IMAGE_BINDING_NAME, 3), GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_NAME, 3));
assertEquals(GL30.glGetInteger(GL42.GL_IMAGE_BINDING_ACCESS, 3), GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_ACCESS, 3));
assertEquals(GL30.glGetInteger(GL42.GL_IMAGE_BINDING_FORMAT, 3), GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_FORMAT, 3));
} finally {
unbind(1);
GLStateManager.glDeleteTextures(tex);
}
}
@Test
void bindingTextureZeroClearsLayeredAndLayer() {
try {
GLStateManager.glBindImageTexture(2, 0, 0, true, 3, GL15.GL_READ_WRITE, GL11.GL_RGBA8);
assertEquals(GL11.GL_FALSE, GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_LAYERED, 2));
assertEquals(0, GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_LAYER, 2));
if (GLStateManager.capabilities != null && GLStateManager.capabilities.OpenGL42) {
assertEquals(GL30.glGetInteger(GL42.GL_IMAGE_BINDING_LAYERED, 2), GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_LAYERED, 2));
assertEquals(GL30.glGetInteger(GL42.GL_IMAGE_BINDING_LAYER, 2), GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_LAYER, 2));
}
} finally {
unbind(2);
}
}
}
@@ -856,6 +856,11 @@ public class GLSM_LegacyStackQueries_GLTest {
}
}
private static boolean isDriverHint(int target) {
return target == GL11.GL_LINE_SMOOTH_HINT || target == GL11.GL_POLYGON_SMOOTH_HINT
|| target == GL13.GL_TEXTURE_COMPRESSION_HINT || target == GL20.GL_FRAGMENT_SHADER_DERIVATIVE_HINT;
}
@Test
void hintsRoundTripAndFollowHintBit() {
final int base = GLStateManager.getAttribDepth();
@@ -869,17 +874,21 @@ public class GLSM_LegacyStackQueries_GLTest {
assertEquals(GL11.GL_DONT_CARE, GLStateManager.glGetInteger(target), "default " + name);
GLStateManager.glHint(target, GL11.GL_NICEST);
assertEquals(GL11.GL_NICEST, GLStateManager.glGetInteger(target), "nicest " + name);
if (isDriverHint(target)) assertEquals(GL11.GL_NICEST, GL11.glGetInteger(target), "driver nicest " + name);
GLStateManager.glHint(target, 12345);
assertEquals(GL11.GL_NICEST, GLStateManager.glGetInteger(target), "invalid mode " + name);
if (isDriverHint(target)) assertEquals(GL11.GL_NICEST, GL11.glGetInteger(target), "driver invalid mode " + name);
}
GLStateManager.glPushAttrib(GL11.GL_HINT_BIT);
for (final int target : HINT_TARGETS) {
GLStateManager.glHint(target, GL11.GL_FASTEST);
assertEquals(GL11.GL_FASTEST, GLStateManager.glGetInteger(target), "fastest " + hex(target));
if (isDriverHint(target)) assertEquals(GL11.GL_FASTEST, GL11.glGetInteger(target), "driver fastest " + hex(target));
}
GLStateManager.glPopAttrib();
for (final int target : HINT_TARGETS) {
assertEquals(GL11.GL_NICEST, GLStateManager.glGetInteger(target), "restored " + hex(target));
if (isDriverHint(target)) assertEquals(GL11.GL_NICEST, GL11.glGetInteger(target), "driver restored " + hex(target));
}
assertEquals(GL11.GL_RENDER, GLStateManager.glGetInteger(GL11.GL_RENDER_MODE));
} finally {
@@ -916,6 +925,7 @@ public class GLSM_LegacyStackQueries_GLTest {
assertEquals(0.5f, GLStateManager.glGetFloat(GL14.GL_POINT_SIZE_MIN));
assertEquals(2.5f, GLStateManager.glGetFloat(GL14.GL_POINT_SIZE_MAX));
assertEquals(4.5f, GLStateManager.glGetFloat(GL14.GL_POINT_FADE_THRESHOLD_SIZE));
assertEquals(4.5f, GL11.glGetFloat(GL14.GL_POINT_FADE_THRESHOLD_SIZE));
GLStateManager.glPointParameterf(GL14.GL_POINT_SIZE_MIN, -1f);
assertEquals(0.5f, GLStateManager.glGetFloat(GL14.GL_POINT_SIZE_MIN));
@@ -933,6 +943,7 @@ public class GLSM_LegacyStackQueries_GLTest {
GLStateManager.glPointParameteri(GL20.GL_POINT_SPRITE_COORD_ORIGIN, GL20.GL_LOWER_LEFT);
assertEquals(GL20.GL_LOWER_LEFT, GLStateManager.glGetInteger(GL20.GL_POINT_SPRITE_COORD_ORIGIN));
assertEquals(GL20.GL_LOWER_LEFT, GL11.glGetInteger(GL20.GL_POINT_SPRITE_COORD_ORIGIN));
GLStateManager.glPushAttrib(GL11.GL_POINT_BIT);
GLStateManager.glPointParameterf(GL14.GL_POINT_SIZE_MIN, 1f);
@@ -944,6 +955,7 @@ public class GLSM_LegacyStackQueries_GLTest {
GLStateManager.glPointParameteri(GL20.GL_POINT_SPRITE_COORD_ORIGIN, GL20.GL_UPPER_LEFT);
assertEquals(1f, GLStateManager.glGetFloat(GL14.GL_POINT_SIZE_MIN));
assertEquals(GL20.GL_UPPER_LEFT, GLStateManager.glGetInteger(GL20.GL_POINT_SPRITE_COORD_ORIGIN));
assertEquals(GL20.GL_UPPER_LEFT, GL11.glGetInteger(GL20.GL_POINT_SPRITE_COORD_ORIGIN));
GLStateManager.glPointParameterf(GL14.GL_POINT_SIZE_MIN, Float.NaN);
assertTrue(Float.isNaN(GLStateManager.glGetFloat(GL14.GL_POINT_SIZE_MIN)));
GLStateManager.glPopAttrib();
@@ -951,7 +963,9 @@ public class GLSM_LegacyStackQueries_GLTest {
assertEquals(0.5f, GLStateManager.glGetFloat(GL14.GL_POINT_SIZE_MIN));
assertEquals(2.5f, GLStateManager.glGetFloat(GL14.GL_POINT_SIZE_MAX));
assertEquals(4.5f, GLStateManager.glGetFloat(GL14.GL_POINT_FADE_THRESHOLD_SIZE));
assertEquals(4.5f, GL11.glGetFloat(GL14.GL_POINT_FADE_THRESHOLD_SIZE));
assertEquals(GL20.GL_LOWER_LEFT, GLStateManager.glGetInteger(GL20.GL_POINT_SPRITE_COORD_ORIGIN));
assertEquals(GL20.GL_LOWER_LEFT, GL11.glGetInteger(GL20.GL_POINT_SPRITE_COORD_ORIGIN));
attenuation.clear();
GLStateManager.glGetFloat(GL14.GL_POINT_DISTANCE_ATTENUATION, attenuation);
assertEquals(0.5f, attenuation.get(0));
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -24,6 +25,8 @@ public class GLSM_PolygonOffsetClamp_UnitTest {
GLStateManager.glPolygonOffsetClamp(1.5f, 2.5f, 0.0f);
assertEquals(1.5f, GLStateManager.ctx().polygonState.getOffsetFactor());
assertEquals(2.5f, GLStateManager.ctx().polygonState.getOffsetUnits());
assertEquals(1.5f, GL11.glGetFloat(GL11.GL_POLYGON_OFFSET_FACTOR));
assertEquals(2.5f, GL11.glGetFloat(GL11.GL_POLYGON_OFFSET_UNITS));
assertEquals(0.0f, cachedClamp());
}
@@ -2,9 +2,14 @@ package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import java.util.stream.Stream;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyIsEnabled;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyState;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -42,22 +47,6 @@ public class GLSM_PopAttribMaskFidelity_GLTest {
}
}
@Test
void enableBitRestoresCull() {
try {
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - Initial");
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glEnable(GL11.GL_CULL_FACE);
verifyIsEnabled(GL11.GL_CULL_FACE, true, "Cull - After enable");
GLStateManager.glPopAttrib();
verifyIsEnabled(GL11.GL_CULL_FACE, false, "Cull - GL_ENABLE_BIT member, must restore");
} finally {
GLStateManager.glDisable(GL11.GL_CULL_FACE);
}
}
@Test
void innerNonMemberPopKeepsChange_OuterMemberPopRestores() {
try {
@@ -154,21 +143,28 @@ public class GLSM_PopAttribMaskFidelity_GLTest {
}
}
@Test
void transformBitPopRestoresClipPlaneAndMask() {
static Stream<Arguments> clipPlanePops() {
return Stream.of(
Arguments.of("GL_TRANSFORM_BIT", GL11.GL_TRANSFORM_BIT, GL11.GL_CLIP_PLANE1, true),
Arguments.of("GL_COLOR_BUFFER_BIT", GL11.GL_COLOR_BUFFER_BIT, GL11.GL_CLIP_PLANE2, false));
}
@ParameterizedTest(name = "{0}")
@MethodSource
void clipPlanePops(String bitName, int bit, int plane, boolean member) {
try {
verifyClipPlaneMask("Initial");
GLStateManager.glPushAttrib(GL11.GL_TRANSFORM_BIT);
GLStateManager.glEnable(GL11.GL_CLIP_PLANE1);
verifyIsEnabled(GL11.GL_CLIP_PLANE1, true, "Clip1 - After enable inside GL_TRANSFORM_BIT");
verifyClipPlaneMask("After enable inside GL_TRANSFORM_BIT");
GLStateManager.glPushAttrib(bit);
GLStateManager.glEnable(plane);
verifyIsEnabled(plane, true, "After enable inside " + bitName);
verifyClipPlaneMask("After enable inside " + bitName);
GLStateManager.glPopAttrib();
verifyIsEnabled(GL11.GL_CLIP_PLANE1, false, "Clip1 - GL_TRANSFORM_BIT member, must restore");
verifyClipPlaneMask("After GL_TRANSFORM_BIT pop");
verifyIsEnabled(plane, !member, member ? bitName + " member, must restore" : "Not a " + bitName + " member, must stay toggled");
verifyClipPlaneMask("After " + bitName + " pop");
} finally {
GLStateManager.glDisable(GL11.GL_CLIP_PLANE1);
GLStateManager.glDisable(plane);
}
}
@@ -231,22 +227,4 @@ public class GLSM_PopAttribMaskFidelity_GLTest {
GLStateManager.glClearDepth(1.0);
}
}
@Test
void colorBufferBitPopDoesNotRestoreClipPlaneButMaskStaysFaithful() {
try {
verifyClipPlaneMask("Initial");
GLStateManager.glPushAttrib(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glEnable(GL11.GL_CLIP_PLANE2);
verifyIsEnabled(GL11.GL_CLIP_PLANE2, true, "Clip2 - After enable inside GL_COLOR_BUFFER_BIT");
verifyClipPlaneMask("After enable inside GL_COLOR_BUFFER_BIT");
GLStateManager.glPopAttrib();
verifyIsEnabled(GL11.GL_CLIP_PLANE2, true, "Clip2 - Not a GL_COLOR_BUFFER_BIT member, must stay toggled");
verifyClipPlaneMask("After GL_COLOR_BUFFER_BIT pop");
} finally {
GLStateManager.glDisable(GL11.GL_CLIP_PLANE2);
}
}
}
@@ -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);
@@ -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);
}
}
@@ -330,8 +326,10 @@ public class GLSM_SharedDrawableContext_GLTest {
GLStateManager.glBindVertexArray(bound);
GLStateManager.glCallList(foreignList);
assertEquals(bound, GLStateManager.getBoundVAO());
assertEquals(bound, GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING));
GLStateManager.glCallList(localList);
assertEquals(local, GLStateManager.getBoundVAO());
assertEquals(local, GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING));
GLStateManager.glBindVertexArray(0);
GLStateManager.glDeleteVertexArrays(bound);
@@ -5,17 +5,12 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.opengl.GL11;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Set;
import java.util.function.IntSupplier;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -38,22 +33,6 @@ public class GLSM_SplashCachingCoverage_UnitTest {
GLStateManager.glCullFace(GL11.GL_BACK);
}
static Stream<Arguments> stateSetDuringSplash() {
return Stream.of(
Arguments.of("depth func", (Runnable) () -> GLStateManager.glDepthFunc(GL11.GL_LEQUAL), GL11.GL_LEQUAL,
(IntSupplier) () -> GLStateManager.getDepthState().getFunc()),
Arguments.of("cull face", (Runnable) () -> GLStateManager.glCullFace(GL11.GL_FRONT), GL11.GL_FRONT,
(IntSupplier) () -> GLStateManager.getPolygonState().getCullFaceMode()));
}
@ParameterizedTest(name = "{0}")
@MethodSource("stateSetDuringSplash")
void stateSetDuringSplashLandsInCache(String name, Runnable set, int expected, IntSupplier cached) {
set.run();
assertEquals(expected, cached.getAsInt(), "cache write dropped while caching=false");
}
@Test
void popAttribDoesNotRevertDepthFuncSetDuringSplash() throws IllegalAccessException {
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
@@ -65,6 +44,7 @@ public class GLSM_SplashCachingCoverage_UnitTest {
GLStateManager.glPopAttrib();
assertEquals(GL11.GL_LEQUAL, GLStateManager.getDepthState().getFunc(), "popAttrib reverted to the stale default");
assertEquals(GL11.GL_LEQUAL, GL11.glGetInteger(GL11.GL_DEPTH_FUNC));
}
@Test
@@ -99,6 +79,8 @@ public class GLSM_SplashCachingCoverage_UnitTest {
void replayPushesCachedStateWithoutDisturbingIt() {
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
GLStateManager.glCullFace(GL11.GL_FRONT);
assertEquals(GL11.GL_LEQUAL, GLStateManager.getDepthState().getFunc(), "cache write dropped while caching=false");
assertEquals(GL11.GL_FRONT, GLStateManager.getPolygonState().getCullFaceMode(), "cache write dropped while caching=false");
GLStateManager.replayStateToBackend();
@@ -13,6 +13,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
@GLCompatTest
public class GLSM_StateQueryRoundTrip_UnitTest {
private static IntBuffer driverInts(int pname) {
final IntBuffer buf = BufferUtils.createIntBuffer(16);
GL11.glGetInteger(pname, buf);
return buf;
}
@Test
void activeTextureRoundTrips() {
try {
@@ -31,9 +37,11 @@ public class GLSM_StateQueryRoundTrip_UnitTest {
GLStateManager.glActiveTexture(0);
assertEquals(GL13.GL_TEXTURE2, GLStateManager.glGetInteger(GL13.GL_ACTIVE_TEXTURE));
assertEquals(GL13.GL_TEXTURE2, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE));
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + GLStateManager.MAX_TEXTURE_UNITS);
assertEquals(GL13.GL_TEXTURE2, GLStateManager.glGetInteger(GL13.GL_ACTIVE_TEXTURE));
assertEquals(GL13.GL_TEXTURE2, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE));
} finally {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
}
@@ -44,8 +52,10 @@ public class GLSM_StateQueryRoundTrip_UnitTest {
try {
GLStateManager.glDepthMask(false);
assertEquals(GL11.GL_FALSE, GLStateManager.glGetInteger(GL11.GL_DEPTH_WRITEMASK));
assertEquals(GL11.GL_FALSE, GL11.glGetInteger(GL11.GL_DEPTH_WRITEMASK));
GLStateManager.glDepthMask(true);
assertEquals(GL11.GL_TRUE, GLStateManager.glGetInteger(GL11.GL_DEPTH_WRITEMASK));
assertEquals(GL11.GL_TRUE, GL11.glGetInteger(GL11.GL_DEPTH_WRITEMASK));
} finally {
GLStateManager.glDepthMask(true);
}
@@ -72,6 +82,10 @@ public class GLSM_StateQueryRoundTrip_UnitTest {
GLStateManager.glGetInteger(GL11.GL_POLYGON_MODE, params);
assertEquals(GL11.GL_LINE, params.get(0), "front mode");
assertEquals(GL11.GL_LINE, params.get(1), "back mode");
final IntBuffer driver = driverInts(GL11.GL_POLYGON_MODE);
assertEquals(GL11.GL_LINE, driver.get(0), "driver front mode");
assertEquals(GL11.GL_LINE, driver.get(1), "driver back mode");
} finally {
GLStateManager.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
}
@@ -89,6 +103,11 @@ public class GLSM_StateQueryRoundTrip_UnitTest {
assertEquals(2, GLStateManager.glGetInteger(GL11.GL_UNPACK_SKIP_PIXELS));
assertEquals(3, GLStateManager.glGetInteger(GL11.GL_UNPACK_SKIP_ROWS));
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
assertEquals(64, GL11.glGetInteger(GL11.GL_UNPACK_ROW_LENGTH));
assertEquals(2, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_PIXELS));
assertEquals(3, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_ROWS));
assertEquals(1, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
} finally {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, 0);
@@ -107,6 +126,12 @@ public class GLSM_StateQueryRoundTrip_UnitTest {
assertEquals(5, params[1]);
assertEquals(17, params[2]);
assertEquals(23, params[3]);
final IntBuffer driver = driverInts(GL11.GL_VIEWPORT);
assertEquals(3, driver.get(0));
assertEquals(5, driver.get(1));
assertEquals(17, driver.get(2));
assertEquals(23, driver.get(3));
}
@Test
@@ -118,6 +143,12 @@ public class GLSM_StateQueryRoundTrip_UnitTest {
final int unpackAlignment = GLStateManager.glGetInteger(GL11.GL_UNPACK_ALIGNMENT);
final int[] viewport = new int[4];
GLStateManager.glGetInteger(GL11.GL_VIEWPORT, viewport);
final int driverActiveTexture = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
final int driverDepthWriteMask = GL11.glGetInteger(GL11.GL_DEPTH_WRITEMASK);
final int driverCullMode = GL11.glGetInteger(GL11.GL_CULL_FACE_MODE);
final int driverPolygonMode = GL11.glGetInteger(GL11.GL_POLYGON_MODE);
final int driverUnpackAlignment = GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT);
final IntBuffer driverViewport = driverInts(GL11.GL_VIEWPORT);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + 4);
GLStateManager.glDepthMask(false);
@@ -144,6 +175,16 @@ public class GLSM_StateQueryRoundTrip_UnitTest {
assertEquals(viewport[1], restored[1]);
assertEquals(viewport[2], restored[2]);
assertEquals(viewport[3], restored[3]);
assertEquals(driverActiveTexture, GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE));
assertEquals(driverDepthWriteMask, GL11.glGetInteger(GL11.GL_DEPTH_WRITEMASK));
assertEquals(driverCullMode, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE));
assertEquals(driverPolygonMode, GL11.glGetInteger(GL11.GL_POLYGON_MODE));
assertEquals(driverUnpackAlignment, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
final IntBuffer driverRestored = driverInts(GL11.GL_VIEWPORT);
for (int i = 0; i < 4; i++) {
assertEquals(driverViewport.get(i), driverRestored.get(i));
}
}
@Test
@@ -464,6 +464,7 @@ public class GLSM_StateSet_GLTest {
GLStateManager.popStateTo(d);
assertEquals(d, GLStateManager.getAttribDepth());
assertEquals(GL11.GL_ONE, GLStateManager.getBlendState().getSrcRgb(), "popStateTo repairs the unbalanced non-member push and carries the change through to the BLEND member pop, restoring the pre-bracket baseline");
assertEquals(GL11.GL_ONE, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb");
}
@Test
@@ -92,6 +92,7 @@ public class GLSM_TextureInfoCache_UnitTest {
int first = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
assertFalse(info.needsInternalFormatResolve());
int second = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
assertEquals(GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT), second);
assertEquals(first, second);
} finally {
GL11.glDeleteTextures(texId);
@@ -37,16 +37,6 @@ public class GLSM_TextureUploadGeneration_GLTest {
}
}
@Test
void anUploadLeavesANonZeroGeneration() {
final int texId = allocate(4);
try {
assertNotEquals(0, TextureInfoCache.INSTANCE.getInfo(texId).getUploadGeneration());
} finally {
GLStateManager.glDeleteTextures(texId);
}
}
@Test
void recreatingATextureAtAReusedIdYieldsAFreshInfo() {
final int texId = allocate(4);
@@ -72,6 +62,7 @@ public class GLSM_TextureUploadGeneration_GLTest {
try {
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(texId);
final int afterAlloc = info.getUploadGeneration();
assertNotEquals(0, afterAlloc);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, BufferUtils.createByteBuffer(2 * 2 * 4));
assertNotEquals(afterAlloc, info.getUploadGeneration(), "content changed, so anything caching the pixels is stale");
@@ -6,11 +6,15 @@ import com.gtnewhorizons.angelica.util.GLSMUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import java.nio.IntBuffer;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyState;
@GLCompatTest
@@ -131,64 +135,67 @@ public class GLSM_VAO_UnitTest {
@Test
void testGenericAttributeSupersedesFfpConventional() {
final int vao = UniversalVAO.genVertexArrays();
UniversalVAO.bindVertexArray(vao);
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
withVaoAndVbo(() -> {
GLStateManager.glVertexAttribPointer(0, 1, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(0);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
GLStateManager.glVertexAttribPointer(0, 1, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(0);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
Assertions.assertEquals(1, VAOManager.get(0).size, "enabled generic attribute must supersede the FFP conventional array at the same location");
Assertions.assertEquals(1, driverAttrib0Size());
Assertions.assertTrue(VAOManager.get(0).genericPointer, "location must remain generic-sourced");
Assertions.assertEquals(1, VAOManager.get(0).size, "enabled generic attribute must supersede the FFP conventional array at the same location");
Assertions.assertTrue(VAOManager.get(0).genericPointer, "location must remain generic-sourced");
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
GLStateManager.glDisableVertexAttribArray(0);
});
}
@Test
void testGenericAttributeWinsRegardlessOfOrder() {
final int vao = UniversalVAO.genVertexArrays();
UniversalVAO.bindVertexArray(vao);
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
withVaoAndVbo(() -> {
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
GLStateManager.glVertexAttribPointer(0, 1, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(0);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
GLStateManager.glVertexAttribPointer(0, 1, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(0);
Assertions.assertEquals(1, VAOManager.get(0).size, "generic attribute set after the FFP conventional array must still win");
Assertions.assertEquals(1, driverAttrib0Size());
Assertions.assertTrue(VAOManager.get(0).genericPointer, "location must remain generic-sourced");
Assertions.assertEquals(1, VAOManager.get(0).size, "generic attribute set after the FFP conventional array must still win");
Assertions.assertTrue(VAOManager.get(0).genericPointer, "location must remain generic-sourced");
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
GLStateManager.glDisableVertexAttribArray(0);
});
}
@Test
void testFfpConventionalAppliesWithoutGenericAttribute() {
withVaoAndVbo(() -> {
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
Assertions.assertEquals(3, VAOManager.get(0).size, "plain FFP conventional array must apply when no generic attribute occupies the location");
Assertions.assertEquals(3, driverAttrib0Size());
Assertions.assertFalse(VAOManager.get(0).genericPointer, "location must be conventional-sourced");
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
});
}
private static int driverAttrib0Size() {
final IntBuffer out = BufferUtils.createIntBuffer(16);
GL20.glGetVertexAttrib(0, GL20.GL_VERTEX_ATTRIB_ARRAY_SIZE, out);
return out.get(0);
}
private static void withVaoAndVbo(Runnable body) {
final int vao = UniversalVAO.genVertexArrays();
UniversalVAO.bindVertexArray(vao);
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
Assertions.assertEquals(3, VAOManager.get(0).size, "plain FFP conventional array must apply when no generic attribute occupies the location");
Assertions.assertFalse(VAOManager.get(0).genericPointer, "location must be conventional-sourced");
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
try {
body.run();
} finally {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
}
}
private static void testClientStates() {
@@ -4,7 +4,9 @@ import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyIsEnabled;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -23,6 +25,7 @@ public class GLSM_WorkerContextStacks_GLTest {
GLStateManager.popStateTo(d);
assertFalse(GLStateManager.glIsEnabled(GL11.GL_CULL_FACE), "cull face restored on the worker context");
verifyIsEnabled(GL11.GL_CULL_FACE, false, "driver cull face restored on the worker context");
} finally {
GLStateManager.glDisable(GL11.GL_CULL_FACE);
GLStateManager.exitWorkerContext();
@@ -42,7 +45,9 @@ public class GLSM_WorkerContextStacks_GLTest {
GLStateManager.popStateTo(d);
assertFalse(GLStateManager.glIsEnabled(GL11.GL_BLEND), "blend enable restored on the worker context");
verifyIsEnabled(GL11.GL_BLEND, false, "driver blend enable restored on the worker context");
assertEquals(GL11.GL_ONE, GLStateManager.getBlendState().getSrcRgb(), "blend src rgb restored on the worker context");
assertEquals(GL11.GL_ONE, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb restored on the worker context");
} finally {
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
@@ -1,6 +1,8 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.List;
import java.util.Random;
@@ -12,7 +14,6 @@ class ReservedWordRenameTest {
private static final List<Pattern> LEGACY_ALWAYS = List.of(Pattern.compile("\\bsample\\b"), Pattern.compile("\\bnew\\b"));
private static final Pattern LEGACY_400 = Pattern.compile("\\bsampler\\b(?!\\d)");
private static final String[] WORDS = { "sample", "sampler", "new" };
private static String legacyRename(String source, int targetVersion) {
String out = source;
@@ -30,17 +31,17 @@ class ReservedWordRenameTest {
}
}
@Test
void edgeCasesMatchLegacy() {
final String[] cases = {
"", "sample", "new", "sampler", " sample ", "sample;", "(sample)", "_sample", "sample_", "sample2", "2sample",
"samples", "resample", "sampler2D", "samplerX", "sampler_", "sampler;", "uniform sampler s;", "sampler2DShadow",
"new_", "renew", "news", "new;new new", "sample sample sample", "samplesampler", "sampler sample new",
"ésample", "sampleé", "ßnewß", "中sampler", "sampler中", "samplé",
"// sample in a comment\nfloat sample = 1.0;\n", "#define new sample\n", "vec4 c = texture(sampler, uv) * sample;",
"float x = sample.x + new_value + newValue + sample1;", "sample\nnew\nsampler\n", "\tsample\t", "sample-new+sampler*",
};
for (String c : cases) assertSameAsLegacy(c);
@ParameterizedTest
@ValueSource(strings = {
"", "sample", "new", "sampler", " sample ", "sample;", "(sample)", "_sample", "sample_", "sample2", "2sample",
"samples", "resample", "sampler2D", "samplerX", "sampler_", "sampler;", "uniform sampler s;", "sampler2DShadow",
"new_", "renew", "news", "new;new new", "sample sample sample", "samplesampler", "sampler sample new",
"\u00e9sample", "sample\u00e9", "\u00dfnew\u00df", "\u4e2dsampler", "sampler\u4e2d", "sampl\u00e9",
"// sample in a comment\nfloat sample = 1.0;\n", "#define new sample\n", "vec4 c = texture(sampler, uv) * sample;",
"float x = sample.x + new_value + newValue + sample1;", "sample\nnew\nsampler\n", "\tsample\t", "sample-new+sampler*",
})
void edgeCasesMatchLegacy(String source) {
assertSameAsLegacy(source);
}
@Test
@@ -55,10 +56,9 @@ class ReservedWordRenameTest {
}
}
@Test
void literalWordsAreTheOnlyCandidates() {
for (String w : WORDS) {
assertEquals("angelica_renamed_" + w, GlslTransformUtils.renameReservedWords(w, 460));
}
@ParameterizedTest
@ValueSource(strings = { "sample", "sampler", "new" })
void literalWordsAreTheOnlyCandidates(String word) {
assertEquals("angelica_renamed_" + word, GlslTransformUtils.renameReservedWords(word, 460));
}
}
@@ -1,15 +1,47 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.taumc.glsl.grammar.GLSLParser;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ReservedWordRestoreTest {
private static void assertTreeRestoreMatchesStringRestore(String label, String renamedSource) {
static Stream<Arguments> restores() {
return Stream.of(
Arguments.of("all three renames restore identically",
"float angelica_renamed_sample = 1.0;\n"
+ "uniform sampler2D angelica_renamed_sampler;\n"
+ "vec4 angelica_renamed_texture = vec4(0.0);\n"
+ "void main() {\n"
+ " vec4 c = angelica_renamed_texture + vec4(angelica_renamed_sample);\n"
+ "}\n",
new String[0], new String[0]),
Arguments.of("new is reserved in every GLSL version and must stay renamed",
"float angelica_renamed_new = 2.0;\n"
+ "void main() { float x = angelica_renamed_new; }\n",
new String[] { "angelica_renamed_new" }, new String[0]),
Arguments.of("suffixed rename must strip only the prefix",
"uniform sampler2D tex;\n"
+ "void main() {\n"
+ " vec4 c = angelica_renamed_texture2D(tex, vec2(0.5));\n"
+ "}\n",
new String[] { "texture2D" }, new String[] { "angelica_renamed_" }),
Arguments.of("untouched source prints identically",
"layout(location = 0) out vec4 fragColor;\n"
+ "void main() { fragColor = vec4(1.0); }\n",
new String[0], new String[0]));
}
@ParameterizedTest(name = "{0}")
@MethodSource
void restores(String label, String renamedSource, String[] present, String[] absent) {
final GLSLParser.Translation_unitContext treeA = GlslTransformUtils.parseFullQuiet(renamedSource);
final String legacy = GlslTransformUtils.restoreReservedWords(GlslTransformUtils.getFormattedShader(treeA, ""));
@@ -17,51 +49,8 @@ class ReservedWordRestoreTest {
GlslTransformUtils.restoreReservedWordsInTree(treeB);
final String inTree = GlslTransformUtils.getFormattedShader(treeB, "");
assertEquals(legacy, inTree, "in-tree restore diverged from string restore for " + label);
}
@Test
void allThreeRenamesRestoreIdentically() {
final String src = "float angelica_renamed_sample = 1.0;\n"
+ "uniform sampler2D angelica_renamed_sampler;\n"
+ "vec4 angelica_renamed_texture = vec4(0.0);\n"
+ "void main() {\n"
+ " vec4 c = angelica_renamed_texture + vec4(angelica_renamed_sample);\n"
+ "}\n";
assertTreeRestoreMatchesStringRestore("all-three", src);
}
@Test
void renamedNewStaysRenamed() {
final String src = "float angelica_renamed_new = 2.0;\n"
+ "void main() { float x = angelica_renamed_new; }\n";
assertTreeRestoreMatchesStringRestore("new-identifier", src);
final GLSLParser.Translation_unitContext tree = GlslTransformUtils.parseFullQuiet(src);
GlslTransformUtils.restoreReservedWordsInTree(tree);
final String out = GlslTransformUtils.getFormattedShader(tree, "");
assertTrue(out.contains("angelica_renamed_new"), "new is reserved in every GLSL version and must stay renamed");
}
@Test
void renamedTextureFunctionCallRestores() {
final String src = "uniform sampler2D tex;\n"
+ "void main() {\n"
+ " vec4 c = angelica_renamed_texture2D(tex, vec2(0.5));\n"
+ "}\n";
assertTreeRestoreMatchesStringRestore("texture2D-call", src);
final GLSLParser.Translation_unitContext tree = GlslTransformUtils.parseFullQuiet(src);
GlslTransformUtils.restoreReservedWordsInTree(tree);
final String out = GlslTransformUtils.getFormattedShader(tree, "");
assertTrue(out.contains("texture2D"), "suffixed rename must strip only the prefix");
assertFalse(out.contains("angelica_renamed_"), "no renamed remnants expected");
}
@Test
void untouchedSourcePrintsIdentically() {
final String src = "layout(location = 0) out vec4 fragColor;\n"
+ "void main() { fragColor = vec4(1.0); }\n";
assertTreeRestoreMatchesStringRestore("no-renames", src);
assertEquals(legacy, inTree, "in-tree restore diverged from string restore");
for (String needle : present) assertTrue(inTree.contains(needle), needle);
for (String needle : absent) assertFalse(inTree.contains(needle), needle);
}
}
@@ -25,16 +25,19 @@ class VertexAttribDefaultBridgeTest {
return new float[] { buf.get(0), buf.get(1), buf.get(2), buf.get(3) };
}
private void assertAttrib(int index, String label, float... expected) {
final float[] actual = getVertexAttrib4f(index);
for (int i = 0; i < expected.length; i++) {
assertEquals(expected[i], actual[i], EPSILON, label + "[" + i + "]");
}
}
@Test
void testGlColor4fSetsVertexAttrib() {
GLStateManager.glColor4f(0.5f, 0.25f, 0.75f, 0.8f);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.5f, color[0], EPSILON, "color.r");
assertEquals(0.25f, color[1], EPSILON, "color.g");
assertEquals(0.75f, color[2], EPSILON, "color.b");
assertEquals(0.8f, color[3], EPSILON, "color.a");
assertAttrib(COLOR_ATTRIB, "color", 0.5f, 0.25f, 0.75f, 0.8f);
}
@Test
@@ -42,10 +45,7 @@ class VertexAttribDefaultBridgeTest {
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] normal = getVertexAttrib4f(NORMAL_ATTRIB);
assertEquals(0.0f, normal[0], EPSILON, "normal.x");
assertEquals(1.0f, normal[1], EPSILON, "normal.y");
assertEquals(0.0f, normal[2], EPSILON, "normal.z");
assertAttrib(NORMAL_ATTRIB, "normal", 0.0f, 1.0f, 0.0f);
}
@Test
@@ -53,11 +53,7 @@ class VertexAttribDefaultBridgeTest {
GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, 240.0f, 128.0f);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] lm = getVertexAttrib4f(SECONDARY_UV_ATTRIB);
assertEquals(240.0f, lm[0], EPSILON, "lightmap.s");
assertEquals(128.0f, lm[1], EPSILON, "lightmap.t");
assertEquals(0.0f, lm[2], EPSILON, "lightmap.r");
assertEquals(1.0f, lm[3], EPSILON, "lightmap.q");
assertAttrib(SECONDARY_UV_ATTRIB, "lightmap", 240.0f, 128.0f, 0.0f, 1.0f);
}
@Test
@@ -66,9 +62,7 @@ class VertexAttribDefaultBridgeTest {
GLStateManager.glTexCoord2f(0.5f, 0.75f);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] uv = getVertexAttrib4f(PRIMARY_UV_ATTRIB);
assertEquals(0.5f, uv[0], EPSILON, "texcoord.s");
assertEquals(0.75f, uv[1], EPSILON, "texcoord.t");
assertAttrib(PRIMARY_UV_ATTRIB, "texcoord", 0.5f, 0.75f);
}
@Test
@@ -79,11 +73,7 @@ class VertexAttribDefaultBridgeTest {
GLStateManager.glColor4f(0.3f, 0.6f, 0.9f, 1.0f);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.3f, color[0], EPSILON, "color.r after dedup");
assertEquals(0.6f, color[1], EPSILON, "color.g after dedup");
assertEquals(0.9f, color[2], EPSILON, "color.b after dedup");
assertEquals(1.0f, color[3], EPSILON, "color.a after dedup");
assertAttrib(COLOR_ATTRIB, "color after dedup", 0.3f, 0.6f, 0.9f, 1.0f);
}
@Test
@@ -97,11 +87,7 @@ class VertexAttribDefaultBridgeTest {
// Color during COMPILE should NOT reach changeColor (early return)
GLStateManager.glColor4f(0.9f, 0.8f, 0.7f, 0.6f);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.1f, color[0], EPSILON, "color.r unchanged during COMPILE");
assertEquals(0.2f, color[1], EPSILON, "color.g unchanged during COMPILE");
assertEquals(0.3f, color[2], EPSILON, "color.b unchanged during COMPILE");
assertEquals(1.0f, color[3], EPSILON, "color.a unchanged during COMPILE");
assertAttrib(COLOR_ATTRIB, "color unchanged during COMPILE", 0.1f, 0.2f, 0.3f, 1.0f);
GLStateManager.glEndList();
GLStateManager.glDeleteLists(listId, 1);
@@ -1,8 +1,8 @@
package com.gtnewhorizons.angelica.glsm.backend;
import org.junit.jupiter.api.Test;
import java.util.function.Predicate;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -17,60 +17,38 @@ class BackendPolicyTest {
assertFalse(VSyncMode.OFF.tearFree());
}
private static final Predicate<VSyncMode> NO_MAILBOX = mode -> mode != VSyncMode.MAILBOX;
private static final Predicate<VSyncMode> EVERYTHING = mode -> true;
@Test
void mailboxIsTakenWhenTheBackendOffersIt() {
assertEquals(VSyncMode.MAILBOX, RenderBackend.resolveTearFreeMode(null, EVERYTHING));
@ParameterizedTest(name = "{0}")
@CsvSource({
"vsync is the default even when the backend offers mailbox, , true, ON",
"vsync is the default when mailbox is unsupported, , false, ON",
"an unsupported mailbox preference falls back to vsync, MAILBOX, false, ON",
"an explicit supported vsync preference wins, ON, true, ON",
"an explicit supported mailbox preference wins, MAILBOX, true, MAILBOX",
"off is never a tear-free preference with mailbox, OFF, true, ON",
"off is never a tear-free preference without mailbox, OFF, false, ON",
})
void tearFreeModeResolution(String label, VSyncMode preference, boolean mailboxSupported, VSyncMode expected) {
assertEquals(expected, RenderBackend.resolveTearFreeMode(preference, mode -> mailboxSupported || mode != VSyncMode.MAILBOX));
}
@Test
void vsyncIsTheFallbackWhenMailboxIsUnsupported() {
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(null, NO_MAILBOX));
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(VSyncMode.MAILBOX, NO_MAILBOX));
@ParameterizedTest(name = "{0}/{1} or {2} Hz -> {3} ns")
@CsvSource({
"60000, 1001, 0.0, 16683333",
"144, 1, 0.0, 6944444",
"0, 0, 59.94, 16683350",
"0, 0, 60.0, 16666667",
"0, 0, 0.0, 0",
"-1, -1, -1.0, 0",
"144, 0, 0.0, 0",
})
void periodPrefersTheExactRationalThenTheFloatRate(int numerator, int denominator, float hz, long expected) {
assertEquals(expected, RenderBackend.periodFromRational(numerator, denominator, hz));
}
@Test
void anExplicitSupportedPreferenceWins() {
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(VSyncMode.ON, EVERYTHING));
assertEquals(VSyncMode.MAILBOX, RenderBackend.resolveTearFreeMode(VSyncMode.MAILBOX, EVERYTHING));
}
@Test
void offIsNeverATearFreePreference() {
assertEquals(VSyncMode.MAILBOX, RenderBackend.resolveTearFreeMode(VSyncMode.OFF, EVERYTHING));
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(VSyncMode.OFF, NO_MAILBOX));
}
@Test
void periodPrefersTheExactRational() {
assertEquals(16_683_333L, RenderBackend.periodFromRational(60000, 1001, 0.0f));
assertEquals(6_944_444L, RenderBackend.periodFromRational(144, 1, 0.0f));
}
@Test
void periodFallsBackToTheFloatRate() {
assertEquals(16_683_350L, RenderBackend.periodFromRational(0, 0, 59.94f));
assertEquals(16_666_667L, RenderBackend.periodFromRational(0, 0, 60.0f));
}
@Test
void periodIsZeroWhenUnknown() {
assertEquals(0L, RenderBackend.periodFromRational(0, 0, 0.0f));
assertEquals(0L, RenderBackend.periodFromRational(-1, -1, -1.0f));
assertEquals(0L, RenderBackend.periodFromRational(144, 0, 0.0f));
}
@Test
void integerNtscRatesAreAdjusted() {
assertEquals(16_683_333L, RenderBackend.periodFromIntegerHz(59));
assertEquals(6_951_389L, RenderBackend.periodFromIntegerHz(143));
assertEquals(16_666_667L, RenderBackend.periodFromIntegerHz(60));
assertEquals(6_944_444L, RenderBackend.periodFromIntegerHz(144));
assertEquals(0L, RenderBackend.periodFromIntegerHz(0));
assertEquals(0L, RenderBackend.periodFromIntegerHz(-60));
assertEquals(60, RenderBackend.hzFromPeriod(RenderBackend.periodFromIntegerHz(59)));
@ParameterizedTest(name = "{0} Hz -> {1} ns")
@CsvSource({"59, 16683333", "143, 6951389", "60, 16666667", "144, 6944444", "0, 0", "-60, 0"})
void integerNtscRatesAreAdjusted(int hz, long expected) {
assertEquals(expected, RenderBackend.periodFromIntegerHz(hz));
}
@Test
@@ -79,6 +57,7 @@ class BackendPolicyTest {
assertEquals(144, RenderBackend.hzFromPeriod(6_944_444L));
assertEquals(0, RenderBackend.hzFromPeriod(0L));
assertEquals(0, RenderBackend.hzFromPeriod(-1L));
assertEquals(60, RenderBackend.hzFromPeriod(RenderBackend.periodFromIntegerHz(59)));
}
@Test
@@ -124,12 +124,7 @@ class FFPCombineShaderTest {
@Test
void testSingleUnitCombineCompiles() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
enableCombineUnit(GL13.GL_TEXTURE0, GL11.GL_MODULATE, GL11.GL_MODULATE);
FragmentKey fk = FragmentKey.fromState();
String fragSource = FragmentShaderGenerator.generate(fk);
@@ -181,12 +176,7 @@ class FFPCombineShaderTest {
@ParameterizedTest(name = "COMBINE function {0}")
@MethodSource("allCombineFunctions")
void testAllCombineFunctionsCompile(String name, int glFunc, int numArgs) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, glFunc);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
enableCombineUnit(GL13.GL_TEXTURE0, glFunc, GL11.GL_REPLACE);
// Set up appropriate sources
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
@@ -215,12 +205,7 @@ class FFPCombineShaderTest {
@Test
void testCombineScopeDeclaresColorOutsideBlock() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
enableCombineUnit(GL13.GL_TEXTURE0, GL11.GL_MODULATE, GL11.GL_MODULATE);
FragmentKey fk = FragmentKey.fromState();
String src = FragmentShaderGenerator.generate(fk);
@@ -270,14 +255,18 @@ class FFPCombineShaderTest {
* Unit 2: COMBINE, RGB=ADD, SRC0=TEXTURE/SRC_COLOR, SRC1=PREVIOUS/SRC_COLOR
* Unit 3: MODULATE (simple)
*/
private void setupXaeroState() {
// Unit 0: COMBINE / SUBTRACT
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
private void enableCombineUnit(int unit, int rgbFunc, int alphaFunc) {
GLStateManager.glActiveTexture(unit);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL13.GL_SUBTRACT);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, rgbFunc);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, alphaFunc);
}
private void setupXaeroState() {
// Unit 0: COMBINE / SUBTRACT
enableCombineUnit(GL13.GL_TEXTURE0, GL13.GL_SUBTRACT, GL11.GL_REPLACE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE1_RGB, GL13.GL_PRIMARY_COLOR);
@@ -288,12 +277,7 @@ class FFPCombineShaderTest {
// Unit 1: disabled (skip — default is disabled)
// Unit 2: COMBINE / ADD
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_ADD);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
enableCombineUnit(GL13.GL_TEXTURE2, GL11.GL_ADD, GL11.GL_REPLACE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE1_RGB, GL13.GL_PREVIOUS);
@@ -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);
@@ -94,6 +94,7 @@ class FfpUniformBlockDedupTest {
second.upload(GLStateManager.ctx());
assertEquals(1, second.blockWrites, "a never-bound instance must write regardless of content");
assertEquals(second.getRing().getBufferId(), GL30.glGetInteger(GL31.GL_UNIFORM_BUFFER_BINDING, FFPUniformBlock.BINDING_POINT));
} finally {
first.destroy();
second.destroy();
@@ -101,6 +102,24 @@ class FfpUniformBlockDedupTest {
}
}
@Test
void unchangedContentStillWritesAfterFrameEnd() {
resetState();
final Uniforms uniforms = new Uniforms();
try {
uniforms.upload(GLStateManager.ctx());
final int firstOffset = boundRangeStart();
uniforms.endFrame();
uniforms.upload(GLStateManager.ctx());
assertEquals(1, uniforms.blockWrites, "the frame-end fence owns the old range, so the next frame must not reuse it");
assertNotEquals(firstOffset, boundRangeStart());
} finally {
uniforms.destroy();
resetState();
}
}
@TestFactory
List<DynamicTest> everyStagingCategoryForcesAWrite() {
final List<DynamicTest> tests = new ArrayList<>();
@@ -147,9 +147,7 @@ class InstancedFFPDrawGLTest {
GLStateManager.glViewport(0, 0, PARITY_SIZE, PARITY_SIZE);
CubeParityFixture.setupScene(lightmap, lit);
final ShaderManager sm = ShaderManager.getInstance();
ShaderManager.enable();
sm.activate();
FfpFixture.activateFfp();
final CubeParityFixture.Spec[] specs = CubeParityFixture.specs();
@@ -172,9 +170,7 @@ class InstancedFFPDrawGLTest {
GLStateManager.glViewport(0, 0, PARITY_SIZE, PARITY_SIZE);
ParticleParityFixture.setupScene(true);
final ShaderManager sm = ShaderManager.getInstance();
ShaderManager.enable();
sm.activate();
FfpFixture.activateFfp();
final ParticleParityFixture.Particle[] particles = ParticleParityFixture.particles();
@@ -237,20 +233,7 @@ class InstancedFFPDrawGLTest {
@Test
void twoInstancesDrawWithDistinctTransformsAndColors() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glViewport(0, 0, 800, 600);
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
buildTemplate();
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
final ShaderManager sm = ShaderManager.getInstance();
ShaderManager.enable();
sm.activate();
prepareTwoInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
@@ -267,20 +250,7 @@ class InstancedFFPDrawGLTest {
@Test
void instancedAlphaTestDiscardsLowAlphaInstance() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glViewport(0, 0, 800, 600);
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
buildTemplate();
uploadInstances(0xFF0000FF, 0, 0x0D0000FF, 0);
final ShaderManager sm = ShaderManager.getInstance();
ShaderManager.enable();
sm.activate();
prepareTwoInstances(0xFF0000FF, 0, 0x0D0000FF, 0);
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
@@ -311,9 +281,7 @@ class InstancedFFPDrawGLTest {
buildTemplate();
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
final ShaderManager sm = ShaderManager.getInstance();
ShaderManager.enable();
sm.activate();
FfpFixture.activateFfp();
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
@@ -340,20 +308,7 @@ class InstancedFFPDrawGLTest {
@Test
void perInstanceOverlayMixesOnlyTintedInstances() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glViewport(0, 0, 800, 600);
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
buildTemplate();
uploadInstances(0xFFFFFFFF, 0, 0xFFFFFFFF, 0xFF0000FF);
final ShaderManager sm = ShaderManager.getInstance();
ShaderManager.enable();
sm.activate();
prepareTwoInstances(0xFFFFFFFF, 0, 0xFFFFFFFF, 0xFF0000FF);
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
@@ -370,6 +325,19 @@ class InstancedFFPDrawGLTest {
assertTrue(right[0] > 200 && right[1] < 50 && right[2] < 50, "right instance is mixed to its own opaque red overlay, got " + right[0] + "," + right[1] + "," + right[2]);
}
private void prepareTwoInstances(int leftColor, int leftOverlay, int rightColor, int rightOverlay) {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glViewport(0, 0, 800, 600);
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
buildTemplate();
uploadInstances(leftColor, leftOverlay, rightColor, rightOverlay);
FfpFixture.activateFfp();
}
private void uploadInstances(int leftColor, int leftOverlay, int rightColor, int rightOverlay) {
final ByteBuffer instances = BufferUtils.createByteBuffer(2 * INSTANCE_STRIDE);
putInstance(instances, -0.5f, leftColor, leftOverlay);
@@ -17,8 +17,7 @@ class LogicOpGLTest {
@BeforeEach
void setUp() {
FfpFixture.resetFfpState();
ShaderManager.enable();
ShaderManager.getInstance().activate();
FfpFixture.activateFfp();
GLStateManager.glViewport(0, 0, SIZE, SIZE);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
@@ -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);
}
}
@@ -3,7 +3,8 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -15,18 +16,13 @@ class UniformsRescaleTest {
return Uniforms.rescaleFactor(normalMatrix, new Vector3f());
}
@Test
void rescaleFactorUsesTheInverseModelviewBottomRow() {
assertEquals(8.0f, rescaleFactorFor(4.0f, 2.0f, 8.0f), 1.0e-5f, "wrong normal matrix column");
}
@Test
void uniformScaleIsRowIndependent() {
assertEquals(2.0f, rescaleFactorFor(2.0f, 2.0f, 2.0f), 1.0e-5f);
}
@Test
void singularMatrixFallsBackToUnitScale() {
assertEquals(1.0f, rescaleFactorFor(1.0f, 1.0f, 0.0f), 1.0e-5f, "a degenerate normal matrix must not divide by zero");
@ParameterizedTest(name = "{0}")
@CsvSource({
"wrong normal matrix column, 4, 2, 8, 8",
"uniform scale, 2, 2, 2, 2",
"a degenerate normal matrix must not divide by zero, 1, 1, 0, 1",
})
void rescaleFactorUsesTheInverseModelviewBottomRow(String label, float sx, float sy, float sz, float expected) {
assertEquals(expected, rescaleFactorFor(sx, sy, sz), 1.0e-5f, label);
}
}
@@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class VAOManagerEboIndexTest {
@@ -127,64 +126,4 @@ public class VAOManagerEboIndexTest {
}
assertTrue(sharedEboDeletes > 20, "op stream never freed a multi-owner EBO (hits: " + sharedEboDeletes + ")");
}
@Test
void sharedEboClearsEveryOwner() {
final int ebo = 77;
bindVao(1);
bindEbo(ebo);
bindVao(2);
bindEbo(ebo);
bindVao(3);
bindEbo(ebo);
bindVao(DEFAULT_VAO);
deleteBuffer(ebo);
assertEquals(0, vaos.vaoMap.get(1).ebo, "vao 1 kept a freed shared EBO");
assertEquals(0, vaos.vaoMap.get(2).ebo, "vao 2 kept a freed shared EBO");
assertEquals(0, vaos.vaoMap.get(3).ebo, "vao 3 kept a freed shared EBO");
assertFalse(vaos.eboOwners.containsKey(ebo), "index kept owners for a freed EBO");
}
@Test
void sharedEboEntryDropsOnlyAfterItsLastOwnerGoes() {
final int ebo = 88;
bindVao(1);
bindEbo(ebo);
bindVao(2);
bindEbo(ebo);
bindVao(DEFAULT_VAO);
deleteVao(1);
assertTrue(vaos.eboOwners.containsKey(ebo), "index dropped a still-live owner");
deleteVao(2);
assertFalse(vaos.eboOwners.containsKey(ebo), "index kept an entry after its last owner went away");
}
@Test
void deletingTheBoundVaoDoesNotOrphanAnIndexEntry() {
final int ebo = 99;
bindVao(1);
bindEbo(ebo);
deleteVao(1);
bindVao(DEFAULT_VAO);
assertFalse(vaos.eboOwners.containsKey(ebo), "deleting the bound VAO left an unreachable owner in the index");
assertEquals(0, vaos.eboOwners.size(), "index leaked an entry for a deleted VAO");
}
@Test
void indexEmptiesOutWhenEverythingIsDeleted() {
for (int vao = 1; vao <= 64; vao++) {
bindVao(vao);
bindEbo(1000 + vao);
}
bindVao(DEFAULT_VAO);
for (int vao = 1; vao <= 64; vao++) {
deleteVao(vao);
}
assertEquals(0, vaos.eboOwners.size(), "index leaked entries after every VAO was deleted");
}
}
@@ -39,8 +39,7 @@ class WideLineClipGLTest {
GLStateManager.wideLineEmulationEnabled = true;
FfpFixture.resetFfpState();
ShaderManager.enable();
ShaderManager.getInstance().activate();
FfpFixture.activateFfp();
GLStateManager.glViewport(0, 0, SIZE, SIZE);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
@@ -139,6 +139,10 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
assertNotNull(cmd);
assertEquals(6, cmd.getIndexCount());
assertEquals(GL11.GL_UNSIGNED_INT, cmd.getIndexType(), "batching always widens indices to UINT for safe base-vertex shifting");
final ByteBuffer ib = readBufferBytes(GL15.GL_ELEMENT_ARRAY_BUFFER, firstBatch(list).getSharedEBO(), 6 * 4);
assertUintIndices(ib, 0, 1, 2, 2, 3, 0);
MemoryUtilities.memFree(ib);
}
@Test
@@ -156,13 +160,7 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
final int vbo = defaultQuadVbo();
final int vao = setupPosColorVao(vbo);
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
final int list = recordSingleIndexedDraw(vao, 0, GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT);
assertTrue(allBatchedCmds(list).isEmpty(), "no EBO bound → no batched command attached");
assertTrue(batchesOf(list).isEmpty(), "no batches either");
@@ -357,51 +355,6 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
MemoryUtilities.memFree(ib);
}
@Test
void multiDrawDifferentFormats_splitBatches() {
// Draw A: pos+color (stride=12). Draw B: pos-only (stride=8). Different layout → two batches.
final int vboA = defaultQuadVbo();
final int vaoA = setupPosColorVao(vboA);
final int vboB = newBuffer();
final ByteBuffer bData = MemoryUtilities.memAlloc(4 * 8).order(ByteOrder.nativeOrder());
for (int i = 0; i < 4; i++) {
bData.putFloat(DEFAULT_QUAD_POSITIONS[i * 2]);
bData.putFloat(DEFAULT_QUAD_POSITIONS[i * 2 + 1]);
}
bData.flip();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboB);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, bData, GL15.GL_STATIC_DRAW);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
MemoryUtilities.memFree(bData);
final int vaoB = newVao();
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboB);
GLStateManager.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, 8, 0);
GLStateManager.glEnableVertexAttribArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glBindVertexArray(vaoA);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
final List<BatchedIndexedDrawCmd> cmds = allBatchedCmds(list);
assertEquals(2, cmds.size());
assertEquals(2, batchesOf(list).size(), "incompatible layouts → two batches");
assertNotSame(batchOf(cmds.get(0)), batchOf(cmds.get(1)), "separate layouts must get distinct batches");
}
@Test
void sameSizesDifferentLocations_splitIntoSeparateBatches() {
// Both VAOs: pos 2f + color 4ub (same sizes/types/normalized). Only the enabled
@@ -424,16 +377,7 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glBindVertexArray(vaoA);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
final int list = recordTwoVaoDraws(vaoA, vaoB, ebo);
final List<BatchedIndexedDrawCmd> cmds = allBatchedCmds(list);
assertEquals(2, cmds.size());
@@ -472,30 +416,14 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
// Record (A, B) and (B, A) as independent lists; each list's batch order
// must match its own recording order.
final int listAB = newList();
GLStateManager.glNewList(listAB, GL11.GL_COMPILE);
GLStateManager.glBindVertexArray(vaoA);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
final int listAB = recordTwoVaoDraws(vaoA, vaoB, ebo);
final int listBA = newList();
GLStateManager.glNewList(listBA, GL11.GL_COMPILE);
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(vaoA);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
final int listBA = recordTwoVaoDraws(vaoB, vaoA, ebo);
final List<BatchedIndexedDrawCmd> ab = allBatchedCmds(listAB);
final List<BatchedIndexedDrawCmd> ba = allBatchedCmds(listBA);
assertEquals(2, ab.size());
assertEquals(2, batchesOf(listAB).size(), "incompatible layouts -> two batches");
// Placeholders sit in command-stream order (A-first vs B-first).
// Batches must be ordered so that placeholder[0] points at batch[0].
assertSame(batchesOf(listAB).get(0), batchOf(ab.get(0)),
@@ -514,44 +442,48 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
final ByteBuffer idx = BufferUtils.createByteBuffer(3);
idx.put(new byte[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3),
}, GL11.GL_TRIANGLES, 3, new int[]{0, 1, 2}),
arguments("short indices", GL11.GL_COMPILE, (Runnable) () -> {
final ShortBuffer idx = BufferUtils.createShortBuffer(3);
idx.put(new short[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3),
}, GL11.GL_TRIANGLES, 3, new int[]{0, 1, 2}),
arguments("int indices", GL11.GL_COMPILE, (Runnable) () -> {
final IntBuffer idx = BufferUtils.createIntBuffer(3);
idx.put(new int[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3),
}, GL11.GL_TRIANGLES, 3, new int[]{0, 1, 2}),
arguments("explicit count+type", GL11.GL_COMPILE, (Runnable) () -> {
final ByteBuffer idx = BufferUtils.createByteBuffer(3 * 2).order(ByteOrder.nativeOrder());
idx.putShort((short) 0).putShort((short) 1).putShort((short) 2).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, idx);
}, GL11.GL_TRIANGLES, 3),
}, GL11.GL_TRIANGLES, 3, new int[]{0, 1, 2}),
arguments("quads triangulate at record", GL11.GL_COMPILE, (Runnable) () -> {
final IntBuffer idx = BufferUtils.createIntBuffer(4);
idx.put(new int[]{0, 1, 2, 3}).flip();
GLStateManager.glDrawElements(GL11.GL_QUADS, idx);
}, GL11.GL_TRIANGLES, 6),
}, GL11.GL_TRIANGLES, 6, new int[]{0, 1, 3, 1, 2, 3}),
arguments("compile-and-execute fires live and bakes", GL11.GL_COMPILE_AND_EXECUTE, (Runnable) () -> {
final IntBuffer idx = BufferUtils.createIntBuffer(3);
idx.put(new int[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3)
}, GL11.GL_TRIANGLES, 3, new int[]{0, 1, 2})
);
}
@ParameterizedTest(name = "{0}")
@MethodSource("clientIndexedDrawCases")
void clientIndexedDraw_capturedAndWidenedToUnsignedInt(String name, int listMode, Runnable draw, int expectedMode, int expectedCount) {
void clientIndexedDraw_capturedAndWidenedToUnsignedInt(String name, int listMode, Runnable draw, int expectedMode, int expectedCount, int[] expectedIndices) {
final int list = recordClientIndexDraw(listMode, draw);
final BatchedIndexedDrawCmd cmd = firstBatchedCmd(list);
assertNotNull(cmd);
assertEquals(expectedMode, cmd.getDrawMode());
assertEquals(expectedCount, cmd.getIndexCount());
assertEquals(GL11.GL_UNSIGNED_INT, cmd.getIndexType(), "client indices always widen to UNSIGNED_INT");
final ByteBuffer ib = readBufferBytes(GL15.GL_ELEMENT_ARRAY_BUFFER, firstBatch(list).getSharedEBO(), expectedCount * 4);
assertUintIndices(ib, expectedIndices);
MemoryUtilities.memFree(ib);
}
@Test
@@ -79,6 +79,7 @@ class IndexedDrawReplayTest extends DisplayListTestFixture {
GLStateManager.glBindVertexArray(vaoOf(batch));
assertEquals(batch.getSharedEBO(), GLStateManager.getBoundEBO(),
"batched VAO's EBO binding corrupted");
assertEquals(batch.getSharedEBO(), GL11.glGetInteger(GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING));
GLStateManager.glBindVertexArray(0);
}
@@ -148,6 +148,19 @@ public abstract class DisplayListTestFixture {
return list;
}
protected final int recordTwoVaoDraws(int firstVao, int secondVao, int ebo) {
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
for (int vao : new int[] { firstVao, secondVao }) {
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
}
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
return list;
}
protected static CompiledDisplayList compiledOf(int listId) {
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(listId);
assertNotNull(compiled, "compiled display list must exist for id " + listId);
@@ -119,6 +119,7 @@ class GLSMRedirectorTest {
assertEquals(expectedName, result.getName());
assertEquals(desc, result.getDesc());
assertEquals(Opcodes.H_INVOKESTATIC, result.getTag());
assertDoesNotThrow(() -> cn.accept(new ClassWriter(ClassWriter.COMPUTE_MAXS)));
}
@Test
@@ -158,21 +159,6 @@ class GLSMRedirectorTest {
assertTrue(unmappedSeen().contains("org/lwjgl/opengl/ARBSparseTexture.glTexPageCommitmentARB()V"));
}
@Test
void debugLabelIsRedirectedNotReported() {
assertRedirected("org/lwjgl/opengl/KHRDebug", "glPushDebugGroup", "(IILjava/lang/CharSequence;)V", "glPushDebugGroup");
assertRedirected("org/lwjgl/opengl/GL43C", "glObjectLabel", "(IILjava/lang/CharSequence;)V", "glObjectLabel");
assertTrue(debugSeen().isEmpty());
}
@Test
void nonAwareDebugCallbackIsRedirectedNotPassedThrough() {
assertRedirected("org/lwjgl/opengl/GL43", "glDebugMessageCallback", "(Lorg/lwjgl/opengl/KHRDebugCallback;)V", "glDebugMessageCallback");
assertRedirected("org/lwjgl/opengl/KHRDebug", "glDebugMessageCallback", "(Lorg/lwjgl/opengl/KHRDebugCallback;)V", "glDebugMessageCallback");
assertTrue(debugSeen().isEmpty());
assertTrue(unmappedSeen().isEmpty());
}
@Test
void debugOwnerAndWrongDescriptorArePassthrough() {
makeAndTransform("org/lwjgl/opengl/AMDDebugOutput", "glDebugMessageEnableAMD", "(IILjava/nio/IntBuffer;Z)V");
@@ -221,42 +207,6 @@ class GLSMRedirectorTest {
assertTrue(unmappedSeen().isEmpty());
}
@ParameterizedTest(name = "{1}")
@CsvSource(delimiter = '|', value = {
"org/lwjgl/opengl/EXTFramebufferObject | glBindFramebufferEXT | (II)V | glBindFramebuffer",
"org/lwjgl/opengl/ARBFramebufferObject | glGenerateMipmap | (I)V | glGenerateMipmap",
"org/lwjgl/opengl/EXTFramebufferObject | glGenerateMipmapEXT | (I)V | glGenerateMipmap",
"org/lwjgl/opengl/GL11 | glReadPixels | (IIIIIILjava/nio/IntBuffer;)V | glReadPixels",
"org/lwjgl/opengl/ARBOcclusionQuery | glGenQueriesARB | (Ljava/nio/IntBuffer;)V | glGenQueries"
})
void aliasIsRedirected(String owner, String name, String desc, String expectedName) {
assertEquals(desc, assertRedirected(owner, name, desc, expectedName).desc);
}
@Test
void pixelPackBufferReadbackOverloadsHaveDeclaredTargets() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{
{"org/lwjgl/opengl/GL11", "glReadPixels", "(IIIIIIJ)V"},
{"org/lwjgl/opengl/GL11C", "glReadPixels", "(IIIIIIJ)V"},
{"org/lwjgl/opengl/GL11", "glGetTexImage", "(IIIIJ)V"},
});
}
@Test
void utilCheckGLErrorIsRedirected() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{{"org/lwjgl/opengl/Util", "checkGLError", "()V"}});
}
@Test
void indexedGettersHaveDeclaredTargets() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{
{"org/lwjgl/opengl/GL30", "glGetInteger", "(II)I"},
{"org/lwjgl/opengl/GL31C", "glGetIntegeri", "(II)I"},
{"org/lwjgl/opengl/GL30", "glGetIntegeri_v", "(IILjava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/GL30", "glGetBooleani_v", "(IILjava/nio/ByteBuffer;)V"},
});
}
private MethodInsnNode assertRedirected(String owner, String name, String desc, String expectedName) {
final ClassNode cn = makeClassWithMethodCall(owner, name, desc);
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
@@ -277,80 +227,134 @@ class GLSMRedirectorTest {
return false;
}
private static void assertRedirectedToDeclaredTarget(String[][] cases) throws ClassNotFoundException {
@ParameterizedTest(name = "{0}.{1}{2}")
@CsvSource(delimiter = '|', value = {
"org/lwjgl/opengl/EXTFramebufferObject | glBindFramebufferEXT | (II)V | glBindFramebuffer",
"org/lwjgl/opengl/EXTFramebufferObject | glFramebufferTexture3DEXT | (IIIIII)V | glFramebufferTexture3D",
"org/lwjgl/opengl/ARBFramebufferObject | glGenerateMipmap | (I)V | glGenerateMipmap",
"org/lwjgl/opengl/EXTFramebufferObject | glGenerateMipmapEXT | (I)V | glGenerateMipmap",
"org/lwjgl/opengl/GL11 | glReadPixels | (IIIIIILjava/nio/IntBuffer;)V | glReadPixels",
"org/lwjgl/opengl/ARBOcclusionQuery | glGenQueriesARB | (Ljava/nio/IntBuffer;)V | glGenQueries",
"org/lwjgl/opengl/KHRDebug | glPushDebugGroup | (IILjava/lang/CharSequence;)V | glPushDebugGroup",
"org/lwjgl/opengl/GL43C | glObjectLabel | (IILjava/lang/CharSequence;)V | glObjectLabel",
"org/lwjgl/opengl/GL43 | glDebugMessageCallback | (Lorg/lwjgl/opengl/KHRDebugCallback;)V | glDebugMessageCallback",
"org/lwjgl/opengl/KHRDebug | glDebugMessageCallback | (Lorg/lwjgl/opengl/KHRDebugCallback;)V | glDebugMessageCallback",
"org/lwjgl/opengl/GL32C | glFenceSync | (II)J | glFenceSync",
"org/lwjgl/opengl/GL32C | glDeleteSync | (J)V | glDeleteSync",
"org/lwjgl/opengl/GL15C | nglMapBuffer | (II)J | nglMapBuffer",
"org/lwjgl/opengl/GL33C | glQueryCounter | (II)V | glQueryCounter",
"org/lwjgl/opengl/GL33C | glGetQueryObjectui64 | (II)J | glGetQueryObjectui64",
"org/lwjgl/opengl/ARBTimerQuery | glGetQueryObjectui64 | (II)J | glGetQueryObjectui64",
"org/lwjgl/opengl/EXTTimerQuery | glGetQueryObjectuEXT | (II)J | glGetQueryObjectui64",
"org/lwjgl/opengl/EXTTimerQuery | glGetQueryObjectui64EXT | (II)J | glGetQueryObjectui64",
"org/lwjgl/opengl/GL30C | glBindFragDataLocation | (IILjava/lang/CharSequence;)V | glBindFragDataLocation",
"org/lwjgl/opengl/GL11C | glGetIntegerv | (I[I)V | glGetInteger",
"org/lwjgl/opengl/GL20C | glUniform4fv | (I[F)V | glUniform4",
"org/lwjgl/opengl/GL20C | glUniform4fv | (ILjava/nio/FloatBuffer;)V | glUniform4",
"org/lwjgl/opengl/EXTGpuShader4 | glVertexAttribIPointerEXT | (IIIIJ)V | glVertexAttribIPointer",
"org/lwjgl/opengl/EXTGPUShader4 | glVertexAttribIPointerEXT | (IIIIJ)V | glVertexAttribIPointer",
"org/lwjgl/opengl/ARBUniformBufferObject | glGetUniformBlockIndex | (ILjava/lang/CharSequence;)I | glGetUniformBlockIndex",
"org/lwjgl/opengl/GL30C | glBindBufferBase | (III)V | glBindBufferBase",
"org/lwjgl/opengl/GL31C | glUniformBlockBinding | (III)V | glUniformBlockBinding",
"org/lwjgl/opengl/GL32C | glDrawElementsBaseVertex | (IIIJI)V | glDrawElementsBaseVertex",
"org/lwjgl/opengl/GL43C | glMultiDrawElementsIndirect | (IIJII)V | glMultiDrawElementsIndirect",
"org/lwjgl/opengl/GL20C | glUniform1fv | (ILjava/nio/FloatBuffer;)V | glUniform1",
"org/lwjgl/opengl/GL11 | glLoadMatrixf | (Ljava/nio/FloatBuffer;)V | glLoadMatrix",
"org/lwjgl/opengl/ARBVertexArrayObject | glGenVertexArrays | ()I | glGenVertexArrays",
"org/lwjgl/opengl/APPLEVertexArrayObject | glGenVertexArraysAPPLE | ()I | glGenVertexArrays",
"org/lwjgl/opengl/ARBGeometryShader4 | glFramebufferTextureFaceARB | (IIIII)V | glFramebufferTextureFace",
"org/lwjgl/opengl/EXTGeometryShader4 | glFramebufferTextureEXT | (IIII)V | glFramebufferTexture",
"org/lwjgl/opengl/EXTTextureArray | glFramebufferTextureLayerEXT | (IIIII)V | glFramebufferTextureLayer",
"org/lwjgl/opengl/EXTShaderImageLoadStore | glBindImageTextureEXT | (IIIZIII)V | glBindImageTexture",
"org/lwjgl/opengl/EXTFramebufferObject | glDeleteFramebuffersEXT | (Ljava/nio/IntBuffer;)V | glDeleteFramebuffers",
})
void aliasIsRedirected(String owner, String name, String desc, String expectedName) throws ClassNotFoundException {
assertEquals(desc, assertRedirected(owner, name, desc, expectedName).desc);
assertTrue(glsmDeclares(expectedName, desc), () -> "GLStateManager lacks " + expectedName + desc);
assertTrue(debugSeen().isEmpty());
}
@ParameterizedTest(name = "{0}.{1}{2}")
@CsvSource(delimiter = '|', value = {
"org/lwjgl/opengl/GL11 | glPopMatrix | ()V",
"org/lwjgl/opengl/GL11 | glReadPixels | (IIIIIIJ)V",
"org/lwjgl/opengl/GL11C | glReadPixels | (IIIIIIJ)V",
"org/lwjgl/opengl/GL11 | glGetTexImage | (IIIIJ)V",
"org/lwjgl/opengl/Util | checkGLError | ()V",
"org/lwjgl/opengl/GL30 | glGetInteger | (II)I",
"org/lwjgl/opengl/GL31C | glGetIntegeri | (II)I",
"org/lwjgl/opengl/GL30 | glGetIntegeri_v | (IILjava/nio/IntBuffer;)V",
"org/lwjgl/opengl/GL30 | glGetBooleani_v | (IILjava/nio/ByteBuffer;)V",
"org/lwjgl/opengl/GL33 | glGenSamplers | ()I",
"org/lwjgl/opengl/GL33 | glGenSamplers | (Ljava/nio/IntBuffer;)V",
"org/lwjgl/opengl/GL33C | glGenSamplers | ([I)V",
"org/lwjgl/opengl/GL33 | glGenSamplers | ([I)V",
"org/lwjgl/opengl/GL33 | glDeleteSamplers | (I)V",
"org/lwjgl/opengl/GL33 | glDeleteSamplers | (Ljava/nio/IntBuffer;)V",
"org/lwjgl/opengl/GL33C | glDeleteSamplers | ([I)V",
"org/lwjgl/opengl/GL33 | glDeleteSamplers | ([I)V",
"org/lwjgl/opengl/GL44 | glBindSamplers | (IILjava/nio/IntBuffer;)V",
"org/lwjgl/opengl/GL44C | glBindSamplers | (ILjava/nio/IntBuffer;)V",
"org/lwjgl/opengl/GL44 | glBindSamplers | (I[I)V",
"org/lwjgl/opengl/ARBMultiBind | glBindSamplers | (IILjava/nio/IntBuffer;)V",
"org/lwjgl/opengl/ARBMultiBind | glBindSamplers | (ILjava/nio/IntBuffer;)V",
"org/lwjgl/opengl/ARBMultiBind | glBindSamplers | (I[I)V",
"org/lwjgl/opengl/ARBSamplerObjects | glGenSamplers | ()I",
"org/lwjgl/opengl/ARBSamplerObjects | glGenSamplers | (Ljava/nio/IntBuffer;)V",
"org/lwjgl/opengl/ARBSamplerObjects | glGenSamplers | ([I)V",
"org/lwjgl/opengl/ARBSamplerObjects | glDeleteSamplers | (I)V",
"org/lwjgl/opengl/ARBSamplerObjects | glDeleteSamplers | (Ljava/nio/IntBuffer;)V",
"org/lwjgl/opengl/ARBSamplerObjects | glDeleteSamplers | ([I)V",
"org/lwjgl/opengl/ARBSamplerObjects | glIsSampler | (I)Z",
"org/lwjgl/opengl/ARBSamplerObjects | glBindSampler | (II)V",
"org/lwjgl/opengl/ARBSamplerObjects | glSamplerParameteri | (III)V",
"org/lwjgl/opengl/ARBSamplerObjects | glSamplerParameterf | (IIF)V",
"org/lwjgl/opengl/GL43 | glGetDebugMessageLog | (ILjava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/ByteBuffer;)I",
"org/lwjgl/opengl/GL30 | glFramebufferTexture1D | (IIIII)V",
"org/lwjgl/opengl/GL30 | glFramebufferTexture3D | (IIIIII)V",
"org/lwjgl/opengl/GL30 | glFramebufferTextureLayer | (IIIII)V",
"org/lwjgl/opengl/GL30C | glDeleteFramebuffers | ([I)V",
"org/lwjgl/opengl/GL11C | glDeleteTextures | ([I)V",
"org/lwjgl/opengl/GL11C | glGenTextures | ([I)V",
"org/lwjgl/opengl/GL44 | glBindTextures | (IILjava/nio/IntBuffer;)V",
"org/lwjgl/opengl/GL44C | glBindTextures | (ILjava/nio/IntBuffer;)V",
"org/lwjgl/opengl/GL44 | glBindTextures | (I[I)V",
"org/lwjgl/opengl/GL44 | glBindImageTextures | (IILjava/nio/IntBuffer;)V",
"org/lwjgl/opengl/GL44C | glBindImageTextures | (ILjava/nio/IntBuffer;)V",
"org/lwjgl/opengl/GL44 | glBindImageTextures | (I[I)V",
"org/lwjgl/opengl/GL45 | glBindTextureUnit | (II)V",
"org/lwjgl/opengl/GL45 | glCreateTextures | (I)I",
"org/lwjgl/opengl/GL45C | glCreateTextures | (ILjava/nio/IntBuffer;)V",
"org/lwjgl/opengl/GL45C | glCreateTextures | (I[I)V",
"org/lwjgl/opengl/GL45 | glNamedFramebufferTexture | (IIII)V",
"org/lwjgl/opengl/GL45 | glNamedFramebufferTextureLayer | (IIIII)V",
"org/lwjgl/opengl/ARBDirectStateAccess | glNamedFramebufferRenderbuffer | (IIII)V",
"org/lwjgl/opengl/EXTDirectStateAccess | glNamedFramebufferTexture1DEXT | (IIIII)V",
"org/lwjgl/opengl/EXTDirectStateAccess | glNamedFramebufferTexture2DEXT | (IIIII)V",
"org/lwjgl/opengl/EXTDirectStateAccess | glNamedFramebufferTexture3DEXT | (IIIIII)V",
"org/lwjgl/opengl/EXTDirectStateAccess | glNamedFramebufferTextureEXT | (IIII)V",
"org/lwjgl/opengl/EXTDirectStateAccess | glNamedFramebufferTextureFaceEXT | (IIIII)V",
"org/lwjgl/opengl/EXTDirectStateAccess | glBindMultiTextureEXT | (III)V",
"org/lwjgl/opengl/ARBShaderImageLoadStore | glBindImageTexture | (IIIZIII)V",
})
void redirectsToADeclaredGlsmTarget(String owner, String name, String desc) throws ClassNotFoundException {
for (boolean aware : new boolean[]{false, true}) {
for (String[] c : cases) {
final String call = c[0] + "." + c[1] + c[2] + (aware ? " aware" : "");
final ClassNode cn = makeClassWithMethodCall(c[0], c[1], c[2]);
assertTrue(new GLSMRedirector().transformClassNode("com.example.TestClass", cn, aware), call);
final MethodInsnNode target = firstCall(cn);
assertEquals(GLSM, target.owner, call);
assertEquals(c[1], target.name, call);
assertEquals(c[2], target.desc, call);
assertTrue(glsmDeclares(target.name, target.desc), () -> "GLStateManager lacks the target of " + call);
}
final String call = owner + "." + name + desc + (aware ? " aware" : "");
final ClassNode cn = makeClassWithMethodCall(owner, name, desc);
assertTrue(new GLSMRedirector().transformClassNode("com.example.TestClass", cn, aware), call);
final MethodInsnNode target = firstCall(cn);
assertEquals(GLSM, target.owner, call);
assertEquals(name, target.name, call);
assertEquals(desc, target.desc, call);
assertTrue(glsmDeclares(target.name, target.desc), () -> "GLStateManager lacks the target of " + call);
}
assertTrue(unmappedSeen().isEmpty(), () -> "unexpected reports: " + unmappedSeen());
}
@Test
void samplerGenDeleteOverloadsHaveDeclaredTargets() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{
{"org/lwjgl/opengl/GL33", "glGenSamplers", "()I"},
{"org/lwjgl/opengl/GL33", "glGenSamplers", "(Ljava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/GL33C", "glGenSamplers", "([I)V"},
{"org/lwjgl/opengl/GL33", "glGenSamplers", "([I)V"},
{"org/lwjgl/opengl/GL33", "glDeleteSamplers", "(I)V"},
{"org/lwjgl/opengl/GL33", "glDeleteSamplers", "(Ljava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/GL33C", "glDeleteSamplers", "([I)V"},
{"org/lwjgl/opengl/GL33", "glDeleteSamplers", "([I)V"},
});
}
@Test
void bindSamplersHasDeclaredTargets() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{
{"org/lwjgl/opengl/GL44", "glBindSamplers", "(IILjava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/GL44C", "glBindSamplers", "(ILjava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/GL44", "glBindSamplers", "(I[I)V"},
{"org/lwjgl/opengl/ARBMultiBind", "glBindSamplers", "(IILjava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/ARBMultiBind", "glBindSamplers", "(ILjava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/ARBMultiBind", "glBindSamplers", "(I[I)V"},
});
}
@Test
void arbSamplerObjectsHasDeclaredTargets() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{
{"org/lwjgl/opengl/ARBSamplerObjects", "glGenSamplers", "()I"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glGenSamplers", "(Ljava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glGenSamplers", "([I)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glDeleteSamplers", "(I)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glDeleteSamplers", "(Ljava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glDeleteSamplers", "([I)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glIsSampler", "(I)Z"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glBindSampler", "(II)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glSamplerParameteri", "(III)V"},
{"org/lwjgl/opengl/ARBSamplerObjects", "glSamplerParameterf", "(IIF)V"},
});
}
@Test
void descKeyedSyncRedirectsOnlyLongForm() {
assertRedirected("org/lwjgl/opengl/GL32C", "glFenceSync", "(II)J", "glFenceSync");
assertRedirected("org/lwjgl/opengl/GL32C", "glDeleteSync", "(J)V", "glDeleteSync");
assertRedirected("org/lwjgl/opengl/GL15C", "nglMapBuffer", "(II)J", "nglMapBuffer");
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/GL32", "glFenceSync", "(II)Lorg/lwjgl/opengl/GLSync;");
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertFalse(changed);
assertEquals("org/lwjgl/opengl/GL32", firstCall(cn).owner);
assertTrue(unmappedSeen().isEmpty());
}
@Test
void glSyncObjectFormsAreNotReported() {
final String[][] cases = {
{"org/lwjgl/opengl/GL32", "glFenceSync", "(II)Lorg/lwjgl/opengl/GLSync;"},
{"org/lwjgl/opengl/GL32", "glClientWaitSync", "(Lorg/lwjgl/opengl/GLSync;IJ)I"},
{"org/lwjgl/opengl/ARBSync", "glWaitSync", "(Lorg/lwjgl/opengl/GLSync;IJ)V"},
{"org/lwjgl/opengl/ARBSync", "glDeleteSync", "(Lorg/lwjgl/opengl/GLSync;)V"},
@@ -362,79 +366,39 @@ class GLSMRedirectorTest {
assertTrue(unmappedSeen().isEmpty());
}
@Test
void phase2NameKeyedGapsRedirect() {
assertRedirected("org/lwjgl/opengl/GL33C", "glQueryCounter", "(II)V", "glQueryCounter");
assertRedirected("org/lwjgl/opengl/GL33C", "glGetQueryObjectui64", "(II)J", "glGetQueryObjectui64");
assertRedirected("org/lwjgl/opengl/ARBTimerQuery", "glGetQueryObjectui64", "(II)J", "glGetQueryObjectui64");
assertRedirected("org/lwjgl/opengl/EXTTimerQuery", "glGetQueryObjectuEXT", "(II)J", "glGetQueryObjectui64");
assertRedirected("org/lwjgl/opengl/EXTTimerQuery", "glGetQueryObjectui64EXT", "(II)J", "glGetQueryObjectui64");
assertRedirected("org/lwjgl/opengl/GL30C", "glBindFragDataLocation", "(IILjava/lang/CharSequence;)V", "glBindFragDataLocation");
assertRedirected("org/lwjgl/opengl/GL11C", "glGetIntegerv", "(I[I)V", "glGetInteger");
assertRedirected("org/lwjgl/opengl/GL20C", "glUniform4fv", "(I[F)V", "glUniform4");
assertRedirected("org/lwjgl/opengl/GL20C", "glUniform4fv", "(ILjava/nio/FloatBuffer;)V", "glUniform4");
}
@Test
void phase1CeleritasGapsRedirect() {
assertRedirected("org/lwjgl/opengl/EXTGpuShader4", "glVertexAttribIPointerEXT", "(IIIIJ)V", "glVertexAttribIPointer");
assertRedirected("org/lwjgl/opengl/EXTGPUShader4", "glVertexAttribIPointerEXT", "(IIIIJ)V", "glVertexAttribIPointer");
assertRedirected("org/lwjgl/opengl/ARBUniformBufferObject", "glGetUniformBlockIndex", "(ILjava/lang/CharSequence;)I", "glGetUniformBlockIndex");
assertRedirected("org/lwjgl/opengl/GL30C", "glBindBufferBase", "(III)V", "glBindBufferBase");
assertRedirected("org/lwjgl/opengl/GL31C", "glUniformBlockBinding", "(III)V", "glUniformBlockBinding");
assertRedirected("org/lwjgl/opengl/GL32C", "glDrawElementsBaseVertex", "(IIIJI)V", "glDrawElementsBaseVertex");
assertRedirected("org/lwjgl/opengl/GL43C", "glMultiDrawElementsIndirect", "(IIJII)V", "glMultiDrawElementsIndirect");
assertRedirected("org/lwjgl/opengl/GL20C", "glUniform1fv", "(ILjava/nio/FloatBuffer;)V", "glUniform1");
assertRedirected("org/lwjgl/opengl/GL11", "glLoadMatrixf", "(Ljava/nio/FloatBuffer;)V", "glLoadMatrix");
assertRedirected("org/lwjgl/opengl/ARBVertexArrayObject", "glGenVertexArrays", "()I", "glGenVertexArrays");
assertRedirected("org/lwjgl/opengl/APPLEVertexArrayObject", "glGenVertexArraysAPPLE", "()I", "glGenVertexArrays");
}
private static final String AWARE_SHIM = "com/gtnewhorizons/angelica/sdlgpu/compat/AwareGLDebugShim";
private static final String CALLBACK_I = "Lorg/lwjgl/opengl/GLDebugMessageCallbackI;";
@Test
void awareCallerStillHasPlainGlCallsRedirected() {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/GL11", "glPopMatrix", "()V");
assertTrue(new GLSMRedirector().transformClassNode("com.example.TestClass", cn, true));
assertEquals(GLSM, firstCall(cn).owner);
assertEquals("glPopMatrix", firstCall(cn).name);
}
@Test
void awareDebugMessageCallbackTargetsTheAwareShim() {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/GL43", "glDebugMessageCallback", "(" + CALLBACK_I + "J)V");
@ParameterizedTest(name = "{0}.{1}{2}")
@CsvSource(delimiter = '|', value = {
"org/lwjgl/opengl/GL43 | glDebugMessageCallback | (Lorg/lwjgl/opengl/GLDebugMessageCallbackI;J)V",
"org/lwjgl/opengl/GLUtil | setupDebugMessageCallback | ()Lorg/lwjgl/system/Callback;",
"org/lwjgl/opengl/GLUtil | setupDebugMessageCallback | (Ljava/io/PrintStream;)Lorg/lwjgl/system/Callback;",
})
void awareDebugCallbackSetupTargetsTheAwareShim(String owner, String name, String desc) {
final ClassNode cn = makeClassWithMethodCall(owner, name, desc);
assertTrue(new GLSMRedirector().transformClassNode("com.example.TestClass", cn, true));
assertEquals(AWARE_SHIM, firstCall(cn).owner, "GLStateManager cannot carry an org.lwjgl-typed descriptor for an aware caller");
assertEquals("glDebugMessageCallback", firstCall(cn).name);
assertEquals(name, firstCall(cn).name);
}
@Test
void awareGLUtilSetupDebugMessageCallbackIsRedirected() {
for (String desc : new String[]{"()Lorg/lwjgl/system/Callback;", "(Ljava/io/PrintStream;)Lorg/lwjgl/system/Callback;"}) {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/GLUtil", "setupDebugMessageCallback", desc);
@ParameterizedTest(name = "{0}.{1}{2}")
@CsvSource(delimiter = '|', value = {
"org/lwjgl/opengl/GL | getCapabilities | ()Lorg/lwjgl/opengl/GLCapabilities;",
"org/lwjgl/opengl/GL | setCapabilities | (Lorg/lwjgl/opengl/GLCapabilities;)V",
"org/lwjgl/opengl/GL | getFunctionProvider | ()Lorg/lwjgl/system/FunctionProvider;",
"org/lwjgl/opengl/GLCapabilities | getAddressBuffer | ()Lorg/lwjgl/PointerBuffer;",
"org/lwjgl/opengl/GLDebugMessageCallback | create | (Lorg/lwjgl/opengl/GLDebugMessageCallbackI;)Lorg/lwjgl/opengl/GLDebugMessageCallback;",
})
void awareSafeLwjglTypedMembersAreNeitherRedirectedNorReported(String owner, String name, String desc) {
final ClassNode cn = makeClassWithMethodCall(owner, name, desc);
assertTrue(new GLSMRedirector().transformClassNode("com.example.TestClass", cn, true), desc);
assertFalse(new GLSMRedirector().transformClassNode("com.example.TestClass", cn, true));
assertEquals(AWARE_SHIM, firstCall(cn).owner, desc);
}
}
@Test
void getDebugMessageLogReachesGLStateManager() {
final String buffers = "Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/ByteBuffer;";
for (String desc : new String[]{"(I" + buffers + ")I"}) {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/GL43", "glGetDebugMessageLog", desc);
assertTrue(new GLSMRedirector().transformClassNode("com.example.TestClass", cn, false), desc);
assertEquals(GLSM, firstCall(cn).owner, desc);
assertEquals(desc, firstCall(cn).desc, "descriptor must be preserved so the right overload is hit");
}
assertEquals(owner, firstCall(cn).owner);
assertTrue(unmappedSeen().isEmpty(), () -> "must not be reported: " + unmappedSeen());
}
@Test
@@ -446,14 +410,6 @@ class GLSMRedirectorTest {
assertTrue(unmappedSeen().stream().anyMatch(s -> s.startsWith("aware ") && s.contains("glSomethingExotic")), () -> "expected an aware-unroutable report, got " + unmappedSeen());
}
@Test
void awareUnroutableCallFailsFastWhenRequested() {
Reflect.setStaticFinal(SystemProperties.class, "UNMAPPED_GL", UnmappedGLMode.class, UnmappedGLMode.STRICT);
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/GL32C", "glSomethingExotic", "(" + CALLBACK_I + ")V");
assertThrows(IllegalStateException.class, () -> new GLSMRedirector().transformClassNode("com.example.TestClass", cn, true));
}
@Test
void nonAwareCallerNeverTargetsTheAwareShim() {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/GL43", "glDebugMessageCallback", "(" + CALLBACK_I + "J)V");
@@ -463,17 +419,6 @@ class GLSMRedirectorTest {
assertFalse(AWARE_SHIM.equals(firstCall(cn).owner));
}
/** Crashed the game: it carries an LWJGL type but is only a ThreadLocal read plus a null check. */
@Test
void awareGetCapabilitiesIsNeitherRedirectedNorReported() {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/GL", "getCapabilities", "()Lorg/lwjgl/opengl/GLCapabilities;");
assertFalse(new GLSMRedirector().transformClassNode("com.example.TestClass", cn, true));
assertEquals("org/lwjgl/opengl/GL", firstCall(cn).owner);
assertTrue(unmappedSeen().isEmpty(), () -> "must not be reported: " + unmappedSeen());
}
@Test
void awareGetCapabilitiesDoesNotFailFastEvenInStrictMode() {
Reflect.setStaticFinal(SystemProperties.class, "UNMAPPED_GL", UnmappedGLMode.class, UnmappedGLMode.STRICT);
@@ -482,22 +427,6 @@ class GLSMRedirectorTest {
assertDoesNotThrow(() -> new GLSMRedirector().transformClassNode("com.example.TestClass", cn, true));
}
@Test
void awareSafeLwjglTypedMembersAreNotReported() {
final String[][] safe = {
{"org/lwjgl/opengl/GL", "setCapabilities", "(Lorg/lwjgl/opengl/GLCapabilities;)V"},
{"org/lwjgl/opengl/GL", "getFunctionProvider", "()Lorg/lwjgl/system/FunctionProvider;"},
{"org/lwjgl/opengl/GLCapabilities", "getAddressBuffer", "()Lorg/lwjgl/PointerBuffer;"},
{"org/lwjgl/opengl/GLDebugMessageCallback", "create", "(Lorg/lwjgl/opengl/GLDebugMessageCallbackI;)Lorg/lwjgl/opengl/GLDebugMessageCallback;"},
};
for (String[] m : safe) {
unmappedSeen().clear();
final ClassNode cn = makeClassWithMethodCall(m[0], m[1], m[2]);
new GLSMRedirector().transformClassNode("com.example.TestClass", cn, true);
assertTrue(unmappedSeen().isEmpty(), () -> m[0] + "." + m[1] + " must not be reported");
}
}
@Test
void awareDispatchingMembersOnTheGLClassAreStillReported() {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/GL", "createCapabilities", "(Z)Lorg/lwjgl/opengl/GLCapabilities;");
@@ -516,19 +445,4 @@ class GLSMRedirectorTest {
assertThrows(IllegalStateException.class, () -> new GLSMRedirector().transformClassNode("com.example.TestClass", cn, true), () -> "attempt " + attempt + " must still fail");
}
}
@Test
void transformedClassProducesValidBytecode() {
final Handle impl = new Handle(Opcodes.H_INVOKESTATIC, "org/lwjgl/opengl/GL11", "glPopMatrix", "()V", false);
final ClassNode cn = makeClassWithInvokeDynamic(impl);
new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
assertDoesNotThrow(() -> {
cn.accept(cw);
final byte[] bytes = cw.toByteArray();
assertTrue(bytes.length > 0);
});
}
}
@@ -1,10 +1,14 @@
package com.gtnewhorizons.angelica.glsm.shader;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -12,267 +16,232 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class SpirvShaderTranslatorTest {
@Test
void selectionBoxVs_producesNamedLooseUniforms() {
final String src = """
#version 330 core
uniform mat4 u_MVP;
uniform vec3 u_Min;
uniform vec3 u_Max;
const vec3 edges[24] = vec3[24](
vec3(0,0,0), vec3(1,0,0), vec3(1,0,0), vec3(1,0,1),
vec3(1,0,1), vec3(0,0,1), vec3(0,0,1), vec3(0,0,0),
vec3(0,1,0), vec3(1,1,0), vec3(1,1,0), vec3(1,1,1),
vec3(1,1,1), vec3(0,1,1), vec3(0,1,1), vec3(0,1,0),
vec3(0,0,0), vec3(0,1,0), vec3(1,0,0), vec3(1,1,0),
vec3(1,0,1), vec3(1,1,1), vec3(0,0,1), vec3(0,1,1)
);
void main() {
vec3 pos = mix(u_Min, u_Max, edges[gl_VertexID]);
gl_Position = u_MVP * vec4(pos, 1.0);
}
""";
static Stream<Arguments> translates() {
return Stream.of(
Arguments.of("selection box VS produces named loose uniforms", GL20.GL_VERTEX_SHADER, "selection_box.vert", """
#version 330 core
uniform mat4 u_MVP;
uniform vec3 u_Min;
uniform vec3 u_Max;
const vec3 edges[24] = vec3[24](
vec3(0,0,0), vec3(1,0,0), vec3(1,0,0), vec3(1,0,1),
vec3(1,0,1), vec3(0,0,1), vec3(0,0,1), vec3(0,0,0),
vec3(0,1,0), vec3(1,1,0), vec3(1,1,0), vec3(1,1,1),
vec3(1,1,1), vec3(0,1,1), vec3(0,1,1), vec3(0,1,0),
vec3(0,0,0), vec3(0,1,0), vec3(1,0,0), vec3(1,1,0),
vec3(1,0,1), vec3(1,1,1), vec3(0,0,1), vec3(0,1,1)
);
void main() {
vec3 pos = mix(u_Min, u_Max, edges[gl_VertexID]);
gl_Position = u_MVP * vec4(pos, 1.0);
}
""",
new String[] { "mat4 u_MVP", "vec3 u_Min", "vec3 u_Max" }, new String[0], new String[] { "_RESERVED_IDENTIFIER_FIXUP" }),
Arguments.of("selection box FS produces a named uniform and a locationed frag output", GL20.GL_FRAGMENT_SHADER, "selection_box.frag", """
#version 330 core
uniform vec4 u_Color;
out vec4 fragColor;
void main() {
fragColor = u_Color;
}
""",
new String[] { "vec4 u_Color" }, new String[] { "layout(location = 0)" }, new String[0]),
Arguments.of("uvec3 * vec3 must be made explicit by the SPIR-V round-trip", GL20.GL_VERTEX_SHADER, "chunk.vert", """
#version 330 core
uniform mat4 u_ModelViewProjectionMatrix;
uvec3 unpack(uint p) {
return uvec3(p) >> uvec3(5u,0u,2u) & uvec3(7u,3u,7u);
}
vec3 translation(uint p) {
return unpack(p) * vec3(16.0);
}
void main() {
gl_Position = u_ModelViewProjectionMatrix * vec4(translation(0u), 1.0);
}
""",
new String[0], new String[0], new String[] { "unpack(p) * vec3" }),
Arguments.of("global init must be hoisted or inlined by the SPIR-V round-trip", GL20.GL_VERTEX_SHADER, "botania_fog.vert", """
#version 330 core
struct Fog { vec4 color; float density; };
uniform vec4 fogColor;
uniform float fogDensity;
Fog fog = Fog(fogColor, fogDensity);
void main() {
gl_Position = vec4(fog.density) + fog.color;
}
""",
new String[0], new String[0], new String[] { "Fog fog = Fog(fogColor, fogDensity);" }),
Arguments.of("uniform bool round-trips as bool, not uint", GL20.GL_FRAGMENT_SHADER, "cloud.frag", """
#version 330 core
uniform bool u_FogEnabled;
in vec4 color;
out vec4 fragColor;
void main() {
vec4 c = color;
if (u_FogEnabled) {
c.a *= 0.5;
}
fragColor = c;
}
""",
new String[] { "bool u_FogEnabled" }, new String[0], new String[] { "uniform uint u_FogEnabled", "uniform highp uint u_FogEnabled", "uniform mediump uint u_FogEnabled" }),
Arguments.of("redefined builtin translates", GL20.GL_FRAGMENT_SHADER, "redefines_clamp.frag", SpirvCompilerDiskCacheTest.REDEFINES_CLAMP,
new String[0], new String[0], new String[0]),
Arguments.of("uniform bool with initializer round-trips as bool", GL20.GL_FRAGMENT_SHADER, "iris_common_bool_init.frag", """
#version 330 core
uniform bool isRightHanded = true;
uniform bool heavyFog = false;
uniform bool firstPersonCamera = true;
in vec4 color;
out vec4 fragColor;
void main() {
float alpha = 1.0;
if (isRightHanded) alpha *= 0.9;
if (heavyFog) alpha *= 0.8;
if (firstPersonCamera) alpha *= 0.95;
fragColor = vec4(color.rgb, color.a * alpha);
}
""",
new String[] { "bool isRightHanded", "bool heavyFog", "bool firstPersonCamera" }, new String[0], new String[] { "uniform uint isRightHanded", "uniform highp uint isRightHanded", "uniform mediump uint isRightHanded" }),
Arguments.of("Vulkan-reserved sampler identifier is renamed before shaderc and sampler2D is untouched", GL20.GL_FRAGMENT_SHADER, "fontFilter.frag", """
#version 330 core
uniform sampler2D sampler;
in vec2 texCoord;
out vec4 fragColor;
void main() {
fragColor = texture(sampler, texCoord);
}
""",
new String[0], new String[] { "angelica_sampler_renamed", "sampler2D" }, new String[0]),
Arguments.of("C++ keywords as identifiers are renamed and the not() builtin is untouched", GL20.GL_FRAGMENT_SHADER, "reserved.frag", """
#version 330 core
in vec2 texCoord;
out vec4 fragColor;
void main() {
vec4 try = vec4(texCoord, 0.0, 1.0);
float catch = try.x;
bvec2 flipped = not(lessThan(texCoord, vec2(0.5)));
fragColor = flipped.x ? try : vec4(catch);
}
""",
new String[0], new String[] { "angelica_renamed_try", "angelica_renamed_catch", "not(" }, new String[] { "angelica_renamed_not" }),
Arguments.of("multi-declarator bool uniforms are all restored", GL20.GL_FRAGMENT_SHADER, "multi_bool.frag", """
#version 330 core
uniform bool a, b, c;
in vec4 color;
out vec4 fragColor;
void main() {
float alpha = 1.0;
if (a) alpha *= 0.9;
if (b) alpha *= 0.8;
if (c) alpha *= 0.7;
fragColor = vec4(color.rgb, color.a * alpha);
}
""",
new String[] { "bool a", "bool b", "bool c" }, new String[0], new String[] { "uniform uint", "uniform highp uint", "uniform mediump uint" }),
Arguments.of("compound bool expression rewrites both branches", GL20.GL_FRAGMENT_SHADER, "compound_bool.frag", """
#version 330 core
uniform bool a;
uniform bool b;
in vec4 color;
out vec4 fragColor;
void main() {
vec4 c = color;
if (a && b) { c.a *= 0.5; }
fragColor = c;
}
""",
new String[0], new String[0], new String[] { "!= 0u", "uniform uint" }));
}
final String es = SpirvShaderTranslator.glslToGlslEs(src, GL20.GL_VERTEX_SHADER, "selection_box.vert");
dumpOnFailure("selection_box.vert", src, es);
@ParameterizedTest(name = "{0}")
@MethodSource
void translates(String label, int stage, String file, String src, String[] uniformDecls, String[] present, String[] absent) {
final String es = SpirvShaderTranslator.glslToGlslEs(src, stage, file);
dumpOnFailure(file, src, es);
assertNotNull(es);
assertTrue(es.startsWith("#version 320 es"));
assertContainsDecl(es, "mat4", "u_MVP");
assertContainsDecl(es, "vec3", "u_Min");
assertContainsDecl(es, "vec3", "u_Max");
assertFalse(es.contains("_RESERVED_IDENTIFIER_FIXUP"), "uniforms must not be wrapped in spvc's default-block struct");
for (String decl : uniformDecls) assertContainsDecl(es, decl.substring(0, decl.indexOf(' ')), decl.substring(decl.indexOf(' ') + 1));
for (String needle : present) assertTrue(compact(es).contains(compact(needle)), needle);
for (String needle : absent) assertFalse(compact(es).contains(compact(needle)), needle);
}
@Test
void selectionBoxFs_producesNamedUniformAndLocationedFragOutput() {
final String src = """
#version 330 core
uniform vec4 u_Color;
out vec4 fragColor;
void main() {
fragColor = u_Color;
}
""";
final String es = SpirvShaderTranslator.glslToGlslEs(src, GL20.GL_FRAGMENT_SHADER, "selection_box.frag");
dumpOnFailure("selection_box.frag", src, es);
assertNotNull(es);
assertContainsDecl(es, "vec4", "u_Color");
assertTrue(es.contains("layout(location = 0)") || es.contains("layout(location=0)"), "fragment output must have layout(location=0)");
}
@Test
void celeritasChunk_impliedUvecVecPromotion_becomesExplicit() {
final String src = """
#version 330 core
uniform mat4 u_ModelViewProjectionMatrix;
uvec3 unpack(uint p) {
return uvec3(p) >> uvec3(5u,0u,2u) & uvec3(7u,3u,7u);
}
vec3 translation(uint p) {
return unpack(p) * vec3(16.0);
}
void main() {
gl_Position = u_ModelViewProjectionMatrix * vec4(translation(0u), 1.0);
}
""";
final String es = SpirvShaderTranslator.glslToGlslEs(src, GL20.GL_VERTEX_SHADER, "chunk.vert");
dumpOnFailure("chunk.vert", src, es);
assertNotNull(es);
assertFalse(es.contains("unpack(p) * vec3"), "uvec3 * vec3 must be made explicit by the SPIR-V round-trip");
}
@Test
void globalInitReferencingUniforms_survivesRoundTrip() {
final String src = """
#version 330 core
struct Fog { vec4 color; float density; };
uniform vec4 fogColor;
uniform float fogDensity;
Fog fog = Fog(fogColor, fogDensity);
void main() {
gl_Position = vec4(fog.density) + fog.color;
}
""";
final String es = SpirvShaderTranslator.glslToGlslEs(src, GL20.GL_VERTEX_SHADER, "botania_fog.vert");
dumpOnFailure("botania_fog.vert", src, es);
assertNotNull(es);
assertFalse(es.contains("Fog fog = Fog(fogColor, fogDensity);"), "global init must be hoisted / inlined by the SPIR-V round-trip");
}
@Test
void uniformBool_roundTripsAsBool_notUint() {
final String src = """
#version 330 core
uniform bool u_FogEnabled;
in vec4 color;
out vec4 fragColor;
void main() {
vec4 c = color;
if (u_FogEnabled) {
c.a *= 0.5;
static Stream<Arguments> varyingLocations() {
return Stream.of(
Arguments.of("varyings stripped from VS outputs, kept on attributes", GL20.GL_VERTEX_SHADER, "strip.vert", """
#version 330 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec4 a_Color;
uniform mat4 u_MVP;
out vec4 v_Color;
out vec2 v_TexCoord;
void main() {
v_Color = a_Color;
v_TexCoord = a_Position.xy;
gl_Position = u_MVP * vec4(a_Position, 1.0);
}
fragColor = c;
}
""";
final String es = SpirvShaderTranslator.glslToGlslEs(src, GL20.GL_FRAGMENT_SHADER, "cloud.frag");
dumpOnFailure("cloud.frag (bool uniform)", src, es);
assertNotNull(es);
assertContainsDecl(es, "bool", "u_FogEnabled");
assertFalse(es.contains("uniform uint u_FogEnabled") || es.contains("uniform highp uint u_FogEnabled") || es.contains("uniform mediump uint u_FogEnabled"),
"emitted uniform must not be `uint` - `glUniform1i` only accepts int/bool uniforms");
}
@Test
void redefinedBuiltin_translates() {
assertNotNull(SpirvShaderTranslator.glslToGlslEs(SpirvCompilerDiskCacheTest.REDEFINES_CLAMP, GL20.GL_FRAGMENT_SHADER, "redefines_clamp.frag"));
}
@Test
void uniformBoolWithInitializer_roundTripsAsBool() {
final String src = """
#version 330 core
uniform bool isRightHanded = true;
uniform bool heavyFog = false;
uniform bool firstPersonCamera = true;
in vec4 color;
out vec4 fragColor;
void main() {
float alpha = 1.0;
if (isRightHanded) alpha *= 0.9;
if (heavyFog) alpha *= 0.8;
if (firstPersonCamera) alpha *= 0.95;
fragColor = vec4(color.rgb, color.a * alpha);
}
""";
final String es = SpirvShaderTranslator.glslToGlslEs(src, GL20.GL_FRAGMENT_SHADER, "iris_common_bool_init.frag");
dumpOnFailure("iris_common_bool_init.frag", src, es);
assertNotNull(es);
assertContainsDecl(es, "bool", "isRightHanded");
assertContainsDecl(es, "bool", "heavyFog");
assertContainsDecl(es, "bool", "firstPersonCamera");
assertFalse(es.contains("uniform uint isRightHanded") || es.contains("uniform highp uint isRightHanded") || es.contains("uniform mediump uint isRightHanded"),
"isRightHanded must not be emitted as uint\n\n" + es);
}
@Test
void varyingLocations_strippedFromVsOutAndFsIn_keptOnAttributesAndMrtOutputs() {
final String vs = """
#version 330 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec4 a_Color;
uniform mat4 u_MVP;
out vec4 v_Color;
out vec2 v_TexCoord;
void main() {
v_Color = a_Color;
v_TexCoord = a_Position.xy;
gl_Position = u_MVP * vec4(a_Position, 1.0);
}
""";
final String fs = """
#version 330 core
in vec2 v_TexCoord;
in vec4 v_Color;
uniform sampler2D u_Tex;
out vec4 fragColor;
void main() {
fragColor = v_Color * texture(u_Tex, v_TexCoord);
}
""";
final String esVs = SpirvShaderTranslator.glslToGlslEs(vs, GL20.GL_VERTEX_SHADER, "strip.vert");
final String esFs = SpirvShaderTranslator.glslToGlslEs(fs, GL20.GL_FRAGMENT_SHADER, "strip.frag");
dumpOnFailure("strip.vert", vs, esVs);
dumpOnFailure("strip.frag", fs, esFs);
assertNotNull(esVs);
assertNotNull(esFs);
assertFalse(hasLayoutLocation(esVs, "v_Color"));
assertFalse(hasLayoutLocation(esVs, "v_TexCoord"));
assertFalse(hasLayoutLocation(esFs, "v_Color"));
assertFalse(hasLayoutLocation(esFs, "v_TexCoord"));
assertTrue(hasLayoutLocation(esVs, "a_Position"));
assertTrue(hasLayoutLocation(esVs, "a_Color"));
assertTrue(hasLayoutLocation(esFs, "fragColor"));
}
@Test
void varyingLocations_strippedFromGeometryShaderInAndOut() {
final String gs = """
#version 330 core
layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;
in vec4 v_Color[];
in vec2 v_TexCoord[];
out vec4 g_Color;
out vec2 g_TexCoord;
void main() {
for (int i = 0; i < 3; i++) {
gl_Position = gl_in[i].gl_Position;
g_Color = v_Color[i];
g_TexCoord = v_TexCoord[i];
EmitVertex();
""",
new String[] { "a_Position", "a_Color" }, new String[] { "v_Color", "v_TexCoord" }),
Arguments.of("varyings stripped from FS inputs, kept on MRT outputs", GL20.GL_FRAGMENT_SHADER, "strip.frag", """
#version 330 core
in vec2 v_TexCoord;
in vec4 v_Color;
uniform sampler2D u_Tex;
out vec4 fragColor;
void main() {
fragColor = v_Color * texture(u_Tex, v_TexCoord);
}
EndPrimitive();
}
""";
final String esGs = SpirvShaderTranslator.glslToGlslEs(gs, GL32.GL_GEOMETRY_SHADER, "passthrough.geom");
dumpOnFailure("passthrough.geom", gs, esGs);
assertNotNull(esGs);
assertFalse(hasLayoutLocation(esGs, "v_Color"));
assertFalse(hasLayoutLocation(esGs, "v_TexCoord"));
assertFalse(hasLayoutLocation(esGs, "g_Color"));
assertFalse(hasLayoutLocation(esGs, "g_TexCoord"));
""",
new String[] { "fragColor" }, new String[] { "v_Color", "v_TexCoord" }),
Arguments.of("varyings stripped from geometry shader inputs and outputs", GL32.GL_GEOMETRY_SHADER, "passthrough.geom", """
#version 330 core
layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;
in vec4 v_Color[];
in vec2 v_TexCoord[];
out vec4 g_Color;
out vec2 g_TexCoord;
void main() {
for (int i = 0; i < 3; i++) {
gl_Position = gl_in[i].gl_Position;
g_Color = v_Color[i];
g_TexCoord = v_TexCoord[i];
EmitVertex();
}
EndPrimitive();
}
""",
new String[0], new String[] { "v_Color", "v_TexCoord", "g_Color", "g_TexCoord" }),
Arguments.of("VS inputs keep explicit layouts and strip auto-assigned ones", GL20.GL_VERTEX_SHADER, "gbuffers_terrain.vert", """
#version 330 core
layout(location = 0) in vec4 iris_Vertex;
layout(location = 1) in vec4 iris_Color;
layout(location = 2) in vec4 iris_MultiTexCoord0;
layout(location = 3) in vec4 iris_MultiTexCoord1;
layout(location = 4) in vec3 iris_Normal;
in vec4 mc_Entity;
in vec4 mc_midTexCoord;
in vec4 at_midBlock;
uniform mat4 iris_MVP;
out vec4 v_Color;
void main() {
v_Color = iris_Color + mc_Entity + at_midBlock + mc_midTexCoord;
gl_Position = iris_MVP * iris_Vertex;
}
""",
new String[] { "iris_Vertex", "iris_Color", "iris_MultiTexCoord0", "iris_MultiTexCoord1", "iris_Normal" }, new String[] { "mc_Entity", "mc_midTexCoord", "at_midBlock" }));
}
@Test
void vsInputs_preserveExplicitLayouts_stripAutoAssignedOnes() {
final String vs = """
#version 330 core
layout(location = 0) in vec4 iris_Vertex;
layout(location = 1) in vec4 iris_Color;
layout(location = 2) in vec4 iris_MultiTexCoord0;
layout(location = 3) in vec4 iris_MultiTexCoord1;
layout(location = 4) in vec3 iris_Normal;
in vec4 mc_Entity;
in vec4 mc_midTexCoord;
in vec4 at_midBlock;
uniform mat4 iris_MVP;
out vec4 v_Color;
void main() {
v_Color = iris_Color + mc_Entity + at_midBlock + mc_midTexCoord;
gl_Position = iris_MVP * iris_Vertex;
}
""";
final String es = SpirvShaderTranslator.glslToGlslEs(vs, GL20.GL_VERTEX_SHADER, "gbuffers_terrain.vert");
dumpOnFailure("gbuffers_terrain.vert", vs, es);
@ParameterizedTest(name = "{0}")
@MethodSource
void varyingLocations(String label, int stage, String file, String src, String[] withLocation, String[] withoutLocation) {
final String es = SpirvShaderTranslator.glslToGlslEs(src, stage, file);
dumpOnFailure(file, src, es);
assertNotNull(es);
// Explicit iris_* layouts preserved.
assertTrue(hasLayoutLocation(es, "iris_Vertex"), "iris_Vertex must keep its explicit layout");
assertTrue(hasLayoutLocation(es, "iris_Color"), "iris_Color must keep its explicit layout");
assertTrue(hasLayoutLocation(es, "iris_MultiTexCoord0"), "iris_MultiTexCoord0 must keep its explicit layout");
assertTrue(hasLayoutLocation(es, "iris_MultiTexCoord1"), "iris_MultiTexCoord1 must keep its explicit layout");
assertTrue(hasLayoutLocation(es, "iris_Normal"), "iris_Normal must keep its explicit layout");
assertFalse(hasLayoutLocation(es, "mc_Entity"), "mc_Entity layout must be stripped");
assertFalse(hasLayoutLocation(es, "mc_midTexCoord"), "mc_midTexCoord layout must be stripped");
assertFalse(hasLayoutLocation(es, "at_midBlock"), "at_midBlock layout must be stripped");
for (String name : withLocation) assertTrue(hasLayoutLocation(es, name), name + " must keep its layout");
for (String name : withoutLocation) assertFalse(hasLayoutLocation(es, name), name + " layout must be stripped");
}
private static boolean hasLayoutLocation(String source, String name) {
@@ -282,98 +251,6 @@ class SpirvShaderTranslatorTest {
return p.matcher(source).find();
}
@Test
void vulkanReservedSamplerAsIdentifier_isRenamedBeforeShaderc() {
final String src = """
#version 330 core
uniform sampler2D sampler;
in vec2 texCoord;
out vec4 fragColor;
void main() {
fragColor = texture(sampler, texCoord);
}
""";
final String es = SpirvShaderTranslator.glslToGlslEs(src, GL20.GL_FRAGMENT_SHADER, "fontFilter.frag");
dumpOnFailure("fontFilter.frag", src, es);
assertNotNull(es);
assertTrue(es.contains("angelica_sampler_renamed"));
assertTrue(es.contains("sampler2D"), "sampler2D type must not be touched by the rename");
}
@Test
void cppKeywordsAsIdentifiers_areRenamed() {
final String src = """
#version 330 core
in vec2 texCoord;
out vec4 fragColor;
void main() {
vec4 try = vec4(texCoord, 0.0, 1.0);
float catch = try.x;
bvec2 flipped = not(lessThan(texCoord, vec2(0.5)));
fragColor = flipped.x ? try : vec4(catch);
}
""";
final String es = SpirvShaderTranslator.glslToGlslEs(src, GL20.GL_FRAGMENT_SHADER, "reserved.frag");
dumpOnFailure("reserved.frag", src, es);
assertNotNull(es);
assertTrue(es.contains("angelica_renamed_try"), es);
assertTrue(es.contains("angelica_renamed_catch"), es);
assertTrue(es.contains("not("), "the not() builtin must not be touched by the rename\n" + es);
assertFalse(es.contains("angelica_renamed_not"), es);
}
@Test
void uniformBool_multiDeclarator_allRestored() {
final String src = """
#version 330 core
uniform bool a, b, c;
in vec4 color;
out vec4 fragColor;
void main() {
float alpha = 1.0;
if (a) alpha *= 0.9;
if (b) alpha *= 0.8;
if (c) alpha *= 0.7;
fragColor = vec4(color.rgb, color.a * alpha);
}
""";
final String es = SpirvShaderTranslator.glslToGlslEs(src, GL20.GL_FRAGMENT_SHADER, "multi_bool.frag");
dumpOnFailure("multi_bool.frag", src, es);
assertNotNull(es);
assertContainsDecl(es, "bool", "a");
assertContainsDecl(es, "bool", "b");
assertContainsDecl(es, "bool", "c");
assertFalse(es.contains("uniform uint") || es.contains("uniform highp uint") || es.contains("uniform mediump uint"),
"no bool uniform should leak through as uint");
}
@Test
void uniformBool_compoundExpression_bothBranchesRewritten() {
final String src = """
#version 330 core
uniform bool a;
uniform bool b;
in vec4 color;
out vec4 fragColor;
void main() {
vec4 c = color;
if (a && b) { c.a *= 0.5; }
fragColor = c;
}
""";
final String es = SpirvShaderTranslator.glslToGlslEs(src, GL20.GL_FRAGMENT_SHADER, "compound_bool.frag");
dumpOnFailure("compound_bool.frag", src, es);
assertNotNull(es);
assertFalse(es.contains("!= 0u"), "no bool use site should still compare against 0u");
assertFalse(es.contains("uniform uint"), "bool uniforms must not leak as uint");
}
@Test
void uniformBool_throughDefaultBlock_restoresWithoutLeakingMemberSyntax() {
final String src = """
@@ -424,6 +301,10 @@ class SpirvShaderTranslatorTest {
assertFalse(es.contains("GL_EXT_samplerless_texture_functions"), "the samplerless extension is an SDL_GPU concern and is not valid to inject here\n\n" + es);
}
private static String compact(String s) {
return s.replaceAll("\\s+", "");
}
private static void assertContainsDecl(String source, String type, String name) {
final String needle1 = "uniform " + type + " " + name;
final String needle2 = "uniform highp " + type + " " + name;
@@ -1,82 +1,69 @@
package com.gtnewhorizons.angelica.glsm.shader;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
class UserFunctionRenameTest {
@Test
void definitionForwardDeclarationAndCallsAreRenamed() {
final String out = GlslVulkanPreprocess.renameUserFunctions("""
#version 330 core
float fade(float t);
out vec4 color;
void main() { color = vec4(fade(0.5)); }
float fade(float t) { return t * t; }
""");
assertEquals("""
#version 330 core
float angelica_renamed_fade(float t);
out vec4 color;
void main() { color = vec4(angelica_renamed_fade(0.5)); }
float angelica_renamed_fade(float t) { return t * t; }
""", out);
static Stream<Arguments> renames() {
return Stream.of(
Arguments.of("definition, forward declaration and calls are renamed", """
#version 330 core
float fade(float t);
out vec4 color;
void main() { color = vec4(fade(0.5)); }
float fade(float t) { return t * t; }
""", """
#version 330 core
float angelica_renamed_fade(float t);
out vec4 color;
void main() { color = vec4(angelica_renamed_fade(0.5)); }
float angelica_renamed_fade(float t) { return t * t; }
"""),
Arguments.of("overloads of one name are renamed together", """
#version 330 core
out vec4 color;
float clamp(float min, float f, float max) { return f; }
vec2 clamp(vec2 f) { return vec2(clamp(0.0, f.x, 1.0), clamp(0.0, f.y, 1.0)); }
void main() { color = vec4(clamp(vec2(0.5)), 0.0, 1.0); }
""", """
#version 330 core
out vec4 color;
float angelica_renamed_clamp(float min, float f, float max) { return f; }
vec2 angelica_renamed_clamp(vec2 f) { return vec2(angelica_renamed_clamp(0.0, f.x, 1.0), angelica_renamed_clamp(0.0, f.y, 1.0)); }
void main() { color = vec4(angelica_renamed_clamp(vec2(0.5)), 0.0, 1.0); }
"""),
Arguments.of("name used as macro argument is left alone", """
#version 330 core
#define SAT(fn, x) fn(0.0, x, 1.0)
out vec4 color;
float clamp(float min, float f, float max) { return f; }
void main() { color = vec4(SAT(clamp, 0.5)); }
""", null),
Arguments.of("name used in macro body is left alone", """
#version 330 core
#define FADE(x) fade(x)
out vec4 color;
float fade(float t) { return t * t; }
void main() { color = vec4(FADE(0.5)); }
""", null),
Arguments.of("name shared with a variable is left alone", """
#version 330 core
out vec4 color;
float fade(float t) { return t * t; }
void main() { float fade = 2.0; color = vec4(fade); }
""", null),
Arguments.of("shader with only main has nothing to rename", "#version 330 core\nout vec4 color;\nvoid main() { color = vec4(1.0); }\n", null));
}
@Test
void overloadsOfOneNameAreRenamedTogether() {
final String out = GlslVulkanPreprocess.renameUserFunctions("""
#version 330 core
out vec4 color;
float clamp(float min, float f, float max) { return f; }
vec2 clamp(vec2 f) { return vec2(clamp(0.0, f.x, 1.0), clamp(0.0, f.y, 1.0)); }
void main() { color = vec4(clamp(vec2(0.5)), 0.0, 1.0); }
""");
assertEquals("""
#version 330 core
out vec4 color;
float angelica_renamed_clamp(float min, float f, float max) { return f; }
vec2 angelica_renamed_clamp(vec2 f) { return vec2(angelica_renamed_clamp(0.0, f.x, 1.0), angelica_renamed_clamp(0.0, f.y, 1.0)); }
void main() { color = vec4(angelica_renamed_clamp(vec2(0.5)), 0.0, 1.0); }
""", out);
}
@Test
void nameUsedAsMacroArgumentIsLeftAlone() {
assertNull(GlslVulkanPreprocess.renameUserFunctions("""
#version 330 core
#define SAT(fn, x) fn(0.0, x, 1.0)
out vec4 color;
float clamp(float min, float f, float max) { return f; }
void main() { color = vec4(SAT(clamp, 0.5)); }
"""));
}
@Test
void nameUsedInMacroBodyIsLeftAlone() {
assertNull(GlslVulkanPreprocess.renameUserFunctions("""
#version 330 core
#define FADE(x) fade(x)
out vec4 color;
float fade(float t) { return t * t; }
void main() { color = vec4(FADE(0.5)); }
"""));
}
@Test
void nameSharedWithVariableIsLeftAlone() {
assertNull(GlslVulkanPreprocess.renameUserFunctions("""
#version 330 core
out vec4 color;
float fade(float t) { return t * t; }
void main() { float fade = 2.0; color = vec4(fade); }
"""));
}
@Test
void shaderWithOnlyMainHasNothingToRename() {
assertNull(GlslVulkanPreprocess.renameUserFunctions("#version 330 core\nout vec4 color;\nvoid main() { color = vec4(1.0); }\n"));
@ParameterizedTest(name = "{0}")
@MethodSource
void renames(String label, String source, String expected) {
assertEquals(expected, GlslVulkanPreprocess.renameUserFunctions(source));
}
}
@@ -30,18 +30,6 @@ class FenceQueueTest {
assertTrue(q.isEmpty());
}
@Test
void firstBytesTracksFirstId() {
final FenceQueue q = new FenceQueue();
q.enqueue(100L, 7);
q.enqueue(200L, 9);
assertEquals(100L, q.firstId());
assertEquals(7, q.firstBytes());
q.dequeue();
assertEquals(200L, q.firstId());
assertEquals(9, q.firstBytes());
}
@Test
void drainAndRefillKeepsBackingArray() {
final FenceQueue q = new FenceQueue();
@@ -53,26 +53,6 @@ class StreamingBufferTest {
}
}
@Test
void persistent_uploadAlignment() {
Assumptions.assumeTrue(RenderSystem.supportsBufferStorage(), "GL4.4+ required");
PersistentStreamingBuffer buf = PersistentStreamingBuffer.createOrNull(4096);
assertNotNull(buf);
try {
ByteBuffer data = memAlloc(128);
fillPattern(data);
data.flip();
int firstVertex = buf.upload(data, 32);
assertTrue(firstVertex >= 0, "Upload should succeed");
assertEquals(0, firstVertex % 4, "firstVertex must be quad-aligned");
memFree(data);
} finally {
buf.destroy();
}
}
@Test
void persistent_uploadAlignment_nonPow2Stride() {
Assumptions.assumeTrue(RenderSystem.supportsBufferStorage(), "GL4.4+ required");
@@ -142,7 +122,7 @@ class StreamingBufferTest {
data1.flip();
int v1 = buf.upload(data1, stride);
assertTrue(v1 >= 0);
buf.postDraw();
buf.endFrame();
memFree(data1);
// Wait for GPU to finish so fence is signaled
@@ -162,7 +142,7 @@ class StreamingBufferTest {
assertEquals((byte) ((i + 100) & 0xFF), mapped.get(offset2 + i), "Mismatch at byte " + i);
}
buf.postDraw();
buf.endFrame();
memFree(data2);
} finally {
buf.destroy();
@@ -182,14 +162,14 @@ class StreamingBufferTest {
buf.upload(data, 32);
assertEquals(4096 - 1024, buf.getRemaining(), "upload consumes its bytes");
buf.postDraw();
buf.endFrame();
data.position(0).limit(512);
buf.upload(data, 32);
buf.postDraw();
buf.endFrame();
GL11.glFinish();
buf.postDraw();
buf.endFrame();
assertEquals(4096, buf.getRemaining(), "all fenced regions reclaimed after completion");
memFree(data);
} finally {
@@ -224,7 +204,25 @@ class StreamingBufferTest {
}
@Test
void persistent_exhaustionReclaimsPendingWithoutPostDraw() {
void persistent_thresholdFenceExcludesNewestReservation() {
Assumptions.assumeTrue(RenderSystem.supportsBufferStorage(), "GL4.4+ required");
final GlStreamingRing ring = GlStreamingRing.create(GL15.GL_ARRAY_BUFFER, 1024);
assertNotNull(ring);
try {
assertEquals(0, ring.reserve(256, 16));
assertEquals(0, ring.fencesIssued(), "the caller has not drawn the reserved bytes yet");
assertEquals(256, ring.reserve(16, 16));
assertEquals(1, ring.fencesIssued(), "the next reservation fences earlier draws");
ring.endFrame();
assertEquals(2, ring.fencesIssued(), "the newest reservation still needs a fence after its draw");
} finally {
ring.destroy();
}
}
@Test
void persistent_exhaustionReclaimsPendingWithoutEndFrame() {
Assumptions.assumeTrue(RenderSystem.supportsBufferStorage(), "GL4.4+ required");
PersistentStreamingBuffer buf = PersistentStreamingBuffer.createOrNull(1024);
@@ -238,7 +236,7 @@ class StreamingBufferTest {
data.position(0).limit(512);
assertTrue(buf.upload(data, 32) >= 0);
data.position(0).limit(512);
assertTrue(buf.upload(data, 32) >= 0, "ring must self-fence and reclaim without postDraw");
assertTrue(buf.upload(data, 32) >= 0, "ring must self-fence and reclaim without endFrame");
memFree(data);
} finally {
buf.destroy();
@@ -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) {
@@ -224,11 +228,15 @@ public final class FfpFixture {
assertTrue(coveredPixels > base.length / 8, "base draw covered only " + coveredPixels + " of " + base.length + " pixels");
}
public static void activateFfp() {
ShaderManager.enable();
ShaderManager.getInstance().activate();
}
public static void resetFfpState() {
GLStateManager.setFfpInstancing(Instancing.NONE);
final ShaderManager sm = ShaderManager.getInstance();
if (sm.isActive()) sm.deactivate();
ShaderManager.disable();
GLStateManager.glBindVertexArray(0);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
@@ -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) {
+1 -1
View File
@@ -12,7 +12,7 @@ lwjgl3 = "3.4.2"
lwjgl3Release = "3.4.2"
lwjgl3ify = "3.0.31"
celeritas = "2.5.12-GTNH"
celeritas = "2.5.13-GTNH"
gtnhlib = "0.11.36"
eventbus = "7.0-beta.15"
glslTransformLib = "0.2.0-33.g3ec381e"
@@ -747,6 +747,21 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL30C.glFramebufferTexture2D(target, attachment, textarget, texture, level);
}
@Override
public void framebufferTexture1D(int target, int attachment, int textarget, int texture, int level) {
GL30C.glFramebufferTexture1D(target, attachment, textarget, texture, level);
}
@Override
public void framebufferTexture3D(int target, int attachment, int textarget, int texture, int level, int zoffset) {
GL30C.glFramebufferTexture3D(target, attachment, textarget, texture, level, zoffset);
}
@Override
public void framebufferTextureLayer(int target, int attachment, int texture, int level, int layer) {
GL30C.glFramebufferTextureLayer(target, attachment, texture, level, layer);
}
@Override
public void framebufferTexture(int target, int attachment, int texture, int level) {
GL32C.glFramebufferTexture(target, attachment, texture, level);
+2 -2
View File
@@ -8,7 +8,7 @@ import sys
CSVEXPORT = os.environ.get("TRACY_CSVEXPORT", os.path.expanduser("~/dev/mc/tracy/csvexport/build/tracy-csvexport"))
PLOTS = [
"gl.drawCalls", "gl.listPlaybacks", "gl.streamDraws", "entitiesRendered", "blockEntitiesRendered", "tesr.modelParts", "tesr.retainedDraws", "tesr.cacheMisses", "tesr.instancedDraws", "tesr.instancedInstances", "tesr.cubeInstances",
"gl.drawCalls", "gl.listPlaybacks", "gl.streamDraws", "gl.allRingFences", "entitiesRendered", "blockEntitiesRendered", "tesr.modelParts", "tesr.retainedDraws", "tesr.cacheMisses", "tesr.instancedDraws", "tesr.instancedInstances", "tesr.cubeInstances",
"tesr.streamedInstances", "tesr.texMatrixRuns", "tesr.liveFallbacks", "tesr.bail.foreignProgram",
"tesr.bail.material", "tesr.bail.texture", "entity.shadowQuads", "entity.shadowDraws", "items.instanced", "items.fallback", "items.glintInstanced",
"items.bail.ineligible", "items.bail.material", "items.bail.isbrh", "items.bail.blockState", "items.bail.notAllowed", "items.bail.template", "items.bail.queue",
@@ -16,7 +16,7 @@ PLOTS = [
"sdl.acquireWaitUs",
"weather.strength", "weather.quads", "weather.drawCalls", "weather.micros",
]
ZONES = ["entityModelParts", "tesrBatch", "tesrInstanced", "entityLayerLoop", "tesrOpaque", "particlePass", "cl:weather"]
ZONES = ["entityModelParts", "tesrBatch", "entityLayerLoop", "entityDeferred", "tesrOpaque", "particlePass", "cl:weather", "streamingEndFrame"]
CLOCK = "gl.drawCalls"
@@ -0,0 +1,58 @@
# OpenComputers TESR scene, close ring. ~ is the flight start; ground-standing level is ~-2. Yaw 0 faces south (+z).
# Use with FLYBY_ORIGIN=x,z,-21 SMOKE_PITCH=30.
# setup
gamerule doMobSpawning false
gamerule doFireTick false
difficulty 1
# northwest corner: test ISBRH
setblock ~-7 ~-2 ~-7 angelica:test_isbrh
# south: geolyzers
setblock ~-1 ~-2 ~4 OpenComputers:geolyzer
setblock ~0 ~-2 ~4 OpenComputers:geolyzer
setblock ~1 ~-2 ~4 OpenComputers:geolyzer
setblock ~0 ~-2 ~5 OpenComputers:geolyzer
# southwest: power distributor, assembler, capacitor
setblock ~-3 ~-2 ~3 OpenComputers:powerDistributor
setblock ~-3 ~-1 ~3 OpenComputers:assembler
setblock ~-4 ~-2 ~3 OpenComputers:capacitor
# west: relay, transposer, adapter
setblock ~-4 ~-2 ~0 OpenComputers:relay
setblock ~-4 ~-2 ~1 OpenComputers:transposer
setblock ~-4 ~-1 ~1 OpenComputers:adapter
# northwest: powered chargers
setblock ~-3 ~-2 ~-3 OpenComputers:charger
setblock ~-3 ~-2 ~-4 minecraft:redstone_block
setblock ~-4 ~-2 ~-2 OpenComputers:charger
setblock ~-4 ~-2 ~-3 minecraft:redstone_block
# north: microcontrollers, raids
setblock ~-1 ~-2 ~-4 OpenComputers:microcontroller
setblock ~0 ~-2 ~-4 OpenComputers:microcontroller
setblock ~1 ~-2 ~-4 OpenComputers:raid
setblock ~-1 ~-1 ~-4 OpenComputers:microcontroller
setblock ~0 ~-1 ~-4 OpenComputers:raid
setblock ~1 ~-1 ~-4 OpenComputers:raid
# northeast: cases, disk drive
setblock ~3 ~-2 ~-3 OpenComputers:case1
setblock ~4 ~-2 ~-3 OpenComputers:case2
setblock ~3 ~-1 ~-3 OpenComputers:case3
setblock ~4 ~-1 ~-3 OpenComputers:diskDrive
# east: rack, screens
setblock ~4 ~-2 ~-2 OpenComputers:rack
setblock ~4 ~-2 ~-1 OpenComputers:screen2
setblock ~4 ~-1 ~-1 OpenComputers:screen2
setblock ~4 ~-2 ~1 OpenComputers:screen1
# southeast: hologram projectors, disassembler, net splitter
setblock ~2 ~-2 ~3 OpenComputers:hologram1
setblock ~3 ~-2 ~2 OpenComputers:hologram2
setblock ~3 ~-2 ~3 OpenComputers:disassembler
setblock ~3 ~-1 ~3 OpenComputers:netSplitter
@@ -0,0 +1,67 @@
# OpenComputers TESR perf scene: running machines and a 12x12 overlay grid. ~ is the flight start; ground-standing level is ~-2. Yaw 0 faces south (+z).
# Use with FLYBY_ORIGIN=x,z,-21 SMOKE_PITCH=20 SMOKE_WARMUP_TICKS=400.
define BUFFER_3X2 {width:24,height:8,depth:1,buffer:["ANGELICA TESR FLYBY TEST","RED GREEN BLUE YELLOW ok","0123456789ABCDEF01234567","========================","abcdefghijklmnopqrstuvwx","The quick brown fox jump","+-*/=<>#%&!?.+-*/=<>#%&!","screen1 3x2 16 color pal"],color:[11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,3599,3599,3599,2063,1295,1295,1295,1295,1295,2063,783,783,783,783,2063,1039,1039,1039,1039,1039,1039,2063,2063,2063,7,263,519,775,1031,1287,1543,1807,2055,2311,2567,2823,3079,3335,3591,3847,7,263,519,775,1031,1287,1543,1807,3840,3840,3841,3842,3842,3843,3844,3844,3845,3846,3846,3847,3848,3848,3849,3850,3850,3851,3852,3852,3853,3854,3854,3855,1039,1039,1039,1039,1039,1039,1039,1039,1039,1039,1039,1039,1039,1039,1039,1039,1039,1039,1039,1039,1039,1039,1039,1039,1295,1295,1295,1295,1295,1295,1295,1295,1295,1295,1295,1295,1295,1295,1295,1295,1295,1295,1295,1295,1295,1295,1295,1295,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,266,266,266,266,266,266,266,266,266,266,266,266,266,266,266,266,266,266,266,266,266,266,266,266]}
define BUFFER_1X1 {width:8,height:4,depth:0,buffer:["ONE BIT.","01234567","abcdefgh","+-*/=<>#"],color:[256,256,256,256,256,256,256,256,1,1,1,1,1,1,1,1,256,256,256,256,256,256,256,256,256,1,256,1,256,1,256,1]}
define EEPROM {id:"OpenComputers:eeprom",Count:1b,Damage:0s,tag:{oc;data:{oc;eeprom#:"local c,k=component,computer local function f(t) return (c.list(t)()) end local h,m,r,g=f('hologram'),f('modem'),f('robot'),f('debug') local function s(t) if g then pcall(c.invoke,g,'runCommand','say flyby-oc '..t) end end s('boot hologram='..tostring(h)..' modem='..tostring(m)..' robot='..tostring(r)) if h then s('hologram '..tostring(pcall(function() c.invoke(h,'clear') local n=1 if c.invoke(h,'maxDepth')==2 then n=3 end for x=1,48 do for z=1,48 do local a,b=x-24.5,z-24.5 local d=math.sqrt(a*a+b*b) if d<23 then c.invoke(h,'fill',x,z,1,math.floor(math.sqrt(529-d*d))+1,math.floor(d/4)%n+1) end end end end))) end while true do if m then pcall(c.invoke,m,'broadcast',1,'flyby') end if r then pcall(c.invoke,r,'turn',true) end k.pullSignal(0.5) end",oc;label:"flyby",oc;readonly:1b}}}
# setup
gamerule doMobSpawning false
gamerule doFireTick false
difficulty 1
# held screen: up-facing screens draw the up indicator
clear @p
give @p OpenComputers:screen1 1
# south: test ISBRH
setblock ~0 ~-2 ~5 angelica:test_isbrh
# southwest: up-facing screen on a creative case
setblock ~-3 ~-2 ~4 OpenComputers:caseCreative
tile ~-3 ~-2 ~3 OpenComputers:screen1 0 {oc;pitch:1,buffer:${BUFFER_1X1}}
# north: creative case, 3x2 16-color screen, 1x1 mono screen (every wall block carries the buffer, the merge keeps the origin's)
setblock ~2 ~-2 ~-5 OpenComputers:caseCreative
setblock ~-1 ~-2 ~-5 OpenComputers:screen1 0 replace {buffer:${BUFFER_3X2}}
setblock ~0 ~-2 ~-5 OpenComputers:screen1 0 replace {buffer:${BUFFER_3X2}}
setblock ~1 ~-2 ~-5 OpenComputers:screen1 0 replace {buffer:${BUFFER_3X2}}
setblock ~-1 ~-1 ~-5 OpenComputers:screen1 0 replace {buffer:${BUFFER_3X2}}
setblock ~0 ~-1 ~-5 OpenComputers:screen1 0 replace {buffer:${BUFFER_3X2}}
setblock ~1 ~-1 ~-5 OpenComputers:screen1 0 replace {buffer:${BUFFER_3X2}}
setblock ~3 ~-2 ~-5 OpenComputers:screen1 0 replace {buffer:${BUFFER_1X1}}
# west: powered case, distributor, capacitor, chargers
setblock ~-4 ~-2 ~0 OpenComputers:caseCreative
setblock ~-4 ~-1 ~0 OpenComputers:powerDistributor
setblock ~-4 ~-2 ~1 OpenComputers:capacitor
setblock ~-4 ~-2 ~-1 OpenComputers:charger
setblock ~-5 ~-2 ~-1 minecraft:redstone_block
setblock ~-4 ~-2 ~2 OpenComputers:charger
setblock ~-5 ~-2 ~2 minecraft:redstone_block
# east: running computer, tier 2 projector on top, relay
tile ~5 ~-2 ~0 OpenComputers:caseCreative 0 {oc;tier:3b,oc;yaw:4,oc;items:[{slot:0b,item:{id:"OpenComputers:item",Count:1b,Damage:11s}},{slot:1b,item:{id:"OpenComputers:item",Count:1b,Damage:73s}},{slot:3b,item:{id:"OpenComputers:item",Count:1b,Damage:39s}},{slot:8b,item:{id:"OpenComputers:item",Count:1b,Damage:43s}},{slot:9b,item:${EEPROM}}]}
tile ~5 ~-1 ~0 OpenComputers:hologram2 0 {oc;tier:1b,oc;scale:1.0d}
setblock ~5 ~-2 ~1 OpenComputers:relay
after 30 activate ~5 ~-2 ~0 sneak
# far southeast: same computer, projector at scale 4 in the hologram fade band
tile ~40 ~-2 ~40 OpenComputers:caseCreative 0 {oc;tier:3b,oc;yaw:4,oc;items:[{slot:1b,item:{id:"OpenComputers:item",Count:1b,Damage:73s}},{slot:3b,item:{id:"OpenComputers:item",Count:1b,Damage:39s}},{slot:8b,item:{id:"OpenComputers:item",Count:1b,Damage:43s}},{slot:9b,item:${EEPROM}}]}
tile ~40 ~-1 ~40 OpenComputers:hologram2 0 {oc;tier:1b,oc;scale:4.0d}
after 30 activate ~40 ~-2 ~40 sneak
# southeast: empty tier 1 and 2 projectors
setblock ~3 ~-3 ~2 OpenComputers:caseCreative
setblock ~3 ~-2 ~2 OpenComputers:hologram1
setblock ~3 ~-3 ~4 OpenComputers:caseCreative
setblock ~3 ~-2 ~4 OpenComputers:hologram2
# northwest: turning robot
tile ~-4 ~-2 ~-4 OpenComputers:robot 0 {display:{Name:"Flyby"},oc;tier:3,oc;components:[{id:"OpenComputers:item",Count:1b,Damage:73s},{id:"OpenComputers:item",Count:1b,Damage:43s},{id:"OpenComputers:item",Count:1b,Damage:39s},${EEPROM}]}
after 30 activate ~-4 ~-2 ~-4 sneak
# south: 12x12 overlay grid, one 12x4 band per block
fill ~-6 ~-2 ~9 ~5 ~-2 ~12 OpenComputers:geolyzer
fill ~-6 ~-2 ~13 ~5 ~-2 ~16 OpenComputers:microcontroller
fill ~-6 ~-2 ~17 ~5 ~-2 ~20 OpenComputers:raid
Loaded 100 of 326 files, more files were not shown because too many files have changed in this diff. Show more