Compare commits

...
23 Commits
Author SHA1 Message Date
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
Jason Mitchell 32f8eef15b Fixes a startup CME when BLS (#2274) 2026-10-18 20:40:14 -07:00
Eclipse-Sol 2d9d1a45b7 initial commit for entity-atlases (#2272) 2026-10-18 19:31:16 -07:00
Jason Mitchell fa8842a64b Fix birch and spruce leaves showing the oak biome color with fancy biome blending (#2273) 2026-10-18 17:06:35 -07:00
gorbit99 69019b9a62 Implement metadata checking into the NBT path of block.properties parsing (#2267) 2026-10-17 18:39:12 -07:00
Jason Mitchell 482dece0f7 Expand SDL-GPU's GLSM redirector to GLSM and remove unredirected GL workarounds and move action back to master (#2268) 2026-10-17 17:58:50 -07:00
Eclipse-Sol ab3bf17696 Set entity, block entity and item IDs per vertex (#2265) 2026-10-16 21:47:40 -07:00
Eclipse-Sol cfc6e12e00 Minor Compute Cull Optimizations (#2263) 2026-10-16 21:34:59 -07:00
Eclipse-Sol 8537835d0c Fix Player Capture for EP in SDL-GPU (#2262) 2026-10-16 21:34:16 -07:00
Jason Mitchell 8e1809622c Stop resetting SDL GPU to OpenGL on unrelated crashes (#2266) 2026-10-16 21:33:06 -07:00
HariboApfel f81ee4b03a fixed some osx metal compability with Distant horizon 1.7.10 backport (#2261) 2026-10-15 09:59:28 -07:00
308 changed files with 15934 additions and 6675 deletions

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@ on:
jobs:
build-and-test:
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@05e7cd7ce0ea015acda8c10c1c39fb3a20a49e60
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@master
with:
disable-spotless-pr: true
java-distribution: temurin
+8
View File
@@ -170,7 +170,13 @@ val archScanAsm: Configuration by configurations.creating {
isCanBeResolved = true
}
val glsmRedirectAgent: Configuration by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
dependencies {
glsmRedirectAgent(project(":glsm", "redirectAgent"))
archScanAsm(libs.asm.tree.test)
archScanAsm(libs.asm.commons.test)
}
@@ -212,6 +218,8 @@ val glCoreTest by tasks.registering(Test::class) {
.plus(downgraded(downgradeRootMainClasses))
.plus(sourceSets["test"].runtimeClasspath.minus(rawMain).minus(rawTest))
javaAgent(glsmRedirectAgent)
configureAngelicaJava8()
}
+8
View File
@@ -0,0 +1,8 @@
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.testing.Test
import org.gradle.process.CommandLineArgumentProvider
fun Test.javaAgent(jar: FileCollection) {
inputs.files(jar).withPropertyName("javaAgent")
jvmArgumentProviders.add(CommandLineArgumentProvider { listOf("-javaagent:${jar.singleFile.path}") })
}
+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")
+17
View File
@@ -267,6 +267,22 @@ tasks.test {
configureGlsmJava8()
}
val redirectAgentJar by tasks.registering(Jar::class) {
archiveClassifier = "redirect-agent"
manifest {
attributes("Premain-Class" to "com.gtnewhorizons.angelica.glsm.GlsmRedirectAgent")
}
}
val redirectAgent by configurations.creating {
isCanBeConsumed = true
isCanBeResolved = false
}
artifacts {
add(redirectAgent.name, redirectAgentJar)
}
val glTestTasks = GL_TASK_TAGS.map { (taskName, tag) ->
tasks.register<Test>(taskName) {
description = "Runs the $tag GL tests in their own JVM, so they get their own Display and GL profile."
@@ -275,6 +291,7 @@ val glTestTasks = GL_TASK_TAGS.map { (taskName, tag) ->
includeTags = setOf(tag)
}
configureGlsmJava8()
javaAgent(files(redirectAgentJar))
}
}
@@ -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;
@@ -34,6 +35,7 @@ import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
import com.gtnewhorizons.angelica.glsm.states.ImageUnitArray;
import com.gtnewhorizons.angelica.glsm.states.SamplerUnitArray;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
@@ -57,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<>();
@@ -104,6 +107,8 @@ public class GLContextState {
public boolean dirtyNormalAttrib;
public boolean dirtyTexCoordAttrib;
public boolean dirtyLightmapAttrib = true;
public long entityIds = InstancedAttribs.DEFAULT_ENTITY_IDS;
public boolean dirtyEntityIdsAttrib = true;
public boolean unit23TexCoordSetDuringDraw = false;
public int maxBoundTextureUnit = 0;
public int maxBoundImageUnit = 0;
@@ -236,6 +241,8 @@ public class GLContextState {
public int boundCopyReadBuffer;
public int boundCopyWriteBuffer;
public final Int2IntMap boundOtherBuffers = new Int2IntOpenHashMap();
public final int[] uniformBufferBindings = new int[GLStateManager.INDEXED_BUFFER_SLOTS];
public final int[] storageBufferBindings = new int[GLStateManager.INDEXED_BUFFER_SLOTS];
public PixelStoreState pixelUnpackState = PixelStoreState.DEFAULT;
public PixelStoreState pixelPackState = PixelStoreState.DEFAULT;
public final int[] clientAttribSavedMask = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
@@ -245,7 +252,6 @@ public class GLContextState {
public final int[] clientAttribSavedPackBuffer = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final VAOManager.ClientArrays[] clientAttribSavedArrays = new VAOManager.ClientArrays[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public int clientAttribStackPointer = 0;
public float maxPointSize = Float.NaN;
public int restoreChangedMask;
public long restoreUnitChangedMask;
public final BlendState vanillaBlendBefore = new BlendState();
@@ -253,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();
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
@@ -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,118 +232,151 @@ 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();
TextureInfoCache.INSTANCE.onTextureImage2D(texture, target, level, internalformat, width, height);
}
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();
TextureInfoCache.INSTANCE.onTextureImage2D(texture, target, level, internalformat, width, height);
}
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();
if (level == 0) TextureInfoCache.INSTANCE.onContentsChanged(texture);
}
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();
if (level == 0) TextureInfoCache.INSTANCE.onContentsChanged(texture);
}
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();
@@ -346,30 +387,37 @@ public class RenderSystem {
}
public static int bufferStorage(int target, FloatBuffer data, int usage) {
GLStateManager.rejectExitCapture();
return dsaState.bufferStorage(target, data, usage);
}
public static void vertexAttrib4f(int index, float v0, float v1, float v2, float v3) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.vertexAttrib4f(index, v0, v1, v2, v3);
}
public static void detachShader(int program, int shader) {
GLStateManager.rejectExitCapture();
RENDER_BACKEND.detachShader(program, shader);
}
public static void framebufferTexture2D(int fb, int fbtarget, int attachment, int target, int texture, int levels) {
GLStateManager.rejectExitCapture();
dsaState.framebufferTexture2D(fb, fbtarget, attachment, target, texture, levels);
}
public static int getTexParameteri(int texture, int target, int pname) {
GLStateManager.rejectExitCapture();
return dsaState.getTexParameteri(texture, target, pname);
}
public static float getTexParameterf(int texture, int target, int pname) {
GLStateManager.rejectExitCapture();
return dsaState.getTexParameterf(texture, target, pname);
}
public static int getTexLevelParameteri(int texture, int level, int pname) {
GLStateManager.rejectExitCapture();
return dsaState.getTexLevelParameteri(texture, level, pname);
}
@@ -453,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);
}
@@ -478,6 +537,7 @@ public class RenderSystem {
}
public static void memoryBarrier(int barriers) {
GLStateManager.rejectExitCapture();
if (supportsCompute) {
RENDER_BACKEND.memoryBarrier(barriers);
}
@@ -500,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);
}
@@ -525,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 {
@@ -535,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);
}
@@ -556,7 +621,8 @@ public class RenderSystem {
}
public static int getIndexedBufferBinding(int target, int index) {
return RENDER_BACKEND.getIndexedBufferBinding(target, index);
GLStateManager.rejectExitCapture();
return GLStateManager.getIndexedBufferBinding(target, index);
}
public static void bindBufferBase(int target, int index, int buffer) {
@@ -587,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);
}
@@ -21,7 +21,11 @@ public final class BackendOptions {
}
public static String sdlGpuDriver() {
return SystemProperties.SDL_GPU_DRIVER_OVERRIDE != null ? SystemProperties.SDL_GPU_DRIVER_OVERRIDE : configDriver;
return sdlGpuDriver(configDriver);
}
public static String sdlGpuDriver(String driver) {
return SystemProperties.SDL_GPU_DRIVER_OVERRIDE != null ? SystemProperties.SDL_GPU_DRIVER_OVERRIDE : driver;
}
public static boolean configSdlGpu() {
@@ -1,55 +1,96 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.google.common.primitives.Ints;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
public final class BackendStartGuard {
private static final Logger LOGGER = LogManager.getLogger("Angelica/BackendStartGuard");
private static File pending;
static final int STRIKE_LIMIT = 3;
private static File marker;
private static File verified;
private static String key;
private static int strikes;
private static boolean pending;
private static boolean tripped;
private BackendStartGuard() {}
public static boolean begin(File marker, boolean sdlGpuConfigured, boolean flagForced) {
pending = null;
tripped = false;
if (!sdlGpuConfigured || flagForced) {
public static boolean begin(File dir, boolean sdlGpuConfigured, boolean flagForced, int requirementsRevision, String requestedDriver) {
marker = new File(dir, "sdlgpu-starting");
verified = new File(dir, "sdlgpu-verified");
key = requirementsRevision + "/" + requestedDriver;
pending = tripped = false;
if (flagForced) return false;
if (!sdlGpuConfigured) {
delete(marker);
return false;
}
if (marker.isFile()) {
if (key.equals(read(verified))) return false;
final String count = read(marker);
final Integer parsed = count == null ? null : Ints.tryParse(count.trim());
strikes = parsed == null ? 0 : parsed;
if (strikes >= STRIKE_LIMIT) {
LOGGER.warn("SDL GPU failed to bring up the device {} times in a row; resetting the render backend to OpenGL", strikes);
delete(marker);
tripped = true;
return true;
}
try {
final File dir = marker.getParentFile();
if (dir != null && !dir.isDirectory() && !dir.mkdirs()) throw new IOException("cannot create " + dir);
if (marker.createNewFile()) pending = marker;
} catch (IOException e) {
LOGGER.warn("Could not write {}: {}", marker, e.toString());
if (strikes == STRIKE_LIMIT - 1) {
LOGGER.warn("SDL GPU failed to bring up the device {} times in a row; one more and the render backend switches to OpenGL", strikes);
} else if (strikes > 0) {
LOGGER.info("The last start ended while bringing up the SDL GPU device");
}
pending = true;
return false;
}
public static void finish() {
if (pending == null) return;
delete(pending);
pending = null;
public static void arm() {
if (pending) write(marker, Integer.toString(strikes + 1));
}
public static void disarm() {
if (pending) delete(marker);
}
public static void latch() {
if (!pending) return;
pending = false;
write(verified, key);
LOGGER.info("SDL GPU booted; the startup guard stays off until the requirements revision or driver changes");
}
public static boolean tripped() {
return tripped;
}
private static void delete(File marker) {
if (marker.isFile() && !marker.delete()) {
LOGGER.warn("Could not delete {}", marker);
private static String read(File file) {
try {
return file.isFile() ? new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8) : null;
} catch (IOException e) {
LOGGER.warn("Could not read {}: {}", file, e.toString());
return null;
}
}
private static void write(File file, String content) {
try {
Files.createDirectories(file.toPath().getParent());
Files.write(file.toPath(), content.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
LOGGER.warn("Could not write {}: {}", file, e.toString());
}
}
private static void delete(File file) {
if (file.isFile() && !file.delete()) LOGGER.warn("Could not delete {}", file);
}
}
@@ -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);
@@ -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() {
@@ -5,6 +5,7 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
public record CubeParams(float minX, float minY, float minZ, float spanX, float spanY, float spanZ, float u0, float v0, float uScaleX, float vScaleY, float uScaleZ, float vScaleZ) {
private static final float MIN_SPAN = 1.0e-4f;
private static final float UV_EPSILON = 1.0e-5f;
public CubeParams {
if (!(spanX > MIN_SPAN && spanY > MIN_SPAN && spanZ > MIN_SPAN)) throw new IllegalArgumentException("degenerate cube span");
@@ -43,12 +44,25 @@ public record CubeParams(float minX, float minY, float minZ, float spanX, float
}
}
public void writeTexture(long ptr) {
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 8, uScaleZ);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 12, vScaleZ);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX, u0);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 4, v0);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 8, uScaleX);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 12, vScaleY);
public boolean uvInsideTexture() {
final float u1 = u0 + 2.0f * (Math.abs(uScaleX) + uScaleZ);
final float v1 = v0 + vScaleY + vScaleZ;
return u0 >= 0.0f && v0 >= 0.0f && u1 <= 1.0f + UV_EPSILON && v1 <= 1.0f + UV_EPSILON;
}
public static boolean allInsideTexture(CubeParams[] cubes) {
for (CubeParams cube : cubes) {
if (!cube.uvInsideTexture()) return false;
}
return true;
}
public void writeTexture(long ptr, float regionU, float regionV, float regionW, float regionH) {
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 8, uScaleZ * regionW);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 12, vScaleZ * regionH);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX, regionU + u0 * regionW);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 4, regionV + v0 * regionH);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 8, uScaleX * regionW);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_CUBE_TEX + 12, vScaleY * regionH);
}
}
@@ -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)";
}
}
@@ -65,6 +65,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;
@@ -117,15 +127,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()) {
@@ -305,7 +305,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 +335,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");
}
@@ -25,6 +25,8 @@ public final class InstancedAttribs {
public static final int OFFSET_LIGHTMAP = HEAD_SIZE;
public static final int STRIDE = 72;
public static final long DEFAULT_ENTITY_IDS = packEntityInfo(-1, 0, -1);
public static void writeHead(long ptr, float[] colMajor, int off, int colorABGR, int overlayABGR, long entityInfo) {
writeRows(ptr, colMajor, off);
writeTail(ptr, colorABGR, overlayABGR, entityInfo);
@@ -60,13 +62,21 @@ public final class InstancedAttribs {
return id == 0xFFFF ? -1 : id;
}
public static float normalizedId(long info, int shift) {
return ((info >>> shift) & 0xFFFF) / 65535.0f;
}
public static void pointHead(long base, int stride) {
for (int r = 0; r < 3; r++) {
GLStateManager.glVertexAttribPointer(LOC_ROW0 + r, 4, GL11.GL_FLOAT, false, stride, base + OFFSET_ROWS + r * 16L);
}
GLStateManager.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_COLOR);
GLStateManager.glVertexAttribPointer(LOC_OVERLAY, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_OVERLAY);
GLStateManager.glVertexAttribIPointer(LOC_ENTITY, 4, GL11.GL_UNSIGNED_SHORT, stride, base + OFFSET_ENTITY);
pointEntityIds(LOC_ENTITY, stride, base + OFFSET_ENTITY);
}
public static void pointEntityIds(int location, int stride, long offset) {
GLStateManager.glVertexAttribPointer(location, 4, GL11.GL_UNSIGNED_SHORT, true, stride, offset);
}
public static void pointTemplate(long base) {
@@ -139,6 +139,7 @@ public final class ShaderManager {
GLSMHooks.resolvePendingProgram();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null) bh.flushDeferredBlend();
GLStateManager.flushEntityIds(glCtx);
// Handle FFP & Iris uniforms
final int currentProgramId = glCtx.activeProgram;
@@ -198,18 +199,21 @@ public final class ShaderManager {
}
public void setNormal(float x, float y, float z) {
GLStateManager.recordExitNormal();
normalStack.beforeModify();
currentNormal.set(x, y, z);
normalGeneration++;
}
public void setTexCoord(float s, float t, float r, float q) {
GLStateManager.recordExitTexCoord(0);
texCoordStack.beforeModify();
currentTexCoords[0].set(s, t, r, q);
texCoordGeneration++;
}
public void setTexCoord(int unit, float s, float t, float r, float q) {
GLStateManager.recordExitTexCoord(unit);
if (unit == 0) texCoordStack.beforeModify();
currentTexCoords[unit].set(s, t, r, q);
texCoordGeneration++;
@@ -80,9 +80,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;
@@ -233,8 +230,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 +343,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 +411,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();
}
@@ -3,15 +3,18 @@ package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
import org.lwjgl.opengl.GL11;
public interface ImmediateExtendedAttribHandler {
int EXT_STRIDE = 12;
int ENTITY_IDS_STRIDE = 8;
int LOC_MC_ENTITY = 11;
int LOC_MID_TEX = 12;
int LOC_TANGENT = 13;
int LOC_MIDBLOCK = 14;
int LOC_ENTITY_IDS = 15;
int RAW_NORMAL_INDEX = 6;
@@ -19,6 +22,8 @@ public interface ImmediateExtendedAttribHandler {
boolean wantsTangent();
boolean wantsEntityIds();
boolean wantsExtendedCapture();
boolean wantsFaceNormals();
@@ -65,4 +70,9 @@ public interface ImmediateExtendedAttribHandler {
GLStateManager.glEnableVertexAttribArray(LOC_TANGENT);
GLStateManager.glVertexAttribPointer(LOC_TANGENT, 4, GL11.GL_BYTE, true, stride, baseOffset + 8);
}
static void setupEntityIdsPointer(long offset, int stride) {
GLStateManager.glEnableVertexAttribArray(LOC_ENTITY_IDS);
InstancedAttribs.pointEntityIds(LOC_ENTITY_IDS, stride, offset);
}
}
@@ -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;
@@ -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();
}
@@ -5,12 +5,25 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
public final class StreamingVaos {
private static final int FORMAT_COUNT = VertexFlags.BITSET_SIZE;
static final int MID_TEX_AND_TANGENT = 1;
static final int ENTITY_IDS = 2;
private static final int EXTENDED_LAYOUTS = MID_TEX_AND_TANGENT | ENTITY_IDS;
final int[] persistentVAOs = new int[FORMAT_COUNT];
final int[] orphanVAOs = new int[FORMAT_COUNT];
final int[] extendedPersistentVAOs = new int[FORMAT_COUNT];
final int[] extendedOrphanVAOs = new int[FORMAT_COUNT];
// An extended draw always carries at least one layout, so row 0 is layout 1
private final int[][] extendedPersistentVAOs = new int[EXTENDED_LAYOUTS][FORMAT_COUNT];
private final int[][] extendedOrphanVAOs = new int[EXTENDED_LAYOUTS][FORMAT_COUNT];
final int[] lineQuadPersistentVAOs = new int[FORMAT_COUNT];
final int[] lineQuadOrphanVAOs = new int[FORMAT_COUNT];
int[] extendedPersistent(int layout) {
return extendedPersistentVAOs[layout - 1];
}
int[] extendedOrphan(int layout) {
return extendedOrphanVAOs[layout - 1];
}
}
@@ -0,0 +1,78 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import net.minecraft.client.renderer.Tessellator;
import java.util.Arrays;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutLong;
/**
* Entity, block entity, and item ids of the vertices in the main Tessellator batch.
*/
public final class TessellatorEntityIds {
private static Tessellator owner;
private static int runCount;
private static int[] runStarts = new int[8];
private static long[] runIds = new long[8];
private TessellatorEntityIds() {}
public static void onIdsChanged(long previous, long next) {
final Tessellator tess = Tessellator.instance;
if (tess == null || !tess.isDrawing || tess.vertexCount == 0) return;
final int start = tess.vertexCount;
if (owner != tess) {
owner = tess;
runCount = 0;
}
if (runCount == 0) addRun(0, previous);
if (runStarts[runCount - 1] == start) {
runIds[runCount - 1] = next;
if (runCount > 1 && runIds[runCount - 2] == next) runCount--;
} else {
addRun(start, next);
}
}
public static void onReset(Tessellator tess) {
if (tess != owner) return;
owner = null;
runCount = 0;
}
public static boolean hasIdChanges(Tessellator tess) {
return tess == owner && runCount > 1;
}
static int runsFor(Tessellator tess, int vertexCount) {
if (tess != owner) return 0;
int runs = runCount;
while (runs > 0 && runStarts[runs - 1] >= vertexCount) runs--;
return runs;
}
static long firstRunIds() {
return runIds[0];
}
static void write(long dst, int stride, int vertexCount, int runs) {
for (int run = 0; run < runs; run++) {
final int end = run + 1 < runs ? runStarts[run + 1] : vertexCount;
final long ids = runIds[run];
for (int vertex = runStarts[run]; vertex < end; vertex++) {
memPutLong(dst + (long) vertex * stride, ids);
}
}
}
private static void addRun(int start, long ids) {
if (runCount == runStarts.length) {
runStarts = Arrays.copyOf(runStarts, runCount * 2);
runIds = Arrays.copyOf(runIds, runCount * 2);
}
runStarts[runCount] = start;
runIds[runCount] = ids;
runCount++;
}
}
@@ -130,20 +130,34 @@ public class TessellatorStreamingDrawer {
final int extPrim = extHandler == null ? 0 : ImmediateExtendedAttribHandler.extPrimVerts(tess.drawMode, vertexCount);
final boolean wantsExt = extHandler != null && extHandler.wantsExtended();
final boolean doExt = extPrim != 0 && tess.hasTexture && wantsExt;
if (doExt) {
final int combinedStride = vertexSize + ImmediateExtendedAttribHandler.EXT_STRIDE;
final int combinedBytes = vertexCount * combinedStride;
ensureExtScratch(combinedBytes);
interleaveBase(repackAddress, extScratchAddress, vertexCount, vertexSize, combinedStride);
// format expansion backfills a normal into rawBuffer, so trust effectiveFlags rather than tess.hasNormals
final int normalIndex = (effectiveFlags & VertexFlags.NORMAL_BIT) != 0 ? ImmediateExtendedAttribHandler.RAW_NORMAL_INDEX : -1;
extHandler.build(tess.rawBuffer, vertexCount, extPrim, normalIndex, extScratchAddress + vertexSize, combinedStride);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, effectiveFlags, format, combinedStride, tess.drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
} else {
uploadAndDraw(repackBuffer, effectiveFlags, format, vertexSize, tess.drawMode, vertexCount);
final int idRuns = TessellatorEntityIds.runsFor(tess, vertexCount);
final boolean doIds = idRuns > 1 && extHandler != null && extHandler.wantsEntityIds() && !LineQuads.isLineMode(tess.drawMode);
final GLContextState swappedCtx = idRuns >= 1 && !doIds ? GLStateManager.ctx() : null;
final long restoreIds = swappedCtx != null ? swapEntityIds(swappedCtx, TessellatorEntityIds.firstRunIds()) : 0L;
try {
if (doExt || doIds) {
final int layout = (doExt ? StreamingVaos.MID_TEX_AND_TANGENT : 0) | (doIds ? StreamingVaos.ENTITY_IDS : 0);
final int combinedStride = combinedStride(format, layout);
final int combinedBytes = vertexCount * combinedStride;
ensureExtScratch(combinedBytes);
interleaveBase(repackAddress, extScratchAddress, vertexCount, vertexSize, combinedStride);
long extAddress = extScratchAddress + vertexSize;
if (doExt) {
// format expansion backfills a normal into rawBuffer, so trust effectiveFlags rather than tess.hasNormals
final int normalIndex = (effectiveFlags & VertexFlags.NORMAL_BIT) != 0 ? ImmediateExtendedAttribHandler.RAW_NORMAL_INDEX : -1;
extHandler.build(tess.rawBuffer, vertexCount, extPrim, normalIndex, extAddress, combinedStride);
extAddress += ImmediateExtendedAttribHandler.EXT_STRIDE;
}
if (doIds) TessellatorEntityIds.write(extAddress, combinedStride, vertexCount, idRuns);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, effectiveFlags, format, layout, combinedStride, tess.drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
} else {
uploadAndDraw(repackBuffer, effectiveFlags, format, vertexSize, tess.drawMode, vertexCount);
}
} finally {
if (swappedCtx != null) swapEntityIds(swappedCtx, restoreIds);
}
} finally {
if (locked) GLStateManager.releaseDrawLock();
@@ -183,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) {
@@ -253,7 +268,7 @@ public class TessellatorStreamingDrawer {
final long srcBase = memAddress0(packed) + packed.position();
final int texOffset = ImmediateExtendedAttribHandler.texOffset(format);
final int normalOffset = ImmediateExtendedAttribHandler.normalOffset(format);
final int combinedStride = vertexSize + ImmediateExtendedAttribHandler.EXT_STRIDE;
final int combinedStride = combinedStride(format, StreamingVaos.MID_TEX_AND_TANGENT);
final int combinedBytes = vertexCount * combinedStride;
final boolean locked = GLStateManager.acquireDrawLock();
try {
@@ -262,7 +277,7 @@ public class TessellatorStreamingDrawer {
extHandler.buildPacked(srcBase, vertexSize, 0, texOffset, normalOffset, vertexCount, extPrim, extScratchAddress + vertexSize, combinedStride);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, flags, format, combinedStride, drawMode, vertexCount);
uploadAndDrawExtended(extScratch, flags, format, StreamingVaos.MID_TEX_AND_TANGENT, combinedStride, drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
} finally {
if (locked) GLStateManager.releaseDrawLock();
@@ -316,7 +331,7 @@ public class TessellatorStreamingDrawer {
public static void endFrame() {
if (persistentBuffer != null) {
persistentBuffer.postDraw();
persistentBuffer.endFrame();
}
}
@@ -458,11 +473,11 @@ public class TessellatorStreamingDrawer {
}
}
private static void uploadAndDrawExtended(ByteBuffer combined, int flags, VertexFormat format, int combinedStride, int drawMode, int vertexCount) {
private static void uploadAndDrawExtended(ByteBuffer combined, int flags, VertexFormat format, int layout, int combinedStride, int drawMode, int vertexCount) {
final GLContextState glCtx = GLStateManager.ctx();
final StreamingVaos vaos = glCtx.streamingVaos;
ensureVAO(vaos.persistentVAOs, vaos.orphanVAOs, flags, format);
ensureExtendedVAOs(vaos, flags, format);
ensureExtendedVAOs(vaos, flags, format, layout);
int firstVertex = -1;
@@ -471,9 +486,9 @@ public class TessellatorStreamingDrawer {
}
if (firstVertex >= 0) {
GLStateManager.glBindVertexArray(vaos.extendedPersistentVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.extendedPersistent(layout)[flags]);
} else {
GLStateManager.glBindVertexArray(vaos.extendedOrphanVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.extendedOrphan(layout)[flags]);
orphanBuffers[flags].upload(combined);
firstVertex = 0;
}
@@ -482,14 +497,14 @@ public class TessellatorStreamingDrawer {
GLStateManager.glBindVertexArray(0);
}
private static void ensureExtendedVAOs(StreamingVaos vaos, int flags, VertexFormat format) {
final int[] extendedOrphanVAOs = vaos.extendedOrphanVAOs;
final int[] extendedPersistentVAOs = vaos.extendedPersistentVAOs;
private static void ensureExtendedVAOs(StreamingVaos vaos, int flags, VertexFormat format, int layout) {
final int[] extendedOrphanVAOs = vaos.extendedOrphan(layout);
final int[] extendedPersistentVAOs = vaos.extendedPersistent(layout);
if (extendedOrphanVAOs[flags] == 0) {
extendedOrphanVAOs[flags] = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(extendedOrphanVAOs[flags]);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, orphanBuffers[flags].getBufferId());
setupExtendedBufferState(format);
setupExtendedBufferState(format, layout);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
}
@@ -498,20 +513,42 @@ public class TessellatorStreamingDrawer {
extendedPersistentVAOs[flags] = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(extendedPersistentVAOs[flags]);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, persistentBuffer.getBufferId());
setupExtendedBufferState(format);
setupExtendedBufferState(format, layout);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
}
}
private static void setupExtendedBufferState(VertexFormat format) {
final int combinedStride = format.getVertexSize() + ImmediateExtendedAttribHandler.EXT_STRIDE;
private static long swapEntityIds(GLContextState glCtx, long ids) {
final long previous = glCtx.entityIds;
if (previous != ids) {
glCtx.entityIds = ids;
glCtx.dirtyEntityIdsAttrib = true;
}
return previous;
}
private static int combinedStride(VertexFormat format, int layout) {
int stride = format.getVertexSize();
if ((layout & StreamingVaos.MID_TEX_AND_TANGENT) != 0) stride += ImmediateExtendedAttribHandler.EXT_STRIDE;
if ((layout & StreamingVaos.ENTITY_IDS) != 0) stride += ImmediateExtendedAttribHandler.ENTITY_IDS_STRIDE;
return stride;
}
private static void setupExtendedBufferState(VertexFormat format, int layout) {
final int combinedStride = combinedStride(format, layout);
long offset = 0;
for (final VertexFormatElement element : format.elementsArray) {
element.setupBufferState(offset, combinedStride);
offset += element.getByteSize();
}
ImmediateExtendedAttribHandler.setupExtAttribPointers(offset, combinedStride);
if ((layout & StreamingVaos.MID_TEX_AND_TANGENT) != 0) {
ImmediateExtendedAttribHandler.setupExtAttribPointers(offset, combinedStride);
offset += ImmediateExtendedAttribHandler.EXT_STRIDE;
}
if ((layout & StreamingVaos.ENTITY_IDS) != 0) {
ImmediateExtendedAttribHandler.setupEntityIdsPointer(offset, combinedStride);
}
}
private static void ensureExtScratch(int requiredBytes) {
@@ -22,8 +22,8 @@ public class TextureInfo {
protected int width = -1;
protected int height = -1;
@Setter protected int minFilter = UNSET_INT;
@Setter protected int magFilter = UNSET_INT;
protected int minFilter = UNSET_INT;
protected int magFilter = UNSET_INT;
@Setter protected int wrapS = UNSET_INT;
@Setter protected int wrapT = UNSET_INT;
@Setter protected int maxLevel = UNSET_INT;
@@ -37,9 +37,12 @@ public class TextureInfo {
@Getter @Setter protected boolean generateMipmap = false;
@Getter protected int uploadGeneration;
@Getter protected boolean writtenOutsideUploads;
@Getter protected int changeStamp;
void bumpUploadGeneration() {
uploadGeneration++;
changeStamp++;
}
protected TextureInfo(int id) {
@@ -78,6 +81,16 @@ public class TextureInfo {
return height;
}
public void setMinFilter(int minFilter) {
this.minFilter = minFilter;
changeStamp++;
}
public void setMagFilter(int magFilter) {
this.magFilter = magFilter;
changeStamp++;
}
public int getMinFilter() {
if (minFilter == UNSET_INT) minFilter = queryParameteri(GL11.GL_TEXTURE_MIN_FILTER);
return minFilter;
@@ -14,7 +14,10 @@ import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
import java.nio.Buffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.IntConsumer;
public class TextureInfoCache {
/**
@@ -28,10 +31,45 @@ public class TextureInfoCache {
private final Int2ObjectMap<TextureInfo> cache = new Int2ObjectOpenHashMap<>();
private final ReentrantLock lock = new ReentrantLock();
private final List<IntConsumer> deleteListeners = new ArrayList<>();
private int deleteCount;
private TextureInfoCache() {
}
public int deleteCount() {
return deleteCount;
}
public void addDeleteListener(IntConsumer listener) {
deleteListeners.add(listener);
}
public void onContentsChanged(int texture) {
if (texture <= 0) return;
final TextureInfo info = getInfo(texture);
if (info != null) info.bumpUploadGeneration();
}
public void onWrittenOutsideUploads(int texture) {
if (texture <= 0) return;
final TextureInfo info = getInfo(texture);
if (info == null || info.writtenOutsideUploads) return;
info.writtenOutsideUploads = true;
info.changeStamp++;
}
private void defineLevelZero(int texture, int target, int internalformat, int width, int height) {
final TextureInfo info = getInfo(texture);
if (info == null) return;
info.setTarget(target);
info.internalFormat = internalformat;
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
info.bumpUploadGeneration();
}
public TextureInfo getInfo(int id) {
if (id < 0) return null;
@@ -48,48 +86,27 @@ public class TextureInfoCache {
}
public void onTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable Buffer pixels) {
if (isTrackedTarget(target) && level == 0) {
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.setTarget(target);
info.internalFormat = internalformat;
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
info.bumpUploadGeneration();
}
if (isTrackedTarget(target) && level == 0) defineLevelZero(GLStateManager.getBoundTextureForServerState(), target, internalformat, width, height);
}
public void onTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, long pixels_buffer_offset) {
if (isTrackedTarget(target) && level == 0) {
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.setTarget(target);
info.internalFormat = internalformat;
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
info.bumpUploadGeneration();
}
if (isTrackedTarget(target) && level == 0) defineLevelZero(GLStateManager.getBoundTextureForServerState(), target, internalformat, width, height);
}
public void onCopyTexImage2D(int target, int level, int internalformat, int width, int height) {
if (isTrackedTarget(target) && level == 0) defineLevelZero(GLStateManager.getBoundTextureForServerState(), target, internalformat, width, height);
}
public void onTextureImage2D(int texture, int target, int level, int internalformat, int width, int height) {
if (isTrackedTarget(target) && level == 0) defineLevelZero(texture, target, internalformat, width, height);
}
public void onTexStorage2D(int target, int internalformat, int width, int height) {
if (!isTrackedTarget(target)) return;
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.setTarget(target);
info.internalFormat = internalformat;
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
info.bumpUploadGeneration();
if (isTrackedTarget(target)) defineLevelZero(GLStateManager.getBoundTextureForServerState(), target, internalformat, width, height);
}
public void onTexSubImage2D(int target, int level) {
if (!isTrackedTarget(target) || level != 0) return;
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.bumpUploadGeneration();
if (isTrackedTarget(target) && level == 0) onContentsChanged(GLStateManager.getBoundTextureForServerState());
}
public static boolean isGenericCompressedInternalFormat(int internalFormat) {
@@ -131,9 +148,11 @@ public class TextureInfoCache {
public void onDeleteTexture(int id) {
if (id < 0) return;
deleteCount++;
if (GLStateManager.isSplashComplete()) {
cache.remove(id);
for (IntConsumer listener : deleteListeners) listener.accept(id);
return;
}
@@ -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;
};
}
}
@@ -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,7 @@ import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -61,6 +62,18 @@ public class GLSM_DeletedBufferBinding_GLTest {
}
}
@Test
void deletedIndexedUniformBufferIsNotRestoredFromCache() {
final int index = 3;
final int ubo = makeBoundBuffer(GL31.GL_UNIFORM_BUFFER);
GLStateManager.glBindBufferBase(GL31.GL_UNIFORM_BUFFER, index, ubo);
assertEquals(ubo, GLStateManager.getIndexedBufferBinding(GL31.GL_UNIFORM_BUFFER, index));
GLStateManager.glDeleteBuffers(ubo);
assertEquals(0, GL30.glGetInteger(GL31.GL_UNIFORM_BUFFER_BINDING, index));
assertEquals(0, GLStateManager.getIndexedBufferBinding(GL31.GL_UNIFORM_BUFFER, index));
}
@Test
void deletedElementArrayBufferDoesNotBlockClientDrawElements() {
final int ebo = makeBoundBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER);
@@ -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() {
@@ -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);
}
}
}
@@ -330,8 +330,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
@@ -18,7 +22,7 @@ public class GLSM_VAO_UnitTest {
@AfterEach
void resetClientArrayState() {
glBindVertexArray(0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLSMUtil.setClientState(GL11.GL_NORMAL_ARRAY, false);
}
@@ -66,7 +70,7 @@ public class GLSM_VAO_UnitTest {
int vao = UniversalVAO.genVertexArrays();
Assertions.assertTrue(vao > 0, "GL_VERTEX_ARRAY_BINDING - VAO Created");
glBindVertexArray(vao);
UniversalVAO.bindVertexArray(vao);
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, vao, "GL_VERTEX_ARRAY_BINDING - Bound");
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
@@ -91,7 +95,7 @@ public class GLSM_VAO_UnitTest {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
// Check if everything got reset to default
glBindVertexArray(0);
UniversalVAO.bindVertexArray(0);
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, GLStateManager.ctx().defaultVAO, "GL_VERTEX_ARRAY_BINDING - Unbound");
verifyState(GL15.GL_ARRAY_BUFFER_BINDING, 0, "GL_ARRAY_BUFFER_BINDING - Unbound");
@@ -103,18 +107,18 @@ public class GLSM_VAO_UnitTest {
verifyState(GL11.GL_NORMAL_ARRAY, true, "GL_NORMAL_ARRAY - Reset");
// Check if the VAO states are still valid after re-binding it
glBindVertexArray(vao);
UniversalVAO.bindVertexArray(vao);
verifyState(GL11.GL_VERTEX_ARRAY, vaoVertexEnabled, "GL_VERTEX_ARRAY - Preserved");
verifyState(GL11.GL_VERTEX_ARRAY_SIZE, vaoVertexSize, "GL_VERTEX_ARRAY_SIZE - Preserved");
verifyState(GL11.GL_VERTEX_ARRAY_TYPE, vaoVertexType, "GL_VERTEX_ARRAY_TYPE - Preserved");
verifyState(GL11.GL_VERTEX_ARRAY_STRIDE, vaoVertexStride, "GL_VERTEX_ARRAY_STRIDE - Preserved");
glBindVertexArray(0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
glBindVertexArray(vao);
UniversalVAO.bindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glPopAttrib();
@@ -123,7 +127,7 @@ public class GLSM_VAO_UnitTest {
verifyState(GL15.GL_ARRAY_BUFFER_BINDING, vbo, "GL_ARRAY_BUFFER_BINDING - Not affected by glPushAttrib");
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
@@ -131,64 +135,67 @@ public class GLSM_VAO_UnitTest {
@Test
void testGenericAttributeSupersedesFfpConventional() {
final int vao = UniversalVAO.genVertexArrays();
glBindVertexArray(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);
glBindVertexArray(0);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
GLStateManager.glDisableVertexAttribArray(0);
});
}
@Test
void testGenericAttributeWinsRegardlessOfOrder() {
final int vao = UniversalVAO.genVertexArrays();
glBindVertexArray(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);
glBindVertexArray(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();
glBindVertexArray(vao);
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);
glBindVertexArray(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() {
@@ -198,12 +205,4 @@ public class GLSM_VAO_UnitTest {
verifyState(GL11.GL_COLOR_ARRAY, false, "GL_COLOR_ARRAY Default State");
verifyState(GL11.GL_INDEX_ARRAY, false, "GL_INDEX_ARRAY Default State");
}
private static void glBindVertexArray(int array) {
final GLContextState glCtx = GLStateManager.ctx();
if (array == 0) array = glCtx.defaultVAO;
glCtx.boundVAO = array;
glCtx.vaos.onBindVertexArrayPre(array);
UniversalVAO.bindVertexArray(array);
}
}
@@ -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
@@ -4,7 +4,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -12,54 +11,54 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class BackendStartGuardTest {
private static File marker(Path dir) {
return dir.resolve("angelica").resolve("sdlgpu-starting").toFile();
@TempDir
Path dir;
private boolean boot() {
return boot(1);
}
private boolean boot(int revision) {
return BackendStartGuard.begin(dir.toFile(), true, false, revision, "");
}
private File marker() {
return dir.resolve("sdlgpu-starting").toFile();
}
@Test
void firstStartCreatesTheMarkerAndFinishRemovesIt(@TempDir Path dir) {
final File marker = marker(dir);
assertFalse(BackendStartGuard.begin(marker, true, false));
assertFalse(BackendStartGuard.tripped());
assertTrue(marker.isFile());
BackendStartGuard.finish();
assertFalse(marker.exists());
void bootsThatReachTheBackendNeverCountAsStrikes() {
for (int i = 0; i < BackendStartGuard.STRIKE_LIMIT; i++) {
assertFalse(boot());
BackendStartGuard.arm();
BackendStartGuard.disarm();
}
assertFalse(boot());
}
@Test
void leftoverMarkerTripsOnceAndIsRemoved(@TempDir Path dir) {
final File marker = marker(dir);
assertFalse(BackendStartGuard.begin(marker, true, false));
assertTrue(BackendStartGuard.begin(marker, true, false));
void thirdDeathInTheDeviceWindowResets() {
for (int i = 0; i < BackendStartGuard.STRIKE_LIMIT; i++) {
assertFalse(boot());
BackendStartGuard.arm();
}
assertTrue(boot());
assertTrue(BackendStartGuard.tripped());
assertFalse(marker.exists());
BackendStartGuard.finish();
assertFalse(marker.exists());
assertFalse(BackendStartGuard.begin(marker, true, false));
assertFalse(BackendStartGuard.tripped());
BackendStartGuard.finish();
assertFalse(marker().exists());
}
@Test
void openGlConfigWritesNoMarkerAndClearsAStaleOne(@TempDir Path dir) throws IOException {
final File marker = marker(dir);
assertFalse(BackendStartGuard.begin(marker, false, false));
assertFalse(marker.exists());
assertTrue(marker.getParentFile().mkdirs());
assertTrue(marker.createNewFile());
assertFalse(BackendStartGuard.begin(marker, false, false));
assertFalse(BackendStartGuard.tripped());
assertFalse(marker.exists());
}
void latchStopsArmingUntilTheRequirementsRevisionChanges() {
assertFalse(boot());
BackendStartGuard.arm();
BackendStartGuard.disarm();
BackendStartGuard.latch();
assertFalse(boot());
BackendStartGuard.arm();
assertFalse(marker().exists());
@Test
void forcedFlagNeverTripsOrWrites(@TempDir Path dir) throws IOException {
final File marker = marker(dir);
assertFalse(BackendStartGuard.begin(marker, true, true));
assertFalse(marker.exists());
assertTrue(marker.getParentFile().mkdirs());
assertTrue(marker.createNewFile());
assertFalse(BackendStartGuard.begin(marker, true, true));
assertFalse(BackendStartGuard.tripped());
assertFalse(boot(2));
BackendStartGuard.arm();
assertTrue(marker().isFile());
}
}
@@ -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);
@@ -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<>();
@@ -85,7 +85,7 @@ class InstancedFFPDrawGLTest {
GLSMHooks.pendingProgramSelection = null;
GLSMHooks.blendHandler = null;
log.clear();
FfpFixture.IntegerInstances.delete();
FfpFixture.EntityIdInstances.delete();
FfpFixture.resetFfpState();
CubeParityFixture.disableDirectionalLight();
CubeParityFixture.deleteResources();
@@ -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();
@@ -196,14 +192,14 @@ class InstancedFFPDrawGLTest {
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
FfpFixture.IntegerInstances.build(0.66f);
FfpFixture.EntityIdInstances.build(0.66f);
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
FfpFixture.IntegerInstances.draw();
FfpFixture.EntityIdInstances.draw();
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "instanced ivec4 draw must not raise a GL error");
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "instanced entity id draw must not raise a GL error");
assertPixel(51, 50, 255, 0, 0, "entity -1 must render red");
assertPixel(150, 50, 0, 255, 0, "entity 7 must render green");
@@ -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();
@@ -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");
}
}
@@ -35,13 +35,11 @@ class WideLineClipGLTest {
@BeforeEach
void setUp() {
FfpFixture.routeVertexFormats(true);
savedEmulation = GLStateManager.wideLineEmulationEnabled;
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();
@@ -54,7 +52,6 @@ class WideLineClipGLTest {
@AfterEach
void tearDown() {
FfpFixture.routeVertexFormats(false);
GLStateManager.wideLineEmulationEnabled = savedEmulation;
GLStateManager.glLineWidth(1.0f);
if (vbo != 0) { GLStateManager.glDeleteBuffers(vbo); vbo = 0; }
@@ -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();
@@ -0,0 +1,69 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizons.angelica.glsm.EntityIdsOnlyHandler;
import com.gtnewhorizons.angelica.glsm.GLCompatTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
import com.gtnewhorizons.angelica.glsm.ffp.FfpFixture;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@GLCompatTest
class TessellatorEntityIdsGLTest {
private static final int[] GREEN = { 0, 255, 0, 255 };
private static final int[] BLUE = { 0, 0, 255, 255 };
private ImmediateExtendedAttribHandler previousHandler;
private int program;
@BeforeEach
void setUp() {
previousHandler = EntityIdsOnlyHandler.install();
program = GlTestSupport.program(TessellatorEntityIdsFixture.VERTEX, TessellatorEntityIdsFixture.FRAGMENT);
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
GLStateManager.glViewport(0, 0, 8, 8);
GLStateManager.glUseProgram(program);
GLStateManager.glClearColor(0, 0, 0, 1);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
}
@AfterEach
void tearDown() {
GLStateManager.glUseProgram(0);
GLStateManager.glDeleteProgram(program);
GLStateManager.glPopAttrib();
GLSMHooks.immediateExtendedHandler = previousHandler;
}
@Test
void idsChangedMidBatchApplyToTheVerticesAddedAfterTheChange() {
TessellatorEntityIdsFixture.drawIdsChangedMidBatch();
assertColor(1, GREEN, "left quad was added under item 7");
assertColor(6, BLUE, "right quad was added under item 50020");
}
@Test
void idsChangedAfterTheLastVertexLeaveTheBatchAlone() {
TessellatorEntityIdsFixture.drawIdsChangedAfterTheLastVertex();
assertColor(1, GREEN, "every vertex was added under item 7");
assertColor(6, GREEN, "every vertex was added under item 7");
}
private static void assertColor(int x, int[] expected, String message) {
final int[] pixel = FfpFixture.readPixel(x, 4);
assertArrayEquals(expected, pixel, message + ", got " + Arrays.toString(pixel));
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_ERR">
<Console name="Console" target="SYSTEM_ERR" follow="true">
<PatternLayout pattern="[%-5level] %logger{36} - %msg%n"/>
</Console>
</Appenders>
@@ -0,0 +1,24 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
public final class EntityIdsOnlyHandler implements ImmediateExtendedAttribHandler {
public static ImmediateExtendedAttribHandler install() {
final ImmediateExtendedAttribHandler previous = GLSMHooks.immediateExtendedHandler;
GLSMHooks.immediateExtendedHandler = new EntityIdsOnlyHandler();
GLStateManager.ctx().dirtyEntityIdsAttrib = true;
return previous;
}
@Override public boolean wantsExtended() { return false; }
@Override public boolean wantsTangent() { return false; }
@Override public boolean wantsEntityIds() { return GLStateManager.getActiveProgram() != 0; }
@Override public boolean wantsExtendedCapture() { return false; }
@Override public boolean wantsFaceNormals() { return false; }
@Override public void writeFaceNormals(int[] rawBuffer, int vertexCount) {}
@Override public void build(int[] rawBuffer, int vertexCount, int vertsPerPrim, int normalIntIndex, long dstAddr, int dstStride) {}
@Override public void buildPacked(long srcBase, int stride, int posOffset, int texOffset, int normalOffset,
int vertexCount, int vertsPerPrim, long dstAddr, int dstStride) {}
}
@@ -28,6 +28,7 @@ public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback,
@Override
public void beforeAll(ExtensionContext context) throws LWJGLException {
GlsmRedirectAgent.requireInstalled();
if (startFailure != null) {
throw new IllegalStateException("GLSMCoreExtension context setup failed earlier in this JVM", startFailure);
}
@@ -81,6 +82,7 @@ public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback,
public void afterEach(ExtensionContext context) {
final int error = GL11.glGetError();
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
GlsmRedirectAgent.assertNoFailures();
assertEquals(GL11.GL_NO_ERROR, error, () -> "GL Error: 0x" + Integer.toHexString(error));
}
@@ -61,6 +61,7 @@ public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, Aft
@Override
public void beforeAll(ExtensionContext context) throws LWJGLException {
GlsmRedirectAgent.requireInstalled();
if (startFailure != null) {
throw new IllegalStateException("GLSMExtension context setup failed earlier in this JVM", startFailure);
}
@@ -179,6 +180,7 @@ public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, Aft
fail("Test leaked an open display-list recorder! Missing glEndList.");
}
GlsmRedirectAgent.assertNoFailures();
final int error = GL11.glGetError();
assertEquals(GL11.GL_NO_ERROR, error,
() -> "GL Error: 0x" + Integer.toHexString(error));
@@ -0,0 +1,76 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.loading.ClassDump;
import com.gtnewhorizons.angelica.glsm.loading.ModRedirector;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import static org.junit.jupiter.api.Assertions.fail;
public final class GlsmRedirectAgent {
private static final String GTNHLIB = "com/gtnewhorizon/gtnhlib/";
private static final List<Throwable> FAILURES = new CopyOnWriteArrayList<>();
private static final AtomicInteger TRANSFORMED = new AtomicInteger();
private static boolean installed;
private GlsmRedirectAgent() {}
public static void premain(String args, Instrumentation instrumentation) {
install(instrumentation);
}
public static void install(Instrumentation instrumentation) {
final ModRedirector redirector = new ModRedirector(ClassDump.DISABLED);
addTransformer(instrumentation, (className, classfile) -> {
if (!className.startsWith(GTNHLIB)) return null;
final byte[] out = redirector.transform(className.replace('/', '.'), classfile, null);
if (out == classfile) return null;
TRANSFORMED.incrementAndGet();
return out;
});
installed = true;
}
public static void addTransformer(Instrumentation instrumentation, BiFunction<String, byte[], byte[]> transform) {
instrumentation.addTransformer(new Guarded(transform));
}
public static void requireInstalled() {
if (!installed) throw new IllegalStateException("GL tests need the GLSM redirect agent");
}
public static void assertNoFailures() {
if (!FAILURES.isEmpty()) fail("agent transform failures: " + FAILURES, FAILURES.get(0));
}
public static int transformedCount() {
return TRANSFORMED.get();
}
private static final class Guarded implements ClassFileTransformer {
private final BiFunction<String, byte[], byte[]> transform;
private Guarded(BiFunction<String, byte[], byte[]> transform) {
this.transform = transform;
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> redefined, ProtectionDomain domain, byte[] classfile) {
if (className == null) return null;
try {
return transform.apply(className, classfile);
} catch (Throwable t) {
FAILURES.add(new IllegalStateException("transforming " + className, t));
return null;
}
}
}
}
@@ -260,7 +260,7 @@ public final class CubeParityFixture {
InstancedAttribs.writeTail(ptr, 0xFFFFFFFF, 0, 0L);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE, BRIGHTNESS);
memPutFloat(ptr + CubeInstancedAttribs.OFFSET_LIGHTMAP_SCALE + 4, BRIGHTNESS);
cube.writeTexture(ptr);
cube.writeTexture(ptr, 0.0f, 0.0f, 1.0f, 1.0f);
}
records.position(0).limit(specs.length * CubeInstancedAttribs.STRIDE);
Loaded 100 of 308 files, more files were not shown because too many files have changed in this diff. Show more