Reduce test duplication (#2276)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-10-19 00:43:29 -07:00
1 parent b446968e66
commit e98c927a93
112 files changed
+2524 -4069

No files matched your search

@@ -2663,6 +2663,13 @@ public class GLStateManager {
}
}
for (int i = 0; i <= glCtx.maxBoundImageUnit; i++) {
final ImageUnitBinding image = glCtx.imageUnits.get(i);
if (image != null && image.getTexture() == id) {
RENDER_BACKEND.bindImageTexture(i, 0, 0, false, 0, GL15.GL_READ_ONLY, GL30.GL_R8);
}
}
// Restore active unit, then shrink the texture via a temporary bind
if (changedUnit) {
RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + savedUnit);
@@ -4357,7 +4364,7 @@ public class GLStateManager {
for (int i = 0; i <= glCtx.maxBoundImageUnit; i++) {
final ImageUnitBinding image = glCtx.imageUnits.get(i);
if (image != null && image.getTexture() == id) {
image.setBinding(0, 0, false, 0, 0, 0);
image.reset();
}
}
}
@@ -8711,7 +8718,7 @@ public class GLStateManager {
if (binding == null) {
warnOnce("imageUnitRange", "glBindImageTexture unit {} exceeds GL_MAX_IMAGE_UNITS {}", unit, glCtx.imageUnits.size());
} else {
binding.setBinding(texture, level, layered, layer, access, format);
binding.setBinding(texture, level, texture != 0 && layered, texture != 0 ? layer : 0, access, format);
if (texture != 0 && unit > glCtx.maxBoundImageUnit) glCtx.maxBoundImageUnit = unit;
}
if (access != GL15.GL_READ_ONLY) TextureInfoCache.INSTANCE.onWrittenOutsideUploads(texture);
@@ -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;
@@ -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 {
@@ -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
@@ -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
@@ -131,64 +135,67 @@ public class GLSM_VAO_UnitTest {
@Test
void testGenericAttributeSupersedesFfpConventional() {
final int vao = UniversalVAO.genVertexArrays();
UniversalVAO.bindVertexArray(vao);
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
withVaoAndVbo(() -> {
GLStateManager.glVertexAttribPointer(0, 1, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(0);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
GLStateManager.glVertexAttribPointer(0, 1, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(0);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
Assertions.assertEquals(1, VAOManager.get(0).size, "enabled generic attribute must supersede the FFP conventional array at the same location");
Assertions.assertEquals(1, driverAttrib0Size());
Assertions.assertTrue(VAOManager.get(0).genericPointer, "location must remain generic-sourced");
Assertions.assertEquals(1, VAOManager.get(0).size, "enabled generic attribute must supersede the FFP conventional array at the same location");
Assertions.assertTrue(VAOManager.get(0).genericPointer, "location must remain generic-sourced");
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
GLStateManager.glDisableVertexAttribArray(0);
});
}
@Test
void testGenericAttributeWinsRegardlessOfOrder() {
final int vao = UniversalVAO.genVertexArrays();
UniversalVAO.bindVertexArray(vao);
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
withVaoAndVbo(() -> {
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
GLStateManager.glVertexAttribPointer(0, 1, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(0);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
GLStateManager.glVertexAttribPointer(0, 1, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(0);
Assertions.assertEquals(1, VAOManager.get(0).size, "generic attribute set after the FFP conventional array must still win");
Assertions.assertEquals(1, driverAttrib0Size());
Assertions.assertTrue(VAOManager.get(0).genericPointer, "location must remain generic-sourced");
Assertions.assertEquals(1, VAOManager.get(0).size, "generic attribute set after the FFP conventional array must still win");
Assertions.assertTrue(VAOManager.get(0).genericPointer, "location must remain generic-sourced");
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
GLStateManager.glDisableVertexAttribArray(0);
});
}
@Test
void testFfpConventionalAppliesWithoutGenericAttribute() {
withVaoAndVbo(() -> {
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
Assertions.assertEquals(3, VAOManager.get(0).size, "plain FFP conventional array must apply when no generic attribute occupies the location");
Assertions.assertEquals(3, driverAttrib0Size());
Assertions.assertFalse(VAOManager.get(0).genericPointer, "location must be conventional-sourced");
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
});
}
private static int driverAttrib0Size() {
final IntBuffer out = BufferUtils.createIntBuffer(16);
GL20.glGetVertexAttrib(0, GL20.GL_VERTEX_ATTRIB_ARRAY_SIZE, out);
return out.get(0);
}
private static void withVaoAndVbo(Runnable body) {
final int vao = UniversalVAO.genVertexArrays();
UniversalVAO.bindVertexArray(vao);
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
Assertions.assertEquals(3, VAOManager.get(0).size, "plain FFP conventional array must apply when no generic attribute occupies the location");
Assertions.assertFalse(VAOManager.get(0).genericPointer, "location must be conventional-sourced");
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
try {
body.run();
} finally {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
UniversalVAO.bindVertexArray(0);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
}
}
private static void testClientStates() {
@@ -4,7 +4,9 @@ import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyIsEnabled;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -23,6 +25,7 @@ public class GLSM_WorkerContextStacks_GLTest {
GLStateManager.popStateTo(d);
assertFalse(GLStateManager.glIsEnabled(GL11.GL_CULL_FACE), "cull face restored on the worker context");
verifyIsEnabled(GL11.GL_CULL_FACE, false, "driver cull face restored on the worker context");
} finally {
GLStateManager.glDisable(GL11.GL_CULL_FACE);
GLStateManager.exitWorkerContext();
@@ -42,7 +45,9 @@ public class GLSM_WorkerContextStacks_GLTest {
GLStateManager.popStateTo(d);
assertFalse(GLStateManager.glIsEnabled(GL11.GL_BLEND), "blend enable restored on the worker context");
verifyIsEnabled(GL11.GL_BLEND, false, "driver blend enable restored on the worker context");
assertEquals(GL11.GL_ONE, GLStateManager.getBlendState().getSrcRgb(), "blend src rgb restored on the worker context");
assertEquals(GL11.GL_ONE, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb restored on the worker context");
} finally {
GLStateManager.disableBlend();
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
@@ -1,6 +1,8 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.List;
import java.util.Random;
@@ -12,7 +14,6 @@ class ReservedWordRenameTest {
private static final List<Pattern> LEGACY_ALWAYS = List.of(Pattern.compile("\\bsample\\b"), Pattern.compile("\\bnew\\b"));
private static final Pattern LEGACY_400 = Pattern.compile("\\bsampler\\b(?!\\d)");
private static final String[] WORDS = { "sample", "sampler", "new" };
private static String legacyRename(String source, int targetVersion) {
String out = source;
@@ -30,17 +31,17 @@ class ReservedWordRenameTest {
}
}
@Test
void edgeCasesMatchLegacy() {
final String[] cases = {
"", "sample", "new", "sampler", " sample ", "sample;", "(sample)", "_sample", "sample_", "sample2", "2sample",
"samples", "resample", "sampler2D", "samplerX", "sampler_", "sampler;", "uniform sampler s;", "sampler2DShadow",
"new_", "renew", "news", "new;new new", "sample sample sample", "samplesampler", "sampler sample new",
"ésample", "sampleé", "ßnewß", "中sampler", "sampler中", "samplé",
"// sample in a comment\nfloat sample = 1.0;\n", "#define new sample\n", "vec4 c = texture(sampler, uv) * sample;",
"float x = sample.x + new_value + newValue + sample1;", "sample\nnew\nsampler\n", "\tsample\t", "sample-new+sampler*",
};
for (String c : cases) assertSameAsLegacy(c);
@ParameterizedTest
@ValueSource(strings = {
"", "sample", "new", "sampler", " sample ", "sample;", "(sample)", "_sample", "sample_", "sample2", "2sample",
"samples", "resample", "sampler2D", "samplerX", "sampler_", "sampler;", "uniform sampler s;", "sampler2DShadow",
"new_", "renew", "news", "new;new new", "sample sample sample", "samplesampler", "sampler sample new",
"\u00e9sample", "sample\u00e9", "\u00dfnew\u00df", "\u4e2dsampler", "sampler\u4e2d", "sampl\u00e9",
"// sample in a comment\nfloat sample = 1.0;\n", "#define new sample\n", "vec4 c = texture(sampler, uv) * sample;",
"float x = sample.x + new_value + newValue + sample1;", "sample\nnew\nsampler\n", "\tsample\t", "sample-new+sampler*",
})
void edgeCasesMatchLegacy(String source) {
assertSameAsLegacy(source);
}
@Test
@@ -55,10 +56,9 @@ class ReservedWordRenameTest {
}
}
@Test
void literalWordsAreTheOnlyCandidates() {
for (String w : WORDS) {
assertEquals("angelica_renamed_" + w, GlslTransformUtils.renameReservedWords(w, 460));
}
@ParameterizedTest
@ValueSource(strings = { "sample", "sampler", "new" })
void literalWordsAreTheOnlyCandidates(String word) {
assertEquals("angelica_renamed_" + word, GlslTransformUtils.renameReservedWords(word, 460));
}
}
@@ -1,15 +1,47 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.taumc.glsl.grammar.GLSLParser;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ReservedWordRestoreTest {
private static void assertTreeRestoreMatchesStringRestore(String label, String renamedSource) {
static Stream<Arguments> restores() {
return Stream.of(
Arguments.of("all three renames restore identically",
"float angelica_renamed_sample = 1.0;\n"
+ "uniform sampler2D angelica_renamed_sampler;\n"
+ "vec4 angelica_renamed_texture = vec4(0.0);\n"
+ "void main() {\n"
+ " vec4 c = angelica_renamed_texture + vec4(angelica_renamed_sample);\n"
+ "}\n",
new String[0], new String[0]),
Arguments.of("new is reserved in every GLSL version and must stay renamed",
"float angelica_renamed_new = 2.0;\n"
+ "void main() { float x = angelica_renamed_new; }\n",
new String[] { "angelica_renamed_new" }, new String[0]),
Arguments.of("suffixed rename must strip only the prefix",
"uniform sampler2D tex;\n"
+ "void main() {\n"
+ " vec4 c = angelica_renamed_texture2D(tex, vec2(0.5));\n"
+ "}\n",
new String[] { "texture2D" }, new String[] { "angelica_renamed_" }),
Arguments.of("untouched source prints identically",
"layout(location = 0) out vec4 fragColor;\n"
+ "void main() { fragColor = vec4(1.0); }\n",
new String[0], new String[0]));
}
@ParameterizedTest(name = "{0}")
@MethodSource
void restores(String label, String renamedSource, String[] present, String[] absent) {
final GLSLParser.Translation_unitContext treeA = GlslTransformUtils.parseFullQuiet(renamedSource);
final String legacy = GlslTransformUtils.restoreReservedWords(GlslTransformUtils.getFormattedShader(treeA, ""));
@@ -17,51 +49,8 @@ class ReservedWordRestoreTest {
GlslTransformUtils.restoreReservedWordsInTree(treeB);
final String inTree = GlslTransformUtils.getFormattedShader(treeB, "");
assertEquals(legacy, inTree, "in-tree restore diverged from string restore for " + label);
}
@Test
void allThreeRenamesRestoreIdentically() {
final String src = "float angelica_renamed_sample = 1.0;\n"
+ "uniform sampler2D angelica_renamed_sampler;\n"
+ "vec4 angelica_renamed_texture = vec4(0.0);\n"
+ "void main() {\n"
+ " vec4 c = angelica_renamed_texture + vec4(angelica_renamed_sample);\n"
+ "}\n";
assertTreeRestoreMatchesStringRestore("all-three", src);
}
@Test
void renamedNewStaysRenamed() {
final String src = "float angelica_renamed_new = 2.0;\n"
+ "void main() { float x = angelica_renamed_new; }\n";
assertTreeRestoreMatchesStringRestore("new-identifier", src);
final GLSLParser.Translation_unitContext tree = GlslTransformUtils.parseFullQuiet(src);
GlslTransformUtils.restoreReservedWordsInTree(tree);
final String out = GlslTransformUtils.getFormattedShader(tree, "");
assertTrue(out.contains("angelica_renamed_new"), "new is reserved in every GLSL version and must stay renamed");
}
@Test
void renamedTextureFunctionCallRestores() {
final String src = "uniform sampler2D tex;\n"
+ "void main() {\n"
+ " vec4 c = angelica_renamed_texture2D(tex, vec2(0.5));\n"
+ "}\n";
assertTreeRestoreMatchesStringRestore("texture2D-call", src);
final GLSLParser.Translation_unitContext tree = GlslTransformUtils.parseFullQuiet(src);
GlslTransformUtils.restoreReservedWordsInTree(tree);
final String out = GlslTransformUtils.getFormattedShader(tree, "");
assertTrue(out.contains("texture2D"), "suffixed rename must strip only the prefix");
assertFalse(out.contains("angelica_renamed_"), "no renamed remnants expected");
}
@Test
void untouchedSourcePrintsIdentically() {
final String src = "layout(location = 0) out vec4 fragColor;\n"
+ "void main() { fragColor = vec4(1.0); }\n";
assertTreeRestoreMatchesStringRestore("no-renames", src);
assertEquals(legacy, inTree, "in-tree restore diverged from string restore");
for (String needle : present) assertTrue(inTree.contains(needle), needle);
for (String needle : absent) assertFalse(inTree.contains(needle), needle);
}
}
@@ -25,16 +25,19 @@ class VertexAttribDefaultBridgeTest {
return new float[] { buf.get(0), buf.get(1), buf.get(2), buf.get(3) };
}
private void assertAttrib(int index, String label, float... expected) {
final float[] actual = getVertexAttrib4f(index);
for (int i = 0; i < expected.length; i++) {
assertEquals(expected[i], actual[i], EPSILON, label + "[" + i + "]");
}
}
@Test
void testGlColor4fSetsVertexAttrib() {
GLStateManager.glColor4f(0.5f, 0.25f, 0.75f, 0.8f);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.5f, color[0], EPSILON, "color.r");
assertEquals(0.25f, color[1], EPSILON, "color.g");
assertEquals(0.75f, color[2], EPSILON, "color.b");
assertEquals(0.8f, color[3], EPSILON, "color.a");
assertAttrib(COLOR_ATTRIB, "color", 0.5f, 0.25f, 0.75f, 0.8f);
}
@Test
@@ -42,10 +45,7 @@ class VertexAttribDefaultBridgeTest {
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] normal = getVertexAttrib4f(NORMAL_ATTRIB);
assertEquals(0.0f, normal[0], EPSILON, "normal.x");
assertEquals(1.0f, normal[1], EPSILON, "normal.y");
assertEquals(0.0f, normal[2], EPSILON, "normal.z");
assertAttrib(NORMAL_ATTRIB, "normal", 0.0f, 1.0f, 0.0f);
}
@Test
@@ -53,11 +53,7 @@ class VertexAttribDefaultBridgeTest {
GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, 240.0f, 128.0f);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] lm = getVertexAttrib4f(SECONDARY_UV_ATTRIB);
assertEquals(240.0f, lm[0], EPSILON, "lightmap.s");
assertEquals(128.0f, lm[1], EPSILON, "lightmap.t");
assertEquals(0.0f, lm[2], EPSILON, "lightmap.r");
assertEquals(1.0f, lm[3], EPSILON, "lightmap.q");
assertAttrib(SECONDARY_UV_ATTRIB, "lightmap", 240.0f, 128.0f, 0.0f, 1.0f);
}
@Test
@@ -66,9 +62,7 @@ class VertexAttribDefaultBridgeTest {
GLStateManager.glTexCoord2f(0.5f, 0.75f);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] uv = getVertexAttrib4f(PRIMARY_UV_ATTRIB);
assertEquals(0.5f, uv[0], EPSILON, "texcoord.s");
assertEquals(0.75f, uv[1], EPSILON, "texcoord.t");
assertAttrib(PRIMARY_UV_ATTRIB, "texcoord", 0.5f, 0.75f);
}
@Test
@@ -79,11 +73,7 @@ class VertexAttribDefaultBridgeTest {
GLStateManager.glColor4f(0.3f, 0.6f, 0.9f, 1.0f);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.3f, color[0], EPSILON, "color.r after dedup");
assertEquals(0.6f, color[1], EPSILON, "color.g after dedup");
assertEquals(0.9f, color[2], EPSILON, "color.b after dedup");
assertEquals(1.0f, color[3], EPSILON, "color.a after dedup");
assertAttrib(COLOR_ATTRIB, "color after dedup", 0.3f, 0.6f, 0.9f, 1.0f);
}
@Test
@@ -97,11 +87,7 @@ class VertexAttribDefaultBridgeTest {
// Color during COMPILE should NOT reach changeColor (early return)
GLStateManager.glColor4f(0.9f, 0.8f, 0.7f, 0.6f);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.1f, color[0], EPSILON, "color.r unchanged during COMPILE");
assertEquals(0.2f, color[1], EPSILON, "color.g unchanged during COMPILE");
assertEquals(0.3f, color[2], EPSILON, "color.b unchanged during COMPILE");
assertEquals(1.0f, color[3], EPSILON, "color.a unchanged during COMPILE");
assertAttrib(COLOR_ATTRIB, "color unchanged during COMPILE", 0.1f, 0.2f, 0.3f, 1.0f);
GLStateManager.glEndList();
GLStateManager.glDeleteLists(listId, 1);
@@ -1,8 +1,8 @@
package com.gtnewhorizons.angelica.glsm.backend;
import org.junit.jupiter.api.Test;
import java.util.function.Predicate;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -17,60 +17,38 @@ class BackendPolicyTest {
assertFalse(VSyncMode.OFF.tearFree());
}
private static final Predicate<VSyncMode> NO_MAILBOX = mode -> mode != VSyncMode.MAILBOX;
private static final Predicate<VSyncMode> EVERYTHING = mode -> true;
@Test
void mailboxIsTakenWhenTheBackendOffersIt() {
assertEquals(VSyncMode.MAILBOX, RenderBackend.resolveTearFreeMode(null, EVERYTHING));
@ParameterizedTest(name = "{0}")
@CsvSource({
"mailbox is taken when the backend offers it, , true, MAILBOX",
"vsync is the fallback 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, MAILBOX",
"off is never a tear-free preference without mailbox, OFF, false, ON",
})
void tearFreeModeResolution(String label, VSyncMode preference, boolean mailboxSupported, VSyncMode expected) {
assertEquals(expected, RenderBackend.resolveTearFreeMode(preference, mode -> mailboxSupported || mode != VSyncMode.MAILBOX));
}
@Test
void vsyncIsTheFallbackWhenMailboxIsUnsupported() {
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(null, NO_MAILBOX));
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(VSyncMode.MAILBOX, NO_MAILBOX));
@ParameterizedTest(name = "{0}/{1} or {2} Hz -> {3} ns")
@CsvSource({
"60000, 1001, 0.0, 16683333",
"144, 1, 0.0, 6944444",
"0, 0, 59.94, 16683350",
"0, 0, 60.0, 16666667",
"0, 0, 0.0, 0",
"-1, -1, -1.0, 0",
"144, 0, 0.0, 0",
})
void periodPrefersTheExactRationalThenTheFloatRate(int numerator, int denominator, float hz, long expected) {
assertEquals(expected, RenderBackend.periodFromRational(numerator, denominator, hz));
}
@Test
void anExplicitSupportedPreferenceWins() {
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(VSyncMode.ON, EVERYTHING));
assertEquals(VSyncMode.MAILBOX, RenderBackend.resolveTearFreeMode(VSyncMode.MAILBOX, EVERYTHING));
}
@Test
void offIsNeverATearFreePreference() {
assertEquals(VSyncMode.MAILBOX, RenderBackend.resolveTearFreeMode(VSyncMode.OFF, EVERYTHING));
assertEquals(VSyncMode.ON, RenderBackend.resolveTearFreeMode(VSyncMode.OFF, NO_MAILBOX));
}
@Test
void periodPrefersTheExactRational() {
assertEquals(16_683_333L, RenderBackend.periodFromRational(60000, 1001, 0.0f));
assertEquals(6_944_444L, RenderBackend.periodFromRational(144, 1, 0.0f));
}
@Test
void periodFallsBackToTheFloatRate() {
assertEquals(16_683_350L, RenderBackend.periodFromRational(0, 0, 59.94f));
assertEquals(16_666_667L, RenderBackend.periodFromRational(0, 0, 60.0f));
}
@Test
void periodIsZeroWhenUnknown() {
assertEquals(0L, RenderBackend.periodFromRational(0, 0, 0.0f));
assertEquals(0L, RenderBackend.periodFromRational(-1, -1, -1.0f));
assertEquals(0L, RenderBackend.periodFromRational(144, 0, 0.0f));
}
@Test
void integerNtscRatesAreAdjusted() {
assertEquals(16_683_333L, RenderBackend.periodFromIntegerHz(59));
assertEquals(6_951_389L, RenderBackend.periodFromIntegerHz(143));
assertEquals(16_666_667L, RenderBackend.periodFromIntegerHz(60));
assertEquals(6_944_444L, RenderBackend.periodFromIntegerHz(144));
assertEquals(0L, RenderBackend.periodFromIntegerHz(0));
assertEquals(0L, RenderBackend.periodFromIntegerHz(-60));
assertEquals(60, RenderBackend.hzFromPeriod(RenderBackend.periodFromIntegerHz(59)));
@ParameterizedTest(name = "{0} Hz -> {1} ns")
@CsvSource({"59, 16683333", "143, 6951389", "60, 16666667", "144, 6944444", "0, 0", "-60, 0"})
void integerNtscRatesAreAdjusted(int hz, long expected) {
assertEquals(expected, RenderBackend.periodFromIntegerHz(hz));
}
@Test
@@ -79,6 +57,7 @@ class BackendPolicyTest {
assertEquals(144, RenderBackend.hzFromPeriod(6_944_444L));
assertEquals(0, RenderBackend.hzFromPeriod(0L));
assertEquals(0, RenderBackend.hzFromPeriod(-1L));
assertEquals(60, RenderBackend.hzFromPeriod(RenderBackend.periodFromIntegerHz(59)));
}
@Test
@@ -124,12 +124,7 @@ class FFPCombineShaderTest {
@Test
void testSingleUnitCombineCompiles() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
enableCombineUnit(GL13.GL_TEXTURE0, GL11.GL_MODULATE, GL11.GL_MODULATE);
FragmentKey fk = FragmentKey.fromState();
String fragSource = FragmentShaderGenerator.generate(fk);
@@ -181,12 +176,7 @@ class FFPCombineShaderTest {
@ParameterizedTest(name = "COMBINE function {0}")
@MethodSource("allCombineFunctions")
void testAllCombineFunctionsCompile(String name, int glFunc, int numArgs) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, glFunc);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
enableCombineUnit(GL13.GL_TEXTURE0, glFunc, GL11.GL_REPLACE);
// Set up appropriate sources
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
@@ -215,12 +205,7 @@ class FFPCombineShaderTest {
@Test
void testCombineScopeDeclaresColorOutsideBlock() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
enableCombineUnit(GL13.GL_TEXTURE0, GL11.GL_MODULATE, GL11.GL_MODULATE);
FragmentKey fk = FragmentKey.fromState();
String src = FragmentShaderGenerator.generate(fk);
@@ -270,14 +255,18 @@ class FFPCombineShaderTest {
* Unit 2: COMBINE, RGB=ADD, SRC0=TEXTURE/SRC_COLOR, SRC1=PREVIOUS/SRC_COLOR
* Unit 3: MODULATE (simple)
*/
private void setupXaeroState() {
// Unit 0: COMBINE / SUBTRACT
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
private void enableCombineUnit(int unit, int rgbFunc, int alphaFunc) {
GLStateManager.glActiveTexture(unit);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL13.GL_SUBTRACT);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, rgbFunc);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, alphaFunc);
}
private void setupXaeroState() {
// Unit 0: COMBINE / SUBTRACT
enableCombineUnit(GL13.GL_TEXTURE0, GL13.GL_SUBTRACT, GL11.GL_REPLACE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE1_RGB, GL13.GL_PRIMARY_COLOR);
@@ -288,12 +277,7 @@ class FFPCombineShaderTest {
// Unit 1: disabled (skip — default is disabled)
// Unit 2: COMBINE / ADD
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_ADD);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
enableCombineUnit(GL13.GL_TEXTURE2, GL11.GL_ADD, GL11.GL_REPLACE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE0_RGB, GL11.GL_TEXTURE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_OPERAND0_RGB, GL11.GL_SRC_COLOR);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_SOURCE1_RGB, GL13.GL_PREVIOUS);
@@ -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();
@@ -147,9 +147,7 @@ class InstancedFFPDrawGLTest {
GLStateManager.glViewport(0, 0, PARITY_SIZE, PARITY_SIZE);
CubeParityFixture.setupScene(lightmap, lit);
final ShaderManager sm = ShaderManager.getInstance();
ShaderManager.enable();
sm.activate();
FfpFixture.activateFfp();
final CubeParityFixture.Spec[] specs = CubeParityFixture.specs();
@@ -172,9 +170,7 @@ class InstancedFFPDrawGLTest {
GLStateManager.glViewport(0, 0, PARITY_SIZE, PARITY_SIZE);
ParticleParityFixture.setupScene(true);
final ShaderManager sm = ShaderManager.getInstance();
ShaderManager.enable();
sm.activate();
FfpFixture.activateFfp();
final ParticleParityFixture.Particle[] particles = ParticleParityFixture.particles();
@@ -237,20 +233,7 @@ class InstancedFFPDrawGLTest {
@Test
void twoInstancesDrawWithDistinctTransformsAndColors() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glViewport(0, 0, 800, 600);
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
buildTemplate();
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
final ShaderManager sm = ShaderManager.getInstance();
ShaderManager.enable();
sm.activate();
prepareTwoInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
@@ -267,20 +250,7 @@ class InstancedFFPDrawGLTest {
@Test
void instancedAlphaTestDiscardsLowAlphaInstance() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glViewport(0, 0, 800, 600);
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
buildTemplate();
uploadInstances(0xFF0000FF, 0, 0x0D0000FF, 0);
final ShaderManager sm = ShaderManager.getInstance();
ShaderManager.enable();
sm.activate();
prepareTwoInstances(0xFF0000FF, 0, 0x0D0000FF, 0);
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
@@ -311,9 +281,7 @@ class InstancedFFPDrawGLTest {
buildTemplate();
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
final ShaderManager sm = ShaderManager.getInstance();
ShaderManager.enable();
sm.activate();
FfpFixture.activateFfp();
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
@@ -340,20 +308,7 @@ class InstancedFFPDrawGLTest {
@Test
void perInstanceOverlayMixesOnlyTintedInstances() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glViewport(0, 0, 800, 600);
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
buildTemplate();
uploadInstances(0xFFFFFFFF, 0, 0xFFFFFFFF, 0xFF0000FF);
final ShaderManager sm = ShaderManager.getInstance();
ShaderManager.enable();
sm.activate();
prepareTwoInstances(0xFFFFFFFF, 0, 0xFFFFFFFF, 0xFF0000FF);
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
@@ -370,6 +325,19 @@ class InstancedFFPDrawGLTest {
assertTrue(right[0] > 200 && right[1] < 50 && right[2] < 50, "right instance is mixed to its own opaque red overlay, got " + right[0] + "," + right[1] + "," + right[2]);
}
private void prepareTwoInstances(int leftColor, int leftOverlay, int rightColor, int rightOverlay) {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glViewport(0, 0, 800, 600);
GLStateManager.disableDepthTest();
GLStateManager.disableCull();
buildTemplate();
uploadInstances(leftColor, leftOverlay, rightColor, rightOverlay);
FfpFixture.activateFfp();
}
private void uploadInstances(int leftColor, int leftOverlay, int rightColor, int rightOverlay) {
final ByteBuffer instances = BufferUtils.createByteBuffer(2 * INSTANCE_STRIDE);
putInstance(instances, -0.5f, leftColor, leftOverlay);
@@ -17,8 +17,7 @@ class LogicOpGLTest {
@BeforeEach
void setUp() {
FfpFixture.resetFfpState();
ShaderManager.enable();
ShaderManager.getInstance().activate();
FfpFixture.activateFfp();
GLStateManager.glViewport(0, 0, SIZE, SIZE);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
@@ -3,7 +3,8 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -15,18 +16,13 @@ class UniformsRescaleTest {
return Uniforms.rescaleFactor(normalMatrix, new Vector3f());
}
@Test
void rescaleFactorUsesTheInverseModelviewBottomRow() {
assertEquals(8.0f, rescaleFactorFor(4.0f, 2.0f, 8.0f), 1.0e-5f, "wrong normal matrix column");
}
@Test
void uniformScaleIsRowIndependent() {
assertEquals(2.0f, rescaleFactorFor(2.0f, 2.0f, 2.0f), 1.0e-5f);
}
@Test
void singularMatrixFallsBackToUnitScale() {
assertEquals(1.0f, rescaleFactorFor(1.0f, 1.0f, 0.0f), 1.0e-5f, "a degenerate normal matrix must not divide by zero");
@ParameterizedTest(name = "{0}")
@CsvSource({
"wrong normal matrix column, 4, 2, 8, 8",
"uniform scale, 2, 2, 2, 2",
"a degenerate normal matrix must not divide by zero, 1, 1, 0, 1",
})
void rescaleFactorUsesTheInverseModelviewBottomRow(String label, float sx, float sy, float sz, float expected) {
assertEquals(expected, rescaleFactorFor(sx, sy, sz), 1.0e-5f, label);
}
}
@@ -9,7 +9,6 @@ import org.junit.jupiter.api.Test;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class VAOManagerEboIndexTest {
@@ -127,64 +126,4 @@ public class VAOManagerEboIndexTest {
}
assertTrue(sharedEboDeletes > 20, "op stream never freed a multi-owner EBO (hits: " + sharedEboDeletes + ")");
}
@Test
void sharedEboClearsEveryOwner() {
final int ebo = 77;
bindVao(1);
bindEbo(ebo);
bindVao(2);
bindEbo(ebo);
bindVao(3);
bindEbo(ebo);
bindVao(DEFAULT_VAO);
deleteBuffer(ebo);
assertEquals(0, vaos.vaoMap.get(1).ebo, "vao 1 kept a freed shared EBO");
assertEquals(0, vaos.vaoMap.get(2).ebo, "vao 2 kept a freed shared EBO");
assertEquals(0, vaos.vaoMap.get(3).ebo, "vao 3 kept a freed shared EBO");
assertFalse(vaos.eboOwners.containsKey(ebo), "index kept owners for a freed EBO");
}
@Test
void sharedEboEntryDropsOnlyAfterItsLastOwnerGoes() {
final int ebo = 88;
bindVao(1);
bindEbo(ebo);
bindVao(2);
bindEbo(ebo);
bindVao(DEFAULT_VAO);
deleteVao(1);
assertTrue(vaos.eboOwners.containsKey(ebo), "index dropped a still-live owner");
deleteVao(2);
assertFalse(vaos.eboOwners.containsKey(ebo), "index kept an entry after its last owner went away");
}
@Test
void deletingTheBoundVaoDoesNotOrphanAnIndexEntry() {
final int ebo = 99;
bindVao(1);
bindEbo(ebo);
deleteVao(1);
bindVao(DEFAULT_VAO);
assertFalse(vaos.eboOwners.containsKey(ebo), "deleting the bound VAO left an unreachable owner in the index");
assertEquals(0, vaos.eboOwners.size(), "index leaked an entry for a deleted VAO");
}
@Test
void indexEmptiesOutWhenEverythingIsDeleted() {
for (int vao = 1; vao <= 64; vao++) {
bindVao(vao);
bindEbo(1000 + vao);
}
bindVao(DEFAULT_VAO);
for (int vao = 1; vao <= 64; vao++) {
deleteVao(vao);
}
assertEquals(0, vaos.eboOwners.size(), "index leaked entries after every VAO was deleted");
}
}
@@ -39,8 +39,7 @@ class WideLineClipGLTest {
GLStateManager.wideLineEmulationEnabled = true;
FfpFixture.resetFfpState();
ShaderManager.enable();
ShaderManager.getInstance().activate();
FfpFixture.activateFfp();
GLStateManager.glViewport(0, 0, SIZE, SIZE);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
@@ -139,6 +139,10 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
assertNotNull(cmd);
assertEquals(6, cmd.getIndexCount());
assertEquals(GL11.GL_UNSIGNED_INT, cmd.getIndexType(), "batching always widens indices to UINT for safe base-vertex shifting");
final ByteBuffer ib = readBufferBytes(GL15.GL_ELEMENT_ARRAY_BUFFER, firstBatch(list).getSharedEBO(), 6 * 4);
assertUintIndices(ib, 0, 1, 2, 2, 3, 0);
MemoryUtilities.memFree(ib);
}
@Test
@@ -156,13 +160,7 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
final int vbo = defaultQuadVbo();
final int vao = setupPosColorVao(vbo);
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
final int list = recordSingleIndexedDraw(vao, 0, GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT);
assertTrue(allBatchedCmds(list).isEmpty(), "no EBO bound → no batched command attached");
assertTrue(batchesOf(list).isEmpty(), "no batches either");
@@ -357,51 +355,6 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
MemoryUtilities.memFree(ib);
}
@Test
void multiDrawDifferentFormats_splitBatches() {
// Draw A: pos+color (stride=12). Draw B: pos-only (stride=8). Different layout → two batches.
final int vboA = defaultQuadVbo();
final int vaoA = setupPosColorVao(vboA);
final int vboB = newBuffer();
final ByteBuffer bData = MemoryUtilities.memAlloc(4 * 8).order(ByteOrder.nativeOrder());
for (int i = 0; i < 4; i++) {
bData.putFloat(DEFAULT_QUAD_POSITIONS[i * 2]);
bData.putFloat(DEFAULT_QUAD_POSITIONS[i * 2 + 1]);
}
bData.flip();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboB);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, bData, GL15.GL_STATIC_DRAW);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
MemoryUtilities.memFree(bData);
final int vaoB = newVao();
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboB);
GLStateManager.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, 8, 0);
GLStateManager.glEnableVertexAttribArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindVertexArray(0);
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glBindVertexArray(vaoA);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
final List<BatchedIndexedDrawCmd> cmds = allBatchedCmds(list);
assertEquals(2, cmds.size());
assertEquals(2, batchesOf(list).size(), "incompatible layouts → two batches");
assertNotSame(batchOf(cmds.get(0)), batchOf(cmds.get(1)), "separate layouts must get distinct batches");
}
@Test
void sameSizesDifferentLocations_splitIntoSeparateBatches() {
// Both VAOs: pos 2f + color 4ub (same sizes/types/normalized). Only the enabled
@@ -424,16 +377,7 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glBindVertexArray(vaoA);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
final int list = recordTwoVaoDraws(vaoA, vaoB, ebo);
final List<BatchedIndexedDrawCmd> cmds = allBatchedCmds(list);
assertEquals(2, cmds.size());
@@ -472,30 +416,14 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
// Record (A, B) and (B, A) as independent lists; each list's batch order
// must match its own recording order.
final int listAB = newList();
GLStateManager.glNewList(listAB, GL11.GL_COMPILE);
GLStateManager.glBindVertexArray(vaoA);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
final int listAB = recordTwoVaoDraws(vaoA, vaoB, ebo);
final int listBA = newList();
GLStateManager.glNewList(listBA, GL11.GL_COMPILE);
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(vaoA);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
final int listBA = recordTwoVaoDraws(vaoB, vaoA, ebo);
final List<BatchedIndexedDrawCmd> ab = allBatchedCmds(listAB);
final List<BatchedIndexedDrawCmd> ba = allBatchedCmds(listBA);
assertEquals(2, ab.size());
assertEquals(2, batchesOf(listAB).size(), "incompatible layouts -> two batches");
// Placeholders sit in command-stream order (A-first vs B-first).
// Batches must be ordered so that placeholder[0] points at batch[0].
assertSame(batchesOf(listAB).get(0), batchOf(ab.get(0)),
@@ -514,44 +442,48 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
final ByteBuffer idx = BufferUtils.createByteBuffer(3);
idx.put(new byte[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3),
}, GL11.GL_TRIANGLES, 3, new int[]{0, 1, 2}),
arguments("short indices", GL11.GL_COMPILE, (Runnable) () -> {
final ShortBuffer idx = BufferUtils.createShortBuffer(3);
idx.put(new short[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3),
}, GL11.GL_TRIANGLES, 3, new int[]{0, 1, 2}),
arguments("int indices", GL11.GL_COMPILE, (Runnable) () -> {
final IntBuffer idx = BufferUtils.createIntBuffer(3);
idx.put(new int[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3),
}, GL11.GL_TRIANGLES, 3, new int[]{0, 1, 2}),
arguments("explicit count+type", GL11.GL_COMPILE, (Runnable) () -> {
final ByteBuffer idx = BufferUtils.createByteBuffer(3 * 2).order(ByteOrder.nativeOrder());
idx.putShort((short) 0).putShort((short) 1).putShort((short) 2).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, idx);
}, GL11.GL_TRIANGLES, 3),
}, GL11.GL_TRIANGLES, 3, new int[]{0, 1, 2}),
arguments("quads triangulate at record", GL11.GL_COMPILE, (Runnable) () -> {
final IntBuffer idx = BufferUtils.createIntBuffer(4);
idx.put(new int[]{0, 1, 2, 3}).flip();
GLStateManager.glDrawElements(GL11.GL_QUADS, idx);
}, GL11.GL_TRIANGLES, 6),
}, GL11.GL_TRIANGLES, 6, new int[]{0, 1, 3, 1, 2, 3}),
arguments("compile-and-execute fires live and bakes", GL11.GL_COMPILE_AND_EXECUTE, (Runnable) () -> {
final IntBuffer idx = BufferUtils.createIntBuffer(3);
idx.put(new int[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3)
}, GL11.GL_TRIANGLES, 3, new int[]{0, 1, 2})
);
}
@ParameterizedTest(name = "{0}")
@MethodSource("clientIndexedDrawCases")
void clientIndexedDraw_capturedAndWidenedToUnsignedInt(String name, int listMode, Runnable draw, int expectedMode, int expectedCount) {
void clientIndexedDraw_capturedAndWidenedToUnsignedInt(String name, int listMode, Runnable draw, int expectedMode, int expectedCount, int[] expectedIndices) {
final int list = recordClientIndexDraw(listMode, draw);
final BatchedIndexedDrawCmd cmd = firstBatchedCmd(list);
assertNotNull(cmd);
assertEquals(expectedMode, cmd.getDrawMode());
assertEquals(expectedCount, cmd.getIndexCount());
assertEquals(GL11.GL_UNSIGNED_INT, cmd.getIndexType(), "client indices always widen to UNSIGNED_INT");
final ByteBuffer ib = readBufferBytes(GL15.GL_ELEMENT_ARRAY_BUFFER, firstBatch(list).getSharedEBO(), expectedCount * 4);
assertUintIndices(ib, expectedIndices);
MemoryUtilities.memFree(ib);
}
@Test
@@ -79,6 +79,7 @@ class IndexedDrawReplayTest extends DisplayListTestFixture {
GLStateManager.glBindVertexArray(vaoOf(batch));
assertEquals(batch.getSharedEBO(), GLStateManager.getBoundEBO(),
"batched VAO's EBO binding corrupted");
assertEquals(batch.getSharedEBO(), GL11.glGetInteger(GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING));
GLStateManager.glBindVertexArray(0);
}
@@ -148,6 +148,19 @@ public abstract class DisplayListTestFixture {
return list;
}
protected final int recordTwoVaoDraws(int firstVao, int secondVao, int ebo) {
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
for (int vao : new int[] { firstVao, secondVao }) {
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
}
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
return list;
}
protected static CompiledDisplayList compiledOf(int listId) {
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(listId);
assertNotNull(compiled, "compiled display list must exist for id " + listId);
@@ -119,6 +119,7 @@ class GLSMRedirectorTest {
assertEquals(expectedName, result.getName());
assertEquals(desc, result.getDesc());
assertEquals(Opcodes.H_INVOKESTATIC, result.getTag());
assertDoesNotThrow(() -> cn.accept(new ClassWriter(ClassWriter.COMPUTE_MAXS)));
}
@Test
@@ -158,21 +159,6 @@ class GLSMRedirectorTest {
assertTrue(unmappedSeen().contains("org/lwjgl/opengl/ARBSparseTexture.glTexPageCommitmentARB()V"));
}
@Test
void debugLabelIsRedirectedNotReported() {
assertRedirected("org/lwjgl/opengl/KHRDebug", "glPushDebugGroup", "(IILjava/lang/CharSequence;)V", "glPushDebugGroup");
assertRedirected("org/lwjgl/opengl/GL43C", "glObjectLabel", "(IILjava/lang/CharSequence;)V", "glObjectLabel");
assertTrue(debugSeen().isEmpty());
}
@Test
void nonAwareDebugCallbackIsRedirectedNotPassedThrough() {
assertRedirected("org/lwjgl/opengl/GL43", "glDebugMessageCallback", "(Lorg/lwjgl/opengl/KHRDebugCallback;)V", "glDebugMessageCallback");
assertRedirected("org/lwjgl/opengl/KHRDebug", "glDebugMessageCallback", "(Lorg/lwjgl/opengl/KHRDebugCallback;)V", "glDebugMessageCallback");
assertTrue(debugSeen().isEmpty());
assertTrue(unmappedSeen().isEmpty());
}
@Test
void debugOwnerAndWrongDescriptorArePassthrough() {
makeAndTransform("org/lwjgl/opengl/AMDDebugOutput", "glDebugMessageEnableAMD", "(IILjava/nio/IntBuffer;Z)V");
@@ -221,42 +207,6 @@ class GLSMRedirectorTest {
assertTrue(unmappedSeen().isEmpty());
}
@ParameterizedTest(name = "{1}")
@CsvSource(delimiter = '|', value = {
"org/lwjgl/opengl/EXTFramebufferObject | glBindFramebufferEXT | (II)V | glBindFramebuffer",
"org/lwjgl/opengl/ARBFramebufferObject | glGenerateMipmap | (I)V | glGenerateMipmap",
"org/lwjgl/opengl/EXTFramebufferObject | glGenerateMipmapEXT | (I)V | glGenerateMipmap",
"org/lwjgl/opengl/GL11 | glReadPixels | (IIIIIILjava/nio/IntBuffer;)V | glReadPixels",
"org/lwjgl/opengl/ARBOcclusionQuery | glGenQueriesARB | (Ljava/nio/IntBuffer;)V | glGenQueries"
})
void aliasIsRedirected(String owner, String name, String desc, String expectedName) {
assertEquals(desc, assertRedirected(owner, name, desc, expectedName).desc);
}
@Test
void pixelPackBufferReadbackOverloadsHaveDeclaredTargets() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{
{"org/lwjgl/opengl/GL11", "glReadPixels", "(IIIIIIJ)V"},
{"org/lwjgl/opengl/GL11C", "glReadPixels", "(IIIIIIJ)V"},
{"org/lwjgl/opengl/GL11", "glGetTexImage", "(IIIIJ)V"},
});
}
@Test
void utilCheckGLErrorIsRedirected() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{{"org/lwjgl/opengl/Util", "checkGLError", "()V"}});
}
@Test
void indexedGettersHaveDeclaredTargets() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{
{"org/lwjgl/opengl/GL30", "glGetInteger", "(II)I"},
{"org/lwjgl/opengl/GL31C", "glGetIntegeri", "(II)I"},
{"org/lwjgl/opengl/GL30", "glGetIntegeri_v", "(IILjava/nio/IntBuffer;)V"},
{"org/lwjgl/opengl/GL30", "glGetBooleani_v", "(IILjava/nio/ByteBuffer;)V"},
});
}
private MethodInsnNode assertRedirected(String owner, String name, String desc, String expectedName) {
final ClassNode cn = makeClassWithMethodCall(owner, name, desc);
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
@@ -277,80 +227,101 @@ 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/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",
})
void aliasIsRedirected(String owner, String name, String desc, String expectedName) {
assertEquals(desc, assertRedirected(owner, name, desc, 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",
})
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 +333,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 +377,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 +386,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 +394,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 +412,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");
@@ -224,6 +224,11 @@ public final class FfpFixture {
assertTrue(coveredPixels > base.length / 8, "base draw covered only " + coveredPixels + " of " + base.length + " pixels");
}
public static void activateFfp() {
ShaderManager.enable();
ShaderManager.getInstance().activate();
}
public static void resetFfpState() {
GLStateManager.setFfpInstancing(Instancing.NONE);
final ShaderManager sm = ShaderManager.getInstance();
@@ -1379,6 +1379,37 @@ public class SDLGPURenderBackend extends RenderBackend {
resourceManager.markBufferContentsDefined(dstGlId);
readbackShadows.invalidate(dstGlId, writeOffset);
final ByteBuffer dstUboShadow = resourceManager.getUboShadow(dstGlId);
if (dstUboShadow != null) {
final ByteBuffer dstSlice = dstUboShadow.duplicate();
dstSlice.position((int) writeOffset).limit((int) (writeOffset + size));
if (!readUboStore(srcGlId, readOffset, dstSlice, (int) size)) {
final long srcHandle = resourceManager.getBufferHandle(srcGlId);
if (srcHandle != 0) downloadBufferBlocking(srcHandle, (int) readOffset, (int) size, dstSlice);
}
s().uboRangeGen++;
return;
}
if (resourceManager.getUboShadow(srcGlId) != null) {
final long dstHandle = resourceManager.getBufferHandle(dstGlId);
if (dstHandle != 0) {
final ByteBuffer buf = MemoryUtil.memAlloc((int) size);
try {
if (readUboStore(srcGlId, readOffset, buf, (int) size)) {
buf.position(0).limit((int) size);
if (s().deferUploads) {
enqueuePreCopied(buf, dstHandle, writeOffset, false);
} else if (frameManager.getCommandBuffer() != 0) {
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), buf, dstHandle, writeOffset, false);
}
}
} finally {
MemoryUtil.memFree(buf);
}
return;
}
}
if (s().deferUploads) {
final long dstHandle = resourceManager.getBufferHandle(dstGlId);
if (dstHandle != 0) {
@@ -2998,6 +3029,7 @@ public class SDLGPURenderBackend extends RenderBackend {
final ByteBuffer shadow = resourceManager.getOrAllocUboShadow(glId, (int) size);
shadow.position(0).limit((int) size);
for (int i = 0; i < (int) size; i += 8) shadow.putLong(i, 0L);
cs.uboRangeGen++;
}
return;
}
@@ -3053,6 +3085,7 @@ public class SDLGPURenderBackend extends RenderBackend {
shadow.position(0).limit((int) dataSize);
shadow.put(src);
shadow.position(0).limit((int) dataSize);
cs.uboRangeGen++;
}
return;
}
@@ -3138,12 +3171,13 @@ public class SDLGPURenderBackend extends RenderBackend {
if (target == GL31.GL_UNIFORM_BUFFER) {
final ByteBuffer shadow = resourceManager.getUboShadow(glId);
if (shadow != null) {
if (offset < 0 || offset + data.remaining() > shadow.capacity()) {
if (offset < 0 || offset + data.remaining() > resourceManager.getBufferSize(glId)) {
warnInvalidBufferOp("glBufferSubData out of range on uniform buffer", glId);
return;
}
shadow.position(0);
ByteRegionCopy.copyByteRegion(data, data.position(), shadow, (int) offset, data.remaining());
cs.uboRangeGen++;
}
return;
}
@@ -3252,6 +3286,7 @@ public class SDLGPURenderBackend extends RenderBackend {
shadow.put(staging);
shadow.position(0).limit(shadow.capacity());
resourceManager.markBufferContentsDefined(glId);
cs.uboRangeGen++;
}
MemoryUtil.memFree(staging);
return true;
@@ -3340,6 +3375,10 @@ public class SDLGPURenderBackend extends RenderBackend {
private void seedMappedStaging(int target, int glId, long offset, long length, ByteBuffer staging) {
final int len = (int) length;
if (readUboStore(glId, offset, staging, len)) {
staging.position(0).limit(len);
return;
}
final long handle = resourceManager.getBufferHandle(glId);
if (handle == 0 || resourceManager.hasUndefinedContents(glId)) {
@@ -3348,7 +3387,6 @@ public class SDLGPURenderBackend extends RenderBackend {
}
final ByteBuffer direct = switch (target) {
case GL31.GL_UNIFORM_BUFFER -> resourceManager.getUboShadow(glId);
case GL30.GL_PIXEL_UNPACK_BUFFER -> resourceManager.getPboStaging(glId);
default -> null;
};
@@ -3452,9 +3490,10 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void getBufferSubData(int target, long offset, ByteBuffer data) {
final int glId = getBoundBuffer(target);
if (glId == 0 || data == null) return;
final int rem = data.remaining();
if (readUboStore(glId, offset, data, rem)) return;
final long handle = resourceManager.getBufferHandle(glId);
if (handle == 0) return;
final int rem = data.remaining();
if (readbackShadows.serve(glId, offset, data, rem)) return;
if (readBufferRangeCpuFirst(target, glId, handle, offset, data, rem)) return;
@@ -3462,6 +3501,19 @@ public class SDLGPURenderBackend extends RenderBackend {
downloadBufferBlocking(handle, (int) offset, rem, data);
}
private boolean readUboStore(int glId, long offset, ByteBuffer dst, int len) {
final ByteBuffer shadow = resourceManager.getUboShadow(glId);
if (shadow == null) return false;
final PersistentMapping pm = resourceManager.getPersistentMapping(glId);
if (pm != null && pm.covers(offset, len)) {
serveFromShadow(pm.staging, (int) pm.stagingIndex(offset), dst, len);
return true;
}
if (offset < 0 || offset + len > resourceManager.getBufferSize(glId)) return false;
serveFromShadow(shadow, (int) offset, dst, len);
return true;
}
private void downloadBufferBlocking(long handle, int offset, int size, ByteBuffer out) {
if (size <= 0) return;
flushAndSubmitMidFrame();
@@ -4237,10 +4289,19 @@ public class SDLGPURenderBackend extends RenderBackend {
warnInvalidBufferOp("glClearBufferSubData out of range", glId);
return;
}
final int elemBytes = Math.max(1, PixelOps.glPixelSize(format, type));
final ByteBuffer uboShadow = resourceManager.getUboShadow(glId);
if (uboShadow != null) {
for (int i = 0; i < (int) size; i++) {
final int e = i % elemBytes;
uboShadow.put((int) offset + i, data != null && e < data.remaining() ? data.get(data.position() + e) : 0);
}
s().uboRangeGen++;
return;
}
final long handle = resourceManager.getBufferHandle(glId);
if (handle == 0 || frameManager.getCommandBuffer() == 0) return;
final int elemBytes = Math.max(1, PixelOps.glPixelSize(format, type));
final boolean defer = s().deferUploads && target != GL43.GL_SHADER_STORAGE_BUFFER;
// Undeferred clears stage one large pattern and upload it repeatedly
final int chunkBytes = (int) Math.min(size, defer ? CLEAR_CHUNK_BYTES : FILL_CHUNK_BYTES);
@@ -135,15 +135,6 @@ class PresenterTest {
assertEquals(before, fm.registeredFrameCount(), "the splash present must not touch the calling thread's frame");
}
@Test
void tryRequestEnqueuesWhenIdle() {
final DeferredExecutor executor = new DeferredExecutor();
final Presenter p = presenter(executor);
assertTrue(p.tryRequestPresent(1L, 10, 10, SDL_FLIP_NONE));
assertEquals(1, executor.tasks.size());
}
@Test
void tryRequestDropsWithoutBlockingWhileAPresentIsPending() {
final DeferredExecutor executor = new DeferredExecutor();
@@ -7,6 +7,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
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 org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
@@ -181,6 +182,7 @@ class GlsmSdlBufferMappingLifetimeTest {
final int ubo = GLStateManager.glGenBuffers();
final ByteBuffer data = MemoryUtil.memAlloc(256);
final ByteBuffer big = MemoryUtil.memAlloc(64);
ByteBuffer out = null;
try {
fill(data, 6);
fill(big, 7);
@@ -194,13 +196,73 @@ class GlsmSdlBufferMappingLifetimeTest {
big.clear();
GLStateManager.glBufferSubData(GL31.GL_UNIFORM_BUFFER, -8, big);
});
out = readBack(GL31.GL_UNIFORM_BUFFER, 256);
assertPattern(out, 6);
} finally {
if (out != null) MemoryUtil.memFree(out);
MemoryUtil.memFree(data);
MemoryUtil.memFree(big);
GLStateManager.glDeleteBuffers(ubo);
}
}
@Test
void uniformBufferMapForReadSeesItsContents() {
final int ubo = GLStateManager.glGenBuffers();
final ByteBuffer data = MemoryUtil.memAlloc(256);
try {
fill(data, 7);
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, ubo);
GLStateManager.glBufferData(GL31.GL_UNIFORM_BUFFER, data, GL15.GL_DYNAMIC_DRAW);
final ByteBuffer mapped = GLStateManager.glMapBufferRange(GL31.GL_UNIFORM_BUFFER, 0, 256, GL30.GL_MAP_READ_BIT);
assertNotNull(mapped);
assertPattern(mapped, 7);
assertTrue(GLStateManager.glUnmapBuffer(GL31.GL_UNIFORM_BUFFER));
} finally {
MemoryUtil.memFree(data);
GLStateManager.glDeleteBuffers(ubo);
}
}
@Test
void copyBufferSubDataMovesBytesIntoAndOutOfAUniformBuffer() {
final int src = GLStateManager.glGenBuffers();
final int ubo = GLStateManager.glGenBuffers();
final int dst = GLStateManager.glGenBuffers();
final ByteBuffer srcData = MemoryUtil.memAlloc(256);
final ByteBuffer uboData = MemoryUtil.memAlloc(256);
ByteBuffer outUbo = null;
ByteBuffer outDst = null;
try {
fill(srcData, 5);
fill(uboData, 6);
GLStateManager.glBindBuffer(GL31.GL_COPY_READ_BUFFER, src);
GLStateManager.glBufferData(GL31.GL_COPY_READ_BUFFER, srcData, GL15.GL_STATIC_DRAW);
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, ubo);
GLStateManager.glBufferData(GL31.GL_UNIFORM_BUFFER, uboData, GL15.GL_DYNAMIC_DRAW);
GLStateManager.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_UNIFORM_BUFFER, 64, 64, 64);
outUbo = readBack(GL31.GL_UNIFORM_BUFFER, 256);
for (int i = 0; i < 256; i++) {
final boolean copied = i >= 64 && i < 128;
assertEquals(copied ? srcData.get(i) : uboData.get(i), outUbo.get(i), (copied ? "copied" : "untouched") + " byte " + i);
}
GLStateManager.glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, dst);
GLStateManager.glBufferData(GL31.GL_COPY_WRITE_BUFFER, 64L, GL15.GL_STATIC_DRAW);
GLStateManager.glCopyBufferSubData(GL31.GL_UNIFORM_BUFFER, GL31.GL_COPY_WRITE_BUFFER, 0, 0, 64);
outDst = readBack(GL31.GL_COPY_WRITE_BUFFER, 64);
assertPattern(outDst, 6);
} finally {
if (outUbo != null) MemoryUtil.memFree(outUbo);
if (outDst != null) MemoryUtil.memFree(outDst);
MemoryUtil.memFree(srcData);
MemoryUtil.memFree(uboData);
GLStateManager.glDeleteBuffers(src);
GLStateManager.glDeleteBuffers(ubo);
GLStateManager.glDeleteBuffers(dst);
}
}
@Test
void outOfRangeCopyBufferSubDataIsIgnored() {
final int src = GLStateManager.glGenBuffers();
@@ -227,24 +289,25 @@ class GlsmSdlBufferMappingLifetimeTest {
}
}
@Test
void outOfRangeClearBufferSubDataIsIgnored() {
@ParameterizedTest(name = "target {0}")
@ValueSource(ints = {GL43.GL_SHADER_STORAGE_BUFFER, GL31.GL_UNIFORM_BUFFER})
void outOfRangeClearBufferSubDataIsIgnored(int target) {
final int clearStart = 64;
final int clearEnd = 96;
final int ssbo = GLStateManager.glGenBuffers();
final int buffer = GLStateManager.glGenBuffers();
final ByteBuffer data = MemoryUtil.memAlloc(256);
ByteBuffer out = null;
try {
fill(data, 8);
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, ssbo);
GLStateManager.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, data, GL15.GL_DYNAMIC_DRAW);
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, clearStart, clearEnd - clearStart, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, 200, 128, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, -4, 16, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, 1024, 16, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
GLStateManager.glBindBuffer(target, buffer);
GLStateManager.glBufferData(target, data, GL15.GL_DYNAMIC_DRAW);
GLStateManager.glClearBufferSubData(target, GL30.GL_R8, clearStart, clearEnd - clearStart, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
GLStateManager.glClearBufferSubData(target, GL30.GL_R8, 200, 128, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
GLStateManager.glClearBufferSubData(target, GL30.GL_R8, -4, 16, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
GLStateManager.glClearBufferSubData(target, GL30.GL_R8, 1024, 16, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
GlsmSdlHeadlessRig.endFrame();
GlsmSdlHeadlessRig.beginFrame();
out = readBack(GL43.GL_SHADER_STORAGE_BUFFER, 256);
out = readBack(target, 256);
for (int i = 0; i < 256; i++) {
final boolean cleared = i >= clearStart && i < clearEnd;
assertEquals(cleared ? 0 : data.get(i), out.get(i), cleared ? "in-range clear did not zero byte " + i : "out-of-range clear touched byte " + i);
@@ -252,7 +315,7 @@ class GlsmSdlBufferMappingLifetimeTest {
} finally {
if (out != null) MemoryUtil.memFree(out);
MemoryUtil.memFree(data);
GLStateManager.glDeleteBuffers(ssbo);
GLStateManager.glDeleteBuffers(buffer);
}
}
@@ -0,0 +1,85 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
import com.gtnewhorizons.angelica.glsm.ffp.LogicOpFixture;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL31;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.describe;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.pixelAt;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlExternalUboPushTest {
private static final String VERT = """
#version 330 core
layout(location = 0) in vec3 a_Position;
void main() {
gl_Position = vec4(a_Position, 1.0);
}
""";
private static final String FRAG = """
#version 330 core
layout(std140) uniform Tint {
vec4 color;
};
out vec4 fragColor;
void main() {
fragColor = color;
}
""";
private static final int BINDING = 2;
private static final int[] UNUSED_COLOR = LogicOpFixture.SOURCES[0];
private int program;
private int ubo;
@AfterEach
void cleanup() {
GLStateManager.glUseProgram(0);
GLStateManager.glBindBufferBase(GL31.GL_UNIFORM_BUFFER, BINDING, 0);
if (ubo != 0) GLStateManager.glDeleteBuffers(ubo);
if (program != 0) GLStateManager.glDeleteProgram(program);
LogicOpFixture.deleteResources();
}
@Test
void subDataBetweenDrawsReachesTheSecondDraw() {
program = GlTestSupport.program(VERT, FRAG);
GLStateManager.glUniformBlockBinding(program, GLStateManager.glGetUniformBlockIndex(program, "Tint"), BINDING);
ubo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, ubo);
GLStateManager.glBufferData(GL31.GL_UNIFORM_BUFFER, color(1f, 0f, 0f), GL15.GL_DYNAMIC_DRAW);
GLStateManager.glBindBufferBase(GL31.GL_UNIFORM_BUFFER, BINDING, ubo);
GLStateManager.glUseProgram(program);
LogicOpFixture.quad(UNUSED_COLOR);
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, ubo);
GLStateManager.glBufferSubData(GL31.GL_UNIFORM_BUFFER, 0, color(0f, 1f, 0f));
LogicOpFixture.quad(UNUSED_COLOR);
final int got = pixelAt(GlsmSdlHeadlessRig.readTarget(), SIZE, SIZE / 2, SIZE / 2);
assertEquals(0xFF00FF00, got, () -> "second draw must see the sub-data written after the first: " + describe(got));
}
private static FloatBuffer color(float r, float g, float b) {
final FloatBuffer buf = BufferUtils.createFloatBuffer(4);
buf.put(r).put(g).put(b).put(1f).flip();
return buf;
}
}
@@ -155,11 +155,7 @@ class GlsmSdlWideLineTest {
void wideLineCoversItsFirstPixelButNotItsLast() {
final float y = pixelCenter(16);
GLStateManager.glLineWidth(2.0f);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
GLStateManager.glBegin(GL11.GL_LINES);
GLStateManager.glVertex3f(pixelCenter(40), y, 0.0f);
GLStateManager.glVertex3f(pixelCenter(20), y, 0.0f);
GLStateManager.glEnd();
line(new float[] { pixelCenter(40), y, 0.0f }, new float[] { pixelCenter(20), y, 0.0f });
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
assertEquals(2, litInColumn(pixels, 40), "first pixel of a right-to-left line");
@@ -11,7 +11,6 @@ import org.lwjgl.util.spvc.Spvc;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class PerFrameBlockDedupTest {
@@ -54,11 +53,13 @@ class PerFrameBlockDedupTest {
st.boundProgramObj = a;
applier.putUniform(st, 0, new float[]{0.75f});
assertEquals(0.75f, st.uniformBlocks[0].staging(BLOCK_SIZE).get(16 >> 2));
st.uniformBlocks[0].dirty = false;
st.boundProgramObj = b;
applier.putUniform(st, 0, new float[]{0.25f});
assertTrue(st.uniformBlocks[0].dirty, "a genuinely new value must still reach the buffer");
assertEquals(0.25f, st.uniformBlocks[0].staging(BLOCK_SIZE).get(16 >> 2), "the new value must be in the staging buffer");
}
@Test
@@ -84,19 +85,6 @@ class PerFrameBlockDedupTest {
assertFalse(st.uniformBlocks[0].dirty, "per-frame block untouched");
}
@Test
void dedupDoesNotDropTheValueItself() {
st.boundProgramObj = programWithBlockMemberAt(16, ShaderManager.BLOCK_PER_FRAME);
applier.putUniform(st, 0, new float[]{0.75f});
final float first = st.uniformBlocks[0].staging(BLOCK_SIZE).get(16 >> 2);
st.boundProgramObj = programWithBlockMemberAt(16, ShaderManager.BLOCK_PER_FRAME);
applier.putUniform(st, 0, new float[]{0.25f});
final float second = st.uniformBlocks[0].staging(BLOCK_SIZE).get(16 >> 2);
assertNotEquals(first, second, "the new value must be in the staging buffer");
}
@Test
@Tag("sdl-gpu-device")
void flushUniformBlocksEndsTheCopyPassOnceForTwoDirtyBlocks() throws Exception {
@@ -6,6 +6,8 @@ import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
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.GL11;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.util.spvc.Spvc;
@@ -58,6 +60,11 @@ class PipelineCacheVertexInputTest {
return out;
}
private static PipelineCache fp32Cache(int shaderInputMask, int... vecSizesByLocation) {
final int[] vecSizes = vecSizes(vecSizesByLocation);
return cache(shaderInputMask, vecSizes, fp32(vecSizes));
}
private static PipelineCache cache(int shaderInputMask, int[] vecSizes, int[] baseTypes) {
return cache(nextTestSetId++, shaderInputMask, vecSizes, baseTypes);
}
@@ -110,9 +117,7 @@ class PipelineCacheVertexInputTest {
}
private static PipelineCache coercionCache(String name) {
final int[] vecSizes = vecSizes(4);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(1, vecSizes, baseTypes);
final PipelineCache c = fp32Cache(1, 4);
final String[] names = new String[ContextState.MAX_VERTEX_ATTRIBS];
names[0] = name;
c.shaderInputName = names;
@@ -192,9 +197,7 @@ class PipelineCacheVertexInputTest {
@Test
void sparseLayout_locations0and2_buildsDenseBindingArray() {
final int[] vecSizes = vecSizes(3, 0, 2);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(0x5, vecSizes, baseTypes);
final PipelineCache c = fp32Cache(0x5, 3, 0, 2);
final ContextState cs = new ContextState();
enableFloatAttrib(cs, 0, 3, 20);
@@ -246,9 +249,7 @@ class PipelineCacheVertexInputTest {
@Test
void correctedDhGenericLayout_buildsSuccessfully() {
final int[] vecSizes = vecSizes(3, 4);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(0x3, vecSizes, baseTypes);
final PipelineCache c = fp32Cache(0x3, 3, 4);
final ContextState cs = new ContextState();
enableFloatAttrib(cs, 0, 3, 12);
@@ -261,9 +262,7 @@ class PipelineCacheVertexInputTest {
@Test
void denseLayout_locations0_1_2_noPlaceholders() {
final int[] vecSizes = vecSizes(3, 4, 2);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(0x7, vecSizes, baseTypes);
final PipelineCache c = fp32Cache(0x7, 3, 4, 2);
final ContextState cs = new ContextState();
enableFloatAttrib(cs, 0, 3, 24);
@@ -283,9 +282,7 @@ class PipelineCacheVertexInputTest {
@Test
void shaderExpectsButVaoMissing_emitsConstantBinding() {
final int[] vecSizes = vecSizes(3, 4);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(0x3, vecSizes, baseTypes);
final PipelineCache c = fp32Cache(0x3, 3, 4);
final ContextState cs = new ContextState();
enableFloatAttrib(cs, 0, 3, 12);
@@ -305,9 +302,7 @@ class PipelineCacheVertexInputTest {
@Test
void constantBindingUsesZeroPitchExceptOnMetal() {
final int[] vecSizes = vecSizes(0, 0, 0, 0, 3);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(1 << 4, vecSizes, baseTypes);
final PipelineCache c = fp32Cache(1 << 4, 0, 0, 0, 0, 3);
final ContextState cs = new ContextState();
for (String driver : new String[] { "vulkan", "metal", "direct3d12" }) {
@@ -323,9 +318,7 @@ class PipelineCacheVertexInputTest {
@Test
void vaoExtraAttribNotInShader_droppedFromPipeline() {
final int[] vecSizes = vecSizes(4, 0, 4);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(0x5, vecSizes, baseTypes); // shaderInputMask = 0x5
final PipelineCache c = fp32Cache(0x5, 4, 0, 4); // shaderInputMask = 0x5
final ContextState cs = new ContextState();
enableFloatAttrib(cs, 0, 3, 24);
@@ -359,9 +352,7 @@ class PipelineCacheVertexInputTest {
@Test
void interiorGapWithoutExpansion_fillsGapWithPlaceholder() {
final int[] vecSizes = vecSizes(3, 0, 0, 2);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(0x9, vecSizes, baseTypes);
final PipelineCache c = fp32Cache(0x9, 3, 0, 0, 2);
final ContextState cs = new ContextState();
enableFloatAttrib(cs, 0, 3, 20);
@@ -391,9 +382,7 @@ class PipelineCacheVertexInputTest {
@Test
void enabledAttribWithoutPointer_treatedAsDisabled() {
final int[] vecSizes = vecSizes(3, 4, 0, 2);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(0xB, vecSizes, baseTypes); // locs 0, 1, 3
final PipelineCache c = fp32Cache(0xB, 3, 4, 0, 2); // locs 0, 1, 3
final ContextState cs = new ContextState();
enableFloatAttrib(cs, 0, 3, 16);
@@ -442,9 +431,7 @@ class PipelineCacheVertexInputTest {
@Test
void enabledAttribWithDeadBuffer_demotedToConstantExpansion() {
final int[] vecSizes = vecSizes(3, 4);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(0x3, vecSizes, baseTypes);
final PipelineCache c = fp32Cache(0x3, 3, 4);
store.setBufferHandleResolver(id -> id == 42 ? 0xBEEFL : 0L);
final ContextState cs = new ContextState();
@@ -466,9 +453,7 @@ class PipelineCacheVertexInputTest {
@Test
void pipelineKeyDiffersBetweenDeadAndLiveBinding() {
final int[] vecSizes = vecSizes(3, 4);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(0x3, vecSizes, baseTypes);
final PipelineCache c = fp32Cache(0x3, 3, 4);
final ContextState cs = new ContextState();
enableFloatAttrib(cs, 0, 3, 16);
@@ -534,9 +519,7 @@ class PipelineCacheVertexInputTest {
@Test
void bindingsArrayIsDenseIndexedBySlot() {
// D3D12 requirement: SDL's D3D12 backend reads vertex_buffer_descriptions[buffer_slot] as a flat array, so bindings.get(i).slot() must equal i
final int[] vecSizes = vecSizes(3, 0, 0, 0, 0, 2);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(0x21, vecSizes, baseTypes);
final PipelineCache c = fp32Cache(0x21, 3, 0, 0, 0, 0, 2);
final ContextState cs = new ContextState();
enableFloatAttrib(cs, 0, 3, 12);
@@ -552,11 +535,10 @@ class PipelineCacheVertexInputTest {
}
}
@Test
void offsetOnlyPointerChange_keepsKeyAndSkipsRecompute() {
final int[] vecSizes = vecSizes(3);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(0x1, vecSizes, baseTypes);
@ParameterizedTest(name = "second pointer offset {0}")
@ValueSource(longs = {104L, 100L})
void reissuedPointerWithSameAlignment_keepsKeyAndSkipsRecompute(long secondOffset) {
final PipelineCache c = fp32Cache(0x1, 3);
final ContextState cs = new ContextState();
final VertexAttribs attribs = new VertexAttribs();
@@ -565,38 +547,16 @@ class PipelineCacheVertexInputTest {
final long keyA = c.computeKey(store, cs);
final int runsAfterA = c.inputHashLoopRuns;
attribs.applyVertexAttribPointer(store, cs, 0, 3, GL11.GL_FLOAT, false, false, 12, 104L);
attribs.applyVertexAttribPointer(store, cs, 0, 3, GL11.GL_FLOAT, false, false, 12, secondOffset);
final long keyB = c.computeKey(store, cs);
assertEquals(keyA, keyB, "an offset change that keeps the alignment class must not change the pipeline key");
assertEquals(runsAfterA, c.inputHashLoopRuns, "an offset-only change must not force the input-hash loop to rerun");
}
@Test
void identicalReissuedPointer_isNoOp() {
final int[] vecSizes = vecSizes(3);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(0x1, vecSizes, baseTypes);
final ContextState cs = new ContextState();
final VertexAttribs attribs = new VertexAttribs();
attribs.applyVertexAttribPointer(store, cs, 0, 3, GL11.GL_FLOAT, false, false, 12, 100L);
final long keyA = c.computeKey(store, cs);
final int runsAfterA = c.inputHashLoopRuns;
attribs.applyVertexAttribPointer(store, cs, 0, 3, GL11.GL_FLOAT, false, false, 12, 100L);
final long keyB = c.computeKey(store, cs);
assertEquals(keyA, keyB, "an identical re-issued pointer must not change the key");
assertEquals(runsAfterA, c.inputHashLoopRuns, "an identical re-issued pointer must be a no-op and not force a rehash");
assertEquals(keyA, keyB, "a re-issued pointer that keeps the alignment class must not change the pipeline key");
assertEquals(runsAfterA, c.inputHashLoopRuns, "a re-issued pointer that keeps the alignment class must not force the input-hash loop to rerun");
}
@Test
void twoEqualLayoutVaosShareKey() {
final int[] vecSizes = vecSizes(3, 4);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(0x3, vecSizes, baseTypes);
final PipelineCache c = fp32Cache(0x3, 3, 4);
final ContextState csA = new ContextState();
enableFloatAttrib(csA, 0, 3, 28);
@@ -611,9 +571,7 @@ class PipelineCacheVertexInputTest {
@Test
void everyLayoutFieldChangesKey() {
final int[] vecSizes = vecSizes(3);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(0x1, vecSizes, baseTypes);
final PipelineCache c = fp32Cache(0x1, 3);
final ContextState base = new ContextState();
enableFloatAttrib(base, 0, 3, 16);
@@ -670,9 +628,7 @@ class PipelineCacheVertexInputTest {
@Test
void livenessGenBumpForcesRecompute() {
final int[] vecSizes = vecSizes(3);
final int[] baseTypes = fp32(vecSizes);
final PipelineCache c = cache(0x1, vecSizes, baseTypes);
final PipelineCache c = fp32Cache(0x1, 3);
final ContextState cs = new ContextState();
enableFloatAttrib(cs, 0, 3, 12);
@@ -8,11 +8,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class BufferContentsDefinedTest {
@Test
void unknownBuffer_isNotUndefined() {
assertFalse(SdlTestRig.resourceManager().hasUndefinedContents(4242));
}
@Test
void markDefined_clearsTheFlag() {
final ResourceManager rm = SdlTestRig.resourceManager();
@@ -11,14 +11,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
class BufferGlParamsTest {
@Test
void unknownBuffer_reportsGlDefaults() {
final ResourceManager rm = SdlTestRig.resourceManager();
assertEquals(GL15.GL_STATIC_DRAW, rm.getBufferGlUsage(4242));
assertEquals(BufferParams.MUTABLE_STORE, rm.getBufferStorageFlags(4242));
assertEquals(0L, rm.getBufferSize(4242));
}
@Test
void recordedParams_roundTrip() {
final ResourceManager rm = SdlTestRig.resourceManager();
@@ -30,15 +22,10 @@ class BufferGlParamsTest {
rm.recordBufferGlParams(2, GL15.GL_DYNAMIC_DRAW, flags);
assertEquals(GL15.GL_DYNAMIC_DRAW, rm.getBufferGlUsage(2));
assertEquals(flags, rm.getBufferStorageFlags(2));
}
@Test
void recordedParams_overwrittenOnRespecification() {
final ResourceManager rm = SdlTestRig.resourceManager();
rm.recordBufferGlParams(3, GL15.GL_DYNAMIC_DRAW, GL30.GL_MAP_WRITE_BIT);
rm.recordBufferGlParams(3, GL15.GL_STATIC_DRAW, BufferParams.MUTABLE_STORE);
assertEquals(GL15.GL_STATIC_DRAW, rm.getBufferGlUsage(3));
assertEquals(BufferParams.MUTABLE_STORE, rm.getBufferStorageFlags(3));
rm.recordBufferGlParams(2, GL15.GL_STATIC_DRAW, BufferParams.MUTABLE_STORE);
assertEquals(GL15.GL_STATIC_DRAW, rm.getBufferGlUsage(2));
assertEquals(BufferParams.MUTABLE_STORE, rm.getBufferStorageFlags(2));
}
@Test
@@ -7,6 +7,8 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL15;
@@ -33,28 +35,12 @@ class BufferSizeGpuExactnessTest {
if (resourceManager != null) resourceManager.shutdown();
}
@Test
void subBucketSize_reportsRequestedNotBucketed() {
final int glId = 9001;
final long handle = resourceManager.createBuffer(glId, FormatMap.mapBufferUsage(GL15.GL_ARRAY_BUFFER), 1000);
@ParameterizedTest(name = "{0}")
@CsvSource({"sub-bucket,9001,1000", "bucket boundary,9002,4096"})
void createdBufferReportsRequestedSize(String name, int glId, long size) {
final long handle = resourceManager.createBuffer(glId, FormatMap.mapBufferUsage(GL15.GL_ARRAY_BUFFER), size);
assertNotEquals(0L, handle, "buffer creation failed");
assertEquals(1000L, resourceManager.getBufferSize(glId));
resourceManager.deleteBuffer(glId);
}
@Test
void bucketBoundarySize_isUnchanged() {
final int glId = 9002;
resourceManager.createBuffer(glId, FormatMap.mapBufferUsage(GL15.GL_ARRAY_BUFFER), 4096);
assertEquals(4096L, resourceManager.getBufferSize(glId));
resourceManager.deleteBuffer(glId);
}
@Test
void freshStore_reportsUndefinedContents() {
final int glId = 9005;
resourceManager.createBuffer(glId, FormatMap.mapBufferUsage(GL15.GL_ARRAY_BUFFER), 1000);
assertTrue(resourceManager.hasUndefinedContents(glId));
assertEquals(size, resourceManager.getBufferSize(glId));
resourceManager.deleteBuffer(glId);
}
@@ -63,6 +49,7 @@ class BufferSizeGpuExactnessTest {
final int usage = FormatMap.mapBufferUsage(GL15.GL_ARRAY_BUFFER);
final int first = 9006;
resourceManager.createBuffer(first, usage, 1000);
assertTrue(resourceManager.hasUndefinedContents(first));
resourceManager.markBufferContentsDefined(first);
resourceManager.deleteBuffer(first);
@@ -10,12 +10,6 @@ class ColorContentLifetimeTest {
private static final long TEX = 0xC010125L;
@Test
void freshTextureHasUndefinedContents() {
final ResourceManager rm = SdlTestRig.resourceManager();
assertFalse(rm.isTextureContentDefined(TEX), "never written, so the first pass must clear");
}
@Test
void markedTextureHasDefinedContents() {
final ResourceManager rm = SdlTestRig.resourceManager();
@@ -1,7 +1,6 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import java.util.concurrent.atomic.AtomicReference;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
@@ -12,18 +11,6 @@ import static org.junit.jupiter.api.Assertions.*;
class CrossThreadUploadCBIsolationTest {
@Test
void differentThreadsHaveDistinctFrameStates() throws Exception {
final FrameManager fm = SdlTestRig.frameManager();
final FrameManager.FrameState testThreadFs = fm.frame();
final AtomicReference<FrameManager.FrameState> workerFs = new AtomicReference<>();
TestThreads.run("worker-thread", () -> workerFs.set(fm.frame()));
assertNotNull(workerFs.get());
assertNotSame(testThreadFs, workerFs.get(), "each thread must get its own FrameManager.FrameState instance from the FrameManager ThreadLocal");
}
@Test
void clientThreadsRecordingsDoNotAppearInSplashThreadsState() throws Exception {
final FrameManager fm = SdlTestRig.frameManager();
@@ -23,11 +23,6 @@ class FormatMapBufferUsageTest {
assertEquals(expected, FormatMap.mapBufferUsage(GL15.GL_ELEMENT_ARRAY_BUFFER));
}
@Test
void storageBufferTarget_hasNoVertexUsage() {
assertEquals(0, FormatMap.mapBufferUsage(GL43.GL_SHADER_STORAGE_BUFFER) & SDL_GPU_BUFFERUSAGE_VERTEX);
}
@Test
void drawIndirectBufferTarget_singleIndirectFlag() {
assertEquals(SDL_GPU_BUFFERUSAGE_INDIRECT, FormatMap.mapBufferUsage(GL40.GL_DRAW_INDIRECT_BUFFER));
@@ -3,6 +3,11 @@ package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
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.assertFalse;
@@ -24,16 +29,6 @@ class FullCoverageClearDiscardTest {
return new ResourceManager.TextureMeta(0, 0, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, W, H, 1, 1, 0);
}
@Test
void fullTargetColorBlitDropsThePendingClear() {
final ContextState st = new ContextState();
FBOClearTracker.recordPendingColorClear(st, HANDLE, 1f, 0f, 0f, 1f);
assertTrue(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, meta(), true));
assertFalse(st.pendingColorTextures.contains(HANDLE), "pending clear must be dropped, not left queued");
assertFalse(st.pendingColorValues.containsKey(HANDLE));
}
@Test
void copyWithNoPendingClearDefinesTheDestination() {
final ContextState st = new ContextState();
@@ -59,32 +54,25 @@ class FullCoverageClearDiscardTest {
assertFalse(st.pendingDepthValues.containsKey(HANDLE) || st.pendingStencilValues.containsKey(HANDLE));
}
@Test
void partialCoverageKeepsTheClear() {
final int[][] partial = {
{ 0, 0, 0, W / 2, H }, // narrower
{ 0, 0, 0, W, H / 2 }, // shorter
{ 1, 0, 0, W, H }, // offset x
{ 0, 1, 0, W, H }, // offset y
{ 0, 0, 1, W, H }, // not level 0
};
for (int[] c : partial) {
final ContextState st = new ContextState();
FBOClearTracker.recordPendingColorClear(st, HANDLE, 1f, 0f, 0f, 1f);
assertFalse(tracker().discardPendingClearIfFullyCovered(st, HANDLE, c[0], c[1], c[2], c[3], c[4], meta(), true),
"x=" + c[0] + " y=" + c[1] + " level=" + c[2] + " w=" + c[3] + " h=" + c[4]);
assertTrue(st.pendingColorTextures.contains(HANDLE), "the clear must survive a partial overwrite");
}
private static Stream<Arguments> clearsThatSurvive() {
final ResourceManager.TextureMeta volume = new ResourceManager.TextureMeta(0, 0, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, W, H, 4, 1, 0);
return Stream.of(
Arguments.of("narrower", 0, 0, 0, W / 2, H, meta()),
Arguments.of("shorter", 0, 0, 0, W, H / 2, meta()),
Arguments.of("offset x", 1, 0, 0, W, H, meta()),
Arguments.of("offset y", 0, 1, 0, W, H, meta()),
Arguments.of("not level 0", 0, 0, 1, W, H, meta()),
Arguments.of("volume slice", 0, 0, 0, W, H, volume),
Arguments.of("missing meta", 0, 0, 0, W, H, null));
}
@Test
void volumeTextureKeepsTheClear() {
@ParameterizedTest(name = "{0}")
@MethodSource("clearsThatSurvive")
void partialCoverageKeepsTheClear(String name, int x, int y, int level, int w, int h, ResourceManager.TextureMeta meta) {
final ContextState st = new ContextState();
FBOClearTracker.recordPendingColorClear(st, HANDLE, 1f, 0f, 0f, 1f);
final ResourceManager.TextureMeta volume = new ResourceManager.TextureMeta(0, 0, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, W, H, 4, 1, 0);
assertFalse(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, volume, true),
"one slice of a volume is not full coverage");
assertTrue(st.pendingColorTextures.contains(HANDLE));
assertFalse(tracker().discardPendingClearIfFullyCovered(st, HANDLE, x, y, level, w, h, meta, true));
assertTrue(st.pendingColorTextures.contains(HANDLE), "the clear must survive a partial overwrite");
}
@Test
@@ -93,14 +81,6 @@ class FullCoverageClearDiscardTest {
assertFalse(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, meta(), true));
}
@Test
void missingMetaFallsBackToMaterializing() {
final ContextState st = new ContextState();
FBOClearTracker.recordPendingColorClear(st, HANDLE, 1f, 0f, 0f, 1f);
assertFalse(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, null, true));
assertTrue(st.pendingColorTextures.contains(HANDLE));
}
@Test
void discardBumpsMutationGenSoSamplerFlushReevaluates() {
final ContextState st = new ContextState();
@@ -110,5 +90,7 @@ class FullCoverageClearDiscardTest {
assertTrue(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, meta(), true));
assertEquals(before + 1, st.pendingMutationGen);
assertFalse(st.pendingColorTextures.contains(HANDLE), "pending clear must be dropped, not left queued");
assertFalse(st.pendingColorValues.containsKey(HANDLE));
}
}
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import it.unimi.dsi.fastutil.ints.IntArrayList;
@@ -32,7 +31,6 @@ class PersistentBufferSyncDirtyGateTest {
@Test
void gateStaysClosedUntilAWriteMarksARegionDirty() {
final SdlTestRig rig = SdlTestRig.create();
final Device device = rig.device;
final FrameManager fm = rig.frameManager;
final ResourceManager rm = rig.resourceManager;
final CountingSink sink = new CountingSink();
@@ -63,7 +61,6 @@ class PersistentBufferSyncDirtyGateTest {
@Test
void widenAndRemoveKeepTheCountExact() {
final SdlTestRig rig = SdlTestRig.create();
final Device device = rig.device;
final FrameManager fm = rig.frameManager;
final ResourceManager rm = rig.resourceManager;
final PersistentBufferSync sync = new PersistentBufferSync(fm, rm, new CountingSink());
@@ -87,7 +84,6 @@ class PersistentBufferSyncDirtyGateTest {
@Test
void mappingsVersionMovesOnlyWhenTheMapChanges() {
final SdlTestRig rig = SdlTestRig.create();
final Device device = rig.device;
final FrameManager fm = rig.frameManager;
final ResourceManager rm = rig.resourceManager;
@@ -122,7 +118,6 @@ class PersistentBufferSyncDirtyGateTest {
@Test
void repeatedDrainsKeepUploadingWhenTheMappingSetIsUnchanged() {
final SdlTestRig rig = SdlTestRig.create();
final Device device = rig.device;
final FrameManager fm = rig.frameManager;
final ResourceManager rm = rig.resourceManager;
final CountingSink sink = new CountingSink();
@@ -148,7 +143,6 @@ class PersistentBufferSyncDirtyGateTest {
@Test
void aReplacedMappingIsPickedUpByALaterDrain() {
final SdlTestRig rig = SdlTestRig.create();
final Device device = rig.device;
final FrameManager fm = rig.frameManager;
final ResourceManager rm = rig.resourceManager;
final CountingSink sink = new CountingSink();
@@ -178,7 +172,6 @@ class PersistentBufferSyncDirtyGateTest {
@Test
void concurrentWritesAndDrainsLeaveTheGateExact() throws Exception {
final SdlTestRig rig = SdlTestRig.create();
final Device device = rig.device;
final FrameManager fm = rig.frameManager;
final ResourceManager rm = rig.resourceManager;
final CountingSink sink = new CountingSink();
@@ -53,19 +53,6 @@ class PersistentDrainBoundaryTest {
}
}
@Test
void oneDrainCoversEveryRangeDirtiedSinceTheLastDrain() {
try (Fixture f = new Fixture()) {
for (int i = 0; i < 64; i++) f.sync.onPersistentBufferWrite(1, i * 188L, 188L);
f.sync.enqueueDirtyPersistentRegions();
assertEquals(1, f.sink.offsets.size(), "contiguous writes drain as a single upload");
assertEquals(0L, f.sink.offsets.getLong(0));
assertEquals(64L * 188L, f.sink.sizes.getLong(0), "the union spans every byte written since the last drain");
assertFalse(f.rm.hasDirtyPersistentRegions());
}
}
@Test
void theAccumulatedUnionMatchesThePerDrawSum() {
final long batched;
@@ -73,6 +60,10 @@ class PersistentDrainBoundaryTest {
for (int i = 0; i < 64; i++) f.sync.onPersistentBufferWrite(1, i * 188L, 188L);
f.sync.enqueueDirtyPersistentRegions();
batched = f.sink.totalBytes();
assertEquals(1, f.sink.offsets.size(), "contiguous writes drain as a single upload");
assertEquals(0L, f.sink.offsets.getLong(0));
assertEquals(64L * 188L, f.sink.sizes.getLong(0), "the union spans every byte written since the last drain");
assertFalse(f.rm.hasDirtyPersistentRegions());
}
try (Fixture f = new Fixture()) {
for (int i = 0; i < 64; i++) {
@@ -36,6 +36,7 @@ class PersistentMappingDirtyRangeTest {
assertEquals(16, PersistentMapping.rangeOffset(claimed));
assertEquals(32, PersistentMapping.rangeSize(claimed));
assertFalse(pm.isDirty(), "claiming leaves the mapping clean");
assertTrue(PersistentMapping.isClean(pm.claimDirty()), "second claim gets nothing");
} finally {
free(pm);
}
@@ -82,18 +83,6 @@ class PersistentMappingDirtyRangeTest {
}
}
@Test
void claimIsExclusive() {
final PersistentMapping pm = mapping(256);
try {
pm.markDirty(0, 8);
assertFalse(PersistentMapping.isClean(pm.claimDirty()), "first claim wins");
assertTrue(PersistentMapping.isClean(pm.claimDirty()), "second claim gets nothing");
} finally {
free(pm);
}
}
@Test
void concurrentWritersProduceExactlyOneTransitionPerClaim() throws Exception {
final PersistentMapping pm = mapping(4096);
@@ -26,15 +26,6 @@ class ReadbackShadowsTest {
return ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
}
@Test
void serveRefusesAStaleSlot() {
final ReadbackShadows shadows = shadows();
SdlReflect.putReadbackSlot(shadows, 7, 0, SENTINEL_HANDLE, 64, false);
assertFalse(shadows.serve(7, 0, out(64), 64),
"a slot invalidated by a GPU-to-GPU write must not be served as if it were downloaded");
}
@Test
void serveRefusesAnUnknownSlot() {
final ReadbackShadows shadows = shadows();
@@ -3,6 +3,12 @@ package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
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.function.Consumer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -21,70 +27,49 @@ class SDLGPUFboTargetCacheTest {
assertEquals(0L, fbo.primaryTarget);
}
@Test
void depthOnly_primaryTargetIsDepth() {
final FboState fbo = new FboState();
fbo.depthTexture = 0xDDDDL;
fbo.recomputeTargets();
assertFalse(fbo.hasAnyColor);
assertEquals(0xDDDDL, fbo.primaryTarget);
private static Arguments row(String name, Consumer<FboState> setup, boolean anyColor, long primary) {
return Arguments.of(name, setup, anyColor, primary);
}
@Test
void singleColor_primaryTargetIsColor() {
final FboState fbo = new FboState();
fbo.colorTextures[0] = 0xAAAAL;
fbo.depthTexture = 0xDDDDL;
// default drawBuffers = {0}
fbo.recomputeTargets();
assertTrue(fbo.hasAnyColor);
assertEquals(0xAAAAL, fbo.primaryTarget);
private static Stream<Arguments> recomputedTargets() {
return Stream.of(
row("depth only", f -> f.depthTexture = 0xDDDDL, false, 0xDDDDL),
row("single color over depth, default drawBuffers", f -> {
f.colorTextures[0] = 0xAAAAL;
f.depthTexture = 0xDDDDL;
}, true, 0xAAAAL),
row("disabled slot 0, first enabled drawBuffer is index 1", f -> {
f.colorTextures[0] = 0xAAAAL;
f.colorTextures[1] = 0xBBBBL;
f.drawBuffers = new int[]{-1, 1};
f.depthTexture = 0xDDDDL;
}, true, 0xBBBBL),
row("all drawBuffers disabled falls through to depth", f -> {
f.colorTextures[0] = 0xAAAAL;
f.drawBuffers = new int[]{-1};
f.depthTexture = 0xDDDDL;
}, false, 0xDDDDL),
row("MRT first color wins", f -> {
f.colorTextures[0] = 0xAAAAL;
f.colorTextures[1] = 0xBBBBL;
f.colorTextures[2] = 0xCCCCL;
f.drawBuffers = new int[]{0, 1, 2};
}, true, 0xAAAAL),
row("empty drawBuffers means no color even if bound", f -> {
f.colorTextures[0] = 0xAAAAL;
f.drawBuffers = new int[0];
f.depthTexture = 0xDDDDL;
}, false, 0xDDDDL));
}
@Test
void disabledDrawBuffer_doesNotCount() {
@ParameterizedTest(name = "{0}")
@MethodSource("recomputedTargets")
void recomputeTargets_picksPrimaryTarget(String name, Consumer<FboState> setup, boolean anyColor, long primary) {
final FboState fbo = new FboState();
fbo.colorTextures[0] = 0xAAAAL;
fbo.colorTextures[1] = 0xBBBBL;
fbo.drawBuffers = new int[]{-1, 1}; // GL_NONE then attachment 1
fbo.depthTexture = 0xDDDDL;
setup.accept(fbo);
fbo.recomputeTargets();
assertTrue(fbo.hasAnyColor);
assertEquals(0xBBBBL, fbo.primaryTarget, "first enabled drawBuffer is index 1");
}
@Test
void allDrawBuffersDisabled_falsThroughToDepth() {
final FboState fbo = new FboState();
fbo.colorTextures[0] = 0xAAAAL;
fbo.drawBuffers = new int[]{-1};
fbo.depthTexture = 0xDDDDL;
fbo.recomputeTargets();
assertFalse(fbo.hasAnyColor);
assertEquals(0xDDDDL, fbo.primaryTarget);
}
@Test
void mrtFirstColorWins_evenWhenMultipleAttached() {
final FboState fbo = new FboState();
fbo.colorTextures[0] = 0xAAAAL;
fbo.colorTextures[1] = 0xBBBBL;
fbo.colorTextures[2] = 0xCCCCL;
fbo.drawBuffers = new int[]{0, 1, 2};
fbo.recomputeTargets();
assertTrue(fbo.hasAnyColor);
assertEquals(0xAAAAL, fbo.primaryTarget);
}
@Test
void emptyDrawBuffers_noColorEvenIfBound() {
final FboState fbo = new FboState();
fbo.colorTextures[0] = 0xAAAAL;
fbo.drawBuffers = new int[0];
fbo.depthTexture = 0xDDDDL;
fbo.recomputeTargets();
assertFalse(fbo.hasAnyColor);
assertEquals(0xDDDDL, fbo.primaryTarget);
assertEquals(anyColor, fbo.hasAnyColor);
assertEquals(primary, fbo.primaryTarget);
}
@Test
@@ -3,8 +3,13 @@ package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
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.List;
import java.util.function.BiConsumer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -22,26 +27,28 @@ class TextureReleaseUploadOrderTest {
Reflect.setDeclared(ResourceManager.class, rm, "lastTextureUploadSeq", lastUpload);
}
@Test
void releaseIsImmediateWhenNoTransferThreadIsAttached() {
final ResourceManager rm = SdlTestRig.resourceManager();
setUploadSeqs(rm, 50L);
assertFalse(rm.shouldDeferTextureRelease(), "with no transfer thread there is nothing to outlive");
private static Stream<Arguments> releasePaths() {
final BiConsumer<ResourceManager, Integer> delete = ResourceManager::deleteTexture;
final BiConsumer<ResourceManager, Integer> realloc = ResourceManager::releaseTextureHandleForRealloc;
return Stream.of(
Arguments.of("glDeleteTextures", delete, 50L, 10L),
Arguments.of("glTexImage re-specification", realloc, 7L, 0L));
}
@Test
void releaseIsDeferredWhileTextureUploadsAreStillQueued() {
@ParameterizedTest(name = "{0}")
@MethodSource("releasePaths")
void releaseIsDeferredWhileTextureUploadsAreStillQueued(String path, BiConsumer<ResourceManager, Integer> release, long lastUpload, long submitted) {
final SdlTestRig rig = SdlTestRig.create();
final ResourceManager rm = rig.resourceManager;
final TransferThread tt = rig.installTransferThread();
setUploadSeqs(rm, 50L);
Reflect.setDeclared(TransferThread.class, tt, "submittedSeq", 10L);
setUploadSeqs(rm, lastUpload);
Reflect.setDeclared(TransferThread.class, tt, "submittedSeq", submitted);
rm.trackTextureHandle(HANDLE);
final int glId = rm.genTexture();
SdlReflect.putTextureHandle(rm, glId, HANDLE);
rm.deleteTexture(glId);
release.accept(rm, glId);
assertEquals(List.of(HANDLE), deferredReleases(rm), "the free must wait for the queued upload");
}
@@ -64,6 +71,7 @@ class TextureReleaseUploadOrderTest {
final SdlTestRig rig = SdlTestRig.create();
final ResourceManager rm = rig.resourceManager;
rig.frameManager.setResourceManager(rm);
setUploadSeqs(rm, 50L);
assertFalse(rm.shouldDeferTextureRelease(), "nothing queued yet");
rig.frameManager.frame().pendingTexHandles.add(HANDLE);
@@ -90,20 +98,4 @@ class TextureReleaseUploadOrderTest {
assertEquals(0xFEEDL, handles.getLong(1), "other destinations are untouched");
assertEquals(0L, handles.getLong(2));
}
@Test
void reallocReleaseObeysTheSameRule() {
final SdlTestRig rig = SdlTestRig.create();
final ResourceManager rm = rig.resourceManager;
final TransferThread tt = rig.installTransferThread();
setUploadSeqs(rm, 7L);
Reflect.setDeclared(TransferThread.class, tt, "submittedSeq", 0L);
rm.trackTextureHandle(HANDLE);
final int glId = rm.genTexture();
SdlReflect.putTextureHandle(rm, glId, HANDLE);
rm.releaseTextureHandleForRealloc(glId);
assertEquals(List.of(HANDLE), deferredReleases(rm), "glTexImage2D re-specification frees the old handle on the same path as a delete");
}
}
@@ -26,22 +26,12 @@ class TransferThreadWakePolicyTest {
}
@Test
void windowIsEvenlySpaced() {
long previous = -1;
void wakesExactlyOnEveryWindowBoundary() {
for (long seq = 1; seq <= 512; seq++) {
if (!TransferThread.shouldWakeOnEnqueue(seq)) continue;
if (previous >= 0) assertEquals(16, seq - previous, "gaps between unparks must be uniform");
previous = seq;
assertEquals(seq % 16 == 0, TransferThread.shouldWakeOnEnqueue(seq), "seq " + seq);
}
}
@Test
void neighboursOfAWakeDoNotWake() {
assertTrue(TransferThread.shouldWakeOnEnqueue(16));
assertFalse(TransferThread.shouldWakeOnEnqueue(15));
assertFalse(TransferThread.shouldWakeOnEnqueue(17));
}
private static TransferThread stoppedThread(long submittedSeq) {
final SdlTestRig rig = SdlTestRig.create();
final TransferThread tt = rig.newTransferThread();
@@ -76,6 +76,18 @@ class UploadArenaGpuRoundTripTest {
}
}
private static void uploadPattern(long dst, int size, int seed) {
final ByteBuffer data = pattern(size, seed);
assertTrue(resourceManager.arenaUpload(data, dst, 0, false));
MemoryUtil.memFree(data);
}
private static void assertDownloadedPattern(long gpuBuffer, int size, int seed, String what) {
final ByteBuffer got = download(gpuBuffer, size);
assertPattern(got, size, seed, what);
MemoryUtil.memFree(got);
}
private static ByteBuffer download(long gpuBuffer, int size) {
try (var stack = stackPush()) {
final SDL_GPUTransferBufferCreateInfo ci = SDL_GPUTransferBufferCreateInfo.calloc(stack)
@@ -125,15 +137,11 @@ class UploadArenaGpuRoundTripTest {
beginFrame();
final long dst = createVertexBuffer(size);
assertTrue(dst != 0);
final ByteBuffer data = pattern(size, 7);
assertTrue(resourceManager.arenaUpload(data, dst, 0, false));
MemoryUtil.memFree(data);
uploadPattern(dst, size, 7);
endFrame();
SDL_WaitForGPUIdle(sdlDevice);
final ByteBuffer got = download(dst, size);
assertPattern(got, size, 7, "single-frame upload");
MemoryUtil.memFree(got);
assertDownloadedPattern(dst, size, 7, "single-frame upload");
}
@Test
@@ -145,9 +153,7 @@ class UploadArenaGpuRoundTripTest {
beginFrame();
final long armor = createVertexBuffer(size);
assertTrue(armor != 0);
final ByteBuffer armorData = pattern(size, 3);
assertTrue(resourceManager.arenaUpload(armorData, armor, 0, false));
MemoryUtil.memFree(armorData);
uploadPattern(armor, size, 3);
final long junkDst = createVertexBuffer(junkSize);
endFrame();
@@ -171,9 +177,7 @@ class UploadArenaGpuRoundTripTest {
}
SDL_WaitForGPUIdle(sdlDevice);
final ByteBuffer got = download(armor, size);
assertPattern(got, size, 3, "upload after 60 frames of churn");
MemoryUtil.memFree(got);
assertDownloadedPattern(armor, size, 3, "upload after 60 frames of churn");
}
@Test
@@ -186,31 +190,23 @@ class UploadArenaGpuRoundTripTest {
// frame 66
beginFrame();
long big = createVertexBuffer(bigSize);
ByteBuffer bigData = pattern(bigSize, 41);
assertTrue(resourceManager.arenaUpload(bigData, big, 0, false));
MemoryUtil.memFree(bigData);
uploadPattern(big, bigSize, 41);
final long med = createVertexBuffer(medSize);
final ByteBuffer medData = pattern(medSize, 42);
assertTrue(resourceManager.arenaUpload(medData, med, 0, false));
MemoryUtil.memFree(medData);
uploadPattern(med, medSize, 42);
endFrame();
final int bigGlId = nextGlId - 2;
beginFrame();
resourceManager.deleteBuffer(bigGlId);
big = resourceManager.createBuffer(bigGlId, SDL_GPU_BUFFERUSAGE_VERTEX, bigSize);
bigData = pattern(bigSize, 43);
assertTrue(resourceManager.arenaUpload(bigData, big, 0, false));
MemoryUtil.memFree(bigData);
uploadPattern(big, bigSize, 43);
endFrame();
final long[] armor = new long[armorSizes.length];
beginFrame();
for (int i = 0; i < armorSizes.length; i++) {
armor[i] = createVertexBuffer(armorSizes[i]);
final ByteBuffer data = pattern(armorSizes[i], 50 + i);
assertTrue(resourceManager.arenaUpload(data, armor[i], 0, false));
MemoryUtil.memFree(data);
uploadPattern(armor[i], armorSizes[i], 50 + i);
}
endFrame();
@@ -218,17 +214,13 @@ class UploadArenaGpuRoundTripTest {
beginFrame();
resourceManager.deleteBuffer(bigGlId);
big = resourceManager.createBuffer(bigGlId, SDL_GPU_BUFFERUSAGE_VERTEX, bigSize);
bigData = pattern(bigSize, 60 + frame);
assertTrue(resourceManager.arenaUpload(bigData, big, 0, false));
MemoryUtil.memFree(bigData);
uploadPattern(big, bigSize, 60 + frame);
endFrame();
}
SDL_WaitForGPUIdle(sdlDevice);
for (int i = 0; i < armorSizes.length; i++) {
final ByteBuffer got = download(armor[i], armorSizes[i]);
assertPattern(got, armorSizes[i], 50 + i, "armor buffer " + i + " (size " + armorSizes[i] + ")");
MemoryUtil.memFree(got);
assertDownloadedPattern(armor[i], armorSizes[i], 50 + i, "armor buffer " + i + " (size " + armorSizes[i] + ")");
}
}
@@ -238,9 +230,7 @@ class UploadArenaGpuRoundTripTest {
final int size = 54864;
beginFrame();
final long a = createVertexBuffer(size);
final ByteBuffer dataA = pattern(size, 11);
assertTrue(resourceManager.arenaUpload(dataA, a, 0, false));
MemoryUtil.memFree(dataA);
uploadPattern(a, size, 11);
resourceManager.flushUploadArena();
final ByteBuffer readback = download(a, size);
@@ -252,9 +242,7 @@ class UploadArenaGpuRoundTripTest {
endFrame();
SDL_WaitForGPUIdle(sdlDevice);
final ByteBuffer got = download(b, size);
assertPattern(got, size, 11, "B built from mid-frame readback");
MemoryUtil.memFree(got);
assertDownloadedPattern(b, size, 11, "B built from mid-frame readback");
}
@Test
@@ -266,17 +254,13 @@ class UploadArenaGpuRoundTripTest {
beginFrame();
for (int i = 0; i < chunks; i++) {
dsts[i] = createVertexBuffer(chunk);
final ByteBuffer data = pattern(chunk, 20 + i);
assertTrue(resourceManager.arenaUpload(data, dsts[i], 0, false));
MemoryUtil.memFree(data);
uploadPattern(dsts[i], chunk, 20 + i);
}
endFrame();
SDL_WaitForGPUIdle(sdlDevice);
for (int i = 0; i < chunks; i++) {
final ByteBuffer got = download(dsts[i], chunk);
assertPattern(got, chunk, 20 + i, "grow-path upload " + i);
MemoryUtil.memFree(got);
assertDownloadedPattern(dsts[i], chunk, 20 + i, "grow-path upload " + i);
}
}
}
@@ -35,17 +35,6 @@ class UploadArenaTest {
assertFalse(a.fits(-1));
}
@Test
void resetClearsOffsetAndCountButKeepsCapacity() {
final UploadArena a = new UploadArena();
a.reserve(500);
a.reset();
assertEquals(0, a.usedBytes());
assertEquals(0, a.copyCount());
assertEquals(UploadArena.INITIAL_CAPACITY, a.capacity());
assertEquals(0, a.reserve(10));
}
@Test
void growIsDeferredUntilApplyGrow() {
final UploadArena a = new UploadArena();
@@ -92,6 +81,9 @@ class UploadArenaTest {
assertEquals(-1, a.reserve(4096));
a.requestGrow(want);
a.reset();
assertEquals(0, a.usedBytes());
assertEquals(0, a.copyCount());
assertEquals(UploadArena.INITIAL_CAPACITY, a.capacity());
assertTrue(a.applyGrow());
assertEquals(0, a.reserve(a.capacity()));
assertEquals(1, a.copyCount());
@@ -35,9 +35,9 @@ class UploadCBAutoFlushThresholdTest {
@Test
void manyTinyUploadsAccumulateBytes() {
final FrameManager fm = SdlTestRig.frameManager();
for (int i = 0; i < 255; i++) fm.recordUploadCommands(65536, 1);
for (int i = 0; i < 255; i++) fm.recordUploadCommands(65536, 0);
assertTrue(fm.frame().pendingUploadBytes < MAX_PENDING_UPLOAD_BYTES);
fm.recordUploadCommands(65536, 1);
fm.recordUploadCommands(65536, 0);
assertTrue(fm.shouldAutoSubmitPendingUpload());
}
@@ -59,19 +59,6 @@ class AttribLocationPatchTest {
assertEquals(DESIRED[i], prog.resolvedAttribLocations.getInt(NAMES[i]), () -> "resolved location for " + NAMES[idx]);
}
assertEquals(0x3f, prog.vertexInputMask, "six inputs at 0..5");
}
@Test
void metadataFollowsTheRemappedLocation() {
final ShaderManager.ProgramObject prog = new ShaderManager.ProgramObject();
prog.vertexSpirv = ByteBuffer.allocateDirect(NAMES.length * Integer.BYTES).order(ByteOrder.nativeOrder());
final ShaderManager.StageReflection vs = reflectionWithInputs(prog.vertexSpirv);
for (int i = 0; i < NAMES.length; i++) {
prog.attribLocationBindings.put(NAMES[i], DESIRED[i]);
}
ShaderManager.applyAttribLocationsAndInputMask(prog, vs);
assertEquals(4, prog.vertexInputVecSize[1]);
assertEquals(Spvc.SPVC_BASETYPE_FP32, prog.vertexInputBaseType[1]);
assertEquals(1, prog.vertexInputVecSize[5]);
@@ -52,6 +52,8 @@ class PerFrameBlockInjectorTest {
for (Member m : BLOCK.members()) {
assertTrue(out.contains(PerFrameBlockInjector.std140Type(m.type()) + " " + m.name() + ";"), "missing member " + m.name());
}
assertFalse(out.contains("uniform float rainStrength;"));
assertTrue(out.contains(" float rainStrength;"));
}
@Test
@@ -74,17 +76,12 @@ class PerFrameBlockInjectorTest {
assertTrue(out.contains("uniform float rainStrength, packOwnedValue;"));
}
@Test
void emptyMemberListIsANoOp() {
final String src = "#version 330 core\nvoid main() {}\n";
assertEquals(src, ShaderTransformChain.inject(src, new PerFrameUniformBlock(List.of()), null));
}
@Test
void noBlocksReturnsTheSourceInstance() {
final String src = "#version 330 core\nuniform float frameTimeCounter;\nvoid main() {}\n";
assertSame(src, ShaderTransformChain.inject(src, null, null));
assertSame(src, ShaderTransformChain.inject(src, new PerFrameUniformBlock(List.of()), new PerFrameUniformBlock(List.of())));
assertSame(src, ShaderTransformChain.inject(src, new PerFrameUniformBlock(List.of()), null));
}
@Test
@@ -161,18 +158,6 @@ class PerFrameBlockInjectorTest {
memFree(compileOrFail(out, Shaderc.shaderc_fragment_shader));
}
@Test
void uniformDeclaredWithTheMatchingTypeIsStillReplaced() {
final String out = ShaderTransformChain.inject("""
#version 330 core
uniform float rainStrength;
out vec4 fragColor;
void main() { fragColor = vec4(rainStrength); }
""", BLOCK, null);
assertFalse(out.contains("uniform float rainStrength;"));
assertTrue(out.contains(" float rainStrength;"));
}
@Test
void shaderReferencingNoMemberIsUntouched() {
final String src = """
@@ -61,132 +61,44 @@ class Std140WriterTest {
}
}
@Test
void scalarArray_writesEachAtStride16() {
final FloatBuffer ubo = outBuf(16);
try {
Std140Writer.write(ubo, array(/*off*/ 0, /*stride*/ 16, /*len*/ 4, 1, 1), new float[]{1, 2, 3, 4}, 4);
assertEq(1f, ubo.get(0), "elem0");
assertEq(0f, ubo.get(1), "elem0 pad");
assertEq(2f, ubo.get(4), "elem1");
assertEq(3f, ubo.get(8), "elem2");
assertEq(4f, ubo.get(12), "elem3");
} finally {
MemoryUtil.memFree(ubo);
}
private static float[] ramp(int n) {
final float[] out = new float[n];
for (int i = 0; i < n; i++) out[i] = i + 1;
return out;
}
@Test
void vec3Array_eachElementAtStride16_zeroPaddedFourthLane() {
final FloatBuffer ubo = outBuf(16);
try {
Std140Writer.write(ubo, array(/*off*/ 0, /*stride*/ 16, /*len*/ 4, 3, 1),
new float[]{1, 2, 3, 10, 20, 30, 100, 200, 300, -1, -2, -3}, 4);
assertEq(1f, ubo.get(0), "e0.x"); assertEq(2f, ubo.get(1), "e0.y"); assertEq(3f, ubo.get(2), "e0.z");
assertEq(0f, ubo.get(3), "e0.pad");
assertEq(10f, ubo.get(4), "e1.x"); assertEq(20f, ubo.get(5), "e1.y"); assertEq(30f, ubo.get(6), "e1.z");
assertEq(0f, ubo.get(7), "e1.pad");
assertEq(100f, ubo.get(8), "e2.x"); assertEq(200f, ubo.get(9), "e2.y"); assertEq(300f, ubo.get(10), "e2.z");
assertEq(0f, ubo.get(11), "e2.pad");
assertEq(-1f, ubo.get(12), "e3.x"); assertEq(-2f, ubo.get(13), "e3.y"); assertEq(-3f, ubo.get(14), "e3.z");
assertEq(0f, ubo.get(15), "e3.pad");
} finally {
MemoryUtil.memFree(ubo);
}
static Stream<Arguments> layouts() {
final float[] mat3Src = {1, 2, 3, 4, 5, 6, 7, 8, 9};
final float[] mat3Ubo = {1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0};
return Stream.of(
Arguments.of("scalar array at stride 16", array(0, 16, 4, 1, 1), new float[]{1, 2, 3, 4}, 4,
new float[]{1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0}),
Arguments.of("vec3 array zero-pads the fourth lane", array(0, 16, 4, 3, 1),
new float[]{1, 2, 3, 10, 20, 30, 100, 200, 300, -1, -2, -3}, 4,
new float[]{1, 2, 3, 0, 10, 20, 30, 0, 100, 200, 300, 0, -1, -2, -3, 0}),
Arguments.of("vec4 array needs no padding", array(0, 16, 2, 4, 1), ramp(8), 2, ramp(8)),
Arguments.of("mat2 columns are vec4 each", single(0, 32, 2, 2), new float[]{1, 2, 3, 4}, 1,
new float[]{1, 2, 0, 0, 3, 4, 0, 0}),
Arguments.of("mat3 columns padded to vec4", single(0, 48, 3, 3), mat3Src, 1, mat3Ubo),
Arguments.of("mat3 padded when reflection under-reports size", single(0, 36, 3, 3), mat3Src, 1, mat3Ubo),
Arguments.of("mat4 needs no padding", single(0, 64, 4, 4), ramp(16), 1, ramp(16)),
Arguments.of("mat3 array pads each matrix", array(0, 48, 2, 3, 3),
new float[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90}, 2,
new float[]{1, 2, 3, 0, 4, 5, 6, 0, 7, 8, 9, 0, 10, 20, 30, 0, 40, 50, 60, 0, 70, 80, 90, 0}),
Arguments.of("mat4 array at stride 64", array(0, 64, 2, 4, 4), ramp(32), 2, ramp(32)),
Arguments.of("srcCount clamped to array length", array(0, 16, 2, 1, 1), new float[]{1, 2, 3, 4}, 4,
new float[]{1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}),
Arguments.of("srcCount clamped to source size", array(0, 16, 4, 1, 1), new float[]{7, 8}, 4,
new float[]{7, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
}
@Test
void vec4Array_eachElementAtStride16_noPaddingNeeded() {
final FloatBuffer ubo = outBuf(8);
@ParameterizedTest(name = "{0}")
@MethodSource("layouts")
void writesLayout(String name, ShaderManager.UniformMemberInfo info, float[] src, int srcCount, float[] expectedUbo) {
final FloatBuffer ubo = outBuf(expectedUbo.length);
try {
Std140Writer.write(ubo, array(/*off*/ 0, /*stride*/ 16, /*len*/ 2, 4, 1), new float[]{1, 2, 3, 4, 5, 6, 7, 8}, 2);
assertEq(1f, ubo.get(0)); assertEq(2f, ubo.get(1)); assertEq(3f, ubo.get(2)); assertEq(4f, ubo.get(3));
assertEq(5f, ubo.get(4)); assertEq(6f, ubo.get(5)); assertEq(7f, ubo.get(6)); assertEq(8f, ubo.get(7));
} finally {
MemoryUtil.memFree(ubo);
}
}
@Test
void mat2_threeColsTimesVec4Each() {
final FloatBuffer ubo = outBuf(8);
try {
Std140Writer.write(ubo, single(/*off*/ 0, /*size*/ 32, /*rows*/ 2, /*cols*/ 2), new float[]{1, 2, 3, 4}, 1);
assertEq(1f, ubo.get(0), "c0.x"); assertEq(2f, ubo.get(1), "c0.y");
assertEq(0f, ubo.get(2), "c0.pad2"); assertEq(0f, ubo.get(3), "c0.pad3");
assertEq(3f, ubo.get(4), "c1.x"); assertEq(4f, ubo.get(5), "c1.y");
assertEq(0f, ubo.get(6), "c1.pad2"); assertEq(0f, ubo.get(7), "c1.pad3");
} finally {
MemoryUtil.memFree(ubo);
}
}
@Test
void mat3_threeColsEachPaddedToVec4() {
final FloatBuffer ubo = outBuf(12);
try {
Std140Writer.write(ubo, single(/*off*/ 0, /*size*/ 48, 3, 3), new float[]{1, 2, 3, 4, 5, 6, 7, 8, 9}, 1);
assertEq(1f, ubo.get(0)); assertEq(2f, ubo.get(1)); assertEq(3f, ubo.get(2)); assertEq(0f, ubo.get(3));
assertEq(4f, ubo.get(4)); assertEq(5f, ubo.get(5)); assertEq(6f, ubo.get(6)); assertEq(0f, ubo.get(7));
assertEq(7f, ubo.get(8)); assertEq(8f, ubo.get(9)); assertEq(9f, ubo.get(10)); assertEq(0f, ubo.get(11));
} finally {
MemoryUtil.memFree(ubo);
}
}
@Test
void mat3_reflectionUnderreportsSize_paddingStillApplied() {
final FloatBuffer ubo = outBuf(12);
try {
Std140Writer.write(ubo, single(/*off*/ 0, /*size*/ 36, 3, 3), new float[]{1, 2, 3, 4, 5, 6, 7, 8, 9}, 1);
assertEq(1f, ubo.get(0)); assertEq(2f, ubo.get(1)); assertEq(3f, ubo.get(2)); assertEq(0f, ubo.get(3));
assertEq(4f, ubo.get(4)); assertEq(5f, ubo.get(5)); assertEq(6f, ubo.get(6)); assertEq(0f, ubo.get(7));
assertEq(7f, ubo.get(8)); assertEq(8f, ubo.get(9)); assertEq(9f, ubo.get(10)); assertEq(0f, ubo.get(11));
} finally {
MemoryUtil.memFree(ubo);
}
}
@Test
void mat4_fourColsNoPaddingNeeded() {
final FloatBuffer ubo = outBuf(16);
try {
Std140Writer.write(ubo, single(/*off*/ 0, /*size*/ 64, 4, 4), new float[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}, 1);
for (int i = 0; i < 16; i++) assertEq(i + 1, ubo.get(i), "[" + i + "]");
} finally {
MemoryUtil.memFree(ubo);
}
}
@Test
void mat3Array_perElementStride48_eachMatrixGetsItsOwnPadding() {
final FloatBuffer ubo = outBuf(24);
try {
Std140Writer.write(ubo, array(/*off*/ 0, /*stride*/ 48, /*len*/ 2, 3, 3),
new float[]{
1, 2, 3, 4, 5, 6, 7, 8, 9, // matrix 0 (col-major packed)
10, 20, 30, 40, 50, 60, 70, 80, 90 // matrix 1
}, 2);
assertEq(1f, ubo.get(0)); assertEq(2f, ubo.get(1)); assertEq(3f, ubo.get(2)); assertEq(0f, ubo.get(3));
assertEq(4f, ubo.get(4)); assertEq(5f, ubo.get(5)); assertEq(6f, ubo.get(6)); assertEq(0f, ubo.get(7));
assertEq(7f, ubo.get(8)); assertEq(8f, ubo.get(9)); assertEq(9f, ubo.get(10)); assertEq(0f, ubo.get(11));
assertEq(10f, ubo.get(12)); assertEq(20f, ubo.get(13)); assertEq(30f, ubo.get(14)); assertEq(0f, ubo.get(15));
assertEq(40f, ubo.get(16)); assertEq(50f, ubo.get(17)); assertEq(60f, ubo.get(18)); assertEq(0f, ubo.get(19));
assertEq(70f, ubo.get(20)); assertEq(80f, ubo.get(21)); assertEq(90f, ubo.get(22)); assertEq(0f, ubo.get(23));
} finally {
MemoryUtil.memFree(ubo);
}
}
@Test
void mat4Array_perElementStride64() {
final FloatBuffer ubo = outBuf(32);
try {
final float[] data = new float[32];
for (int i = 0; i < 32; i++) data[i] = i;
Std140Writer.write(ubo, array(/*off*/ 0, /*stride*/ 64, /*len*/ 2, 4, 4), data, 2);
for (int i = 0; i < 32; i++) assertEq(i, ubo.get(i), "[" + i + "]");
Std140Writer.write(ubo, info, src, srcCount);
for (int i = 0; i < expectedUbo.length; i++) assertEq(expectedUbo[i], ubo.get(i), "[" + i + "]");
} finally {
MemoryUtil.memFree(ubo);
}
@@ -208,34 +120,6 @@ class Std140WriterTest {
}
}
@Test
void srcCount_clampedToArrayLen() {
final FloatBuffer ubo = outBuf(16);
try {
Std140Writer.write(ubo, array(/*off*/ 0, /*stride*/ 16, /*len*/ 2, 1, 1), new float[]{1, 2, 3, 4}, 4);
assertEq(1f, ubo.get(0));
assertEq(2f, ubo.get(4));
assertEq(0f, ubo.get(8)); // not written
assertEq(0f, ubo.get(12)); // not written
} finally {
MemoryUtil.memFree(ubo);
}
}
@Test
void srcCount_clampedToSrcSize() {
final FloatBuffer ubo = outBuf(16);
try {
Std140Writer.write(ubo, array(/*off*/ 0, /*stride*/ 16, /*len*/ 4, 1, 1), new float[]{7, 8}, 4);
assertEq(7f, ubo.get(0));
assertEq(8f, ubo.get(4));
assertEq(0f, ubo.get(8)); // beyond src
assertEq(0f, ubo.get(12));
} finally {
MemoryUtil.memFree(ubo);
}
}
@Test
void degenerateTypeDescriptor_throws() {
assertThrows(IllegalStateException.class, () -> {
@@ -2,6 +2,8 @@ package com.gtnewhorizons.angelica.sdlgpu.shader;
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.lwjgl.util.shaderc.Shaderc;
import java.util.List;
@@ -30,12 +32,16 @@ class UscaledRetypeTest {
assertTrue(out.contains("v_uv = a_TexCoord * 0.5;"), out);
}
@Test
void signedPromotedWidthSwizzlesDown() {
final String out = UscaledRetype.retype(VS, List.of(new UscaledRetype.Attrib("at_midBlock", 0, 3, 4, true)));
@ParameterizedTest(name = "{0}")
@CsvSource(delimiter = '|', value = {
"signed promoted width swizzles down|4|in ivec4 at_midBlock_i|vec3 at_midBlock = vec3(at_midBlock_i.xyz);",
"narrower bound width pads with GL attribute defaults|2|in ivec2 at_midBlock_i|vec3 at_midBlock = vec3(vec2(at_midBlock_i), 0.0);"
})
void signedRetypeAdaptsToTheBoundWidth(String label, int boundWidth, String decl, String conversion) {
final String out = UscaledRetype.retype(VS, List.of(new UscaledRetype.Attrib("at_midBlock", 0, 3, boundWidth, true)));
assertNotNull(out);
assertTrue(out.contains("in ivec4 at_midBlock_i"), out);
assertTrue(out.contains("vec3 at_midBlock = vec3(at_midBlock_i.xyz);"), out);
assertTrue(out.contains(decl), out);
assertTrue(out.contains(conversion), out);
}
@Test
@@ -58,14 +64,6 @@ class UscaledRetypeTest {
assertNull(UscaledRetype.retype(VS, List.of(new UscaledRetype.Attrib("not_present", 0, 2, 2, false))));
}
@Test
void narrowerBoundWidthPadsWithGlAttributeDefaults() {
final String out = UscaledRetype.retype(VS, List.of(new UscaledRetype.Attrib("at_midBlock", 0, 3, 2, true)));
assertNotNull(out);
assertTrue(out.contains("in ivec2 at_midBlock_i"), out);
assertTrue(out.contains("vec3 at_midBlock = vec3(vec2(at_midBlock_i), 0.0);"), out);
}
@Test
void vec4FromShort2PadsZeroThenOne() {
final String src = """
@@ -128,11 +126,4 @@ class UscaledRetypeTest {
assertTrue(out.contains("in uvec2 a_TexCoord_i"), out);
assertTrue(out.contains("vec2 a_TexCoord = vec2(a_TexCoord_i);"), out);
}
@Test
void retypedInputKeepsTheNameSuffixTheLocationFixupKeysOn() {
final String out = UscaledRetype.retype(VS, List.of(new UscaledRetype.Attrib("a_TexCoord", 4, 2, 2, false)));
assertNotNull(out);
assertTrue(out.contains("a_TexCoord" + UscaledRetype.SUFFIX), out);
}
}
@@ -50,48 +50,28 @@ class MslCrossCompileTest {
@Test
void vertexShaderCrossCompiles() {
final ByteBuffer spirv = compileOrFail(SpirvTestShaders.VERTEX_GLSL, Shaderc.shaderc_vertex_shader);
try {
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_VERTEX_SHADER);
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER, false);
try {
final String msl = decode(out.code());
assertTrue(msl.length() > 0, "MSL output non-empty");
assertTrue(msl.contains("vertex "), "MSL should contain vertex stage qualifier:\n" + msl);
assertFalse(msl.contains("descriptor_set"), "MSL must not leak descriptor_set decorations:\n" + msl);
assertTrue(msl.contains("[[buffer(0)]]"), "UBO should land at [[buffer(0)]]:\n" + msl);
assertTrue(msl.contains("[[texture(0)]]"), "sampler2D should land at [[texture(0)]]:\n" + msl);
assertTrue(msl.contains("[[sampler(0)]]"), "sampler2D should land at [[sampler(0)]]:\n" + msl);
assertEquals("main0", out.entrypoint(), "Metal forbids main; expect main0");
} finally {
MemoryUtil.memFree(out.code());
}
} finally {
MemoryUtil.memFree(spirv);
}
final Msl out = crossCompile(SpirvTestShaders.VERTEX_GLSL, Shaderc.shaderc_vertex_shader, GL20.GL_VERTEX_SHADER, false);
final String msl = out.code();
assertTrue(msl.length() > 0, "MSL output non-empty");
assertTrue(msl.contains("vertex "), "MSL should contain vertex stage qualifier:\n" + msl);
assertFalse(msl.contains("descriptor_set"), "MSL must not leak descriptor_set decorations:\n" + msl);
assertTrue(msl.contains("[[buffer(0)]]"), "UBO should land at [[buffer(0)]]:\n" + msl);
assertTrue(msl.contains("[[texture(0)]]"), "sampler2D should land at [[texture(0)]]:\n" + msl);
assertTrue(msl.contains("[[sampler(0)]]"), "sampler2D should land at [[sampler(0)]]:\n" + msl);
assertEquals("main0", out.entrypoint(), "Metal forbids main; expect main0");
}
@Test
void fragmentShaderCrossCompiles() {
final ByteBuffer spirv = compileOrFail(SpirvTestShaders.FRAGMENT_GLSL, Shaderc.shaderc_fragment_shader);
try {
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_FRAGMENT_SHADER);
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER, false);
try {
final String msl = decode(out.code());
assertTrue(msl.contains("fragment "), "MSL should contain fragment stage qualifier:\n" + msl);
assertTrue(msl.contains("[[buffer(0)]]"), "Fragment UBO should land at [[buffer(0)]]:\n" + msl);
assertTrue(msl.contains("[[texture(0)]]"), "First sampler at [[texture(0)]]:\n" + msl);
assertTrue(msl.contains("[[texture(1)]]"), "Second sampler at [[texture(1)]]:\n" + msl);
assertTrue(msl.contains("[[sampler(0)]]") && msl.contains("[[sampler(1)]]"),
"Samplers at slots 0 and 1:\n" + msl);
assertEquals("main0", out.entrypoint());
} finally {
MemoryUtil.memFree(out.code());
}
} finally {
MemoryUtil.memFree(spirv);
}
final Msl out = crossCompile(SpirvTestShaders.FRAGMENT_GLSL, Shaderc.shaderc_fragment_shader, GL20.GL_FRAGMENT_SHADER, false);
final String msl = out.code();
assertTrue(msl.contains("fragment "), "MSL should contain fragment stage qualifier:\n" + msl);
assertTrue(msl.contains("[[buffer(0)]]"), "Fragment UBO should land at [[buffer(0)]]:\n" + msl);
assertTrue(msl.contains("[[texture(0)]]"), "First sampler at [[texture(0)]]:\n" + msl);
assertTrue(msl.contains("[[texture(1)]]"), "Second sampler at [[texture(1)]]:\n" + msl);
assertTrue(msl.contains("[[sampler(0)]]") && msl.contains("[[sampler(1)]]"),
"Samplers at slots 0 and 1:\n" + msl);
assertEquals("main0", out.entrypoint());
}
private static final String CHUNK_CULL_GLSL = """
@@ -111,27 +91,17 @@ class MslCrossCompileTest {
@Test
void ldsMultiMipComputeCrossCompiles() {
final ByteBuffer spirv = compileOrFail(SpirvTestShaders.LDS_MULTI_MIP_GLSL, Shaderc.shaderc_compute_shader);
try {
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, false);
try {
final String msl = decode(out.code());
assertTrue(msl.contains("kernel "), "Compute MSL should contain kernel stage qualifier:\n" + msl);
assertTrue(msl.contains("threadgroup spvUnsafeArray<float") || msl.contains("threadgroup float"), "shared float array must become threadgroup memory:\n" + msl);
assertTrue(msl.contains("threadgroup_barrier"), "barrier() must become threadgroup_barrier:\n" + msl);
assertTrue(msl.contains("[[texture(0)]]"), "u_Src RW image at [[texture(0)]]:\n" + msl);
assertTrue(msl.contains("[[texture(1)]]"), "u_Dst1 at [[texture(1)]]:\n" + msl);
assertTrue(msl.contains("[[texture(2)]]"), "u_Dst2 at [[texture(2)]]:\n" + msl);
assertTrue(msl.contains("[[buffer(0)]]"), "Down UBO at [[buffer(0)]]:\n" + msl);
assertFalse(msl.contains("descriptor_set"), "MSL must not leak descriptor_set decorations:\n" + msl);
assertEquals("main0", out.entrypoint());
} finally {
MemoryUtil.memFree(out.code());
}
} finally {
MemoryUtil.memFree(spirv);
}
final Msl out = crossCompile(SpirvTestShaders.LDS_MULTI_MIP_GLSL, Shaderc.shaderc_compute_shader, GL43.GL_COMPUTE_SHADER, false);
final String msl = out.code();
assertTrue(msl.contains("kernel "), "Compute MSL should contain kernel stage qualifier:\n" + msl);
assertTrue(msl.contains("threadgroup spvUnsafeArray<float") || msl.contains("threadgroup float"), "shared float array must become threadgroup memory:\n" + msl);
assertTrue(msl.contains("threadgroup_barrier"), "barrier() must become threadgroup_barrier:\n" + msl);
assertTrue(msl.contains("[[texture(0)]]"), "u_Src RW image at [[texture(0)]]:\n" + msl);
assertTrue(msl.contains("[[texture(1)]]"), "u_Dst1 at [[texture(1)]]:\n" + msl);
assertTrue(msl.contains("[[texture(2)]]"), "u_Dst2 at [[texture(2)]]:\n" + msl);
assertTrue(msl.contains("[[buffer(0)]]"), "Down UBO at [[buffer(0)]]:\n" + msl);
assertFalse(msl.contains("descriptor_set"), "MSL must not leak descriptor_set decorations:\n" + msl);
assertEquals("main0", out.entrypoint());
}
/**
@@ -141,47 +111,27 @@ class MslCrossCompileTest {
@Test
void clearImage3dComputeCrossCompiles() {
final String glsl = TestPaths.readString("src/main/resources/assets/angelica/shaders/sdlgpu/clear_image3d.csh");
final ByteBuffer spirv = compileOrFail(glsl, Shaderc.shaderc_compute_shader);
try {
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, false);
try {
final String msl = decode(out.code());
assertTrue(msl.contains("kernel "), "compute MSL should contain kernel stage qualifier:\n" + msl);
assertTrue(msl.contains("texture3d<uint"), "formatless writeonly uimage3D must resolve to a uint 3D texture:\n" + msl);
assertTrue(msl.contains("access::write"), "the image must carry write access:\n" + msl);
assertTrue(msl.contains("[[texture(0)]]"), "the target image should land at [[texture(0)]]:\n" + msl);
assertEquals("main0", out.entrypoint());
} finally {
MemoryUtil.memFree(out.code());
}
} finally {
MemoryUtil.memFree(spirv);
}
final Msl out = crossCompile(glsl, Shaderc.shaderc_compute_shader, GL43.GL_COMPUTE_SHADER, false);
final String msl = out.code();
assertTrue(msl.contains("kernel "), "compute MSL should contain kernel stage qualifier:\n" + msl);
assertTrue(msl.contains("texture3d<uint"), "formatless writeonly uimage3D must resolve to a uint 3D texture:\n" + msl);
assertTrue(msl.contains("access::write"), "the image must carry write access:\n" + msl);
assertTrue(msl.contains("[[texture(0)]]"), "the target image should land at [[texture(0)]]:\n" + msl);
assertEquals("main0", out.entrypoint());
}
@Test
void chunkCullComputeCrossCompiles() {
final ByteBuffer spirv = compileOrFail(CHUNK_CULL_GLSL, Shaderc.shaderc_compute_shader);
try {
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, false);
try {
final String msl = decode(out.code());
assertTrue(msl.contains("kernel "), "Compute MSL should contain kernel stage qualifier:\n" + msl);
assertTrue(msl.contains("[[texture(0)]]"), "u_Hiz at [[texture(0)]]:\n" + msl);
assertTrue(msl.contains("[[sampler(0)]]"), "u_Hiz sampler at [[sampler(0)]]:\n" + msl);
assertTrue(msl.contains("[[buffer(0)]]"), "Frustum UBO at [[buffer(0)]]:\n" + msl);
assertTrue(msl.contains("[[buffer(1)]]"), "visible RO SSBO at [[buffer(1)]]:\n" + msl);
assertTrue(msl.contains("[[buffer(2)]]"), "meta RO SSBO at [[buffer(2)]]:\n" + msl);
assertTrue(msl.contains("[[buffer(3)]]"), "indirect RW SSBO at [[buffer(3)]] (numUBOs+numROBuf=3):\n" + msl);
assertEquals("main0", out.entrypoint());
} finally {
MemoryUtil.memFree(out.code());
}
} finally {
MemoryUtil.memFree(spirv);
}
final Msl out = crossCompile(CHUNK_CULL_GLSL, Shaderc.shaderc_compute_shader, GL43.GL_COMPUTE_SHADER, false);
final String msl = out.code();
assertTrue(msl.contains("kernel "), "Compute MSL should contain kernel stage qualifier:\n" + msl);
assertTrue(msl.contains("[[texture(0)]]"), "u_Hiz at [[texture(0)]]:\n" + msl);
assertTrue(msl.contains("[[sampler(0)]]"), "u_Hiz sampler at [[sampler(0)]]:\n" + msl);
assertTrue(msl.contains("[[buffer(0)]]"), "Frustum UBO at [[buffer(0)]]:\n" + msl);
assertTrue(msl.contains("[[buffer(1)]]"), "visible RO SSBO at [[buffer(1)]]:\n" + msl);
assertTrue(msl.contains("[[buffer(2)]]"), "meta RO SSBO at [[buffer(2)]]:\n" + msl);
assertTrue(msl.contains("[[buffer(3)]]"), "indirect RW SSBO at [[buffer(3)]] (numUBOs+numROBuf=3):\n" + msl);
assertEquals("main0", out.entrypoint());
}
private static final String VERTEX_WRITES_IMAGE_GLSL = """
@@ -197,22 +147,12 @@ class MslCrossCompileTest {
@Test
void vertexShaderWritingStorageImageStillReturnsPosition() {
final ByteBuffer spirv = compileOrFail(VERTEX_WRITES_IMAGE_GLSL, Shaderc.shaderc_vertex_shader);
try {
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_VERTEX_SHADER);
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_VERTEX_SHADER, false);
try {
final String msl = decode(out.code());
assertFalse(msl.contains("vertex void main0"),
"vertex stage must not be emitted with void return when gl_Position is written:\n" + msl);
assertTrue(msl.contains("[[position]]"),
"vertex output struct should include [[position]] decoration:\n" + msl);
} finally {
MemoryUtil.memFree(out.code());
}
} finally {
MemoryUtil.memFree(spirv);
}
final Msl out = crossCompile(VERTEX_WRITES_IMAGE_GLSL, Shaderc.shaderc_vertex_shader, GL20.GL_VERTEX_SHADER, false);
final String msl = out.code();
assertFalse(msl.contains("vertex void main0"),
"vertex stage must not be emitted with void return when gl_Position is written:\n" + msl);
assertTrue(msl.contains("[[position]]"),
"vertex output struct should include [[position]] decoration:\n" + msl);
}
private static final String CPP_KEYWORD_LOCAL_GLSL = """
@@ -231,20 +171,10 @@ class MslCrossCompileTest {
void cppKeywordLocalIsRenamedBeforeMsl() {
final GlslVulkanPreprocess.Result pre = GlslVulkanPreprocess.run(CPP_KEYWORD_LOCAL_GLSL, GL20.GL_FRAGMENT_SHADER, "test", true);
assertNotNull(pre);
final ByteBuffer spirv = compileOrFail(pre.rewrittenSource(), Shaderc.shaderc_fragment_shader);
try {
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_FRAGMENT_SHADER);
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL20.GL_FRAGMENT_SHADER, false);
try {
final String msl = decode(out.code());
assertTrue(msl.contains("angelica_renamed_try"), "the local must keep a renamed identifier:\n" + msl);
assertFalse(Pattern.compile("\\btry\\b").matcher(msl).find(), "try is reserved in MSL:\n" + msl);
} finally {
MemoryUtil.memFree(out.code());
}
} finally {
MemoryUtil.memFree(spirv);
}
final Msl out = crossCompile(pre.rewrittenSource(), Shaderc.shaderc_fragment_shader, GL20.GL_FRAGMENT_SHADER, false);
final String msl = out.code();
assertTrue(msl.contains("angelica_renamed_try"), "the local must keep a renamed identifier:\n" + msl);
assertFalse(Pattern.compile("\\btry\\b").matcher(msl).find(), "try is reserved in MSL:\n" + msl);
}
// Mimics the shape of RwImageStoreExtractor's chunk-vertex compute output:
@@ -356,19 +286,9 @@ class MslCrossCompileTest {
@ParameterizedTest(name = "{0}")
@MethodSource("computeShadersWithImageAtomics")
void computeWithImageAtomic_mslBufferSlotsAreUnique(String name, String glsl) {
final ByteBuffer spirv = compileOrFail(glsl, Shaderc.shaderc_compute_shader);
try {
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, false);
try {
final String msl = decode(out.code());
assertUniqueBufferSlots(msl);
} finally {
MemoryUtil.memFree(out.code());
}
} finally {
MemoryUtil.memFree(spirv);
}
final Msl out = crossCompile(glsl, Shaderc.shaderc_compute_shader, GL43.GL_COMPUTE_SHADER, false);
final String msl = out.code();
assertUniqueBufferSlots(msl);
}
private static void assertUniqueBufferSlots(String msl) {
@@ -407,18 +327,7 @@ class MslCrossCompileTest {
final String[] names = { "chunk_cull.csh" };
for (String name : names) {
final String glsl = TestPaths.readString("src/main/resources/assets/angelica/shaders/culling/" + name);
final ByteBuffer spirv = compileOrFail(glsl, Shaderc.shaderc_compute_shader);
try {
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, false);
try {
assertTrue(decode(out.code()).contains("kernel "), name + " should produce compute MSL");
} finally {
MemoryUtil.memFree(out.code());
}
} finally {
MemoryUtil.memFree(spirv);
}
assertTrue(crossCompile(glsl, Shaderc.shaderc_compute_shader, GL43.GL_COMPUTE_SHADER, false).code().contains("kernel "), name + " should produce compute MSL");
}
}
@@ -507,12 +416,19 @@ class MslCrossCompileTest {
}
private static String computeMsl(String glsl, boolean textureAtomics) {
final ByteBuffer spirv = compileOrFail(glsl, Shaderc.shaderc_compute_shader);
return crossCompile(glsl, Shaderc.shaderc_compute_shader, GL43.GL_COMPUTE_SHADER, textureAtomics).code();
}
private record Msl(String code, String entrypoint) {}
private static Msl crossCompile(String glsl, int shaderKind, int glType, boolean textureAtomics) {
final ByteBuffer spirv = compileOrFail(glsl, shaderKind);
try {
ShaderManager.remapSpirvForComputeSDLGPU(spirv);
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, GL43.GL_COMPUTE_SHADER, textureAtomics);
if (glType == GL43.GL_COMPUTE_SHADER) ShaderManager.remapSpirvForComputeSDLGPU(spirv);
else ShaderManager.remapSpirvForSDLGPU(spirv, glType);
final CrossCompileCache.Output out = MslCrossCompile.compile(spirv, glType, textureAtomics);
try {
return decode(out.code());
return new Msl(decode(out.code()), out.entrypoint());
} finally {
MemoryUtil.memFree(out.code());
}
@@ -113,16 +113,12 @@ class GLRedirectsTest {
"Foo.bar",
"String.valueOf",
"ARBShaderObjects.GL_OBJECT_COMPILE_STATUS_ARB",
"noDotHere",
})
void nonRedirectedReturnsNull(String classAndMethod) {
assertNull(GLRedirects.getTargetMethodName(classAndMethod));
}
@Test
void nullDotReturnsNull() {
assertNull(GLRedirects.getTargetMethodName("noDotHere"));
}
@ParameterizedTest(name = "pattern matches {0}")
@CsvSource({
"GL11.glPushMatrix",
@@ -2,6 +2,8 @@ package com.gtnewhorizons.angelica.api.tesr;
import net.minecraft.util.ResourceLocation;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -15,12 +17,10 @@ class TesrShadersTest {
assertThrows(IllegalStateException.class, () -> TesrShaders.register("test:duplicate", NOOP, NOOP));
}
@Test
void malformedNamesThrow() {
assertThrows(IllegalArgumentException.class, () -> TesrShaders.register("nonamespace", NOOP, NOOP));
assertThrows(IllegalArgumentException.class, () -> TesrShaders.register(":path", NOOP, NOOP));
assertThrows(IllegalArgumentException.class, () -> TesrShaders.register("ns:", NOOP, NOOP));
assertThrows(IllegalArgumentException.class, () -> TesrShaders.register("a:b:c", NOOP, NOOP));
@ParameterizedTest
@ValueSource(strings = {"nonamespace", ":path", "ns:", "a:b:c"})
void malformedNamesThrow(String name) {
assertThrows(IllegalArgumentException.class, () -> TesrShaders.register(name, NOOP, NOOP));
}
@Test
@@ -8,10 +8,12 @@ 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.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
@@ -136,6 +138,18 @@ class TextDrawStateGLTest {
moved.apply();
GLStateManager.popStateTo(depth);
final ByteBuffer driverColorMask = BufferUtils.createByteBuffer(16);
GL11.glGetBoolean(GL11.GL_COLOR_WRITEMASK, driverColorMask);
assertFalse(GL11.glIsEnabled(GL11.GL_DEPTH_TEST), "driver depth test");
assertEquals(GL11.GL_LESS, GL11.glGetInteger(GL11.GL_DEPTH_FUNC), "driver depth func");
assertTrue(GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK), "driver depth mask");
for (int i = 0; i < 4; i++) assertNotEquals(0, driverColorMask.get(i), "driver color mask channel " + i);
assertFalse(GL11.glIsEnabled(GL11.GL_CULL_FACE), "driver cull face enable");
assertEquals(GL11.GL_BACK, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face mode");
assertEquals(GL11.GL_CCW, GL11.glGetInteger(GL11.GL_FRONT_FACE), "driver front face");
assertFalse(GL11.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL), "driver polygon offset fill");
assertEquals(0.0f, GL11.glGetFloat(GL11.GL_POLYGON_OFFSET_FACTOR), "driver polygon offset factor");
final TextDrawState after = capture();
for (Field field : stateFields()) {
if (!isGlState(field)) continue;
@@ -14,6 +14,7 @@ class DeferredDrawBatcherTest {
"basic, 42, 0x0302, 0x0001, true, false",
"zero fields, 0, 0, 0, false, true",
"largest 20-bit texture id, 1048575, 0x0302, 0x0303, true, true",
"dst alpha blend constants, 1, 0x0304, 0x0305, true, true",
})
void stateKey_roundtrip(String name, int textureId, int srcRgb, int dstRgb, boolean blend, boolean depthMask) {
long key = DeferredDrawBatcher.packStateKey(textureId, srcRgb, dstRgb, blend, depthMask, true, true);
@@ -33,27 +34,6 @@ class DeferredDrawBatcherTest {
assertNotEquals(keyA, keyC, "Different blend funcs should produce different keys");
}
@Test
void stateKey_blendConstants() {
// Verify all common GL blend constants roundtrip correctly
int[] blendConstants = {
GL11.GL_ZERO, // 0x0000
GL11.GL_ONE, // 0x0001
GL11.GL_SRC_ALPHA, // 0x0302
GL11.GL_ONE_MINUS_SRC_ALPHA, // 0x0303
GL11.GL_DST_ALPHA, // 0x0304
GL11.GL_ONE_MINUS_DST_ALPHA, // 0x0305
};
for (int src : blendConstants) {
for (int dst : blendConstants) {
long key = DeferredDrawBatcher.packStateKey(1, src, dst, true, true, true, true);
assertEquals(src, DeferredDrawBatcher.unpackSrcRgb(key), "srcRgb roundtrip failed for 0x" + Integer.toHexString(src));
assertEquals(dst, DeferredDrawBatcher.unpackDstRgb(key), "dstRgb roundtrip failed for 0x" + Integer.toHexString(dst));
}
}
}
@Test
void stateKey_textureEnableRoundtrip() {
// tex0=true, tex1=false
@@ -70,18 +70,6 @@ class FogAggregateTest {
}
}
@Test
void colorEvictionDoesNotDiscardInteriorTotals() {
final AtomicInteger color = new AtomicInteger(0x010203);
final World world = world(new AtomicBoolean(true), color);
final float[] actual = new float[4];
FogColourGrid.accumulateInterior(world, 0, 64, 0, 1, actual);
color.set(0x040506);
FogColourGrid.colour(world, 128, 64, 0);
FogColourGrid.accumulateInterior(world, 0, 64, 0, 1, actual);
assertBits(new float[] {1,2,3,1}, actual);
}
@Test
void biomeEvictionDoesNotDiscardInteriorTotals() {
final AtomicInteger color = new AtomicInteger(0x010203);
@@ -4,8 +4,13 @@ import net.minecraft.world.World;
import net.minecraft.world.biome.BiomeGenBase;
import net.minecraft.world.chunk.IChunkProvider;
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.mockito.Mockito;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
@@ -23,24 +28,23 @@ class FogBiomeCacheTest {
return world;
}
@Test
void repeatLookupsHitTheCache() {
@ParameterizedTest(name = "{0}")
@MethodSource("lookupCounts")
void worldLookupsPerCoordinateList(String label, boolean chunkLoaded, int[][] xz, int expectedWorldLookups) {
final BiomeGenBase biome = Mockito.mock(BiomeGenBase.class);
final World world = worldWith(biome, true);
final World world = worldWith(biome, chunkLoaded);
assertSame(biome, FogBiomeCache.get(world, 100, -7));
assertSame(biome, FogBiomeCache.get(world, 100, -7));
verify(world, times(1)).getBiomeGenForCoords(anyInt(), anyInt());
for (int[] c : xz) {
assertSame(biome, FogBiomeCache.get(world, c[0], c[1]));
}
verify(world, times(expectedWorldLookups)).getBiomeGenForCoords(anyInt(), anyInt());
}
@Test
void unloadedChunksAreNotCached() {
final BiomeGenBase biome = Mockito.mock(BiomeGenBase.class);
final World world = worldWith(biome, false);
FogBiomeCache.get(world, 5000, 5000);
FogBiomeCache.get(world, 5000, 5000);
verify(world, times(2)).getBiomeGenForCoords(anyInt(), anyInt());
private static Stream<Arguments> lookupCounts() {
return Stream.of(
Arguments.of("repeat lookups hit the cache", true, new int[][] {{100, -7}, {100, -7}}, 1),
Arguments.of("unloaded chunks are not cached", false, new int[][] {{5000, 5000}, {5000, 5000}}, 2),
Arguments.of("negative coordinates do not collide", true, new int[][] {{-1, 0}, {0, -1}, {-1, -1}}, 3));
}
@Test
@@ -54,15 +58,4 @@ class FogBiomeCacheTest {
assertSame(b, FogBiomeCache.get(worldB, 0, 0), "same coords in a new world must re-resolve");
assertSame(a, FogBiomeCache.get(worldA, 0, 0), "switching back re-resolves rather than serving worldB state");
}
@Test
void negativeCoordinatesDoNotCollide() {
final BiomeGenBase biome = Mockito.mock(BiomeGenBase.class);
final World world = worldWith(biome, true);
FogBiomeCache.get(world, -1, 0);
FogBiomeCache.get(world, 0, -1);
FogBiomeCache.get(world, -1, -1);
verify(world, times(3)).getBiomeGenForCoords(anyInt(), anyInt());
}
}
@@ -5,8 +5,12 @@ import net.minecraft.world.biome.BiomeGenBase;
import net.minecraft.world.chunk.IChunkProvider;
import org.junit.jupiter.api.BeforeEach;
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.function.IntBinaryOperator;
import java.util.stream.Stream;
import com.gtnewhorizons.angelica.compat.bop.BopFogTestSupport.BopFog;
@@ -64,28 +68,23 @@ class FogColourGridTest {
}
}
@Test
void oneBlockShiftIsAColdFill() {
final World world = mockWorld(true);
@ParameterizedTest(name = "{0}")
@MethodSource("requeryCounts")
void fogColourCallsPerCoordinateList(String label, boolean chunksLoaded, int[][] xyz, int expectedFogColourCalls) {
final World world = mockWorld(chunksLoaded);
final BiomeGenBase biome = fixedFogBiome(world, 0x123456);
FogColourGrid.colour(world, 100, 64, 200);
FogColourGrid.colour(world, 100, 64, 200);
BopFog.verifyCall(biome, times(1));
FogColourGrid.colour(world, 101, 64, 200);
BopFog.verifyCall(biome, times(2));
for (int[] c : xyz) {
FogColourGrid.colour(world, c[0], c[1], c[2]);
}
BopFog.verifyCall(biome, times(expectedFogColourCalls));
}
@Test
void nonCacheableCellIsRequeried() {
final World world = mockWorld(false);
final BiomeGenBase biome = fixedFogBiome(world, 0x654321);
FogColourGrid.colour(world, 5, 64, 9);
FogColourGrid.colour(world, 5, 64, 9);
BopFog.verifyCall(biome, times(2));
private static Stream<Arguments> requeryCounts() {
return Stream.of(
Arguments.of("non-cacheable cell is requeried", false, new int[][] {{5, 64, 9}, {5, 64, 9}}, 2),
Arguments.of("loaded repeat hits the cache", true, new int[][] {{100, 64, 200}, {100, 64, 200}}, 1),
Arguments.of("one block shift is a cold fill", true, new int[][] {{100, 64, 200}, {100, 64, 200}, {101, 64, 200}}, 2));
}
@Test
@@ -19,6 +19,7 @@ import net.coderbot.iris.pipeline.FixedFunctionWorldRenderingPipeline;
import net.coderbot.iris.pipeline.PipelineManager;
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.coderbot.iris.uniforms.CapturedStateScope;
import net.coderbot.iris.uniforms.ItemIdManager;
import net.coderbot.iris.uniforms.ItemMaterialHelper;
import net.coderbot.iris.shaderpack.materialmap.NamespacedId;
@@ -34,6 +35,7 @@ import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import java.nio.IntBuffer;
import java.util.ArrayList;
@@ -169,6 +171,11 @@ class IrisDisplayListStateTest {
try {
GLStateManager.glCallList(parent);
assertTrue(GLStateManager.isEffectiveBlendEnabled());
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND), "driver blend enable");
assertEquals(GL11.GL_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb");
assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_DST_RGB), "driver blend dst rgb");
assertEquals(GL11.GL_ZERO, GL11.glGetInteger(GL14.GL_BLEND_SRC_ALPHA), "driver blend src alpha");
assertEquals(GL11.GL_ONE, GL11.glGetInteger(GL14.GL_BLEND_DST_ALPHA), "driver blend dst alpha");
final var blend = GLStateManager.getEffectiveBlendState(new com.gtnewhorizons.angelica.glsm.states.BlendState());
assertEquals(GL11.GL_SRC_ALPHA, blend.getSrcRgb());
assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, blend.getDstRgb());
@@ -187,31 +194,24 @@ class IrisDisplayListStateTest {
@Test
void idsAndColorReplayInsideNestedScopesAndRestoreThePlaybackCaller() {
final CapturedRenderingState state = CapturedRenderingState.INSTANCE;
state.pushCurrentEntityAndItem();
state.pushCurrentBlockEntity();
state.pushCurrentEntityColor();
try {
try (CapturedStateScope ignored = new CapturedStateScope()) {
for (int mode : new int[] {GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE}) {
state.setCurrentEntityAndItem(10, 11);
state.setCurrentBlockEntity(12);
state.setCurrentEntityColor(1, 0, 0, 0.5f);
final int child = newList();
GLStateManager.glNewList(child, mode);
state.pushCurrentEntityAndItem();
state.pushCurrentBlockEntity();
state.pushCurrentEntityColor();
state.setCurrentEntityAndItem(45010, 45011);
state.setCurrentBlockEntity(45012);
state.setCurrentEntityColor(0, 1, 0, 0.25f);
DisplayListManager.recordStateCommand(() -> {
assertEquals(45010, state.getCurrentRenderedEntity());
assertEquals(45011, state.getCurrentRenderedItem());
assertEquals(45012, state.getCurrentRenderedBlockEntity());
assertTrue(state.getCurrentEntityColor().equals(0, 1, 0, 0.25f));
});
state.popCurrentEntityColor();
state.popCurrentBlockEntity();
state.popCurrentEntityAndItem();
try (CapturedStateScope nestedIgnored = new CapturedStateScope()) {
state.setCurrentEntityAndItem(45010, 45011);
state.setCurrentBlockEntity(45012);
state.setCurrentEntityColor(0, 1, 0, 0.25f);
DisplayListManager.recordStateCommand(() -> {
assertEquals(45010, state.getCurrentRenderedEntity());
assertEquals(45011, state.getCurrentRenderedItem());
assertEquals(45012, state.getCurrentRenderedBlockEntity());
assertTrue(state.getCurrentEntityColor().equals(0, 1, 0, 0.25f));
});
}
GLStateManager.glEndList();
final int parent = newList();
@@ -234,20 +234,13 @@ class IrisDisplayListStateTest {
assertTrue(state.getCurrentEntityColor().equals(0, 0, 1, 0.75f));
}
}
} finally {
state.popCurrentEntityColor();
state.popCurrentBlockEntity();
state.popCurrentEntityAndItem();
}
}
@Test
void explicitStateRecordsEvenWhenUnchangedAndOnlyDeduplicatesAdjacentCommands() {
final CapturedRenderingState state = CapturedRenderingState.INSTANCE;
state.pushCurrentEntityAndItem();
state.pushCurrentBlockEntity();
state.pushCurrentEntityColor();
try {
try (CapturedStateScope ignored = new CapturedStateScope()) {
state.setCurrentEntityAndItem(7, 8);
state.setCurrentBlockEntity(9);
state.setCurrentEntityColor(1, 0, 0, 0.5f);
@@ -266,10 +259,6 @@ class IrisDisplayListStateTest {
assertEquals(8, state.getCurrentRenderedItem());
assertEquals(9, state.getCurrentRenderedBlockEntity());
assertTrue(state.getCurrentEntityColor().equals(1, 0, 0, 0.5f));
} finally {
state.popCurrentEntityColor();
state.popCurrentBlockEntity();
state.popCurrentEntityAndItem();
}
}
@@ -377,6 +377,17 @@ class PacerCoreSimTest {
return sim;
}
private static Sim tearingSim(String name, int cap) {
final Sim sim = new Sim().named(name);
sim.tearFree = false;
sim.vsyncOn = false;
sim.mode = Mode.IMMEDIATE;
sim.gate = Gate.NONE;
sim.cap = cap;
sim.gpuTail = 200 * US;
return sim;
}
private static void assertNeverBunches(Result r, long slopNanos) {
for (int f = WARMUP; f < FRAMES; f++) {
if (r.driven[f] || r.driven[f - 1]) continue;
@@ -509,14 +520,8 @@ class PacerCoreSimTest {
@Test
void aTearingCapHoldsItsPeriodAndKeepsLatencyToOneFrameOfWork() {
final Sim sim = new Sim().named("vsyncOff cap100");
sim.tearFree = false;
sim.vsyncOn = false;
sim.mode = Mode.IMMEDIATE;
sim.gate = Gate.NONE;
sim.cap = 100;
final Sim sim = tearingSim("vsyncOff cap100", 100);
sim.work = 4 * MS;
sim.gpuTail = 200 * US;
final Result r = run(sim);
assertTrue(Math.abs(r.minInterval - 10 * MS) < 200 * US, r.toString());
@@ -527,14 +532,8 @@ class PacerCoreSimTest {
@Test
void aFrameSlowerThanTheCadenceIsNeverSleptOn() {
final Sim sim = new Sim().named("cpuBound R=20ms cap60");
sim.tearFree = false;
sim.vsyncOn = false;
sim.mode = Mode.IMMEDIATE;
sim.gate = Gate.NONE;
sim.cap = 60;
final Sim sim = tearingSim("cpuBound R=20ms cap60", 60);
sim.work = 20 * MS;
sim.gpuTail = 200 * US;
final Result r = run(sim);
assertEquals(0, r.startParks, r.toString());
@@ -544,14 +543,8 @@ class PacerCoreSimTest {
@Test
void aCoarseSleepTimerWidensTheMarginWithoutSaturatingIt() {
final Sim sim = new Sim().named("coarseTimer cap60");
sim.tearFree = false;
sim.vsyncOn = false;
sim.mode = Mode.IMMEDIATE;
sim.gate = Gate.NONE;
sim.cap = 60;
final Sim sim = tearingSim("coarseTimer cap60", 60);
sim.granularity = 4 * MS;
sim.gpuTail = 200 * US;
final Result r = run(sim);
assertTrue(r.margin > PacerCore.MARGIN_MIN_NANOS, r + " a coarse timer must buy extra lead");
@@ -1,9 +1,9 @@
package com.gtnewhorizons.angelica.rendering;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
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 java.util.ArrayList;
@@ -70,12 +70,22 @@ class PacerCoreTest {
time.now = core.beginFrame(time.now);
}
void feedRun(int count, long firstEndNanos, long durationNanos, long strideNanos) {
long feedRun(int count, long firstEndNanos, long durationNanos, long strideNanos) {
long end = firstEndNanos;
for (int i = 0; i < count; i++) {
feed(end, durationNanos, end);
end += strideNanos;
}
return end;
}
long feedOffGrid(long endNanos, int count) {
long end = endNanos;
for (int i = 0; i < count; i++) {
end += P60 / 2;
feed(end, 5 * MS, end);
}
return end;
}
void idleFrames(int count, long atNanos) {
@@ -94,6 +104,8 @@ class PacerCoreTest {
Arguments.of("a cap above the refresh rate collapses onto the refresh period", 240, P60, true, P60),
Arguments.of("a cap far from every multiple keeps its literal period", 45, P60, true, 1_000_000_000L / 45),
Arguments.of("a cap far from every multiple keeps its literal period", 100, P144, true, 1_000_000_000L / 100),
Arguments.of("a cap near a vblank multiple snaps to that multiple", 30, P60, true, 2 * P60),
Arguments.of("a cap near a vblank multiple snaps to that multiple", 50, P144, true, 3 * P144),
Arguments.of("a tearing mode ignores the refresh period", 100, P60, false, 10 * MS),
Arguments.of("a tearing mode with no cap is unpaced", 0, P60, false, 0L),
Arguments.of("a zero refresh period with no cap is unpaced", 0, 0L, true, 0L),
@@ -106,13 +118,6 @@ class PacerCoreTest {
assertEquals(expected, PacerCore.cadencePeriodNanos(capHz, refreshPeriodNanos, tearFree));
}
@Test
void aCapNearAVblankMultipleSnapsToThatMultiple() {
assertEquals(2 * P60, PacerCore.cadencePeriodNanos(30, P60, true));
assertEquals(3 * P144, PacerCore.cadencePeriodNanos(50, P144, true));
assertEquals(48, RenderBackend.hzFromPeriod(3 * P144));
}
@Test
void threeConsistentPairsOnTheRefreshGridLockThePhase() {
final Rig rig = new Rig(P60, true, 0);
@@ -136,11 +141,9 @@ class PacerCoreTest {
@Test
void oneBadPairRestartsTheRunOfConsistentPairs() {
final Rig rig = new Rig(P60, true, 0);
rig.feedRun(PacerCore.LOCK_PAIRS, 20 * MS, 5 * MS, P60);
long end = 20 * MS + PacerCore.LOCK_PAIRS * P60;
long end = rig.feedRun(PacerCore.LOCK_PAIRS, 20 * MS, 5 * MS, P60);
end += P60 / 2;
rig.feed(end, 5 * MS, end);
end = rig.feedOffGrid(end, 1);
assertFalse(rig.core.locked());
for (int i = 0; i < PacerCore.LOCK_PAIRS; i++) {
@@ -153,60 +156,31 @@ class PacerCoreTest {
@Test
void aRunOfInconsistentPairsGivesUpOnTheGateEntirely() {
final Rig rig = new Rig(P60, true, 0);
rig.feedRun(PacerCore.LOCK_PAIRS + 1, 20 * MS, 5 * MS, P60);
final long end = rig.feedRun(PacerCore.LOCK_PAIRS + 1, 20 * MS, 5 * MS, P60);
assertTrue(rig.core.locked());
long end = 20 * MS + (PacerCore.LOCK_PAIRS + 1) * P60;
for (int i = 0; i < PacerCore.LOCK_WINDOW + 1; i++) {
end += P60 / 2;
rig.feed(end, 5 * MS, end);
}
rig.feedOffGrid(end, PacerCore.LOCK_WINDOW + 1);
assertFalse(rig.core.locked(), "a gate that stops landing on the grid must be released");
assertFalse(rig.core.probing(), "a gate proven off the grid must not be probed again");
}
@Test
void anAbandonedGateStaysRetiredThroughAnotherOnGridRun() {
@ParameterizedTest(name = "{0}")
@CsvSource({
"an abandoned gate stays retired through another on-grid run, false, false",
"invalidate clears an abandoned gate, true, true"})
void abandonedGate(String label, boolean invalidate, boolean lockedAfter) {
final Rig rig = new Rig(P60, true, 120);
rig.feedRun(PacerCore.LOCK_PAIRS + 1, 20 * MS, 5 * MS, P60);
long end = rig.feedRun(PacerCore.LOCK_PAIRS + 1, 20 * MS, 5 * MS, P60);
assertTrue(rig.core.locked());
assertEquals(60, rig.core.effectiveCapHz());
long end = 20 * MS + (PacerCore.LOCK_PAIRS + 1) * P60;
for (int i = 0; i < PacerCore.LOCK_WINDOW + 1; i++) {
end += P60 / 2;
rig.feed(end, 5 * MS, end);
}
end = rig.feedOffGrid(end, PacerCore.LOCK_WINDOW + 1);
assertFalse(rig.core.locked());
assertFalse(rig.core.probing());
for (int i = 0; i < PacerCore.LOCK_PAIRS + 1; i++) {
end += P60;
rig.feed(end, 5 * MS, end);
}
assertFalse(rig.core.locked(), "a retired inconsistent gate must not lock again");
assertFalse(rig.core.probing());
}
@Test
void invalidateClearsAnAbandonedGate() {
final Rig rig = new Rig(P60, true, 120);
rig.feedRun(PacerCore.LOCK_PAIRS + 1, 20 * MS, 5 * MS, P60);
long end = 20 * MS + (PacerCore.LOCK_PAIRS + 1) * P60;
for (int i = 0; i < PacerCore.LOCK_WINDOW + 1; i++) {
end += P60 / 2;
rig.feed(end, 5 * MS, end);
}
assertFalse(rig.core.locked());
assertFalse(rig.core.probing());
rig.core.invalidate();
for (int i = 0; i < PacerCore.LOCK_PAIRS + 1; i++) {
end += P60;
rig.feed(end, 5 * MS, end);
}
assertTrue(rig.core.locked());
if (invalidate) rig.core.invalidate();
rig.feedRun(PacerCore.LOCK_PAIRS + 1, end + P60, 5 * MS, P60);
assertEquals(lockedAfter, rig.core.locked(), label);
assertFalse(rig.core.probing());
}
@@ -348,8 +322,9 @@ class PacerCoreTest {
@Test
void theFirstPacedFrameAfterAnUnpacedStretchIsAnchoredWithoutSleeping() {
final Rig rig = new Rig(P60, false, 0);
for (int i = 0; i < 5; i++) rig.frame(4 * MS);
for (int i = 0; i < 50; i++) rig.frame(4 * MS);
assertEquals(0, rig.time.parks);
assertEquals(0L, rig.core.slackNanos());
rig.core.setCap(60);
rig.frame(4 * MS);
@@ -384,14 +359,6 @@ class PacerCoreTest {
assertTrue(snapped.core.paced());
}
@Test
void anUnpacedFrameNeverSleeps() {
final Rig rig = new Rig(P60, false, 0);
for (int i = 0; i < 50; i++) rig.frame(4 * MS);
assertEquals(0, rig.time.parks);
assertEquals(0L, rig.core.slackNanos());
}
@Test
void theMarginIsClampedIntoItsBandByTheCadence() {
assertEquals(PacerCore.MARGIN_MIN_NANOS, PacerCore.clampMargin(0L, 16_666_666L));
@@ -9,6 +9,7 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.function.Consumer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -89,32 +90,24 @@ class ReducerStateMachineTest {
assertEquals(State.FOCUSED, machine.state());
}
@Test
void toggleForcedCountsAsInput() {
time += 10_000_000_000L;
machine.evaluate(true, true, true, 1_000_000_000L);
assertEquals(State.IDLE, machine.state());
machine.toggleForced();
machine.evaluate(true, true, true, 1_000_000_000L);
assertEquals(State.FORCED, machine.state());
machine.toggleForced();
machine.evaluate(true, true, true, 1_000_000_000L);
assertEquals(State.FOCUSED, machine.state());
static Stream<Arguments> inputToggles() {
return Stream.of(
Arguments.of("forced toggle", (Consumer<ReducerStateMachine>) ReducerStateMachine::toggleForced, State.FORCED),
Arguments.of("keybind-disabled toggle", (Consumer<ReducerStateMachine>) ReducerStateMachine::toggleKeybindDisabled, State.DISABLED));
}
@Test
void toggleKeybindDisabledCountsAsInput() {
@ParameterizedTest(name = "{0}")
@MethodSource("inputToggles")
void toggleCountsAsInput(String label, Consumer<ReducerStateMachine> toggle, State expected) {
time += 10_000_000_000L;
machine.evaluate(true, true, true, 1_000_000_000L);
assertEquals(State.IDLE, machine.state());
machine.toggleKeybindDisabled();
toggle.accept(machine);
machine.evaluate(true, true, true, 1_000_000_000L);
assertEquals(State.DISABLED, machine.state());
assertEquals(expected, machine.state());
machine.toggleKeybindDisabled();
toggle.accept(machine);
machine.evaluate(true, true, true, 1_000_000_000L);
assertEquals(State.FOCUSED, machine.state());
}
@@ -180,13 +173,15 @@ class ReducerStateMachineTest {
assertEquals(0.01f, ReducerStateMachine.masterVolume(1.0f, 0), DELTA);
}
@Test
void stateVolumeSelectsProfile() {
assertEquals(10, ReducerStateMachine.stateVolume(State.FORCED, 10, 20, 30));
assertEquals(10, ReducerStateMachine.stateVolume(State.UNFOCUSED, 10, 20, 30));
assertEquals(20, ReducerStateMachine.stateVolume(State.MINIMIZED, 10, 20, 30));
assertEquals(30, ReducerStateMachine.stateVolume(State.IDLE, 10, 20, 30));
assertEquals(100, ReducerStateMachine.stateVolume(State.FOCUSED, 10, 20, 30));
assertEquals(100, ReducerStateMachine.stateVolume(State.DISABLED, 10, 20, 30));
@ParameterizedTest(name = "{0}")
@CsvSource({
"FORCED, 10",
"UNFOCUSED, 10",
"MINIMIZED, 20",
"IDLE, 30",
"FOCUSED, 100",
"DISABLED, 100"})
void stateVolumeSelectsProfile(State state, int expected) {
assertEquals(expected, ReducerStateMachine.stateVolume(state, 10, 20, 30));
}
}
@@ -7,11 +7,16 @@ import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildOutput;
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkTaskOutput;
import org.embeddedt.embeddium.impl.render.chunk.compile.executor.ChunkJobResult;
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.mockito.Mockito;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.function.Supplier;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
@@ -41,144 +46,127 @@ class DeferredMeshSchedulerTest {
return new ChunkJobResult.Failure<>(new RuntimeException("x"));
}
@Test
void importantItemRunsOnRunImportant() {
ArrayDeque<Runnable> queue = new ArrayDeque<>();
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
DeferredSectionMesh m = mesh(buffers, scheduler.acquireSlice(), true, false, success());
scheduler.submit(m);
assertEquals(0, queue.size());
assertEquals(1, scheduler.pendingCount());
scheduler.runImportant();
assertEquals(1, results.size());
assertTrue(results.peek() instanceof ChunkJobResult.Success);
assertEquals(0, scheduler.pendingCount());
assertSame(buffers, scheduler.acquireBuffers());
private static final class Harness {
final ArrayDeque<Runnable> queue = new ArrayDeque<>();
final Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
final DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
}
@Test
void normalItemRunsThroughMainThreadQueue() {
ArrayDeque<Runnable> queue = new ArrayDeque<>();
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
void importantItemRunsOnRunImportant() {
Harness h = new Harness();
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
DeferredSectionMesh m = mesh(buffers, scheduler.acquireSlice(), false, false, success());
scheduler.submit(m);
ChunkBuildBuffers buffers = h.scheduler.acquireBuffers();
DeferredSectionMesh m = mesh(buffers, h.scheduler.acquireSlice(), true, false, success());
h.scheduler.submit(m);
assertEquals(1, queue.size());
assertEquals(0, h.queue.size());
assertEquals(1, h.scheduler.pendingCount());
scheduler.runImportant();
assertEquals(0, results.size());
h.scheduler.runImportant();
queue.poll().run();
assertEquals(1, h.results.size());
assertTrue(h.results.peek() instanceof ChunkJobResult.Success);
assertEquals(0, h.scheduler.pendingCount());
assertSame(buffers, h.scheduler.acquireBuffers());
}
assertEquals(1, results.size());
assertTrue(results.peek() instanceof ChunkJobResult.Success);
assertEquals(0, scheduler.pendingCount());
@ParameterizedTest(name = "{0}")
@MethodSource("normalResults")
void normalItemRunsThroughMainThreadQueue(String label, Supplier<ChunkJobResult<ChunkBuildOutput>> result, Class<?> expected) {
Harness h = new Harness();
ChunkBuildBuffers buffers = h.scheduler.acquireBuffers();
DeferredSectionMesh m = mesh(buffers, h.scheduler.acquireSlice(), false, false, result.get());
h.scheduler.submit(m);
assertEquals(1, h.queue.size());
h.scheduler.runImportant();
assertEquals(0, h.results.size());
h.queue.poll().run();
assertEquals(1, h.results.size());
assertTrue(expected.isInstance(h.results.peek()));
assertEquals(0, h.scheduler.pendingCount());
}
private static Stream<Arguments> normalResults() {
return Stream.of(
Arguments.of("success", (Supplier<ChunkJobResult<ChunkBuildOutput>>) DeferredMeshSchedulerTest::success, ChunkJobResult.Success.class),
Arguments.of("failure is recorded and counters still decrement", (Supplier<ChunkJobResult<ChunkBuildOutput>>) DeferredMeshSchedulerTest::failure, ChunkJobResult.Failure.class));
}
@Test
void importantItemAfterDestroyReleasesBuffers() {
ArrayDeque<Runnable> queue = new ArrayDeque<>();
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
Harness h = new Harness();
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
WorldSlice slice = scheduler.acquireSlice();
ChunkBuildBuffers buffers = h.scheduler.acquireBuffers();
WorldSlice slice = h.scheduler.acquireSlice();
DeferredSectionMesh m = mesh(buffers, slice, true, false, success());
scheduler.submit(m);
scheduler.markDestroyed();
scheduler.runImportant();
h.scheduler.submit(m);
h.scheduler.markDestroyed();
h.scheduler.runImportant();
assertEquals(0, results.size());
assertEquals(0, scheduler.pendingCount());
assertSame(buffers, scheduler.acquireBuffers());
assertSame(slice, scheduler.acquireSlice());
assertEquals(0, h.results.size());
assertEquals(0, h.scheduler.pendingCount());
assertSame(buffers, h.scheduler.acquireBuffers());
assertSame(slice, h.scheduler.acquireSlice());
Mockito.verify(slice).releaseContext();
}
@Test
void markDestroyedDiscardsQueuedItemAndReturnsBuffersToPool() {
ArrayDeque<Runnable> queue = new ArrayDeque<>();
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
Harness h = new Harness();
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
DeferredSectionMesh m = mesh(buffers, scheduler.acquireSlice(), false, false, success());
scheduler.submit(m);
scheduler.markDestroyed();
queue.poll().run();
ChunkBuildBuffers buffers = h.scheduler.acquireBuffers();
DeferredSectionMesh m = mesh(buffers, h.scheduler.acquireSlice(), false, false, success());
h.scheduler.submit(m);
h.scheduler.markDestroyed();
h.queue.poll().run();
assertEquals(0, results.size());
assertEquals(0, scheduler.pendingCount());
assertSame(buffers, scheduler.acquireBuffers());
assertEquals(0, h.results.size());
assertEquals(0, h.scheduler.pendingCount());
assertSame(buffers, h.scheduler.acquireBuffers());
}
@Test
void obsoleteItemIsDiscarded() {
ArrayDeque<Runnable> queue = new ArrayDeque<>();
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
Harness h = new Harness();
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
DeferredSectionMesh m = mesh(buffers, scheduler.acquireSlice(), false, true, success());
scheduler.submit(m);
queue.poll().run();
ChunkBuildBuffers buffers = h.scheduler.acquireBuffers();
DeferredSectionMesh m = mesh(buffers, h.scheduler.acquireSlice(), false, true, success());
h.scheduler.submit(m);
h.queue.poll().run();
assertEquals(0, results.size());
assertEquals(0, scheduler.pendingCount());
}
@Test
void failureResultIsRecordedAndCountersStillDecrement() {
ArrayDeque<Runnable> queue = new ArrayDeque<>();
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
DeferredSectionMesh m = mesh(buffers, scheduler.acquireSlice(), false, false, failure());
scheduler.submit(m);
queue.poll().run();
assertEquals(1, results.size());
assertTrue(results.peek() instanceof ChunkJobResult.Failure);
assertEquals(0, scheduler.pendingCount());
assertEquals(0, h.results.size());
assertEquals(0, h.scheduler.pendingCount());
}
@Test
void acquireBuffersAfterCompletedRunReturnsTheSameInstance() {
ArrayDeque<Runnable> queue = new ArrayDeque<>();
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
Harness h = new Harness();
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
DeferredSectionMesh m = mesh(buffers, scheduler.acquireSlice(), false, false, success());
scheduler.submit(m);
queue.poll().run();
ChunkBuildBuffers buffers = h.scheduler.acquireBuffers();
DeferredSectionMesh m = mesh(buffers, h.scheduler.acquireSlice(), false, false, success());
h.scheduler.submit(m);
h.queue.poll().run();
assertSame(buffers, scheduler.acquireBuffers());
assertSame(buffers, h.scheduler.acquireBuffers());
}
@Test
void runReturnsSliceToPool() {
ArrayDeque<Runnable> queue = new ArrayDeque<>();
Queue<ChunkJobResult<? extends ChunkTaskOutput>> results = new ConcurrentLinkedDeque<>();
DeferredMeshScheduler scheduler = new DeferredMeshScheduler(null, () -> null, () -> Mockito.mock(WorldSlice.class), queue::add, results);
Harness h = new Harness();
ChunkBuildBuffers buffers = scheduler.acquireBuffers();
WorldSlice slice = scheduler.acquireSlice();
ChunkBuildBuffers buffers = h.scheduler.acquireBuffers();
WorldSlice slice = h.scheduler.acquireSlice();
DeferredSectionMesh m = mesh(buffers, slice, false, false, success());
scheduler.submit(m);
queue.poll().run();
h.scheduler.submit(m);
h.queue.poll().run();
assertSame(slice, scheduler.acquireSlice());
assertSame(slice, h.scheduler.acquireSlice());
Mockito.verify(slice).releaseContext();
}
}
@@ -1,67 +1,39 @@
package com.gtnewhorizons.angelica.rendering.celeritas;
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;
import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ObjectPoolTest {
@Test
void acquireOnEmptyPoolCallsFactoryOnce() {
@ParameterizedTest(name = "n={0}")
@ValueSource(ints = {1, 2, 3})
void releaseThenAcquireReturnsSameInstancesWithoutFactory(int n) {
AtomicInteger calls = new AtomicInteger();
ObjectPool<Object> pool = new ObjectPool<>(() -> {
calls.incrementAndGet();
return new Object();
});
Object o = pool.acquire();
assertTrue(o != null);
assertEquals(1, calls.get());
}
@Test
void releaseThenAcquireReturnsSameInstanceWithoutFactory() {
AtomicInteger calls = new AtomicInteger();
ObjectPool<Object> pool = new ObjectPool<>(() -> {
calls.incrementAndGet();
return new Object();
});
Object o = pool.acquire();
pool.release(o);
Object reacquired = pool.acquire();
assertSame(o, reacquired);
assertEquals(1, calls.get());
}
@Test
void twoReleasesThenTwoAcquiresReturnBothInstances() {
AtomicInteger calls = new AtomicInteger();
ObjectPool<Object> pool = new ObjectPool<>(() -> {
calls.incrementAndGet();
return new Object();
});
Object a = pool.acquire();
Object b = pool.acquire();
pool.release(a);
pool.release(b);
Object[] acquired = new Object[n];
for (int i = 0; i < n; i++) {
acquired[i] = pool.acquire();
assertNotNull(acquired[i]);
}
for (Object o : acquired) pool.release(o);
Set<Object> reacquired = new HashSet<>();
reacquired.add(pool.acquire());
reacquired.add(pool.acquire());
for (int i = 0; i < n; i++) reacquired.add(pool.acquire());
assertEquals(2, reacquired.size());
assertTrue(reacquired.contains(a));
assertTrue(reacquired.contains(b));
assertEquals(2, calls.get());
assertEquals(n, reacquired.size());
for (Object o : acquired) assertTrue(reacquired.contains(o));
assertEquals(n, calls.get());
}
}
@@ -34,6 +34,8 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
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.GL30;
@@ -663,12 +665,9 @@ class IndirectCullDrawParityTest {
}
}
@Test
void cutoutAndTranslucentShareOneChainedDispatchWithoutSolid() {
unsortedAndTranslucentShareOneChainedDispatch(true);
}
private void unsortedAndTranslucentShareOneChainedDispatch(boolean cutout) {
@ParameterizedTest(name = "cutout={0}")
@ValueSource(booleans = {false, true})
void unsortedAndTranslucentShareOneChainedDispatch(boolean cutout) {
assumeTrue(RenderSystem.supportsCompute() && RenderSystem.supportsMultiDrawIndirect(),
"compute-driven indirect culling unsupported");
@@ -18,6 +18,10 @@ import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class SectionMetaBufferTest {
private static int put(SectionMetaBuffer meta, int pass, int x, int y, int z, ByteBuffer src) {
return meta.update(pass, x, y, z, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED);
}
@Test
void slotLayout_originAndFencePosts() {
final SectionMetaBuffer meta = new SectionMetaBuffer();
@@ -57,23 +61,20 @@ class SectionMetaBufferTest {
@Test
void slotAllocation_isStablePerSection() {
final SectionMetaBuffer meta = new SectionMetaBuffer();
final int[] vo = { 0, 0, 0, 0, 0, 0, 0 };
final int[] ec = { 0, 0, 0, 0, 0, 0, 0 };
final int[] io = { 0, 0, 0, 0, 0, 0, 0 };
final ByteBuffer src = SyntheticSectionData.row(0, 0, vo, ec, io);
final ByteBuffer src = SyntheticSectionData.sliceMaskOnly(0);
try {
meta.update(0, 16, 32, 64, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED);
put(meta, 0, 16, 32, 64, src);
final int firstSlot = meta.lookupSlot(0, 16, 32, 64);
meta.update(0, 16, 32, 64, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED);
put(meta, 0, 16, 32, 64, src);
assertEquals(firstSlot, meta.lookupSlot(0, 16, 32, 64), "slot must be stable across updates");
meta.update(0, 0, 0, 0, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED);
put(meta, 0, 0, 0, 0, src);
final int otherSlot = meta.lookupSlot(0, 0, 0, 0);
assertEquals(firstSlot + 1, otherSlot, "fresh section -> next high-water slot");
meta.remove(0, 16, 32, 64, 0);
assertEquals(-1, meta.lookupSlot(0, 16, 32, 64), "removed slot should not resolve");
meta.update(0, 16, 32, 64, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED);
put(meta, 0, 16, 32, 64, src);
assertEquals(firstSlot, meta.lookupSlot(0, 16, 32, 64), "free-list reuse should reclaim the slot");
} finally {
MemoryUtilities.memFree(src);
@@ -84,10 +85,7 @@ class SectionMetaBufferTest {
@Test
void localSectionIndex_unpacking() {
final SectionMetaBuffer meta = new SectionMetaBuffer();
final int[] vo = { 0, 0, 0, 0, 0, 0, 0 };
final int[] ec = { 0, 0, 0, 0, 0, 0, 0 };
final int[] io = { 0, 0, 0, 0, 0, 0, 0 };
final ByteBuffer src = SyntheticSectionData.row(0, 0, vo, ec, io);
final ByteBuffer src = SyntheticSectionData.sliceMaskOnly(0);
try {
final int x = 3, y = 2, z = 5;
final int localIdx = (x << 5) | (z << 2) | y;
@@ -136,16 +134,13 @@ class SectionMetaBufferTest {
@Test
void update_returnsAllocatedSlot() {
final SectionMetaBuffer meta = new SectionMetaBuffer();
final int[] vo = { 0, 0, 0, 0, 0, 0, 0 };
final int[] ec = { 0, 0, 0, 0, 0, 0, 0 };
final int[] io = { 0, 0, 0, 0, 0, 0, 0 };
final ByteBuffer src = SyntheticSectionData.row(0, 0, vo, ec, io);
final ByteBuffer src = SyntheticSectionData.sliceMaskOnly(0);
try {
final int slotA = meta.update(0, 16, 32, 64, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED);
final int slotB = meta.update(0, 0, 0, 0, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED);
final int slotA = put(meta, 0, 16, 32, 64, src);
final int slotB = put(meta, 0, 0, 0, 0, src);
assertEquals(0, slotA, "first allocation -> slot 0");
assertEquals(1, slotB, "second distinct section -> slot 1");
assertEquals(slotA, meta.update(0, 16, 32, 64, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED), "re-update returns the stable slot");
assertEquals(slotA, put(meta, 0, 16, 32, 64, src), "re-update returns the stable slot");
assertEquals(-1, meta.update(0, 0, 0, 0, 0, 0L, 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED), "null dataPtr -> -1");
} finally {
MemoryUtilities.memFree(src);
@@ -156,11 +151,10 @@ class SectionMetaBufferTest {
@Test
void syncIfDirty_positionsAndClearsDirty() {
final SectionMetaBuffer meta = new SectionMetaBuffer();
final int[] zeros = { 0, 0, 0, 0, 0, 0, 0 };
final ByteBuffer src = SyntheticSectionData.row(0, 0, zeros, zeros, zeros);
final ByteBuffer src = SyntheticSectionData.sliceMaskOnly(0);
try {
meta.update(0, 16, 32, 64, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED);
meta.update(0, 0, 0, 0, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED);
put(meta, 0, 16, 32, 64, src);
put(meta, 0, 0, 0, 0, src);
assertTrue(meta.isDirty(), "dirty after update");
final AtomicReference<ByteBuffer> seen = new AtomicReference<>();
final boolean ran = meta.syncIfDirty(b -> { seen.set(b); return true; });
@@ -180,10 +174,9 @@ class SectionMetaBufferTest {
@Test
void syncIfDirty_refusedSinkKeepsDataPending() {
final SectionMetaBuffer meta = new SectionMetaBuffer();
final int[] zeros = { 0, 0, 0, 0, 0, 0, 0 };
final ByteBuffer src = SyntheticSectionData.row(0, 0, zeros, zeros, zeros);
final ByteBuffer src = SyntheticSectionData.sliceMaskOnly(0);
try {
meta.update(0, 16, 32, 64, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED);
put(meta, 0, 16, 32, 64, src);
final AtomicInteger offers = new AtomicInteger();
assertFalse(meta.syncIfDirty(b -> { offers.incrementAndGet(); return false; }), "a refused sink reports no sync");
@@ -214,16 +207,15 @@ class SectionMetaBufferTest {
@Test
void reset_isIdempotent() {
final SectionMetaBuffer meta = new SectionMetaBuffer();
final int[] zeros = { 0, 0, 0, 0, 0, 0, 0 };
final ByteBuffer src = SyntheticSectionData.row(0, 0, zeros, zeros, zeros);
final ByteBuffer src = SyntheticSectionData.sliceMaskOnly(0);
try {
meta.update(0, 16, 32, 64, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED);
put(meta, 0, 16, 32, 64, src);
meta.reset();
assertNull(meta.getMirrorForReadOnly(), "mirror gone after reset");
assertEquals(-1, meta.lookupSlot(0, 16, 32, 64), "slot map cleared");
meta.reset();
meta.reset();
meta.update(0, 16, 32, 64, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED);
put(meta, 0, 16, 32, 64, src);
assertEquals(0, meta.lookupSlot(0, 16, 32, 64), "reset is reusable, allocates fresh slot");
} finally {
MemoryUtilities.memFree(src);
@@ -234,8 +226,7 @@ class SectionMetaBufferTest {
@Test
void concurrent_updates_and_syncs_doNotCrash() throws InterruptedException {
final SectionMetaBuffer meta = new SectionMetaBuffer();
final int[] zeros = { 0, 0, 0, 0, 0, 0, 0 };
final ByteBuffer src = SyntheticSectionData.row(0, 0, zeros, zeros, zeros);
final ByteBuffer src = SyntheticSectionData.sliceMaskOnly(0);
final AtomicBoolean stop = new AtomicBoolean(false);
final long srcAddr = MemoryUtilities.memAddress(src);
@@ -276,13 +267,10 @@ class SectionMetaBufferTest {
@Test
void perPassRemove_leavesSiblingPasses() {
final SectionMetaBuffer meta = new SectionMetaBuffer();
final int[] vo = { 0, 0, 0, 0, 0, 0, 0 };
final int[] ec = { 0, 0, 0, 0, 0, 0, 0 };
final int[] io = { 0, 0, 0, 0, 0, 0, 0 };
final ByteBuffer src = SyntheticSectionData.row(0, 0, vo, ec, io);
final ByteBuffer src = SyntheticSectionData.sliceMaskOnly(0);
try {
meta.update(0, 16, 32, 64, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED);
meta.update(1, 16, 32, 64, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED);
put(meta, 0, 16, 32, 64, src);
put(meta, 1, 16, 32, 64, src);
final int slotA = meta.lookupSlot(0, 16, 32, 64);
final int slotB = meta.lookupSlot(1, 16, 32, 64);
@@ -290,7 +278,7 @@ class SectionMetaBufferTest {
assertEquals(-1, meta.lookupSlot(0, 16, 32, 64), "pass 0 slot must be gone");
assertEquals(slotB, meta.lookupSlot(1, 16, 32, 64), "pass 1 slot must survive pass 0 removal");
meta.update(0, 0, 0, 0, 0, MemoryUtilities.memAddress(src), 0, SectionRenderDataUnsafe.Strategy.FULL, QuadPrimitiveType.TRIANGULATED);
put(meta, 0, 0, 0, 0, src);
assertEquals(slotA, meta.lookupSlot(0, 0, 0, 0), "free-list reuse claims pass 0's vacated slot");
} finally {
MemoryUtilities.memFree(src);
@@ -223,19 +223,6 @@ class TerrainPassPreparationTest {
assertEquals(2, state("totalAppendedEntries"));
}
@Test
void absentSolidSelectionPreservesPreparedCutoutAndSortedPasses() {
final ChunkRenderListIterable lists = lists(6);
prepare(lists);
select(lists, 0);
gpu.endPass();
select(lists, 1);
assertRange("primaryPass", 0);
gpu.endPass();
select(lists, 2);
assertRange("sortedPass", 1);
}
@ParameterizedTest(name = "passMask={0}")
@ValueSource(ints = {4, 5}) // translucent alone, and chained after solid
void unsortedTranslucentPassClearsIndexPointerMask(int passMask) {
@@ -605,14 +605,18 @@ class ParticleInstancingGLTest {
capture.addVertexWithUV(1.0, 0.0, 0.0, 1.0, 1.0);
}
@Test
void aSecondQuadAfterASelfDrawSpillsIntoItsOwnBatchWhenTheRunIsNotDrawing() {
final RecordingTessellator run = new RecordingTessellator();
private static ParticleCaptureTessellator capturedQuad(RecordingTessellator run) {
final ParticleCaptureTessellator capture = new ParticleCaptureTessellator();
capture.begin(100, run, GLStateManager.getModelViewMatrix());
capture.setColorRGBA(5, 6, 7, 8);
feedQuad(capture);
return capture;
}
@Test
void aSecondQuadAfterASelfDrawSpillsIntoItsOwnBatchWhenTheRunIsNotDrawing() {
final RecordingTessellator run = new RecordingTessellator();
final ParticleCaptureTessellator capture = capturedQuad(run);
capture.draw();
run.log.clear();
@@ -630,11 +634,7 @@ class ParticleInstancingGLTest {
@Test
void aClosedSpillReplaysInTheSameOrder() {
final RecordingTessellator run = new RecordingTessellator();
final ParticleCaptureTessellator capture = new ParticleCaptureTessellator();
capture.begin(100, run, GLStateManager.getModelViewMatrix());
capture.setColorRGBA(5, 6, 7, 8);
feedQuad(capture);
final ParticleCaptureTessellator capture = capturedQuad(run);
capture.draw();
run.isDrawing = true;
run.log.clear();
@@ -658,11 +658,7 @@ class ParticleInstancingGLTest {
@Test
void aFifthVertexSpillsAndForwardsRemainingCallsToRun() {
final RecordingTessellator run = new RecordingTessellator();
final ParticleCaptureTessellator capture = new ParticleCaptureTessellator();
capture.begin(100, run, GLStateManager.getModelViewMatrix());
capture.setColorRGBA(5, 6, 7, 8);
feedQuad(capture);
final ParticleCaptureTessellator capture = capturedQuad(run);
assertFalse(capture.spilled());
capture.addVertexWithUV(2.0, 2.0, 0.0, 0.5, 0.5);
@@ -25,6 +25,7 @@ import com.gtnewhorizons.angelica.rendering.tesr.RetainedTesrTestSupport.Countin
import com.gtnewhorizons.angelica.shadercompat.ShaderGlint;
import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource;
import net.coderbot.iris.layer.PassOverride;
import net.coderbot.iris.uniforms.CapturedStateScope;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.ItemRenderer;
import net.minecraft.util.ResourceLocation;
@@ -393,44 +394,40 @@ class RetainedTesrGroupsGLTest {
@Test
void cpuFallbackRestoresEveryIdAndTintBeforeDrawingAndRestoresTheCaller() {
final var state = net.coderbot.iris.uniforms.CapturedRenderingState.INSTANCE;
state.pushCurrentEntityAndItem();
state.pushCurrentBlockEntity();
state.pushCurrentEntityColor();
try {
final var draws = new ArrayList<long[]>();
onBufferSetup = () -> draws.add(new long[] {
state.getCurrentRenderedEntity(), state.getCurrentRenderedBlockEntity(), state.getCurrentRenderedItem(),
AngelicaBufferSource.packEntityColor(state.getCurrentEntityColor())
});
groups.beginPass(new Matrix4f(), 0, 0, 0, instanced, new CountingPipeline(false, false));
final TemplateBuffer template = capturedQuad(0.1);
for (int i = 0; i < 3; i++) {
final long info = com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs.packEntityInfo(45010 + i, 45020 + i, 45030 + i);
final int color = AngelicaBufferSource.packAbgr(i / 2f, 0, 0, 1);
groups.queue(SCOPE, template, layer, RetainedTesrTestSupport.STREAM, new Matrix4f(), 0, -1, color, info, 45020 + i, null);
try (CapturedStateScope ignored = new CapturedStateScope()) {
try {
final var draws = new ArrayList<long[]>();
onBufferSetup = () -> draws.add(new long[] {
state.getCurrentRenderedEntity(), state.getCurrentRenderedBlockEntity(), state.getCurrentRenderedItem(),
AngelicaBufferSource.packEntityColor(state.getCurrentEntityColor())
});
groups.beginPass(new Matrix4f(), 0, 0, 0, instanced, new CountingPipeline(false, false));
final TemplateBuffer template = capturedQuad(0.1);
for (int i = 0; i < 3; i++) {
final long info = com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs.packEntityInfo(45010 + i, 45020 + i, 45030 + i);
final int color = AngelicaBufferSource.packAbgr(i / 2f, 0, 0, 1);
groups.queue(SCOPE, template, layer, RetainedTesrTestSupport.STREAM, new Matrix4f(), 0, -1, color, info, 45020 + i, null);
}
state.setCurrentEntityAndItem(81, 82);
state.setCurrentBlockEntity(83);
state.setCurrentEntityColor(0, 1, 0, 0.5f);
draws.clear();
assertEquals(GL11.GL_QUADS, template.drawMode);
groups.drawLayer(layer);
assertEquals(3, draws.size(), "only differing uniform values split the CPU fallback");
for (int i = 0; i < 3; i++) {
assertArrayEquals(new long[] {45010 + i, 45020 + i, 45030 + i,
AngelicaBufferSource.packAbgr(i / 2f, 0, 0, 1)}, draws.get(i));
}
source.discard();
assertEquals(81, state.getCurrentRenderedEntity());
assertEquals(82, state.getCurrentRenderedItem());
assertEquals(83, state.getCurrentRenderedBlockEntity());
assertTrue(state.getCurrentEntityColor().equals(0, 1, 0, 0.5f));
} finally {
onBufferSetup = null;
source.discard();
}
state.setCurrentEntityAndItem(81, 82);
state.setCurrentBlockEntity(83);
state.setCurrentEntityColor(0, 1, 0, 0.5f);
draws.clear();
assertEquals(GL11.GL_QUADS, template.drawMode);
groups.drawLayer(layer);
assertEquals(3, draws.size(), "only differing uniform values split the CPU fallback");
for (int i = 0; i < 3; i++) {
assertArrayEquals(new long[] {45010 + i, 45020 + i, 45030 + i,
AngelicaBufferSource.packAbgr(i / 2f, 0, 0, 1)}, draws.get(i));
}
source.discard();
assertEquals(81, state.getCurrentRenderedEntity());
assertEquals(82, state.getCurrentRenderedItem());
assertEquals(83, state.getCurrentRenderedBlockEntity());
assertTrue(state.getCurrentEntityColor().equals(0, 1, 0, 0.5f));
} finally {
onBufferSetup = null;
source.discard();
state.popCurrentEntityColor();
state.popCurrentBlockEntity();
state.popCurrentEntityAndItem();
}
}
@@ -10,6 +10,7 @@ import net.coderbot.batchedentityrendering.impl.AngelicaBufferSource;
import net.coderbot.batchedentityrendering.impl.BufferSegment;
import net.coderbot.batchedentityrendering.impl.BufferSourceProbe;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.coderbot.iris.uniforms.CapturedStateScope;
import net.minecraft.util.ResourceLocation;
import org.joml.Matrix4f;
import org.junit.jupiter.api.BeforeEach;
@@ -72,10 +73,7 @@ class RetainedTesrGroupsTest {
@Test
void retainedGroupsSeparateTheFullIdTupleAndTintAndReuseStableKeys() {
final var state = CapturedRenderingState.INSTANCE;
state.pushCurrentEntityAndItem();
state.pushCurrentBlockEntity();
state.pushCurrentEntityColor();
try {
try (CapturedStateScope ignored = new CapturedStateScope()) {
for (int frame = 0; frame < 2; frame++) {
groups.beginPass(new Matrix4f(), 0, 0, 0);
for (int entity : new int[] {11, 12}) {
@@ -92,20 +90,13 @@ class RetainedTesrGroupsTest {
}
assertEquals(16, groups.groupCount(), "only distinct ID/tint combinations allocate groups, including across frames");
}
} finally {
state.popCurrentEntityColor();
state.popCurrentBlockEntity();
state.popCurrentEntityAndItem();
}
}
@Test
void instancedGroupsKeepFullIdsAndTintPerInstanceWithoutSplitting() {
final var state = CapturedRenderingState.INSTANCE;
state.pushCurrentEntityAndItem();
state.pushCurrentBlockEntity();
state.pushCurrentEntityColor();
try {
try (CapturedStateScope ignored = new CapturedStateScope()) {
groups.beginPass(new Matrix4f(), 0, 0, 0, new InstancedTemplateRenderer(ring), new RetainedTesrTestSupport.CountingPipeline(true, true));
for (int i = 0; i < 100; i++) {
state.setCurrentEntityAndItem(i, i + 200);
@@ -122,10 +113,6 @@ class RetainedTesrGroupsTest {
assertEquals(InstancedAttribs.packEntityInfo(i, i + 100, i + 200), group.templateColumns.infos.getLong(i));
assertEquals(AngelicaBufferSource.packAbgr(i / 100f, 0, 0, 1), group.templateColumns.overlays.getInt(i));
}
} finally {
state.popCurrentEntityColor();
state.popCurrentBlockEntity();
state.popCurrentEntityAndItem();
}
}
@@ -19,7 +19,7 @@ import static com.gtnewhorizon.gtnhlib.client.renderer.cel.util.ModelQuadUtil.X_
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
final class RetainedTesrTestSupport {
public final class RetainedTesrTestSupport {
private RetainedTesrTestSupport() {}
@@ -57,7 +57,7 @@ final class RetainedTesrTestSupport {
return byLayer(groups).get(layer);
}
static TemplateBuffer template() {
public static TemplateBuffer template() {
final int[] data = new int[4 * VERTEX_SIZE];
for (int v = 0; v < 4; v++) {
final int base = v * VERTEX_SIZE;
@@ -29,14 +29,17 @@ import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL20;
import java.lang.reflect.Constructor;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.function.IntSupplier;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@@ -76,6 +79,17 @@ class StateSetOverrideGLTest {
if (otherProgram != 0) GLStateManager.glDeleteProgram(otherProgram);
}
private static int[] driverBlendFunc() {
return new int[] {GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), GL11.glGetInteger(GL14.GL_BLEND_DST_RGB),
GL11.glGetInteger(GL14.GL_BLEND_SRC_ALPHA), GL11.glGetInteger(GL14.GL_BLEND_DST_ALPHA)};
}
private static boolean[] driverColorMask() {
final ByteBuffer mask = BufferUtils.createByteBuffer(16);
GL11.glGetBoolean(GL11.GL_COLOR_WRITEMASK, mask);
return new boolean[] {mask.get(0) != 0, mask.get(1) != 0, mask.get(2) != 0, mask.get(3) != 0};
}
/** Vanilla blend is enabled SRC_ALPHA/ONE_MINUS_SRC_ALPHA/ONE/ZERO under an Iris override. */
private static void overrideVanillaBlend() {
VanillaLayerRig.installBlend();
@@ -107,6 +121,10 @@ class StateSetOverrideGLTest {
final boolean liveEnabledBefore = GLStateManager.getBlendMode().isEnabled();
final BlendState liveBefore = new BlendState().set(GLStateManager.getBlendState());
final boolean driverEnabledBefore = GL11.glIsEnabled(GL11.GL_BLEND);
final int[] driverFuncBefore = driverBlendFunc();
assertTrue(driverEnabledBefore, "driver blend enable carries the override");
assertArrayEquals(new int[] {GL11.GL_ONE, GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE}, driverFuncBefore, "driver blend func carries the override");
final int d = GLStateManager.pushState(StateSet.BLEND);
final boolean vanillaEnabledBefore = BlendModeStorage.ENABLE_LAYER.getVanilla();
@@ -115,6 +133,9 @@ class StateSetOverrideGLTest {
setBlendFunc.run();
GLStateManager.popStateTo(d);
assertEquals(driverEnabledBefore, GL11.glIsEnabled(GL11.GL_BLEND), "driver blend enable left untouched by the bracket");
assertArrayEquals(driverFuncBefore, driverBlendFunc(), "driver blend func left untouched by the bracket");
assertTrue(BlendModeStorage.ENABLE_LAYER.getVanilla(), "vanilla shadow enable restored to the pre-bracket value");
final BlendState vanillaAfter = new BlendState();
BlendModeStorage.FUNC_LAYER.readVanilla(vanillaAfter);
@@ -176,12 +197,19 @@ class StateSetOverrideGLTest {
final boolean liveMaskBefore = GLStateManager.getDepthState().isEnabled();
final ColorMask liveColorBefore = new ColorMask().set(GLStateManager.getColorMask());
final boolean driverDepthBefore = GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK);
final boolean[] driverColorBefore = driverColorMask();
assertFalse(driverDepthBefore, "driver depth mask carries the held-off value");
assertFalse(driverColorBefore[0] || driverColorBefore[1] || driverColorBefore[2] || driverColorBefore[3], "driver color mask carries the held-off value");
final int d = GLStateManager.pushState(StateSet.MASKS);
GLStateManager.glDepthMask(false);
GLStateManager.glColorMask(true, true, true, true);
GLStateManager.popStateTo(d);
assertEquals(driverDepthBefore, GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK), "driver depth mask left untouched by the bracket");
assertArrayEquals(driverColorBefore, driverColorMask(), "driver color mask left untouched by the bracket");
final DepthState vanillaDepthAfter = new DepthState();
DepthColorStorage.DEPTH_LAYER.readVanilla(vanillaDepthAfter);
assertTrue(vanillaDepthAfter.isEnabled(), "vanilla shadow depth mask restored to the pre-bracket value");
@@ -33,10 +33,14 @@ 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.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@@ -98,6 +102,12 @@ class TesrLayerStateTest {
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
}
private static void assertDriverColorMask(boolean expected) {
final ByteBuffer mask = BufferUtils.createByteBuffer(16);
GL11.glGetBoolean(GL11.GL_COLOR_WRITEMASK, mask);
for (int i = 0; i < 4; i++) assertEquals(expected, mask.get(i) != 0, "driver color mask channel " + i);
}
private static RenderLayer layer(TesrMaterial material) {
return RenderLayer.tesr(null, material, PassOverride.NONE, 0f, 0f, ShaderGlint.NO_TINT, DrawState.CULL_BACK, true, true);
}
@@ -197,10 +207,14 @@ class TesrLayerStateTest {
() -> {
assertEquals(0, GLStateManager.getActiveProgram(), "program during batch draw");
assertEquals(0, GLStateManager.getDrawFramebuffer(), "framebuffer during batch draw");
assertEquals(0, GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM), "driver program during batch draw");
assertEquals(0, GL11.glGetInteger(GL30.GL_DRAW_FRAMEBUFFER_BINDING), "driver draw framebuffer during batch draw");
},
() -> {
assertEquals(program, GLStateManager.getActiveProgram(), "caller program restored");
assertEquals(framebuffer, GLStateManager.getDrawFramebuffer(), "caller framebuffer restored");
assertEquals(program, GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM), "driver caller program restored");
assertEquals(framebuffer, GL11.glGetInteger(GL30.GL_DRAW_FRAMEBUFFER_BINDING), "driver caller framebuffer restored");
},
() -> {
GLStateManager.glUseProgram(0);
@@ -220,8 +234,16 @@ class TesrLayerStateTest {
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
return new BatchScenario(
() -> assertEquals(GL11.GL_LINEAR, TextureInfoCache.INSTANCE.getInfo(texture).getMinFilter(), "filter from before the window"),
() -> assertEquals(GL11.GL_NEAREST, TextureInfoCache.INSTANCE.getInfo(texture).getMinFilter(), "window filter restored"),
() -> {
assertEquals(GL11.GL_LINEAR, TextureInfoCache.INSTANCE.getInfo(texture).getMinFilter(), "filter from before the window");
assertEquals(GL11.GL_LINEAR, GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER), "driver min filter from before the window");
assertEquals(GL11.GL_LINEAR, GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER), "driver mag filter from before the window");
},
() -> {
assertEquals(GL11.GL_NEAREST, TextureInfoCache.INSTANCE.getInfo(texture).getMinFilter(), "window filter restored");
assertEquals(GL11.GL_NEAREST, GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER), "driver window min filter restored");
assertEquals(GL11.GL_NEAREST, GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER), "driver window mag filter restored");
},
() -> {
TextureUtilHooks.clearSavedFilterTexture();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
@@ -423,6 +445,14 @@ class TesrLayerStateTest {
assertTrue(GLStateManager.getLightingState().isEnabled(), "lit");
assertTrue(GLStateManager.getColorMask().red, "color mask pinned on");
assertFalse(GLStateManager.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL), "no polygon offset");
assertTrue(GL11.glIsEnabled(GL11.GL_DEPTH_TEST), "driver depth test");
assertEquals(GL11.GL_LEQUAL, GL11.glGetInteger(GL11.GL_DEPTH_FUNC), "driver depth func");
assertTrue(GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK), "driver depth write");
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND), "driver opaque");
assertTrue(GL11.glIsEnabled(GL11.GL_CULL_FACE), "driver cull pinned on");
assertEquals(GL11.GL_BACK, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "driver cull face pinned to back");
assertDriverColorMask(true);
assertFalse(GL11.glIsEnabled(GL11.GL_POLYGON_OFFSET_FILL), "driver no polygon offset");
}),
Arguments.of("every material toggle", (Runnable) () -> { },
TesrMaterial.builder().noCull().unlit().noDepthWrite().cutout(0.5f).translucent().depthEqual().build(), (Runnable) () -> {
@@ -435,6 +465,12 @@ class TesrLayerStateTest {
assertEquals(GL11.GL_SRC_ALPHA, GLStateManager.getBlendState().getSrcRgb());
assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, GLStateManager.getBlendState().getDstRgb());
assertEquals(GL11.GL_EQUAL, GLStateManager.getDepthState().getFunc(), "depthEqual");
assertFalse(GL11.glIsEnabled(GL11.GL_CULL_FACE), "driver noCull");
assertFalse(GL11.glGetBoolean(GL11.GL_DEPTH_WRITEMASK), "driver noDepthWrite");
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND), "driver translucent");
assertEquals(GL11.GL_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_SRC_RGB), "driver blend src rgb");
assertEquals(GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.glGetInteger(GL14.GL_BLEND_DST_RGB), "driver blend dst rgb");
assertEquals(GL11.GL_EQUAL, GL11.glGetInteger(GL11.GL_DEPTH_FUNC), "driver depthEqual");
}));
}
@@ -18,12 +18,16 @@ import org.embeddedt.embeddium.impl.render.chunk.vertex.format.ChunkVertexType;
import org.embeddedt.embeddium.impl.render.viewport.CameraTransform;
import org.junit.jupiter.api.AfterEach;
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.ArrayList;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -151,6 +155,15 @@ class ShadowVoxelizerTest {
return sink;
}
private RecordingSink walkSingleSection(RenderRegion region, CameraTransform camera, boolean faceCulling, int... vertsPerFacing) {
final TerrainRenderPass renderPass = pass("solid");
final long heap = heap();
final SectionRenderDataStorage storage = storage(heap);
writeSection(heap, 0, 0, vertsPerFacing);
attachStorage(region, renderPass, storage);
return walk(lists(renderList(region, 0)), renderPass, camera, faceCulling);
}
private final List<Long> heaps = new ArrayList<>();
@AfterEach
@@ -182,82 +195,53 @@ class ShadowVoxelizerTest {
assertArrayEqualsInt(new int[]{116, 4}, sink.ranges.get(4), "section 5 facing 4");
}
@Test
void regionOffsetIsCameraRelative() {
final TerrainRenderPass renderPass = pass("solid");
final RenderRegion region = region(1, 0, 3);
final long heap = heap();
final SectionRenderDataStorage storage = storage(heap);
writeSection(heap, 0, 0, 4, 0, 0, 0, 0, 0, 0);
attachStorage(region, renderPass, storage);
final CameraTransform camera = new CameraTransform(70.25, 12.5, -3.75);
@ParameterizedTest(name = "{0}")
@MethodSource("regionOffsets")
void regionOffsetIsCameraRelativeFromTheRegionOrigin(String label, int rx, int ry, int rz,
double camX, double camY, double camZ, Float literalX) {
final RenderRegion region = region(rx, ry, rz);
final CameraTransform camera = new CameraTransform(camX, camY, camZ);
final RecordingSink sink = walk(lists(renderList(region, 0)), renderPass, camera, false);
final RecordingSink sink = walkSingleSection(region, camera, false, 4, 0, 0, 0, 0, 0, 0);
assertEquals(1, sink.regions.size());
final float[] offset = sink.regions.get(0);
assertEquals(region.getOriginX() - camera.intX - camera.fracX, offset[0], 0f, "x");
assertEquals(region.getOriginY() - camera.intY - camera.fracY, offset[1], 0f, "y");
assertEquals(region.getOriginZ() - camera.intZ - camera.fracZ, offset[2], 0f, "z");
if (literalX != null) {
assertEquals(literalX, offset[0], 0f, "region 1 starts at block 128");
}
}
@Test
void regionOffsetUsesTheRegionOriginNotA256BlockGrid() {
final TerrainRenderPass renderPass = pass("solid");
final RenderRegion region = region(1, 0, 0);
final long heap = heap();
final SectionRenderDataStorage storage = storage(heap);
writeSection(heap, 0, 0, 4, 0, 0, 0, 0, 0, 0);
attachStorage(region, renderPass, storage);
final RecordingSink sink = walk(lists(renderList(region, 0)), renderPass, new CameraTransform(0, 0, 0), false);
assertEquals(128f, sink.regions.get(0)[0], 0f, "region 1 starts at block 128");
private static Stream<Arguments> regionOffsets() {
return Stream.of(
Arguments.of("camera relative", 1, 0, 3, 70.25, 12.5, -3.75, null),
Arguments.of("region origin is not a 256 block grid", 1, 0, 0, 0.0, 0.0, 0.0, 128f));
}
@Test
void contiguousFacingsCollapseToOneRange() {
final TerrainRenderPass renderPass = pass("solid");
final RenderRegion region = region(0, 0, 0);
final long heap = heap();
final SectionRenderDataStorage storage = storage(heap);
writeSection(heap, 0, 0, 4, 4, 4, 4, 4, 4, 4);
attachStorage(region, renderPass, storage);
@ParameterizedTest(name = "{0}")
@MethodSource("facingRuns")
void facingMaskProducesTheExpectedRanges(String label, int[] vertsPerFacing, int[][] expectedRanges) {
final RecordingSink sink = walkSingleSection(region(0, 0, 0), new CameraTransform(0, 0, 0), false, vertsPerFacing);
final RecordingSink sink = walk(lists(renderList(region, 0)), renderPass, new CameraTransform(0, 0, 0), false);
assertEquals(1, sink.ranges.size(), "seven adjacent facings are one vertex span, not seven dispatches");
assertEquals(0, sink.ranges.get(0)[0]);
assertEquals(28, sink.ranges.get(0)[1]);
assertEquals(expectedRanges.length, sink.ranges.size());
for (int i = 0; i < expectedRanges.length; i++) {
assertArrayEqualsInt(expectedRanges[i], sink.ranges.get(i), "range " + i);
}
}
@Test
void gapsInTheFacingMaskSplitRuns() {
final TerrainRenderPass renderPass = pass("solid");
final RenderRegion region = region(0, 0, 0);
final long heap = heap();
final SectionRenderDataStorage storage = storage(heap);
writeSection(heap, 0, 0, 4, 0, 8, 0, 0, 0, 4);
attachStorage(region, renderPass, storage);
final RecordingSink sink = walk(lists(renderList(region, 0)), renderPass, new CameraTransform(0, 0, 0), false);
assertEquals(3, sink.ranges.size());
assertArrayEqualsInt(new int[]{0, 4}, sink.ranges.get(0), "facing 0");
assertArrayEqualsInt(new int[]{4, 8}, sink.ranges.get(1), "facing 2");
assertArrayEqualsInt(new int[]{12, 4}, sink.ranges.get(2), "facing 6");
private static Stream<Arguments> facingRuns() {
return Stream.of(
Arguments.of("seven adjacent facings are one vertex span, not seven dispatches",
new int[] {4, 4, 4, 4, 4, 4, 4}, new int[][] {{0, 28}}),
Arguments.of("gaps in the facing mask split runs",
new int[] {4, 0, 8, 0, 0, 0, 4}, new int[][] {{0, 4}, {4, 8}, {12, 4}}));
}
@Test
void sectionsWithoutGeometryNeverAnnounceTheRegion() {
final TerrainRenderPass renderPass = pass("solid");
final RenderRegion region = region(0, 0, 0);
final long heap = heap();
final SectionRenderDataStorage storage = storage(heap);
writeSection(heap, 0, 0, 0, 0, 0, 0, 0, 0, 0);
attachStorage(region, renderPass, storage);
final RecordingSink sink = walk(lists(renderList(region, 0)), renderPass, new CameraTransform(0, 0, 0), false);
final RecordingSink sink = walkSingleSection(region(0, 0, 0), new CameraTransform(0, 0, 0), false, 0, 0, 0, 0, 0, 0, 0);
assertTrue(sink.regions.isEmpty(), "an empty region must not open an encoder");
assertTrue(sink.ranges.isEmpty());
@@ -297,16 +281,7 @@ class ShadowVoxelizerTest {
@Test
void faceCullingNarrowsTheEmittedRanges() {
final TerrainRenderPass renderPass = pass("solid");
final RenderRegion region = region(0, 0, 0);
final long heap = heap();
final SectionRenderDataStorage storage = storage(heap);
writeSection(heap, 0, 0, 4, 4, 4, 4, 4, 4, 4);
attachStorage(region, renderPass, storage);
final ChunkRenderListIterable renderLists = lists(renderList(region, 0));
final CameraTransform camera = new CameraTransform(500, 200, 500);
final RecordingSink sink = walk(renderLists, renderPass, camera, true);
final RecordingSink sink = walkSingleSection(region(0, 0, 0), new CameraTransform(500, 200, 500), true, 4, 4, 4, 4, 4, 4, 4);
assertFalse(sink.ranges.isEmpty(), "face culling removed everything; the fixture is not exercising the path");
assertTrue(sink.ranges.size() < 7 || sink.ranges.get(0)[1] < 28, "face culling did not narrow anything");
Loaded 100 of 112 files, more files were not shown because too many files have changed in this diff. Show more