Compare commits

..
10 Commits
Author SHA1 Message Date
DarkShadow44 69428c12f6 Fix MixinRendererLivingEntity_GlintClock expected count (#2248) 2026-10-10 18:06:45 -07:00
Jason Mitchell 3978be161d Answer legacy GL queries from tracked state (#2247) 2026-10-10 17:31:02 -07:00
Jason Mitchell 2ed838acf1 Fix shaders that redefine a GLSL built-in failing on SDL-GPU and GLES (#2245)
* Retry a rejected compile with the shader's own functions renamed
2026-10-10 14:05:33 -07:00
Eclipse-Sol d29a8278cd Make API functions for enderstorage to use (#2244)
* Make API functions for enderstorage to use

* move it into its own package
2026-10-10 16:54:21 -04:00
Eclipse-Sol 2f1f380e8e Bug Cleanup after 2.9RC2 (#2239) 2026-10-10 13:22:49 -07:00
Jason Mitchell 990dadc03f Reject non 3.3 core with a more clear message (#2243) 2026-10-10 12:58:51 -07:00
Ancalagon 1946c2ad65 Fix dark AE2 cables when shaders enabled (#2223) 2026-10-08 23:27:24 -07:00
Jason Mitchell bfb66d84ee Fix interop with Serene Seasons and other similar mods (#2240) 2026-10-08 23:09:34 -07:00
DarkShadow44 cae4fea2bb Fix Little Tiles rendering issues (#2238) 2026-10-08 17:13:36 -07:00
Jason Mitchell 78abc05711 Update Readme 2026-10-07 20:34:33 -07:00
349 changed files with 11616 additions and 9680 deletions

No files matched your search

+2
View File
@@ -10,4 +10,6 @@ on:
jobs:
build-and-test:
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@master
with:
disable-spotless-pr: true
secrets: inherit
+40 -12
View File
@@ -2,31 +2,59 @@
<sup>**[CurseForge](https://www.curseforge.com/minecraft/mc-mods/angelica) | [Modrinth](https://modrinth.com/mod/angelica)**</sup>
🌐 **Languages:** [English](README.MD) | [Українська](README_uk.MD)
* The little Angle [sic] that supports shaders while saving your fps from a certain death -boubou
* Is it obtuse, or accute? -Caedis
* It's a cute angle -mitchej123
* It's the angel that pulls you out of OptiFine's hell -Roadhog360
## **Beta Quality - Here there be Dragons**
* Shaders - Beta
## What's in it
* Shaders - Iris backport & enhancements
* Terrain - Celeritas 2.5.12 with threaded chunk building and translucency sorting
* GL 3.3 core profile with Java based fixed function pipeline emulation
* Renderer selection - OpenGL, or SDL GPU for Vulkan/Metal/Direct3D 12
* Batching and instancing for entities, tile entities, items, particles and weather
* Dynamic lights
* HUD caching
* Custom fonts and text effects
* MCPatcher - connected textures, custom item textures, custom colors, random mobs, better skies
* Bundled NotFine, zoom, FPS reducer
## Status
* OpenGL + Celeritas terrain - Default
* Shaders - Generally Available
* Complementary + Euphoria Patches is the primary compatibility target
* Sildurs, Seus, and any other shaders might work
* SDL GPU renderer - Experimental
* MCPatcher features - Alpha
* HUD caching - Semi-stable
## SDL GPU renderer
Optional, OpenGL is the default
* Vulkan, Metal (macOS) and Direct3D 12 (Windows) through SDL3
* Needs [LWJGL3ify](https://github.com/GTNewHorizons/lwjgl3ify/releases/) 3.0.25+
* Falls back to OpenGL when no device can be created
* Vulkan on macOS via MoltenVK from the optional `angelica-moltenvk` jar
## Core Profile
Targets GL 3.3+ core profile
* FFP emulation via generated GLSL shaders
* Display list emulation
* Software matrix stacks and state management through GLSM
* GL_QUADS → GL_TRIANGLES conversion
* GL_QUADS -> GL_TRIANGLES conversion
* Automatic transformation of mod shaders that use compat-only GLSL builtins (`gl_ModelViewMatrix`, `texture2D`, etc.)
# Required Dependencies
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.11.19
* NOTE: Some mods are not required, but a specific version is required if present - see: `Permanent Incompatibilities` below.
* NOTE: Some mods are not required, but a specific version is required if present - see: `Minimum versions` below.
# Minimum versions (if present)
* [LWJGL3ify](https://github.com/GTNewHorizons/lwjgl3ify/releases/) 1.5.3+ (3.0.25+ for the SDL GPU renderer)
* [Hodgepodge](https://github.com/GTNewHorizons/Hodgepodge/releases) 2.4.4+
* [ArchaicFix](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) 0.7.0+
* [CodeChickenCore](https://github.com/GTNewHorizons/CodeChickenCore/releases) 1.2.0+ - Threading issues below that
* [Amazing Trophies](https://github.com/GTNewHorizons/Amazing-Trophies/releases) 1.2.2+
# Known (temporary) Incompatibilities
* Forge Relocation - Currently non Thread Safe; safe if not in use
@@ -45,15 +73,15 @@ Targets GL 3.3+ core profile
* FastCraft [Disabled, won't fix]
* BetterFPS [Disabled, won't fix]
* OptimizationsAndTweaks [Disabled, won't fix]
* [Amazing Trophies 1.2.1 and below](https://github.com/GTNewHorizons/Amazing-Trophies/releases) - Use 1.2.2+
* [LWJGL3ify 1.5.4 and below](https://github.com/GTNewHorizons/lwjgl3ify/releases/) - Use the latest version available, 1.5.7+
* [ArchaicFix 0.6.2 and below](https://www.curseforge.com/minecraft/mc-mods/archaicfix/files/all?page=1&pageSize=20) - Use 0.7.0 or above
* [Hodgepodge 2.4.3 and below](https://github.com/GTNewHorizons/Hodgepodge/releases) - Use 2.4.5 or above
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 - Threading issues, use 1.3.0+
* Neodymium - Untested, but unlikely to be compatible. (Chunk Meshing is definitely incompatible, other features might work depending on config)
* SmoothFont - Equivalent functionality added
* ClientFixer - Use a resource pack like [GTNH-FTI-Standard-Font](https://github.com/Eldrinn-Elantey/GTNH-FTI-Standard-Font)
# Building
* `./gradlew build`, `./gradlew test`
* Subprojects: `glsm` (state manager, FFP emulation, backends), `lwjgl3-backend`, `sdl-gpu`, `tracy-client`, `moltenvk`
* Read the [AI policy](AI_POLICY.md) before opening a PR
# Credits
* ShadersMod
* Original ShadersMods: karyonix, sonic ether, id_miner, daxnitro
@@ -96,7 +124,7 @@ Targets GL 3.3+ core profile
* FalsePattern for [FalseTweaks](https://github.com/falsepattern/falsetweaks) - Item Rendering Optimizations taken from FalseTweaks under the LGPL 3.0 license
* Beddium
* Venn and Falsepattern for [Beddium](https://github.com/vfx-dev/Beddium) - MinimapRenderer, SafeChunkTracker, and some debug commands adapted under the LGPL 3.0 License
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) — Code ported and/or inspired from Mesa - under the MIT licensed
* [Mesa](https://gitlab.freedesktop.org/mesa/mesa) - Code ported and/or inspired from Mesa - under the MIT license
* Display list ID allocator ported from [util_idalloc](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/util/u_idalloc.c) (Copyright 2017 Valve Corporation)
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
@@ -96,6 +96,7 @@ public class Feature {
, GLStateManager.getSecondaryColor() // Current secondary color
, ShaderManager.getNormalStack() // Current normal vector
, ShaderManager.getTexCoordStack() // Current texture coordinates
, GLStateManager.getEdgeFlagState()
// Current color index
// Current raster position
// GL_CURRENT_RASTER_POSITION_VALID flag
@@ -158,6 +159,8 @@ public class Feature {
, GLStateManager.getPolygonOffsetPointState()
, GLStateManager.getPolygonOffsetLineState()
, GLStateManager.getPolygonOffsetFillState()
, GLStateManager.getPointSpriteState()
, GLStateManager.getVertexProgramTwoSideState()
, GLStateManager.getPolygonSmoothState()
, GLStateManager.getPolygonStippleState()
, GLStateManager.getRescaleNormalState()
@@ -178,6 +181,7 @@ public class Feature {
enableBits.add(GLStateManager.getTextures().getTexture1DStates(i));
enableBits.add(GLStateManager.getTextures().getTextureUnitStates(i));
enableBits.add(GLStateManager.getTextures().getTexture3DStates(i));
enableBits.add(GLStateManager.getTextures().getTextureCubeMapStates(i));
}
// Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q
@@ -216,13 +220,7 @@ public class Feature {
// ^^ GL_FOG_MODE value
));
attribToFeatures.put(GL11.GL_HINT_BIT, ImmutableSet.of(
// GL_PERSPECTIVE_CORRECTION_HINT setting
// GL_POINT_SMOOTH_HINT setting
// GL_LINE_SMOOTH_HINT setting
// GL_POLYGON_SMOOTH_HINT setting
// GL_FOG_HINT setting
// GL_GENERATE_MIPMAP_HINT setting
// GL_TEXTURE_COMPRESSION_HINT setting
GLStateManager.getHintState()
));
attribToFeatures.put(GL11.GL_LIGHTING_BIT, ImmutableSet.of(
GLStateManager.getColorMaterial()
@@ -286,6 +284,7 @@ public class Feature {
));
attribToFeatures.put(GL11.GL_POINT_BIT, ImmutableSet.of(
GLStateManager.getPointSmoothState()
, GLStateManager.getPointSpriteState()
, GLStateManager.getPointState()
));
attribToFeatures.put(GL11.GL_POLYGON_BIT, ImmutableSet.of(
@@ -328,6 +327,10 @@ public class Feature {
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
textureAttribs.add(GLStateManager.getTextures().getTextureUnitBindings(i));
textureAttribs.add(GLStateManager.getTextures().getTexEnvState(i));
textureAttribs.add(GLStateManager.getTextures().getTexture1DStates(i));
textureAttribs.add(GLStateManager.getTextures().getTextureUnitStates(i));
textureAttribs.add(GLStateManager.getTextures().getTexture3DStates(i));
textureAttribs.add(GLStateManager.getTextures().getTextureCubeMapStates(i));
}
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import org.joml.Matrix4f;
@@ -141,6 +142,7 @@ public class FeedbackManager {
public static void processDrawArrays(int mode, int first, int count) {
computeMVP();
final GLContextState glCtx = GLStateManager.ctx();
if (!glCtx.vaos.isAttribEnabled(0)) return;
final int stride = positionStride(glCtx);
final long posOffset = positionOffset(glCtx);
final int byteCount = count * stride;
@@ -154,6 +156,7 @@ public class FeedbackManager {
public static void processDrawElements(int mode, int indexCount, int type, long eboOffset) {
computeMVP();
final GLContextState glCtx = GLStateManager.ctx();
if (!glCtx.vaos.isAttribEnabled(0)) return;
final int stride = positionStride(glCtx);
final int elementSize = GLTypes.sizeBytes(type);
@@ -210,6 +213,7 @@ public class FeedbackManager {
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
computeMVP();
final GLContextState glCtx = GLStateManager.ctx();
if (!glCtx.vaos.isAttribEnabled(0)) return;
final int stride = positionStride(glCtx);
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
@@ -261,6 +265,14 @@ public class FeedbackManager {
}
private static void readPositionBuffer(long offset, ByteBuffer destination, GLContextState glCtx) {
final VAOManager.Attrib position = glCtx.vaos.attrib(0);
if (position != null && position.clientPointer != null) {
final ByteBuffer source = position.clientPointer.duplicate();
source.position(source.position() + (int) offset);
if (source.remaining() > destination.remaining()) source.limit(source.position() + destination.remaining());
destination.duplicate().put(source);
return;
}
final int positionVbo = glCtx.vaos.attribVBO(0);
final int previousVbo = glCtx.boundVBO;
@@ -47,6 +47,7 @@ import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import java.nio.DoubleBuffer;
@@ -169,6 +170,9 @@ public class GLContextState {
public final BooleanStateStack lineSmoothState = track(new BooleanStateStack(GL11.GL_LINE_SMOOTH));
public final BooleanStateStack lineStippleState = track(new BooleanStateStack(GL11.GL_LINE_STIPPLE, false, true));
public final BooleanStateStack pointSmoothState = track(new BooleanStateStack(GL11.GL_POINT_SMOOTH, false, true));
public final BooleanStateStack pointSpriteState = track(new BooleanStateStack(GL20.GL_POINT_SPRITE, false, true));
public final BooleanStateStack vertexProgramTwoSideState = track(new BooleanStateStack(GL20.GL_VERTEX_PROGRAM_TWO_SIDE, false, true));
public final BooleanStateStack edgeFlagState = track(new BooleanStateStack(GL11.GL_EDGE_FLAG, true, true));
public final BooleanStateStack polygonSmoothState = track(new BooleanStateStack(GL11.GL_POLYGON_SMOOTH));
public final BooleanStateStack polygonStippleState = track(new BooleanStateStack(GL11.GL_POLYGON_STIPPLE, false, true));
public final BooleanStateStack multisampleState = track(new BooleanStateStack(GL13.GL_MULTISAMPLE, true));
@@ -182,6 +186,7 @@ public class GLContextState {
public final BooleanStateStack polygonOffsetFillState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_FILL));
public final LineStateStack lineState = member(new LineStateStack(StackIdAllocator.nextId()));
public final PointStateStack pointState = member(new PointStateStack(StackIdAllocator.nextId()));
public final IntegerStateStack hintState = member(new IntegerStateStack(0, StackIdAllocator.nextId()).restoreBit(StateSet.R_HINT));
public final PolygonStateStack polygonState = member(new PolygonStateStack(StackIdAllocator.nextId()));
public final StencilStateStack stencilState = member(new StencilStateStack(StackIdAllocator.nextId()));
public int stencilBitMask = 0xFFFFFFFF;
@@ -238,10 +243,9 @@ public class GLContextState {
public final PixelStoreState[] clientAttribSavedPixelPack = new PixelStoreState[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedUnpackBuffer = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedPackBuffer = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedTextureUnit = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedVertexFlags = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final boolean[] clientAttribSavedVertexArray = new boolean[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final VAOManager.ClientArrays[] clientAttribSavedArrays = new VAOManager.ClientArrays[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public int clientAttribStackPointer = 0;
public float maxPointSize = Float.NaN;
public int restoreChangedMask;
public long restoreUnitChangedMask;
public final BlendState vanillaBlendBefore = new BlendState();
@@ -262,7 +266,7 @@ public class GLContextState {
public int savedFilterMin = -1;
public int savedFilterMag = -1;
public float savedFilterAniso = -1.0f;
public boolean wideLineEmulationActive = false;
public boolean lineQuadsActive = false;
public boolean lineStippleActive = false;
public Instancing ffpInstancing = Instancing.NONE;
public float lastBrightnessX;
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,167 @@
package com.gtnewhorizons.angelica.glsm;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
final class LegacyQueries {
static final int BOOL = 1;
static final int INT = 2;
static final int FLOAT = 3;
static final int FLOATN = 4;
private static final int COUNT_SHIFT = 3;
private LegacyQueries() {}
static int desc(int pname) {
return switch (pname) {
case GL11.GL_ALPHA_TEST, GL11.GL_AUTO_NORMAL, GL11.GL_COLOR_MATERIAL, GL14.GL_COLOR_SUM, GL11.GL_FOG,
GL11.GL_INDEX_LOGIC_OP, GL11.GL_LIGHTING, GL11.GL_LIGHT0, GL11.GL_LIGHT1, GL11.GL_LIGHT2, GL11.GL_LIGHT3,
GL11.GL_LIGHT4, GL11.GL_LIGHT5, GL11.GL_LIGHT6, GL11.GL_LIGHT7, GL11.GL_LINE_STIPPLE, GL11.GL_MAP1_COLOR_4,
GL11.GL_MAP1_INDEX, GL11.GL_MAP1_NORMAL, GL11.GL_MAP1_TEXTURE_COORD_1, GL11.GL_MAP1_TEXTURE_COORD_2,
GL11.GL_MAP1_TEXTURE_COORD_3, GL11.GL_MAP1_TEXTURE_COORD_4, GL11.GL_MAP1_VERTEX_3, GL11.GL_MAP1_VERTEX_4,
GL11.GL_MAP2_COLOR_4, GL11.GL_MAP2_INDEX, GL11.GL_MAP2_NORMAL, GL11.GL_MAP2_TEXTURE_COORD_1,
GL11.GL_MAP2_TEXTURE_COORD_2, GL11.GL_MAP2_TEXTURE_COORD_3, GL11.GL_MAP2_TEXTURE_COORD_4,
GL11.GL_MAP2_VERTEX_3, GL11.GL_MAP2_VERTEX_4, GL11.GL_NORMALIZE, GL11.GL_POINT_SMOOTH,
GL11.GL_POLYGON_STIPPLE, GL12.GL_RESCALE_NORMAL, GL11.GL_TEXTURE_1D, GL12.GL_TEXTURE_3D,
GL13.GL_TEXTURE_CUBE_MAP, GL11.GL_TEXTURE_GEN_S, GL11.GL_TEXTURE_GEN_T, GL11.GL_TEXTURE_GEN_R,
GL11.GL_TEXTURE_GEN_Q, GL11.GL_LIGHT_MODEL_LOCAL_VIEWER, GL11.GL_LIGHT_MODEL_TWO_SIDE,
GL11.GL_VERTEX_ARRAY, GL11.GL_NORMAL_ARRAY, GL11.GL_COLOR_ARRAY, GL11.GL_TEXTURE_COORD_ARRAY,
GL11.GL_INDEX_ARRAY, GL11.GL_EDGE_FLAG_ARRAY, GL14.GL_SECONDARY_COLOR_ARRAY, GL14.GL_FOG_COORDINATE_ARRAY,
GL11.GL_EDGE_FLAG, GL20.GL_POINT_SPRITE, GL20.GL_VERTEX_PROGRAM_TWO_SIDE -> BOOL;
case GL11.GL_ALPHA_TEST_FUNC, GL11.GL_LIST_BASE, GL11.GL_MATRIX_MODE, GL11.GL_SHADE_MODEL,
GL11.GL_COLOR_MATERIAL_FACE, GL11.GL_COLOR_MATERIAL_PARAMETER, GL12.GL_LIGHT_MODEL_COLOR_CONTROL,
GL11.GL_FOG_MODE, GL11.GL_LINE_STIPPLE_PATTERN, GL11.GL_LINE_STIPPLE_REPEAT, GL13.GL_CLIENT_ACTIVE_TEXTURE,
GL20.GL_MAX_TEXTURE_COORDS, GL11.GL_RENDER_MODE, GL11.GL_FOG_HINT, GL11.GL_PERSPECTIVE_CORRECTION_HINT,
GL11.GL_POINT_SMOOTH_HINT, GL14.GL_GENERATE_MIPMAP_HINT, GL11.GL_LINE_SMOOTH_HINT, GL11.GL_POLYGON_SMOOTH_HINT,
GL13.GL_TEXTURE_COMPRESSION_HINT, GL20.GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL20.GL_POINT_SPRITE_COORD_ORIGIN,
GL11.GL_VERTEX_ARRAY_SIZE, GL11.GL_VERTEX_ARRAY_TYPE, GL11.GL_VERTEX_ARRAY_STRIDE, GL15.GL_VERTEX_ARRAY_BUFFER_BINDING,
GL11.GL_NORMAL_ARRAY_TYPE, GL11.GL_NORMAL_ARRAY_STRIDE, GL15.GL_NORMAL_ARRAY_BUFFER_BINDING,
GL11.GL_COLOR_ARRAY_SIZE, GL11.GL_COLOR_ARRAY_TYPE, GL11.GL_COLOR_ARRAY_STRIDE, GL15.GL_COLOR_ARRAY_BUFFER_BINDING,
GL11.GL_INDEX_ARRAY_TYPE, GL11.GL_INDEX_ARRAY_STRIDE, GL15.GL_INDEX_ARRAY_BUFFER_BINDING,
GL11.GL_TEXTURE_COORD_ARRAY_SIZE, GL11.GL_TEXTURE_COORD_ARRAY_TYPE, GL11.GL_TEXTURE_COORD_ARRAY_STRIDE,
GL15.GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING, GL11.GL_EDGE_FLAG_ARRAY_STRIDE, GL15.GL_EDGE_FLAG_ARRAY_BUFFER_BINDING,
GL14.GL_SECONDARY_COLOR_ARRAY_SIZE, GL14.GL_SECONDARY_COLOR_ARRAY_TYPE, GL14.GL_SECONDARY_COLOR_ARRAY_STRIDE,
GL15.GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING, GL14.GL_FOG_COORDINATE_ARRAY_TYPE, GL14.GL_FOG_COORDINATE_ARRAY_STRIDE,
GL15.GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING -> INT;
case GL11.GL_FOG_DENSITY, GL11.GL_FOG_START, GL11.GL_FOG_END, GL14.GL_POINT_SIZE_MIN, GL14.GL_POINT_SIZE_MAX,
GL14.GL_POINT_FADE_THRESHOLD_SIZE -> FLOAT;
case GL11.GL_ALPHA_TEST_REF -> FLOATN;
case GL14.GL_POINT_DISTANCE_ATTENUATION -> FLOAT | 2 << COUNT_SHIFT;
case GL11.GL_CURRENT_TEXTURE_COORDS -> FLOAT | 3 << COUNT_SHIFT;
case GL11.GL_MODELVIEW_MATRIX, GL11.GL_PROJECTION_MATRIX, GL11.GL_TEXTURE_MATRIX -> FLOATN | 15 << COUNT_SHIFT;
case GL11.GL_CURRENT_NORMAL -> FLOATN | 2 << COUNT_SHIFT;
case GL11.GL_CURRENT_COLOR, GL14.GL_CURRENT_SECONDARY_COLOR, GL11.GL_FOG_COLOR, GL11.GL_LIGHT_MODEL_AMBIENT -> FLOATN | 3 << COUNT_SHIFT;
default -> 0;
};
}
static int type(int desc) {
return desc & 7;
}
static int count(int desc) {
return (desc >> COUNT_SHIFT) + 1;
}
static int floatToInt(float value) {
return (int) (2147483647.0 * value);
}
static int lround(float value) {
return value < 0 ? -Math.round(-value) : Math.round(value);
}
static int toInt(int type, float value) {
return type == FLOATN ? floatToInt(value) : lround(value);
}
private static FloatBuffer fillScratch(GLContextState glCtx, int pname) {
final FloatBuffer scratch = glCtx.queryScratch;
scratch.clear();
GLStateManager.glGetFloat(pname, scratch);
return scratch;
}
private static IllegalStateException uncached(int pname) {
return new IllegalStateException("Legacy pname 0x" + Integer.toHexString(pname) + " has no cached answer for its declared type");
}
static int getInteger(GLContextState glCtx, int pname, int desc) {
final int type = type(desc);
if (count(desc) > 1) {
return toInt(type, fillScratch(glCtx, pname).get(0));
}
return switch (type) {
case BOOL -> GLStateManager.glGetBoolean(glCtx, pname) ? GL11.GL_TRUE : GL11.GL_FALSE;
case FLOAT -> lround(GLStateManager.glGetFloat(glCtx, pname));
case FLOATN -> floatToInt(GLStateManager.glGetFloat(glCtx, pname));
default -> throw uncached(pname);
};
}
static float getFloat(GLContextState glCtx, int pname, int desc) {
if (count(desc) > 1) {
return fillScratch(glCtx, pname).get(0);
}
return switch (type(desc)) {
case BOOL -> GLStateManager.glGetBoolean(glCtx, pname) ? 1.0f : 0.0f;
case INT -> (float) GLStateManager.glGetInteger(glCtx, pname);
default -> throw uncached(pname);
};
}
static boolean getBoolean(GLContextState glCtx, int pname, int desc) {
if (count(desc) > 1) {
return fillScratch(glCtx, pname).get(0) != 0.0f;
}
return switch (type(desc)) {
case INT -> GLStateManager.glGetInteger(glCtx, pname) != 0;
case FLOAT, FLOATN -> GLStateManager.glGetFloat(glCtx, pname) != 0.0f;
default -> throw uncached(pname);
};
}
static boolean getIntegerv(GLContextState glCtx, int pname, IntBuffer params) {
final int desc = desc(pname);
if (desc == 0) return false;
final int pos = params.position();
final int type = type(desc);
final FloatBuffer scratch = fillScratch(glCtx, pname);
for (int i = 0, count = count(desc); i < count; i++) {
params.put(pos + i, toInt(type, scratch.get(i)));
}
return true;
}
static boolean getIntegerv(GLContextState glCtx, int pname, int[] params) {
final int desc = desc(pname);
if (desc == 0) return false;
final int type = type(desc);
final FloatBuffer scratch = fillScratch(glCtx, pname);
for (int i = 0, count = Math.min(count(desc), params.length); i < count; i++) {
params[i] = toInt(type, scratch.get(i));
}
return true;
}
static boolean getBooleanv(GLContextState glCtx, int pname, ByteBuffer params) {
final int desc = desc(pname);
if (desc == 0) return false;
final int pos = params.position();
final FloatBuffer scratch = fillScratch(glCtx, pname);
for (int i = 0, count = count(desc); i < count; i++) {
params.put(pos + i, (byte) (scratch.get(i) != 0.0f ? GL11.GL_TRUE : GL11.GL_FALSE));
}
return true;
}
}
@@ -688,4 +688,12 @@ public class RenderSystem {
if (actualVersion < 0) return true;
return actualVersion >= requestedMajor * 10 + requestedMinor;
}
public static final int MIN_CORE_VERSION = 33;
public static int probeCapAfterRejection(int requested, int actual) {
if (actual >= MIN_CORE_VERSION && actual < requested) return actual;
if (actual >= 0 && actual < MIN_CORE_VERSION && requested > MIN_CORE_VERSION) return MIN_CORE_VERSION;
return requested - 1;
}
}
@@ -24,6 +24,7 @@ public final class StateSet {
public static final int R_ACTIVE_UNIT = 1 << 13;
public static final int R_PROGRAM = 1 << 14;
public static final int R_SCISSOR = 1 << 15;
public static final int R_HINT = 1 << 16;
public static final int B_FRAG_COLORBUF = 1;
public static final int B_FRAG_TEXTURE = 1 << 1;
@@ -224,6 +225,9 @@ public final class StateSet {
if ((mask & GL11.GL_POINT_BIT) != 0) {
restore |= R_POINT;
}
if ((mask & GL11.GL_HINT_BIT) != 0) {
restore |= R_HINT;
}
if ((mask & GL11.GL_POLYGON_BIT) != 0) {
restore |= R_POLYGON;
}
@@ -1,119 +0,0 @@
package com.gtnewhorizons.angelica.glsm.ffp;
/**
* Generates GLSL 330 core geometry shaders for wide line emulation.
*/
public final class GeometryShaderGenerator {
private GeometryShaderGenerator() {}
public static String generate(VertexKey key) {
final StringBuilder sb = new StringBuilder(1024);
sb.append("#version 330 core\n\n");
sb.append("layout(lines) in;\n");
sb.append("layout(triangle_strip, max_vertices = 4) out;\n\n");
sb.append(FFPUniformBlock.GLSL_DECL);
sb.append('\n');
sb.append("// Pass-through varyings\n");
sb.append("in vec4 v_Color_gs[];\n");
if (key.separateSpecular()) {
sb.append("in vec3 v_SpecularColor_gs[];\n");
}
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append("in vec4 v_TexCoord0_gs[];\n");
if (key.lightmapEnabled()) sb.append("in vec4 v_TexCoord1_gs[];\n");
if (key.unitTexCoordEnabled(2)) sb.append("in vec4 v_TexCoord2_gs[];\n");
if (key.unitTexCoordEnabled(3)) sb.append("in vec4 v_TexCoord3_gs[];\n");
if (key.fogEnabled()) {
sb.append("in float v_FogCoord_gs[];\n");
}
if (key.lineStipple()) {
sb.append("flat in vec2 v_LineStart_gs[];\n");
}
sb.append('\n');
sb.append("out vec4 v_Color;\n");
if (key.separateSpecular()) {
sb.append("out vec3 v_SpecularColor;\n");
}
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append("out vec4 v_TexCoord0;\n");
if (key.lightmapEnabled()) sb.append("out vec4 v_TexCoord1;\n");
if (key.unitTexCoordEnabled(2)) sb.append("out vec4 v_TexCoord2;\n");
if (key.unitTexCoordEnabled(3)) sb.append("out vec4 v_TexCoord3;\n");
if (key.fogEnabled()) {
sb.append("out float v_FogCoord;\n");
}
if (key.lineStipple()) {
sb.append("flat out vec2 v_LineStart;\n");
}
sb.append('\n');
sb.append("void main() {\n");
sb.append(" vec4 c0 = gl_in[0].gl_Position;\n");
sb.append(" vec4 c1 = gl_in[1].gl_Position;\n\n");
sb.append(" float d0 = c0.z + c0.w;\n");
sb.append(" float d1 = c1.z + c1.w;\n");
sb.append(" if (d0 < 0.0 && d1 < 0.0) return;\n");
sb.append(" float t0 = d0 < 0.0 ? d0 / (d0 - d1) : 0.0;\n");
sb.append(" float t1 = d1 < 0.0 ? d0 / (d0 - d1) : 1.0;\n");
sb.append(" vec4 p0 = mix(c0, c1, t0);\n");
sb.append(" vec4 p1 = mix(c0, c1, t1);\n\n");
sb.append(" // NDC positions\n");
sb.append(" vec2 n0 = p0.xy / p0.w;\n");
sb.append(" vec2 n1 = p1.xy / p1.w;\n\n");
sb.append(" // Screen-space direction and perpendicular\n");
sb.append(" vec2 dir = normalize((n1 - n0) * u_ViewportSize);\n");
sb.append(" vec2 offset = vec2(-dir.y, dir.x) * u_LineWidth / u_ViewportSize;\n\n");
emitVertex(sb, key, 0, "+offset");
emitVertex(sb, key, 0, "-offset");
emitVertex(sb, key, 1, "+offset");
emitVertex(sb, key, 1, "-offset");
sb.append(" EndPrimitive();\n");
sb.append("}\n");
return sb.toString();
}
private static void emitVertex(StringBuilder sb, VertexKey key, int endpointIdx, String offsetExpr) {
final String pi = "p" + endpointIdx;
final String ni = "n" + endpointIdx;
final String ti = "t" + endpointIdx;
sb.append(" // Endpoint ").append(endpointIdx).append(' ').append(offsetExpr).append('\n');
sb.append(" v_Color = ").append(lerp("v_Color_gs", ti)).append(";\n");
if (key.separateSpecular()) {
sb.append(" v_SpecularColor = ").append(lerp("v_SpecularColor_gs", ti)).append(";\n");
}
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append(" v_TexCoord0 = ").append(lerp("v_TexCoord0_gs", ti)).append(";\n");
if (key.lightmapEnabled()) sb.append(" v_TexCoord1 = ").append(lerp("v_TexCoord1_gs", ti)).append(";\n");
if (key.unitTexCoordEnabled(2)) sb.append(" v_TexCoord2 = ").append(lerp("v_TexCoord2_gs", ti)).append(";\n");
if (key.unitTexCoordEnabled(3)) sb.append(" v_TexCoord3 = ").append(lerp("v_TexCoord3_gs", ti)).append(";\n");
if (key.fogEnabled()) {
sb.append(" v_FogCoord = ").append(lerp("v_FogCoord_gs", ti)).append(";\n");
}
if (key.lineStipple()) {
sb.append(" v_LineStart = v_LineStart_gs[0];\n");
}
if (key.clipPlanesEnabled()) {
for (int i = 0; i < 8; i++) {
sb.append(" gl_ClipDistance[").append(i).append("] = mix(gl_in[0].gl_ClipDistance[").append(i).append("], gl_in[1].gl_ClipDistance[").append(i).append("], ").append(ti).append(");\n");
}
}
sb.append(" gl_Position = vec4((").append(ni).append(' ').append(offsetExpr).append(") * ").append(pi).append(".w, ").append(pi).append(".z, ").append(pi).append(".w);\n");
sb.append(" EmitVertex();\n\n");
}
private static String lerp(String input, String t) {
return "mix(" + input + "[0], " + input + "[1], " + t + ")";
}
}
@@ -23,7 +23,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* A compiled and linked FFP emulation program (vertex + fragment + optional geometry shaders). Owns the GL program handle and caches uniform locations.
* A compiled and linked FFP emulation program (vertex + fragment shaders). Owns the GL program handle and caches uniform locations.
*/
public class Program {
@@ -49,23 +49,19 @@ public class Program {
}
/**
* Compile vertex + fragment + optional geometry shaders, link into a program, and return the FFPProgram.
* Compile vertex + fragment shaders, link into a program, and return the FFPProgram.
*/
static Program create(VertexKey vk, FragmentKey fk, String vertSrc, String fragSrc, String geomSrc) {
static Program create(VertexKey vk, FragmentKey fk, String vertSrc, String fragSrc) {
final RenderBackend backend = RENDER_BACKEND;
final int id = PROGRAM_COUNTER.getAndIncrement();
final String vkHex = Long.toHexString(vk.pack());
final boolean hasGeom = geomSrc != null;
int shaderCount = 0;
final int[] shaders = new int[5]; // vertex, fragment, geometry, tess control, tess evaluation
final int[] shaders = new int[2];
int program = 0;
try {
shaders[shaderCount++] = compileShader(GL20.GL_VERTEX_SHADER, vertSrc, "ffp_v_" + vkHex);
shaders[shaderCount++] = compileShader(GL20.GL_FRAGMENT_SHADER, fragSrc, "ffp_f_" + id);
if (geomSrc != null) {
shaders[shaderCount++] = compileShader(GL32.GL_GEOMETRY_SHADER, geomSrc, "ffp_g_" + id);
}
program = backend.createProgram();
for (int i = 0; i < shaderCount; i++) backend.attachShader(program, shaders[i]);
@@ -80,7 +76,7 @@ public class Program {
throw new RuntimeException("FFP shader link failed (vk=0x" + vkHex + ", fk=" + fk + "): " + log);
}
final String debugName = "FFP(v=0x" + vkHex + ",f=" + id + (hasGeom ? ",g" : "") + ")";
final String debugName = "FFP(v=0x" + vkHex + ",f=" + id + ")";
GLDebug.nameObject(KHRDebug.GL_PROGRAM, program, debugName);
// Detach and delete individual shaders - they're linked into the program
@@ -32,7 +32,6 @@ public class ShaderCache {
private static final long EMPTY_MARKER = 0L; // slot [1] == 0 means empty (fkLen >= 1 guarantees nonzero)
private final Long2ObjectOpenHashMap<String> vertexSourceCache = new Long2ObjectOpenHashMap<>();
private final Long2ObjectOpenHashMap<String> geometrySourceCache = new Long2ObjectOpenHashMap<>();
// Open-addressed table — parallel arrays
private long[] keys;
@@ -103,7 +102,6 @@ public class ShaderCache {
final FragmentKey fk = FragmentKey.fromPacked(fkPacked, fkLen);
final String vertSrc;
String fragSrc;
final String geomSrc;
final Program program;
if (Tracy.ENABLED) Tracy.beginZone(Z_FFP_SHADER_GEN);
try {
@@ -113,8 +111,7 @@ public class ShaderCache {
fragSrc = FragmentShaderGenerator.generate(fk);
uniqueFragCount++;
}
geomSrc = vk.wideLineEmulation() ? getOrGenerateGeometry(vk) : null;
program = Program.create(vk, fk, vertSrc, fragSrc, geomSrc);
program = Program.create(vk, fk, vertSrc, fragSrc);
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
@@ -135,9 +132,6 @@ public class ShaderCache {
if (dumpDir != null) {
dumpShader(Long.toHexString(vkPacked), ".vert.glsl", vertSrc);
dumpShader("frag_" + fragmentDumpCounter++, ".frag.glsl", fragSrc);
if (geomSrc != null) {
dumpShader(Long.toHexString(vkPacked), ".geom.glsl", geomSrc);
}
}
return program;
@@ -228,10 +222,6 @@ public class ShaderCache {
return vertexSourceCache.computeIfAbsent(vk.pack(), k -> VertexShaderGenerator.generate(vk));
}
private String getOrGenerateGeometry(VertexKey vk) {
return geometrySourceCache.computeIfAbsent(vk.pack(), k -> GeometryShaderGenerator.generate(vk));
}
private void dumpShader(String name, String suffix, String source) {
if (dumpDir == null) return;
try {
@@ -260,11 +250,8 @@ public class ShaderCache {
dumpShader("frag_" + idx, ".frag.glsl", fragSources[i]);
}
}
for (var entry : geometrySourceCache.entrySet()) {
dumpShader(Long.toHexString(entry.getKey()), ".geom.glsl", entry.getValue());
}
GLStateManager.LOGGER.info("Dumped {} vertex, {} geometry, and {} fragment FFP shader sources to {}",
vertexSourceCache.size(), geometrySourceCache.size(), seen.size(), outputDir);
GLStateManager.LOGGER.info("Dumped {} vertex and {} fragment FFP shader sources to {}",
vertexSourceCache.size(), seen.size(), outputDir);
} catch (IOException e) {
GLStateManager.LOGGER.warn("Failed to dump FFP shaders: {}", e.getMessage());
}
@@ -100,7 +100,6 @@ public final class ShaderManager {
VertexShaderGenerator.generate(VertexKey.fromPacked(VertexKey.withInstancing(vkPacked, Instancing.CUBE)));
VertexShaderGenerator.generate(VertexKey.fromPacked(VertexKey.withInstancing(vkPacked, Instancing.PARTICLE)));
FragmentShaderGenerator.generate(FragmentKey.fromPacked(fkScratch, fkLen));
GeometryShaderGenerator.generate(vk);
final Class<?>[] touched = { Program.class, ShaderCache.class, TessellatorStreamingDrawer.class, QuadConverter.class };
for (Class<?> c : touched) {
c.getName();
@@ -1,13 +1,20 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GL41;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.Arrays;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
@@ -19,12 +26,116 @@ public final class VAOManager {
final Int2ObjectOpenHashMap<VAOData> eboOwners = new Int2ObjectOpenHashMap<>();
private VAOData current = null;
public static final int CONV_POSITION = 0;
public static final int CONV_NORMAL = 1;
public static final int CONV_COLOR = 2;
public static final int CONV_SECONDARY_COLOR = 3;
public static final int CONV_FOG_COORD = 4;
public static final int CONV_INDEX = 5;
public static final int CONV_EDGE_FLAG = 6;
public static final int CONV_TEX0 = 7;
public static final int TEX_COORD_UNITS = 8;
public static final int CONV_COUNT = CONV_TEX0 + TEX_COORD_UNITS;
private static final int[] CONV_LOCATION = new int[CONV_COUNT];
private static final int[] LOCATION_CONV = new int[MAX_ATTRIBS];
static {
Arrays.fill(CONV_LOCATION, -1);
Arrays.fill(LOCATION_CONV, -1);
mapConventional(CONV_POSITION, Usage.POSITION);
mapConventional(CONV_NORMAL, Usage.NORMAL);
mapConventional(CONV_COLOR, Usage.COLOR);
mapConventional(CONV_TEX0, Usage.PRIMARY_UV);
mapConventional(CONV_TEX0 + 1, Usage.SECONDARY_UV);
}
private static void mapConventional(int conv, Usage usage) {
final int location = usage.getAttributeLocation();
CONV_LOCATION[conv] = location;
LOCATION_CONV[location] = conv;
}
public static int conventionalLocation(int conv) {
return CONV_LOCATION[conv];
}
private static final int T_BYTE = 1, T_UBYTE = 1 << 1, T_SHORT = 1 << 2, T_USHORT = 1 << 3, T_INT = 1 << 4, T_UINT = 1 << 5, T_FLOAT = 1 << 6;
private static final int T_DOUBLE = 1 << 10, T_HALF = 1 << 11, T_FIXED = 1 << 12, T_PACKED = 1 << 13, T_PACKED_FLOAT = 1 << 14;
private static final int T_INTEGER = T_BYTE | T_UBYTE | T_SHORT | T_USHORT | T_INT | T_UINT;
private static final int T_COLOR = T_INTEGER | T_HALF | T_FLOAT | T_DOUBLE | T_PACKED;
private static final int T_GENERIC = T_COLOR | T_FIXED | T_PACKED_FLOAT;
private static final int[] LEGAL_TYPES = new int[CONV_COUNT];
private static final int[] MIN_SIZE = new int[CONV_COUNT];
private static final int[] MAX_SIZE = new int[CONV_COUNT];
static {
legal(CONV_POSITION, T_SHORT | T_INT | T_FLOAT | T_DOUBLE | T_HALF | T_PACKED, 2, 4);
legal(CONV_NORMAL, T_BYTE | T_SHORT | T_INT | T_HALF | T_FLOAT | T_DOUBLE | T_PACKED, 3, 3);
legal(CONV_COLOR, T_COLOR, 3, 4);
legal(CONV_SECONDARY_COLOR, T_COLOR, 3, 4);
legal(CONV_FOG_COORD, T_HALF | T_FLOAT | T_DOUBLE, 1, 1);
legal(CONV_INDEX, T_UBYTE | T_SHORT | T_INT | T_FLOAT | T_DOUBLE, 1, 1);
legal(CONV_EDGE_FLAG, T_UBYTE, 1, 1);
for (int unit = 0; unit < TEX_COORD_UNITS; unit++) {
legal(CONV_TEX0 + unit, T_SHORT | T_INT | T_HALF | T_FLOAT | T_DOUBLE | T_PACKED, 1, 4);
}
}
private static void legal(int conv, int types, int minSize, int maxSize) {
LEGAL_TYPES[conv] = types;
MIN_SIZE[conv] = minSize;
MAX_SIZE[conv] = maxSize;
}
private static int typeBit(int type) {
if (type >= GL11.GL_BYTE && type <= GL30.GL_HALF_FLOAT) return 1 << type - GL11.GL_BYTE;
return switch (type) {
case GL41.GL_FIXED -> T_FIXED;
case GL12.GL_UNSIGNED_INT_2_10_10_10_REV, GL33.GL_INT_2_10_10_10_REV -> T_PACKED;
case GL30.GL_UNSIGNED_INT_10F_11F_11F_REV -> T_PACKED_FLOAT;
default -> 0;
};
}
private static boolean isLegal(int types, int minSize, int maxSize, boolean bgra, int size, int type) {
final int bit = typeBit(type);
if ((types & bit) == 0) return false;
if (size == GL12.GL_BGRA) return bgra && (bit & (T_UBYTE | T_PACKED)) != 0;
return size >= minSize && size <= maxSize;
}
public static boolean isLegalConventional(int conv, int size, int type) {
return isLegal(LEGAL_TYPES[conv], MIN_SIZE[conv], MAX_SIZE[conv], conv == CONV_COLOR || conv == CONV_SECONDARY_COLOR, size, type);
}
public static boolean isLegalGeneric(int index, int size, int type, boolean integer) {
return index >= 0 && index < MAX_ATTRIBS && isLegal(integer ? T_INTEGER : T_GENERIC, 1, 4, !integer, size, type);
}
private int currentVertexFlags = 0;
private int genericEnabled = 0;
private int conventionalEnabled = 0;
private Attrib[] currentAttribs = null;
private Attrib[] currentGeneric = null;
private Attrib[] currentConventional = null;
public int boundEBO = 0;
private int clientSideEnabledCount = 0;
private int arrayVersion = 0;
private int versionClock = 0;
private int bufferEpoch = 0;
private final Int2IntOpenHashMap bufferDeletedAt = new Int2IntOpenHashMap();
private void touch() {
arrayVersion = ++versionClock;
}
private boolean isBufferLive(int buffer, int sinceEpoch) {
return bufferDeletedAt.get(buffer) <= sinceEpoch;
}
public boolean isBufferLive(Attrib a) {
return isBufferLive(a.vboId, a.epoch);
}
public void init(int defaultVAO) {
final VAOData data = new VAOData();
@@ -45,6 +156,10 @@ public final class VAOManager {
// Save old VAO data
if (current != null) {
current.vertexFlags = currentVertexFlags;
current.genericEnabled = genericEnabled;
current.conventionalEnabled = conventionalEnabled;
current.generic = currentGeneric;
current.conventional = currentConventional;
if (current.ebo != boundEBO) {
removeEboOwner(current.ebo, current);
current.ebo = boundEBO;
@@ -52,6 +167,7 @@ public final class VAOManager {
}
current.attribs = currentAttribs;
current.clientSideEnabledCount = clientSideEnabledCount;
current.arrayVersion = arrayVersion;
}
VAOData data = vaoMap.get(vaoId);
@@ -61,9 +177,14 @@ public final class VAOManager {
}
current = data;
currentVertexFlags = data.vertexFlags;
genericEnabled = data.genericEnabled;
conventionalEnabled = data.conventionalEnabled;
currentAttribs = data.attribs;
currentGeneric = data.generic;
currentConventional = data.conventional;
boundEBO = data.ebo;
clientSideEnabledCount = data.clientSideEnabledCount;
arrayVersion = data.arrayVersion;
}
public void onDeleteVertexArray(int vaoId) {
@@ -73,8 +194,25 @@ public final class VAOManager {
if (data == current) current = null;
}
private static boolean detach(Attrib[] slots, int buffer) {
boolean detached = false;
for (final Attrib a : slots) {
if (a != null && a.vboId == buffer) {
a.vboId = 0;
detached = true;
}
}
return detached;
}
public void onDeleteBuffer(int buffer) {
if (buffer == 0) return;
bufferDeletedAt.put(buffer, ++bufferEpoch);
if (currentAttribs != null) {
boolean detached = detach(currentGeneric != null ? currentGeneric : currentAttribs, buffer);
if (currentConventional != null) detached |= detach(currentConventional, buffer);
if (detached) touch();
}
VAOData owner = eboOwners.remove(buffer);
while (owner != null) {
final VAOData next = owner.nextEboOwner;
@@ -112,14 +250,19 @@ public final class VAOManager {
public void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
public void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
public int getVertexFlags() { return currentVertexFlags; }
public void setVertexFlags(int flags) { currentVertexFlags = flags; }
public boolean isConventionalEnabled(int conv) { return (conventionalEnabled >> conv & 1) != 0; }
public static void setCurrentVertexFlags(int flags) { GLStateManager.ctx().vaos.currentVertexFlags = flags; }
public static final class VAOData {
public Attrib[] attribs;
public int vertexFlags;
public int genericEnabled;
public int conventionalEnabled;
public Attrib[] generic;
public Attrib[] conventional;
public int ebo;
public int clientSideEnabledCount;
int arrayVersion;
private VAOData nextEboOwner;
public VAOData() {
@@ -127,92 +270,307 @@ public final class VAOManager {
}
}
public void setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
public boolean setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
return setGeneric(index, size, type, normalized, false, stride, offset, vboId, null);
}
public boolean setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
return setGeneric(index, size, type, normalized, false, stride, 0, 0, pointer);
}
public boolean setIntegerAttribute(int index, int size, int type, int stride, long offset, int vboId) {
return setGeneric(index, size, type, false, true, stride, offset, vboId, null);
}
public boolean setIntegerAttribute(int index, int size, int type, int stride, ByteBuffer pointer) {
return setGeneric(index, size, type, false, true, stride, 0, 0, pointer);
}
public boolean setConventional(int conv, int size, int type, boolean normalized, int stride, long offset, int vboId) {
return setConventional(conv, size, type, normalized, stride, offset, vboId, null);
}
public boolean setConventional(int conv, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
return setConventional(conv, size, type, normalized, stride, 0, 0, pointer);
}
private boolean setGeneric(int index, int size, int type, boolean normalized, boolean integer, int stride, long offset, int vboId, ByteBuffer pointer) {
if (index < 0 || index >= MAX_ATTRIBS) return false;
final Attrib g = genericSlot(index);
store(g, size, type, normalized, integer, stride, offset, vboId, pointer);
if (currentAttribs[index] == g) return true;
if (!isGenericEnabled(index) && isLocationConventionalEnabled(index)) return false;
apply(index, g);
return true;
}
private boolean setConventional(int conv, int size, int type, boolean normalized, int stride, long offset, int vboId, ByteBuffer pointer) {
if (conv < 0 || conv >= CONV_COUNT) return false;
final Attrib c = conventionalSlot(conv);
store(c, size, type, normalized, false, stride, offset, vboId, pointer);
final int index = CONV_LOCATION[conv];
if (index < 0) return false;
if (currentAttribs[index] == c) return true;
if (isGenericEnabled(index)) return false;
apply(index, c);
return true;
}
private void store(Attrib a, int size, int type, boolean normalized, boolean integer, int stride, long offset, int vboId, ByteBuffer pointer) {
final boolean wasClient = a.isClientSide();
a.size = size;
a.type = type;
a.normalized = normalized;
a.integer = integer;
a.stride = stride;
a.offset = offset;
a.vboId = vboId;
a.clientPointer = null;
a.genericPointer = true;
if (wasClient && a.enabled) {
clientSideEnabledCount--;
}
}
public void setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = 0;
a.vboId = 0;
a.epoch = bufferEpoch;
a.clientPointer = pointer;
a.genericPointer = true;
if (!wasClient && a.enabled) {
clientSideEnabledCount++;
if (wasClient != a.isClientSide()) clientSideEnabledCount += wasClient ? -1 : 1;
touch();
}
private void apply(int index, Attrib source) {
final Attrib old = currentAttribs[index];
if (old != null) {
if (old.isClientSide()) clientSideEnabledCount--;
source.enabled = old.enabled;
old.enabled = false;
if (source.isClientSide()) clientSideEnabledCount++;
}
currentAttribs[index] = source;
}
private boolean isGenericEnabled(int index) {
return (genericEnabled >> index & 1) != 0;
}
private boolean isLocationConventionalEnabled(int index) {
final int conv = LOCATION_CONV[index];
return conv >= 0 && isConventionalEnabled(conv);
}
private Attrib generic(int index) {
return (currentGeneric != null ? currentGeneric : currentAttribs)[index];
}
private Attrib genericSlot(int index) {
final Attrib[] slots = currentGeneric != null ? currentGeneric : currentAttribs;
Attrib g = slots[index];
if (g == null) {
g = new Attrib();
g.genericPointer = true;
slots[index] = g;
if (currentAttribs[index] == null) currentAttribs[index] = g;
}
return g;
}
private Attrib conventionalSlot(int conv) {
if (currentConventional == null) {
currentConventional = new Attrib[CONV_COUNT];
currentGeneric = currentAttribs.clone();
}
Attrib c = currentConventional[conv];
if (c == null) {
c = new Attrib();
currentConventional[conv] = c;
}
return c;
}
private boolean refresh(int index) {
touch();
boolean repoint = false;
final boolean conventional = isLocationConventionalEnabled(index);
final Attrib winner = isGenericEnabled(index) ? genericSlot(index) : conventional ? currentConventional[LOCATION_CONV[index]] : null;
if (winner != null && winner != currentAttribs[index]) {
apply(index, winner);
repoint = true;
}
final boolean enabled = winner != null;
Attrib e = currentAttribs[index];
if (e == null) {
if (!enabled) return repoint;
e = genericSlot(index);
}
if (e.enabled != enabled) {
if (e.clientPointer != null) clientSideEnabledCount += enabled ? 1 : -1;
e.enabled = enabled;
}
return repoint;
}
public boolean enableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return false;
genericEnabled |= 1 << index;
return refresh(index);
}
public boolean disableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return false;
genericEnabled &= ~(1 << index);
return refresh(index);
}
public boolean setConventionalEnabled(int conv, boolean enabled) {
if (conv < 0 || conv >= CONV_COUNT) return false;
conventionalSlot(conv);
if (enabled) {
conventionalEnabled |= 1 << conv;
} else {
conventionalEnabled &= ~(1 << conv);
}
final int index = CONV_LOCATION[conv];
if (index >= 0) return refresh(index);
touch();
return false;
}
public boolean setDivisor(int index, int divisor) {
if (index < 0 || index >= MAX_ATTRIBS) return true;
final Attrib g = genericSlot(index);
g.divisor = divisor;
touch();
return currentAttribs[index] == g;
}
public @Nullable Attrib conventional(int conv) {
return conv >= 0 && currentConventional != null ? currentConventional[conv] : null;
}
public static final class ClientArrays {
public int boundVAO;
public int boundVBO;
public int boundEBO;
public int clientActiveTextureUnit;
int arrayVersion;
int bufferEpoch;
int vertexFlags;
int genericEnabled;
int conventionalEnabled;
int genericPresent;
int conventionalPresent;
int effectiveGeneric;
final Attrib[] generic = new Attrib[MAX_ATTRIBS];
final Attrib[] conventional = new Attrib[CONV_COUNT];
}
private static void saveSlot(Attrib[] saved, int i, Attrib source) {
if (saved[i] == null) saved[i] = new Attrib();
saved[i].copyPointer(source);
}
public boolean isBufferLive(int buffer, ClientArrays saved) {
return isBufferLive(buffer, saved.bufferEpoch);
}
public void saveClientArrays(ClientArrays out) {
out.arrayVersion = arrayVersion;
out.bufferEpoch = bufferEpoch;
out.vertexFlags = currentVertexFlags;
out.genericEnabled = genericEnabled;
out.conventionalEnabled = conventionalEnabled;
out.genericPresent = 0;
out.conventionalPresent = 0;
out.effectiveGeneric = 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
final Attrib g = generic(i);
if (g != null) {
out.genericPresent |= 1 << i;
saveSlot(out.generic, i, g);
}
final Attrib e = currentAttribs[i];
if (e != null && e.genericPointer) out.effectiveGeneric |= 1 << i;
}
if (currentConventional == null) return;
for (int i = 0; i < CONV_COUNT; i++) {
final Attrib c = currentConventional[i];
if (c != null) {
out.conventionalPresent |= 1 << i;
saveSlot(out.conventional, i, c);
}
}
}
public boolean isGenericPointerEnabled(int index) {
private final Attrib[] restoreBefore = new Attrib[MAX_ATTRIBS];
public int restoreClientArrays(ClientArrays in) {
currentVertexFlags = in.vertexFlags;
if (arrayVersion == in.arrayVersion) return 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
if (restoreBefore[i] == null) restoreBefore[i] = new Attrib();
final Attrib e = currentAttribs[i];
if (e == null) {
restoreBefore[i].clearPointer();
restoreBefore[i].enabled = false;
} else {
restoreBefore[i].copyPointer(e);
restoreBefore[i].enabled = e.enabled;
}
}
genericEnabled = in.genericEnabled;
conventionalEnabled = in.conventionalEnabled;
for (int i = 0; i < MAX_ATTRIBS; i++) {
if ((in.genericPresent >> i & 1) != 0) {
genericSlot(i).copyPointer(in.generic[i]);
} else {
final Attrib g = generic(i);
if (g != null) g.clearPointer();
}
}
if (currentConventional != null || in.conventionalPresent != 0) {
for (int i = 0; i < CONV_COUNT; i++) {
if ((in.conventionalPresent >> i & 1) != 0) {
conventionalSlot(i).copyPointer(in.conventional[i]);
} else if (currentConventional != null && currentConventional[i] != null) {
currentConventional[i].clearPointer();
}
}
}
clientSideEnabledCount = 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
final int conv = LOCATION_CONV[i];
final Attrib g = generic(i);
final Attrib c = conventional(conv);
final boolean conventional = isLocationConventionalEnabled(i);
Attrib e;
if (isGenericEnabled(i)) {
e = genericSlot(i);
} else if (conventional) {
e = conventionalSlot(conv);
} else {
e = c == null || (in.effectiveGeneric >> i & 1) != 0 ? g : c;
}
if (g != null) g.enabled = false;
if (c != null) c.enabled = false;
currentAttribs[i] = e;
if (e != null) {
e.enabled = isGenericEnabled(i) || conventional;
if (e.isClientSide()) clientSideEnabledCount++;
}
}
int changed = 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
final Attrib e = currentAttribs[i];
final Attrib before = restoreBefore[i];
if (e == null) {
if (before.enabled) changed |= 0x10000 << i;
} else {
if (e.enabled != before.enabled) changed |= 0x10000 << i;
if (!e.samePointer(before)) changed |= 1 << i;
}
before.clientPointer = null;
}
arrayVersion = in.arrayVersion;
return changed;
}
public boolean isAttribEnabled(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return false;
final Attrib a = currentAttribs[index];
return a != null && a.genericPointer && a.enabled;
}
public void markConventional(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = currentAttribs[index];
if (a != null) a.genericPointer = false;
}
public void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled != enabled) {
clientSideEnabledCount += enabled ? 1 : -1;
}
a.enabled = enabled;
}
public void enableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && !a.enabled) {
clientSideEnabledCount++;
}
a.enabled = true;
}
public void disableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled) {
clientSideEnabledCount--;
}
a.enabled = false;
}
// Lazily allocate each attrib. Most VAOs don't even have 4 attribs, let alone 16.
private Attrib getAttrib(int index) {
Attrib a = currentAttribs[index];
if (a == null) {
a = new Attrib();
currentAttribs[index] = a;
}
return a;
}
public boolean isClientSideAttrib(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null && attrib.isClientSide();
return a != null && a.enabled;
}
public Attrib attrib(int index) {
@@ -286,7 +644,11 @@ public final class VAOManager {
if (clientArraysVBOOffsets[i] < 0) continue;
final VAOManager.Attrib a = currentAttribs[i];
RENDER_BACKEND.bufferSubData(GL15.GL_ARRAY_BUFFER, clientArraysVBOOffsets[i], a.clientPointer.duplicate());
RENDER_BACKEND.vertexAttribPointer(i, a.size, a.type, a.normalized, a.stride, (long) clientArraysVBOOffsets[i]);
if (a.integer) {
RENDER_BACKEND.vertexAttribIPointer(i, a.size, a.type, a.stride, (long) clientArraysVBOOffsets[i]);
} else {
RENDER_BACKEND.vertexAttribPointer(i, a.size, a.type, a.normalized, a.stride, (long) clientArraysVBOOffsets[i]);
}
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
@@ -298,9 +660,12 @@ public final class VAOManager {
public int size;
public int type;
public boolean normalized;
public boolean integer;
public int stride;
public long offset;
public int vboId;
public int divisor;
int epoch;
public ByteBuffer clientPointer;
public boolean genericPointer;
@@ -308,16 +673,35 @@ public final class VAOManager {
return enabled && clientPointer != null;
}
public void reset() {
enabled = false;
void copyPointer(Attrib source) {
size = source.size;
type = source.type;
normalized = source.normalized;
integer = source.integer;
stride = source.stride;
offset = source.offset;
vboId = source.vboId;
divisor = source.divisor;
epoch = source.epoch;
clientPointer = source.clientPointer;
}
boolean samePointer(Attrib other) {
return size == other.size && type == other.type && normalized == other.normalized && integer == other.integer
&& stride == other.stride && offset == other.offset && vboId == other.vboId && divisor == other.divisor
&& clientPointer == other.clientPointer;
}
void clearPointer() {
size = 0;
type = 0;
normalized = false;
integer = false;
stride = 0;
offset = 0;
vboId = 0;
divisor = 0;
clientPointer = null;
genericPointer = false;
}
public int effectiveStride() {
@@ -46,8 +46,8 @@ public final class VertexKey {
private static final int BIT_TEXGEN_R = BIT_TEXGEN_T + 3;
private static final int BIT_TEXGEN_Q = BIT_TEXGEN_R + 3;
private static final int BIT_CLIP_PLANES = BIT_TEXGEN_Q + 3;
private static final int BIT_WIDE_LINE = BIT_CLIP_PLANES + 1;
private static final int BIT_UNIT_TEXMAT_BASE = BIT_WIDE_LINE + 1;
private static final int BIT_LINE_QUADS = BIT_CLIP_PLANES + 1;
private static final int BIT_UNIT_TEXMAT_BASE = BIT_LINE_QUADS + 1;
private static final int BIT_UNIT23_UV_FROM_UNIT0 = BIT_UNIT_TEXMAT_BASE + MAX_UNITS;
private static final int BIT_LINE_STIPPLE = BIT_UNIT23_UV_FROM_UNIT0 + 1;
private static final int BIT_INSTANCING = BIT_LINE_STIPPLE + 1;
@@ -103,8 +103,8 @@ public final class VertexKey {
public int texGenModeQ() { return (int)((packed >> BIT_TEXGEN_Q) & 0x7); }
public boolean texGenEnabled() { return texGenModeS() != TG_NONE || texGenModeT() != TG_NONE || texGenModeR() != TG_NONE || texGenModeQ() != TG_NONE; }
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
public boolean lineQuads() { return bit(BIT_LINE_QUADS); }
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & INSTANCING_MASK)]; }
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
@@ -226,7 +226,7 @@ public final class VertexKey {
// Vertex format flags
if (hasColor) bits |= (1L << BIT_HAS_VERTEX_COLOR);
if (hasNormal) bits |= (1L << BIT_HAS_VERTEX_NORMAL);
if (hasNormal && !glCtx.lineQuadsActive) bits |= (1L << BIT_HAS_VERTEX_NORMAL);
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
@@ -238,14 +238,14 @@ public final class VertexKey {
bits |= (1L << BIT_CLIP_PLANES);
}
if (glCtx.wideLineEmulationActive) {
bits |= (1L << BIT_WIDE_LINE);
}
if (glCtx.lineStippleActive) {
bits |= (1L << BIT_LINE_STIPPLE);
}
if (glCtx.lineQuadsActive) {
bits |= (1L << BIT_LINE_QUADS);
}
bits |= (long) glCtx.ffpInstancing.ordinal() << BIT_INSTANCING;
return bits;
@@ -277,6 +277,6 @@ public final class VertexKey {
@Override
public String toString() {
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b inst=%s]", packed,lightingEnabled(), lightEnabled(0), lightEnabled(1), colorMaterialEnabled(), fogEnabled(), unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0, unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0, hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(), texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation(), instancing());
return String.format("FFPVertexKey[0x%012X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b lquads=%b inst=%s]", packed,lightingEnabled(), lightEnabled(0), lightEnabled(1), colorMaterialEnabled(), fogEnabled(), unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0, unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0, hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(), texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), lineQuads(), instancing());
}
}
@@ -34,28 +34,16 @@ public final class VertexShaderGenerator {
}
emitTexCoordPassthrough(sb, key);
if (key.lineStipple()) {
emitLineStart(sb);
emitLineStart(sb, key.lineQuads() ? "lineCenter" : "gl_Position");
}
if (key.fogEnabled()) {
emitFogDistance(sb, key);
}
if (key.clipPlanesEnabled()) {
emitClipDistances(sb);
emitClipDistances(sb, key);
}
sb.append("}\n");
String source = sb.toString();
if (key.wideLineEmulation()) {
source = source.replace("v_SpecularColor", "v_SpecularColor_gs")
.replace("v_TexCoord0", "v_TexCoord0_gs")
.replace("v_TexCoord1", "v_TexCoord1_gs")
.replace("v_TexCoord2", "v_TexCoord2_gs")
.replace("v_TexCoord3", "v_TexCoord3_gs")
.replace("v_FogCoord", "v_FogCoord_gs")
.replace("v_LineStart", "v_LineStart_gs")
.replace("v_Color", "v_Color_gs");
}
return source;
return sb.toString();
}
private static void emitAttributes(StringBuilder sb, VertexKey key) {
@@ -78,6 +66,8 @@ public final class VertexShaderGenerator {
}
if (key.hasVertexNormal() || kind == Instancing.CUBE) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
} else if (key.lineQuads()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_LineOtherEnd;\n");
}
}
for (String d : InstancedGlslHelpers.attributeDecls("a_", kind)) {
@@ -97,8 +87,8 @@ public final class VertexShaderGenerator {
};
}
private static void emitLineStart(StringBuilder sb) {
sb.append(" vec2 ndc = gl_Position.xy / gl_Position.w;\n");
private static void emitLineStart(StringBuilder sb, String clipPosition) {
sb.append(" vec2 ndc = ").append(clipPosition).append(".xy / ").append(clipPosition).append(".w;\n");
sb.append(" v_LineStart = u_Viewport.xy + (ndc * 0.5 + 0.5) * u_Viewport.zw;\n\n");
}
@@ -151,15 +141,59 @@ public final class VertexShaderGenerator {
sb.append(" v_Overlay = a_InstOverlay;\n");
} else {
sb.append(" gl_Position = u_MVPMatrix * pos4;\n");
if (key.lineQuads()) {
emitLineQuadWidening(sb);
}
// Eye position needed for lighting, fog, EYE_LINEAR texgen, and clip planes
if (key.lightingEnabled() || key.fogEnabled() || texGenNeedsEyePos(key) || key.clipPlanesEnabled()) {
sb.append(" vec4 eyePos = u_ModelViewMatrix * pos4;\n");
}
if (key.lineQuads() && (key.fogEnabled() || key.clipPlanesEnabled())) {
sb.append(" vec4 lineEyeOther = u_ModelViewMatrix * vec4(a_LineOtherEnd, 1.0);\n");
}
}
sb.append('\n');
}
private static void emitLineQuadWidening(StringBuilder sb) {
sb.append(" int lineCorner = gl_VertexID % 6;\n");
sb.append(" bool lineStart = lineCorner == 0 || lineCorner == 1 || lineCorner == 3;\n");
sb.append(" vec4 lineOther = u_MVPMatrix * vec4(a_LineOtherEnd, 1.0);\n");
sb.append(" vec4 lineC0 = lineStart ? gl_Position : lineOther;\n");
sb.append(" vec4 lineC1 = lineStart ? lineOther : gl_Position;\n");
sb.append(" vec4 lineCenter = lineC0;\n");
sb.append(" float lineT = 0.0;\n");
sb.append(" float lineD0 = lineC0.z + lineC0.w;\n");
sb.append(" float lineD1 = lineC1.z + lineC1.w;\n");
sb.append(" if (lineD0 < 0.0 && lineD1 < 0.0) {\n");
sb.append(" gl_Position = vec4(0.0, 0.0, 2.0, 1.0);\n");
sb.append(" } else {\n");
sb.append(" float lineT0 = lineD0 < 0.0 ? lineD0 / (lineD0 - lineD1) : 0.0;\n");
sb.append(" float lineT1 = lineD1 < 0.0 ? lineD0 / (lineD0 - lineD1) : 1.0;\n");
sb.append(" lineT = lineStart ? lineT0 : lineT1;\n");
sb.append(" vec4 lineP0 = mix(lineC0, lineC1, lineT0);\n");
sb.append(" vec4 lineP1 = mix(lineC0, lineC1, lineT1);\n");
sb.append(" vec2 lineN0 = lineP0.xy / lineP0.w;\n");
sb.append(" vec2 lineN1 = lineP1.xy / lineP1.w;\n");
sb.append(" vec2 linePixel = 2.0 / u_ViewportSize;\n");
sb.append(" float lineHalfWidth = 0.5 * u_LineWidth;\n");
sb.append(" vec2 lineOffset;\n");
sb.append(" vec2 lineShift;\n");
sb.append(" vec2 lineSpan = abs(lineN1 - lineN0) * u_ViewportSize;\n");
sb.append(" if (lineSpan.x > lineSpan.y) {\n");
sb.append(" lineOffset = vec2(0.0, lineHalfWidth * linePixel.y);\n");
sb.append(" lineShift = vec2(lineN0.x < lineN1.x ? -0.5 : 0.5, -0.125) * linePixel;\n");
sb.append(" } else {\n");
sb.append(" lineOffset = vec2(lineHalfWidth * linePixel.x, 0.0);\n");
sb.append(" lineShift = vec2(0.125, lineN0.y < lineN1.y ? -0.5 : 0.5) * linePixel;\n");
sb.append(" }\n");
sb.append(" vec4 lineP = lineStart ? lineP0 : lineP1;\n");
sb.append(" vec2 lineN = lineStart ? lineN0 : lineN1;\n");
sb.append(" gl_Position = vec4((lineN + (lineCorner % 2 == 0 ? lineOffset : -lineOffset) + lineShift) * lineP.w, lineP.z, lineP.w);\n");
sb.append(" }\n");
}
private static void emitNormalTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Normal transform\n");
final String srcNormal = key.hasVertexNormal() && key.instancing() != Instancing.PARTICLE ? "a_Normal" : "u_CurrentNormal";
@@ -380,24 +414,30 @@ public final class VertexShaderGenerator {
private static void emitFogDistance(StringBuilder sb, VertexKey key) {
sb.append(" // Fog distance\n");
// Mesa ffvertex_prog.c build_fog(): FDM_EYE_RADIAL/EYE_PLANE/EYE_PLANE_ABS
final int fogDistMode = key.fogDistanceMode();
switch (fogDistMode) {
case 0 -> // FDM_EYE_RADIAL: Euclidean distance
sb.append(" v_FogCoord = length(eyePos.xyz);\n");
case 1 -> // FDM_EYE_PLANE: raw Z (can be negative, clamped by fog factor)
sb.append(" v_FogCoord = eyePos.z;\n");
case 2 -> // FDM_EYE_PLANE_ABS: absolute Z
sb.append(" v_FogCoord = abs(eyePos.z);\n");
default ->
sb.append(" v_FogCoord = abs(eyePos.z);\n");
final String own = fogDistance(key, "eyePos");
final String fog = key.lineQuads() ? acrossLine(own, fogDistance(key, "lineEyeOther")) : own;
sb.append(" v_FogCoord = ").append(fog).append(";\n");
}
// Mesa ffvertex_prog.c build_fog(): FDM_EYE_RADIAL/EYE_PLANE/EYE_PLANE_ABS
private static String fogDistance(VertexKey key, String eye) {
return switch (key.fogDistanceMode()) {
case 0 -> "length(" + eye + ".xyz)"; // FDM_EYE_RADIAL: Euclidean distance
case 1 -> eye + ".z"; // FDM_EYE_PLANE: raw Z (can be negative, clamped by fog factor)
default -> "abs(" + eye + ".z)"; // FDM_EYE_PLANE_ABS: absolute Z
};
}
private static void emitClipDistances(StringBuilder sb, VertexKey key) {
sb.append(" // Clip distances\n");
for (int i = 0; i < 8; i++) {
final String own = "dot(u_ClipPlane[" + i + "], eyePos)";
final String distance = key.lineQuads() ? acrossLine(own, "dot(u_ClipPlane[" + i + "], lineEyeOther)") : own;
sb.append(" gl_ClipDistance[").append(i).append("] = ").append(distance).append(";\n");
}
}
private static void emitClipDistances(StringBuilder sb) {
sb.append(" // Clip distances\n");
for (int i = 0; i < 8; i++) {
sb.append(" gl_ClipDistance[").append(i).append("] = dot(u_ClipPlane[").append(i).append("], eyePos);\n");
}
private static String acrossLine(String own, String other) {
return "mix(lineStart ? " + own + " : " + other + ", lineStart ? " + other + " : " + own + ", lineT)";
}
}
@@ -11,6 +11,7 @@ public final class GLSMHooks {
public static DeferredDepthColorHandler depthColorHandler;
public static GlintColorHandler glintColorHandler;
public static ImmediateExtendedAttribHandler immediateExtendedHandler;
public static LineDrawHandler lineDrawHandler;
public static ShaderTransformPostProcessor postTransformProcessor;
public static PerFrameUniformBlock perFrameUniformBlock;
public static PerFrameUniformBlock perPassUniformBlock;
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface LineDrawHandler {
boolean bindLineProgram();
void restoreProgram();
}
@@ -1,8 +1,11 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import com.gtnewhorizons.angelica.glsm.streaming.LineQuads;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
@@ -55,9 +58,20 @@ public final class DisplayListVBO {
private final int count;
private final int vertexFlags;
private int pendingExtVbo = 0;
private IVertexArrayObject lineQuads;
private int lineQuadStart;
private int lineQuadCount;
private int lineQuadFlags;
void setPendingExtVbo(int extVbo) { this.pendingExtVbo = extVbo; }
void setLineQuads(IVertexArrayObject quads, int start, int count, int flags) {
this.lineQuads = quads;
this.lineQuadStart = start;
this.lineQuadCount = count;
this.lineQuadFlags = flags;
}
public SubVBO(IVertexArrayObject vao, int drawMode, int start, int count, int vertexFlags) {
this.vao = vao;
this.drawMode = drawMode;
@@ -80,10 +94,15 @@ public final class DisplayListVBO {
public void delete() {
vao.delete();
if (lineQuads != null) lineQuads.delete();
}
public void render() {
if (vao == null) return;
if (lineQuads != null && GLStateManager.ffpWidensLineQuads()) {
renderLineQuads();
return;
}
vao.bind();
if (pendingExtVbo != 0) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, pendingExtVbo);
@@ -102,6 +121,22 @@ public final class DisplayListVBO {
vao.unbind();
}
private void renderLineQuads() {
final GLContextState glCtx = GLStateManager.ctx();
lineQuads.bind();
VAOManager.setCurrentVertexFlags(lineQuadFlags);
final boolean culled = LineQuads.disableCulling();
glCtx.lineQuadsActive = true;
FfpExtendedAttribs.beginInternalDraw();
try {
lineQuads.draw(GL11.GL_TRIANGLES, lineQuadStart, lineQuadCount);
} finally {
FfpExtendedAttribs.endInternalDraw();
glCtx.lineQuadsActive = false;
LineQuads.restoreCulling(culled);
}
lineQuads.unbind();
}
public IVertexArrayObject getVAO() {
return vao;
@@ -13,6 +13,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import com.gtnewhorizons.angelica.glsm.streaming.LineQuads;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
@@ -78,6 +79,8 @@ public final class DisplayListVBOBuilder {
final IntArrayList extStarts = wantExt ? new IntArrayList() : null;
final IntArrayList extCounts = wantExt ? new IntArrayList() : null;
final IntArrayList extPrims = wantExt ? new IntArrayList() : null;
// drawIndex, start, vertexCount, drawMode for each line draw
final IntArrayList lineDraws = GLStateManager.wideLineEmulationEnabled ? new IntArrayList() : null;
for (FormatData data : formatData) {
int vertexCount;
@@ -123,6 +126,12 @@ public final class DisplayListVBOBuilder {
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, GL11.GL_TRIANGLE_FAN, start, vertexCount, flags);
} else {
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, data.drawMode, start, vertexCount, flags);
if (lineDraws != null && LineQuads.segmentCount(data.drawMode, vertexCount) > 0) {
lineDraws.add(data.drawIndex);
lineDraws.add(start);
lineDraws.add(vertexCount);
lineDraws.add(data.drawMode);
}
}
@@ -130,6 +139,9 @@ public final class DisplayListVBOBuilder {
}
ByteBuffer bigBuffer = mergeAndDelete(allBuffers);
vbo.allocate(bigBuffer, start);
if (lineDraws != null && !lineDraws.isEmpty()) {
attachLineQuads(vbos, format, bigBuffer, lineDraws);
}
if (wantExt && !extDrawIndexes.isEmpty()) {
final int extVbo = buildExt(extHandler, format, bigBuffer, extStarts, extCounts, extPrims);
@@ -148,6 +160,34 @@ public final class DisplayListVBOBuilder {
return new DisplayListVBO(vbos, extVbos.toIntArray());
}
// The line width is only known at playback, so lines keep their own draw and also get quads to widen
private static void attachLineQuads(DisplayListVBO.SubVBO[] vbos, VertexFormat format, ByteBuffer vertices, IntArrayList lineDraws) {
final VertexFormat quadFormat = LineQuads.quadFormat(format);
final int quadSize = quadFormat.getVertexSize();
int quadVertices = 0;
for (int i = 0; i < lineDraws.size(); i += 4) {
quadVertices += LineQuads.segmentCount(lineDraws.getInt(i + 3), lineDraws.getInt(i + 2)) * LineQuads.VERTICES_PER_SEGMENT;
}
final ByteBuffer quads = memAlloc(quadVertices * quadSize);
final long src = memAddress0(vertices);
final long dst = memAddress0(quads);
final int stride = format.getVertexSize();
final IVertexArrayObject quadVao = VAOManager.createStorageVAO(quadFormat, -1, 0);
int quadStart = 0;
for (int i = 0; i < lineDraws.size(); i += 4) {
final int start = lineDraws.getInt(i + 1);
final int count = lineDraws.getInt(i + 2);
final int mode = lineDraws.getInt(i + 3);
final int corners = LineQuads.segmentCount(mode, count) * LineQuads.VERTICES_PER_SEGMENT;
LineQuads.expandWithOtherEnd(src + (long) start * stride, format, mode, count, dst + (long) quadStart * quadSize);
vbos[lineDraws.getInt(i)].setLineQuads(quadVao, quadStart, corners, quadFormat.getVertexFlags());
quadStart += corners;
}
quadVao.getVBO().allocate(quads, quadVertices);
memFree(quads);
}
private static int buildExt(ImmediateExtendedAttribHandler handler, VertexFormat format, ByteBuffer bigBuffer,
IntArrayList extStarts, IntArrayList extCounts, IntArrayList extPrims) {
final int extStride = ImmediateExtendedAttribHandler.EXT_STRIDE;
@@ -5,6 +5,8 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.lwjgl.opengl.GL11;
@@ -13,6 +15,10 @@ import java.util.Arrays;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetShort;
/**
* Records immediate mode GL calls (glBegin/glEnd/glVertex) via {@link DirectTessellator}.
* Used both during display list compilation and for live immediate mode emulation in core profile.
@@ -213,6 +219,34 @@ public final class ImmediateModeRecorder {
}
}
public static DirectTessellator processDrawElementsFromAttribs(int mode, int count, int indexType, long indices) {
final long minMax = QuadConverter.scanMinMaxIndex(indices, indexType, count);
if (minMax == -1L) return null;
final int minVertex = (int) (minMax & 0xFFFFFFFFL);
final int maxVertex = (int) (minMax >>> 32);
final AttribSnapshot snap = AttribSnapshot.snapshot(minVertex, maxVertex - minVertex + 1);
if (snap == null) return null;
try {
prepareReaders(snap);
if (positionReaderIndex < 0) return null;
begin(mode);
final DirectTessellator t = tessellator();
final int indexSize = GLTypes.sizeBytes(indexType);
for (int i = 0; i < count; i++) {
final long at = indices + (long) i * indexSize;
final int vertex = switch (indexType) {
case GL11.GL_UNSIGNED_BYTE -> memGetByte(at) & 0xFF;
case GL11.GL_UNSIGNED_SHORT -> memGetShort(at) & 0xFFFF;
default -> memGetInt(at);
};
readVertexFromBuffer(t, vertex);
}
return end();
} finally {
snap.free();
}
}
private static void prepareReaders(AttribSnapshot snap) {
nonPositionCount = 0;
positionReaderIndex = -1;
@@ -143,6 +143,7 @@ public class GLSMRedirector {
private static final String OpenGlHelper = "net/minecraft/client/renderer/OpenGlHelper";
private static final String EXTBlendFunc = "org/lwjgl/opengl/EXTBlendFuncSeparate";
private static final String EXTSecondaryColor = "org/lwjgl/opengl/EXTSecondaryColor";
private static final String EXTFogCoord = "org/lwjgl/opengl/EXTFogCoord";
private static final String ARBMultiTexture = "org/lwjgl/opengl/ARBMultitexture";
private static final String ARBShaderObjects = "org/lwjgl/opengl/ARBShaderObjects";
private static final String ARBInstancedArrays = "org/lwjgl/opengl/ARBInstancedArrays";
@@ -378,6 +379,8 @@ public class GLSMRedirector {
.add("glEnableClientState")
.add("glDisableClientState")
.add("glInterleavedArrays")
.add("glEdgeFlagPointer")
.add("glGetPointer")
.add("glPushClientAttrib")
.add("glPopClientAttrib")
.add("glViewport")
@@ -408,11 +411,16 @@ public class GLSMRedirector {
.add("glMultiDrawArrays")
.add("glPointParameterf")
.add("glPointParameteri")
.add("glPointParameter")
.add("glPointParameterfv", "glPointParameter")
.add("glPointParameteriv", "glPointParameter")
// Removed in core profile - emulated by the FFP shaders, calling through would abort the JVM
.add("glSecondaryColor3f")
.add("glSecondaryColor3d")
.add("glSecondaryColor3b")
.add("glSecondaryColor3ub");
.add("glSecondaryColor3ub")
.add("glSecondaryColorPointer")
.add("glFogCoordPointer");
final var gl15 = RedirectMap.newMap()
.add("glGenBuffers")
.add("glBindBuffer")
@@ -604,7 +612,9 @@ public class GLSMRedirector {
.add("glSecondaryColor3bEXT", "glSecondaryColor3b")
.add("glSecondaryColor3fEXT", "glSecondaryColor3f")
.add("glSecondaryColor3dEXT", "glSecondaryColor3d")
.add("glSecondaryColor3ubEXT", "glSecondaryColor3ub"));
.add("glSecondaryColor3ubEXT", "glSecondaryColor3ub")
.add("glSecondaryColorPointerEXT", "glSecondaryColorPointer"));
methodRedirects.put(EXTFogCoord, RedirectMap.newMap().add("glFogCoordPointerEXT", "glFogCoordPointer"));
// ARB
methodRedirects.put(ARBMultiTexture, RedirectMap.newMap()
@@ -1,9 +1,12 @@
package com.gtnewhorizons.angelica.glsm.shader;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
@@ -217,6 +220,40 @@ public final class GlslVulkanPreprocess {
for (Token tok : index.occurrences(name)) edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), replacement));
}
static @Nullable String renameUserFunctions(String source) {
final GLSLParser.Translation_unitContext root;
try {
root = GlslTransformUtils.parseFullQuiet(source);
} catch (Exception e) {
return null;
}
Object2IntOpenHashMap<String> prototypes = null;
for (GLSLParser.External_declarationContext external : root.external_declaration()) {
final GLSLParser.Function_definitionContext function = external.function_definition();
final GLSLParser.DeclarationContext declaration = function == null ? external.declaration() : null;
final GLSLParser.Function_prototypeContext prototype = function != null ? function.function_prototype() : declaration != null ? declaration.function_prototype() : null;
final TerminalNode identifier = prototype == null ? null : prototype.IDENTIFIER();
if (identifier == null) continue;
final String name = identifier.getText();
if ("main".equals(name)) continue;
if (prototypes == null) prototypes = new Object2IntOpenHashMap<>();
prototypes.addTo(name, 1);
}
if (prototypes == null) return null;
final ShaderIndex index = new ShaderIndex(root, source);
final Set<String> inDirectives = index.directives().identifiers();
final List<Edit> edits = new ArrayList<>();
for (Object2IntMap.Entry<String> prototype : prototypes.object2IntEntrySet()) {
final String name = prototype.getKey();
if (inDirectives.contains(name)) continue;
if (index.occurrences(name).size() != prototype.getIntValue() + index.callsTo(name).size()) continue;
rename(index, name, RESERVED_RENAMED_PREFIX + name, edits);
}
return edits.isEmpty() ? null : applyEdits(source, edits);
}
private static void rewriteReadOnlyImageCalls(List<GLSLParser.Postfix_expressionContext> calls, String replacement, Set<String> readOnlyImages, List<Edit> edits) {
for (GLSLParser.Postfix_expressionContext call : calls) {
if (call.RIGHT_PAREN() == null) continue;
@@ -81,7 +81,16 @@ public final class SpirvCompiler {
}
}
final Result result = compileUncached(cleanSrc, shaderKind, debugName, opts);
Result result = compileUncached(cleanSrc, shaderKind, debugName, opts);
if (result.spirv() == null) {
final String renamed = GlslVulkanPreprocess.renameUserFunctions(cleanSrc);
final Result retry = renamed == null ? null : compileUncached(renamed, shaderKind, debugName, opts);
if (retry == null || retry.spirv() == null) {
return new Result(null, result.error(), writeFailureDump(debugName, shaderKind, result.error(), cleanSrc));
}
LOGGER.info("'{}' compiled after renaming its functions; shaderc rejected the original: {}", debugName, result.error());
result = retry;
}
final ByteBuffer spirv = result.spirv();
if (spirv != null) {
final byte[] heap = ShaderCacheIO.toHeap(spirv);
@@ -121,8 +130,7 @@ public final class SpirvCompiler {
final int status = Shaderc.shaderc_result_get_compilation_status(result);
if (status != Shaderc.shaderc_compilation_status_success) {
final String msg = Shaderc.shaderc_result_get_error_message(result);
final Path dumpPath = writeFailureDump(debugName, shaderKind, msg, cleanSrc);
return new Result(null, msg == null ? "(no error message)" : msg, dumpPath);
return new Result(null, msg == null ? "(no error message)" : msg, null);
}
final ByteBuffer spirv = Shaderc.shaderc_result_get_bytes(result);
if (spirv == null || spirv.remaining() == 0) {
@@ -59,8 +59,7 @@ public final class SpirvShaderTranslator {
final SpirvCompiler.Result cr = SpirvCompiler.compile(pre.rewrittenSource(), shaderKind, debugName, SpirvCompiler.Options.vulkanRelaxed());
if (cr.spirv() == null) {
final String firstErr = cr.error() == null ? "(no error)" : cr.error().split("\\R", 2)[0];
LOGGER.error("shaderc failed for '{}' (kind=0x{}): {} (dumped to {})",
debugName, Integer.toHexString(shaderKind), firstErr, cr.dumpPath() == null ? "<dump failed>" : cr.dumpPath());
LOGGER.error("shaderc failed for '{}' (kind=0x{}): {} (dumped to {})", debugName, Integer.toHexString(shaderKind), firstErr, cr.dumpPath() == null ? "<dump failed>" : cr.dumpPath());
return null;
}
try {
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.states;
import lombok.Getter;
import lombok.Setter;
import org.lwjgl.opengl.GL20;
/**
* Tracks GL point state: size.
@@ -10,10 +11,24 @@ import lombok.Setter;
@Getter @Setter
public class PointState implements ISettableState<PointState> {
private float size = 1.0f;
private float minSize = 0.0f;
private float maxSize = Float.NaN;
private float fadeThreshold = 1.0f;
private float attenuationConstant = 1.0f;
private float attenuationLinear = 0.0f;
private float attenuationQuadratic = 0.0f;
private int spriteCoordOrigin = GL20.GL_UPPER_LEFT;
@Override
public PointState set(PointState state) {
this.size = state.size;
this.minSize = state.minSize;
this.maxSize = state.maxSize;
this.fadeThreshold = state.fadeThreshold;
this.attenuationConstant = state.attenuationConstant;
this.attenuationLinear = state.attenuationLinear;
this.attenuationQuadratic = state.attenuationQuadratic;
this.spriteCoordOrigin = state.spriteCoordOrigin;
return this;
}
@@ -21,7 +36,14 @@ public class PointState implements ISettableState<PointState> {
public boolean sameAs(Object state) {
if (this == state) return true;
if (!(state instanceof PointState pointState)) return false;
return Float.compare(size, pointState.size) == 0;
return Float.compare(size, pointState.size) == 0
&& Float.compare(minSize, pointState.minSize) == 0
&& Float.compare(maxSize, pointState.maxSize) == 0
&& Float.compare(fadeThreshold, pointState.fadeThreshold) == 0
&& Float.compare(attenuationConstant, pointState.attenuationConstant) == 0
&& Float.compare(attenuationLinear, pointState.attenuationLinear) == 0
&& Float.compare(attenuationQuadratic, pointState.attenuationQuadratic) == 0
&& spriteCoordOrigin == pointState.spriteCoordOrigin;
}
@Override
@@ -9,6 +9,7 @@ import lombok.Getter;
import org.joml.Matrix4fStack;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.List;
@@ -19,6 +20,7 @@ public class TextureUnitArray {
private final TextureUnitBooleanStateStack[] states;
private final TextureUnitBooleanStateStack[] texture1DStates;
private final TextureUnitBooleanStateStack[] texture3DStates;
private final TextureUnitBooleanStateStack[] textureCubeMapStates;
private final TextureUnitBooleanStateStack[] texGenSStates;
private final TextureUnitBooleanStateStack[] texGenTStates;
private final TextureUnitBooleanStateStack[] texGenRStates;
@@ -36,6 +38,7 @@ public class TextureUnitArray {
states = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
texture1DStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
texture3DStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
textureCubeMapStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
texGenSStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
texGenTStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
texGenRStates = new TextureUnitBooleanStateStack[GLStateManager.MAX_TEXTURE_UNITS];
@@ -49,6 +52,7 @@ public class TextureUnitArray {
states[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_2D, i));
texture1DStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_1D, i));
texture3DStates[i] = track(new TextureUnitBooleanStateStack(GL12.GL_TEXTURE_3D, i));
textureCubeMapStates[i] = track(new TextureUnitBooleanStateStack(GL13.GL_TEXTURE_CUBE_MAP, i));
texGenSStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_S, i));
texGenTStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_T, i));
texGenRStates[i] = track(new TextureUnitBooleanStateStack(GL11.GL_TEXTURE_GEN_R, i));
@@ -90,6 +94,10 @@ public class TextureUnitArray {
return texture3DStates[index];
}
public TextureUnitBooleanStateStack getTextureCubeMapStates(int index) {
return textureCubeMapStates[index];
}
public TextureUnitBooleanStateStack getTexGenSStates(int index) {
return texGenSStates[index];
}
@@ -127,6 +135,7 @@ public class TextureUnitArray {
if (!states[i].sameAs(textureUnitArray.states[i])) return false;
if (!texture1DStates[i].sameAs(textureUnitArray.texture1DStates[i])) return false;
if (!texture3DStates[i].sameAs(textureUnitArray.texture3DStates[i])) return false;
if (!textureCubeMapStates[i].sameAs(textureUnitArray.textureCubeMapStates[i])) return false;
if (!texGenSStates[i].sameAs(textureUnitArray.texGenSStates[i])) return false;
if (!texGenTStates[i].sameAs(textureUnitArray.texGenTStates[i])) return false;
if (!texGenRStates[i].sameAs(textureUnitArray.texGenRStates[i])) return false;
@@ -0,0 +1,154 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
/**
* Rewrites line primitives as quads for shaders that widen lines in the vertex stage.
*/
public final class LineQuads {
public static final int VERTICES_PER_SEGMENT = 6;
private static final VertexFormatElement NORMAL_SLOT = new VertexFormatElement(0, VertexFormatElement.Type.FLOAT,
VertexFormatElement.Usage.NORMAL, 3, VertexFlags.NORMAL_BIT, DefaultVertexFormat.NORMAL_ELEMENT.getWriter());
private static final VertexFormat[] QUAD_FORMATS = new VertexFormat[VertexFlags.BITSET_SIZE];
private static final int[][] SOURCE_OFFSETS = new int[VertexFlags.BITSET_SIZE][];
static {
for (int flags = 0; flags < QUAD_FORMATS.length; flags++) {
if ((flags & VertexFlags.NORMAL_BIT) == 0) continue;
final VertexFormatElement[] elements = DefaultVertexFormat.ALL_FORMATS[flags].elementsArray.clone();
for (int i = 0; i < elements.length; i++) {
if (elements[i] == DefaultVertexFormat.NORMAL_ELEMENT) elements[i] = NORMAL_SLOT;
}
QUAD_FORMATS[flags] = new VertexFormat(elements);
}
for (int flags = 0; flags < SOURCE_OFFSETS.length; flags++) {
final VertexFormatElement[] quadElements = QUAD_FORMATS[flags | VertexFlags.NORMAL_BIT].elementsArray;
final int[] offsets = new int[quadElements.length];
for (int i = 0; i < quadElements.length; i++) {
offsets[i] = offsetOf(DefaultVertexFormat.ALL_FORMATS[flags], quadElements[i]);
}
SOURCE_OFFSETS[flags] = offsets;
}
}
private LineQuads() {}
public static boolean isLineMode(int drawMode) {
return drawMode == GL11.GL_LINES || drawMode == GL11.GL_LINE_STRIP || drawMode == GL11.GL_LINE_LOOP;
}
public static int segmentCount(int drawMode, int vertexCount) {
return switch (drawMode) {
case GL11.GL_LINES -> vertexCount / 2;
case GL11.GL_LINE_STRIP -> Math.max(0, vertexCount - 1);
case GL11.GL_LINE_LOOP -> vertexCount < 2 ? 0 : vertexCount;
default -> 0;
};
}
public static boolean disableCulling() {
final boolean wasEnabled = GLStateManager.getCullState().isEnabled();
if (wasEnabled) {
GLStateManager.disableCull();
}
return wasEnabled;
}
public static void restoreCulling(boolean wasEnabled) {
if (wasEnabled) {
GLStateManager.enableCull();
}
}
public static VertexFormat quadFormat(VertexFormat lineFormat) {
return QUAD_FORMATS[lineFormat.getVertexFlags() | VertexFlags.NORMAL_BIT];
}
public static boolean isQuadFormat(VertexFormat format) {
return QUAD_FORMATS[format.getVertexFlags()] == format;
}
public static void expandWithDirection(long src, VertexFormat srcFormat, int drawMode, int vertexCount, long dst) {
expand(src, srcFormat, drawMode, vertexCount, dst, false);
}
public static void expandWithOtherEnd(long src, VertexFormat srcFormat, int drawMode, int vertexCount, long dst) {
expand(src, srcFormat, drawMode, vertexCount, dst, true);
}
private static void expand(long src, VertexFormat srcFormat, int drawMode, int vertexCount, long dst, boolean otherEnd) {
final VertexFormatElement[] dstElements = quadFormat(srcFormat).elementsArray;
final int[] srcOffsets = SOURCE_OFFSETS[srcFormat.getVertexFlags()];
final int srcStride = srcFormat.getVertexSize();
final int segments = segmentCount(drawMode, vertexCount);
long out = dst;
for (int s = 0; s < segments; s++) {
final int a = drawMode == GL11.GL_LINES ? s * 2 : s;
final int b = drawMode == GL11.GL_LINE_LOOP && s == vertexCount - 1 ? 0 : a + 1;
final long srcA = src + (long) a * srcStride;
final long srcB = src + (long) b * srcStride;
// What A's and B's corners carry in the normal slot
final float ax, ay, az, bx, by, bz;
if (otherEnd) {
ax = memGetFloat(srcB); ay = memGetFloat(srcB + 4); az = memGetFloat(srcB + 8);
bx = memGetFloat(srcA); by = memGetFloat(srcA + 4); bz = memGetFloat(srcA + 8);
} else {
final float x = memGetFloat(srcB) - memGetFloat(srcA);
final float y = memGetFloat(srcB + 4) - memGetFloat(srcA + 4);
final float z = memGetFloat(srcB + 8) - memGetFloat(srcA + 8);
final float lengthSquared = x * x + y * y + z * z;
final float scale = lengthSquared == 0.0f ? 0.0f : 1.0f / (float) Math.sqrt(lengthSquared);
ax = bx = x * scale;
ay = by = y * scale;
az = bz = z * scale;
}
out = writeVertex(srcA, out, dstElements, srcOffsets, ax, ay, az);
out = writeVertex(srcA, out, dstElements, srcOffsets, ax, ay, az);
out = writeVertex(srcB, out, dstElements, srcOffsets, bx, by, bz);
out = writeVertex(srcA, out, dstElements, srcOffsets, ax, ay, az);
out = writeVertex(srcB, out, dstElements, srcOffsets, bx, by, bz);
out = writeVertex(srcB, out, dstElements, srcOffsets, bx, by, bz);
}
}
private static long writeVertex(long src, long dst, VertexFormatElement[] dstElements, int[] srcOffsets, float x, float y, float z) {
long out = dst;
for (int i = 0; i < dstElements.length; i++) {
final VertexFormatElement element = dstElements[i];
final int size = element.getByteSize();
if (element == NORMAL_SLOT) {
memPutFloat(out, x);
memPutFloat(out + 4, y);
memPutFloat(out + 8, z);
} else {
memCopy(src + srcOffsets[i], out, size);
}
out += size;
}
return out;
}
private static int offsetOf(VertexFormat format, VertexFormatElement element) {
int offset = 0;
for (VertexFormatElement candidate : format.elementsArray) {
if (candidate == element) {
return offset;
}
offset += candidate.getByteSize();
}
return -1;
}
}
@@ -10,4 +10,7 @@ public final class StreamingVaos {
final int[] extendedPersistentVAOs = new int[FORMAT_COUNT];
final int[] extendedOrphanVAOs = new int[FORMAT_COUNT];
final int[] lineQuadPersistentVAOs = new int[FORMAT_COUNT];
final int[] lineQuadOrphanVAOs = new int[FORMAT_COUNT];
}
@@ -15,6 +15,7 @@ import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import com.gtnewhorizons.angelica.glsm.hooks.LineDrawHandler;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import net.minecraft.client.renderer.Tessellator;
import org.apache.logging.log4j.LogManager;
@@ -56,6 +57,10 @@ public class TessellatorStreamingDrawer {
private static long repackAddress;
private static int repackCapacity;
private static ByteBuffer lineQuadBuffer;
private static long lineQuadAddress;
private static int lineQuadCapacity;
private static boolean initialized = false;
// Tracy profiling counters
@@ -315,12 +320,18 @@ public class TessellatorStreamingDrawer {
* Tries the persistent ring buffer first, falls back to orphan buffer on overflow.
*/
private static void uploadAndDraw(ByteBuffer packed, int flags, VertexFormat format, int vertexSize, int drawMode, int vertexCount) {
if (LineQuads.isLineMode(drawMode) && drawLinesAsQuads(packed, format, drawMode, vertexCount)) {
return;
}
Tracy.beginZone(Z_SDL_STREAM_DRAW);
final boolean locked = GLStateManager.acquireDrawLock();
try {
final GLContextState glCtx = GLStateManager.ctx();
final StreamingVaos vaos = glCtx.streamingVaos;
ensureVAO(vaos, flags, format);
final boolean lineQuads = LineQuads.isQuadFormat(format);
final int[] persistentVAOs = lineQuads ? vaos.lineQuadPersistentVAOs : vaos.persistentVAOs;
final int[] orphanVAOs = lineQuads ? vaos.lineQuadOrphanVAOs : vaos.orphanVAOs;
ensureVAO(persistentVAOs, orphanVAOs, flags, format);
if (Tracy.ENABLED) {
streamedBytes += packed.remaining();
@@ -330,15 +341,16 @@ public class TessellatorStreamingDrawer {
int firstVertex = -1;
if (persistentBuffer != null) {
firstVertex = persistentBuffer.upload(packed, vertexSize, drawMode == GL11.GL_QUADS ? 4 : 1);
final int alignment = drawMode == GL11.GL_QUADS ? 4 : lineQuads ? LineQuads.VERTICES_PER_SEGMENT : 1;
firstVertex = persistentBuffer.upload(packed, vertexSize, alignment);
}
final boolean fromRing = firstVertex >= 0;
if (fromRing) {
GLStateManager.glBindVertexArray(vaos.persistentVAOs[flags]);
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
} else {
if (Tracy.ENABLED && persistentBuffer != null) orphanFallbacks++;
GLStateManager.glBindVertexArray(vaos.orphanVAOs[flags]);
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
orphanBuffers[flags].upload(packed);
firstVertex = 0;
}
@@ -357,6 +369,72 @@ public class TessellatorStreamingDrawer {
}
}
private static boolean drawLinesAsQuads(ByteBuffer packed, VertexFormat format, int drawMode, int vertexCount) {
final int segments = LineQuads.segmentCount(drawMode, vertexCount);
if (segments == 0) {
return false;
}
final LineDrawHandler handler = GLSMHooks.lineDrawHandler;
if (handler != null && handler.bindLineProgram()) {
try {
drawLineQuads(packed, format, drawMode, vertexCount, segments, false);
} finally {
handler.restoreProgram();
}
return true;
}
if (GLStateManager.ffpWidensLineQuads()) {
final GLContextState glCtx = GLStateManager.ctx();
glCtx.lineQuadsActive = true;
try {
drawLineQuads(packed, format, drawMode, vertexCount, segments, true);
} finally {
glCtx.lineQuadsActive = false;
}
return true;
}
return false;
}
private static void drawLineQuads(ByteBuffer packed, VertexFormat format, int drawMode, int vertexCount, int segments, boolean otherEnd) {
final VertexFormat quadFormat = LineQuads.quadFormat(format);
final int quadVertices = segments * LineQuads.VERTICES_PER_SEGMENT;
final int quadBytes = quadVertices * quadFormat.getVertexSize();
ensureLineQuadCapacity(quadBytes);
final long src = memAddress0(packed) + packed.position();
if (otherEnd) {
LineQuads.expandWithOtherEnd(src, format, drawMode, vertexCount, lineQuadAddress);
} else {
LineQuads.expandWithDirection(src, format, drawMode, vertexCount, lineQuadAddress);
}
lineQuadBuffer.position(0);
lineQuadBuffer.limit(quadBytes);
final boolean culled = LineQuads.disableCulling();
try {
uploadAndDraw(lineQuadBuffer, quadFormat.getVertexFlags(), quadFormat, quadFormat.getVertexSize(), GL11.GL_TRIANGLES, quadVertices);
} finally {
LineQuads.restoreCulling(culled);
}
if (lineQuadCapacity > 0x20000 && quadBytes < (lineQuadCapacity >> 3)) {
memFree(lineQuadBuffer);
lineQuadBuffer = null;
lineQuadCapacity = 0;
}
}
private static void ensureLineQuadCapacity(int requiredBytes) {
if (lineQuadBuffer != null && requiredBytes <= lineQuadCapacity) return;
int newCapacity = Math.max(0x4000, lineQuadCapacity);
while (newCapacity < requiredBytes) {
newCapacity *= 2;
}
if (lineQuadBuffer != null) memFree(lineQuadBuffer);
lineQuadBuffer = memAlloc(newCapacity);
lineQuadAddress = memAddress0(lineQuadBuffer);
lineQuadCapacity = newCapacity;
}
public static long ringWraps() {
return persistentBuffer == null ? 0L : persistentBuffer.getWraps();
}
@@ -377,7 +455,7 @@ public class TessellatorStreamingDrawer {
private static void uploadAndDrawExtended(ByteBuffer combined, int flags, VertexFormat format, int combinedStride, int drawMode, int vertexCount) {
final GLContextState glCtx = GLStateManager.ctx();
final StreamingVaos vaos = glCtx.streamingVaos;
ensureVAO(vaos, flags, format);
ensureVAO(vaos.persistentVAOs, vaos.orphanVAOs, flags, format);
ensureExtendedVAOs(vaos, flags, format);
int firstVertex = -1;
@@ -480,11 +558,9 @@ public class TessellatorStreamingDrawer {
return repackBuffer;
}
private static void ensureVAO(StreamingVaos vaos, int flags, VertexFormat format) {
private static void ensureVAO(int[] persistentVAOs, int[] orphanVAOs, int flags, VertexFormat format) {
init();
if (orphanBuffers[flags] == null) orphanBuffers[flags] = new OrphanStreamingBuffer();
final int[] orphanVAOs = vaos.orphanVAOs;
final int[] persistentVAOs = vaos.persistentVAOs;
if (orphanVAOs[flags] == 0) {
orphanVAOs[flags] = GLStateManager.glGenVertexArrays();
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
@@ -14,29 +13,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
@GLCoreTest
class ArrayUniformUploadGLTest {
private static int compile(int type, String src) {
final int s = GL20.glCreateShader(type);
GL20.glShaderSource(s, src);
GL20.glCompileShader(s);
assertEquals(GL11.GL_TRUE, GL20.glGetShaderi(s, GL20.GL_COMPILE_STATUS), () -> "shader compile failed: " + GL20.glGetShaderInfoLog(s, 4096));
return s;
}
private static int link(String vs, String fs) {
final int v = compile(GL20.GL_VERTEX_SHADER, vs);
final int f = compile(GL20.GL_FRAGMENT_SHADER, fs);
final int p = GL20.glCreateProgram();
GL20.glAttachShader(p, v);
GL20.glAttachShader(p, f);
GL20.glLinkProgram(p);
assertEquals(GL11.GL_TRUE, GL20.glGetProgrami(p, GL20.GL_LINK_STATUS), () -> "link failed: " + GL20.glGetProgramInfoLog(p, 4096));
GL20.glDeleteShader(v);
GL20.glDeleteShader(f);
return p;
}
private static final String VS = "#version 330 core\nvoid main(){ gl_Position = vec4(0.0); }\n";
@Test
void glUniform2FloatBufferUploadsWholeArray() {
// Runtime index keeps the whole array active so element locations stay queryable.
@@ -45,7 +21,7 @@ class ArrayUniformUploadGLTest {
+ "uniform int idx;\n"
+ "out vec4 o;\n"
+ "void main(){ o = vec4(cosmicuvs[idx], cosmicuvs[(idx+1)%10]); }\n";
final int p = link(VS, fs);
final int p = GlTestSupport.program(GlTestSupport.TRIVIAL_VS, fs);
try {
GL20.glUseProgram(p);
@@ -66,7 +42,7 @@ class ArrayUniformUploadGLTest {
}
} finally {
GL20.glUseProgram(0);
GL20.glDeleteProgram(p);
GLStateManager.glDeleteProgram(p);
}
}
@@ -77,7 +53,7 @@ class ArrayUniformUploadGLTest {
+ "uniform int idx;\n"
+ "out vec4 o;\n"
+ "void main(){ o = vec4(float(vals[idx])); }\n";
final int p = link(VS, fs);
final int p = GlTestSupport.program(GlTestSupport.TRIVIAL_VS, fs);
try {
GL20.glUseProgram(p);
@@ -96,7 +72,7 @@ class ArrayUniformUploadGLTest {
}
} finally {
GL20.glUseProgram(0);
GL20.glDeleteProgram(p);
GLStateManager.glDeleteProgram(p);
}
}
}
@@ -5,30 +5,24 @@ import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
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;
import static org.junit.jupiter.api.Assertions.assertTrue;
class CaptureGateTest {
@Test
void gateOffWhenNothingAttached() {
assertFalse(CaptureGate.gateValue(false, false, false));
}
@Test
void gateOnWhenForced() {
assertTrue(CaptureGate.gateValue(true, false, false));
}
@Test
void gateOnWhenToolAttached() {
assertTrue(CaptureGate.gateValue(false, true, false));
}
@Test
void gateOnWhenTracyConnected() {
assertTrue(CaptureGate.gateValue(false, false, true));
@ParameterizedTest(name = "{0}")
@CsvSource({
"nothing attached, false, false, false, false",
"forced, true, false, false, true",
"tool attached, false, true, false, true",
"tracy connected, false, false, true, true"
})
void gateIsOnWhenAnySourceIs(String name, boolean forced, boolean toolAttached, boolean tracyConnected, boolean expected) {
assertEquals(expected, CaptureGate.gateValue(forced, toolAttached, tracyConnected));
}
@Test
@@ -5,9 +5,12 @@ import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import org.junit.jupiter.api.Test;
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;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -17,66 +20,104 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class CompatShaderTransformerTest {
@Test
void testModelViewMatrixReplacement() {
String src = """
#version 120
void main() {
vec4 pos = gl_ModelViewMatrix * gl_Vertex;
gl_Position = gl_ProjectionMatrix * pos;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("angelica_ModelViewMatrix"));
assertTrue(result.contains("angelica_ProjectionMatrix"));
assertFalse(result.contains("gl_ModelViewMatrix"));
assertFalse(result.contains("gl_ProjectionMatrix"));
assertTrue(result.contains("#version 330 core"));
private static String[] strings(String... values) {
return values;
}
@Test
void testNormalMatrixReplacement() {
String src = """
#version 120
void main() {
vec3 n = gl_NormalMatrix * gl_Normal;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("uniform mat3 angelica_NormalMatrix"));
assertFalse(result.contains("gl_NormalMatrix"));
private static void assertRewrite(boolean fragment, String src, String[] present, String[] absent) {
final String result = CompatShaderTransformer.transform(src, fragment);
for (final String s : present) {
assertTrue(result.contains(s), () -> "expected '" + s + "' in\n\n" + result);
}
for (final String s : absent) {
assertFalse(result.contains(s), () -> "'" + s + "' must not remain in\n\n" + result);
}
}
@Test
void testModelViewProjectionMatrixExpression() {
String src = """
#version 120
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_ModelViewProjectionMatrix"));
assertTrue(result.contains("angelica_ProjectionMatrix"));
static Stream<Arguments> matrixBuiltins() {
return Stream.of(
Arguments.of("modelview and projection matrices", false, """
#version 120
void main() {
vec4 pos = gl_ModelViewMatrix * gl_Vertex;
gl_Position = gl_ProjectionMatrix * pos;
}
""", strings("angelica_ModelViewMatrix", "angelica_ProjectionMatrix", "#version 330 core"), strings("gl_ModelViewMatrix", "gl_ProjectionMatrix")),
Arguments.of("normal matrix", false, """
#version 120
void main() {
vec3 n = gl_NormalMatrix * gl_Normal;
}
""", strings("uniform mat3 angelica_NormalMatrix"), strings("gl_NormalMatrix")),
Arguments.of("modelview-projection matrix", false, """
#version 120
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("angelica_ProjectionMatrix"), strings("gl_ModelViewProjectionMatrix")),
Arguments.of("inverse matrices", false, """
#version 120
void main() {
vec4 pos = gl_ModelViewMatrixInverse * vec4(0.0, 0.0, 0.0, 1.0);
vec4 proj = gl_ProjectionMatrixInverse * vec4(0.0, 0.0, -1.0, 1.0);
}
""", strings(), strings("gl_ModelViewMatrixInverse", "gl_ProjectionMatrixInverse")),
Arguments.of("texture matrix", false, """
#version 120
void main() {
vec4 lmCoord = gl_TextureMatrix[1] * gl_MultiTexCoord1;
}
""", strings(), strings("gl_TextureMatrix")),
Arguments.of("several builtins in one fragment shader", true, """
#version 120
uniform sampler2D tex;
void main() {
vec3 n = gl_NormalMatrix * gl_Normal;
vec4 pos = gl_ModelViewMatrix * gl_Vertex;
gl_Position = gl_ProjectionMatrix * pos;
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")));
}
@Test
void testTextureFunctionRenames() {
String src = """
#version 120
uniform sampler2D tex;
void main() {
vec4 color = texture2D(tex, vec2(0.0));
}
""";
@ParameterizedTest(name = "{0}")
@MethodSource("matrixBuiltins")
void matrixBuiltinsAreRewritten(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
String result = CompatShaderTransformer.transform(src, false);
// texture2D -> texture
assertFalse(result.contains("texture2D"));
assertTrue(result.contains("texture"));
static Stream<Arguments> textureFunctions() {
return Stream.of(
Arguments.of("texture2D", false, """
#version 120
uniform sampler2D tex;
void main() {
vec4 color = texture2D(tex, vec2(0.0));
}
""", strings("texture"), strings("texture2D")),
Arguments.of("shadow2D", true, """
#version 120
uniform sampler2DShadow shadowMap;
void main() {
float s = shadow2D(shadowMap, vec3(0.0)).r;
gl_FragColor = vec4(s);
}
""", strings("texture"), strings("shadow2D(")),
Arguments.of("texelFetch3D", true, """
#version 120
uniform sampler3D vol;
void main() {
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
@@ -95,32 +136,26 @@ class CompatShaderTransformerTest {
assertTrue(result.contains("layout"));
}
@Test
void testVersionUpgradeTo330() {
String src = """
#version 110
void main() {
gl_FragColor = vec4(1.0);
}
""";
String result = CompatShaderTransformer.transform(src, true);
assertTrue(result.contains("#version 330 core"));
assertFalse(result.contains("#version 110"));
static Stream<Arguments> versionDirectives() {
return Stream.of(
Arguments.of("110 becomes 330 core", true, """
#version 110
void main() {
gl_FragColor = vec4(1.0);
}
""", strings("#version 330 core"), strings("#version 110")),
Arguments.of("compatibility profile is stripped", false, """
#version 330 compatibility
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("#version 330 core"), strings("compatibility")));
}
@Test
void testCompatibilityProfileStripped() {
String src = """
#version 330 compatibility
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("#version 330 core"));
assertFalse(result.contains("compatibility"));
@ParameterizedTest(name = "{0}")
@MethodSource("versionDirectives")
void versionIsUpgradedToCore(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
@Test
@@ -139,60 +174,6 @@ class CompatShaderTransformerTest {
assertEquals(src, result);
}
@Test
void testCombinedBuiltinsInSingleShader() {
String src = """
#version 120
uniform sampler2D tex;
void main() {
vec3 n = gl_NormalMatrix * gl_Normal;
vec4 pos = gl_ModelViewMatrix * gl_Vertex;
gl_Position = gl_ProjectionMatrix * pos;
vec4 color = texture2D(tex, vec2(0.0));
gl_FragColor = color;
}
""";
String result = CompatShaderTransformer.transform(src, true);
assertTrue(result.contains("angelica_ModelViewMatrix"));
assertTrue(result.contains("angelica_ProjectionMatrix"));
assertTrue(result.contains("angelica_NormalMatrix"));
assertFalse(result.contains("gl_ModelViewMatrix"));
assertFalse(result.contains("gl_ProjectionMatrix"));
assertFalse(result.contains("gl_NormalMatrix"));
assertFalse(result.contains("texture2D"));
assertFalse(result.contains("gl_FragColor"));
assertTrue(result.contains("#version 330 core"));
}
@Test
void testInverseMatrixReplacement() {
String src = """
#version 120
void main() {
vec4 pos = gl_ModelViewMatrixInverse * vec4(0.0, 0.0, 0.0, 1.0);
vec4 proj = gl_ProjectionMatrixInverse * vec4(0.0, 0.0, -1.0, 1.0);
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_ModelViewMatrixInverse"));
assertFalse(result.contains("gl_ProjectionMatrixInverse"));
}
@Test
void testTextureMatrixReplacement() {
String src = """
#version 120
void main() {
vec4 lmCoord = gl_TextureMatrix[1] * gl_MultiTexCoord1;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_TextureMatrix"));
}
@Test
void testFogStructAccess() {
String src = """
@@ -211,34 +192,27 @@ class CompatShaderTransformerTest {
assertTrue(result.contains("uniform vec4 angelica_FogColor"), "Fog color uniform should be injected");
}
@Test
void testFogFragCoordVertex() {
String src = """
#version 120
void main() {
gl_FogFragCoord = length(gl_ModelViewMatrix * gl_Vertex);
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("out float angelica_FogFragCoord"), "Vertex shader should get 'out' declaration");
assertTrue(result.contains("angelica_FogFragCoord"), "gl_FogFragCoord should be renamed");
assertFalse(result.contains("gl_FogFragCoord"), "gl_FogFragCoord should not remain");
static Stream<Arguments> fogFragCoord() {
return Stream.of(
Arguments.of("vertex shader declares an out", false, """
#version 120
void main() {
gl_FogFragCoord = length(gl_ModelViewMatrix * gl_Vertex);
}
""", strings("out float angelica_FogFragCoord"), strings("gl_FogFragCoord")),
Arguments.of("fragment shader declares an in", true, """
#version 120
void main() {
float f = gl_FogFragCoord;
gl_FragColor = vec4(f);
}
""", strings("in float angelica_FogFragCoord"), strings("gl_FogFragCoord")));
}
@Test
void testFogFragCoordFragment() {
String src = """
#version 120
void main() {
float f = gl_FogFragCoord;
gl_FragColor = vec4(f);
}
""";
String result = CompatShaderTransformer.transform(src, true);
assertTrue(result.contains("in float angelica_FogFragCoord"), "Fragment shader should get 'in' declaration");
assertFalse(result.contains("gl_FogFragCoord"), "gl_FogFragCoord should not remain");
@ParameterizedTest(name = "{0}")
@MethodSource("fogFragCoord")
void fogFragCoordBecomesAVarying(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
@Test
@@ -255,23 +229,6 @@ class CompatShaderTransformerTest {
assertFalse(result.contains("gl_FrontColor"), "gl_FrontColor should not remain");
}
@Test
void testShadow2DRename() {
String src = """
#version 120
uniform sampler2DShadow shadowMap;
void main() {
float s = shadow2D(shadowMap, vec3(0.0)).r;
gl_FragColor = vec4(s);
}
""";
String result = CompatShaderTransformer.transform(src, true);
assertFalse(result.contains("shadow2D("), "shadow2D should be renamed");
// shadow2D -> texture with vec4 wrapping
assertTrue(result.contains("texture"), "shadow2D should be renamed to texture");
}
@Test
void testReservedWordTextureAsVariable() {
// Shader declares 'texture' as a sampler variable - this is valid in GLSL 120
@@ -321,155 +278,81 @@ class CompatShaderTransformerTest {
"Alpha test discard should be appended to main()");
}
@Test
void testTexelFetch3DTriggersTransformation() {
String src = """
#version 120
uniform sampler3D vol;
void main() {
vec4 v = texelFetch3D(vol, ivec3(0), 0);
gl_FragColor = v;
}
""";
String result = CompatShaderTransformer.transform(src, true);
// texelFetch3D should be renamed to texelFetch
assertFalse(result.contains("texelFetch3D"), "texelFetch3D should be renamed");
assertTrue(result.contains("texelFetch"), "texelFetch3D should become texelFetch");
static Stream<Arguments> vertexAttributes() {
return Stream.of(
Arguments.of("gl_Vertex at location 0", false, """
#version 120
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("in vec4 angelica_Vertex", "location = 0"), strings("gl_Vertex")),
Arguments.of("gl_Color at location 1", false, """
#version 120
varying vec4 v_Color;
void main() {
v_Color = gl_Color;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("in vec4 angelica_Color", "location = 1"), strings("gl_Color")),
Arguments.of("gl_MultiTexCoord0 at location 2", false, """
#version 120
void main() {
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("in vec4 angelica_MultiTexCoord0", "location = 2"), strings("gl_MultiTexCoord0")),
Arguments.of("gl_MultiTexCoord1 at location 3", false, """
#version 120
void main() {
vec4 lm = gl_TextureMatrix[1] * gl_MultiTexCoord1;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("angelica_MultiTexCoord1", "location = 3"), strings("gl_MultiTexCoord1")),
Arguments.of("gl_Normal at location 4", false, """
#version 120
void main() {
vec3 n = gl_NormalMatrix * gl_Normal;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("in vec3 angelica_Normal", "location = 4"), strings("gl_Normal")));
}
@Test
void testVertexAttributeGlVertex() {
String src = """
#version 120
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_Vertex"), "gl_Vertex should be replaced");
assertTrue(result.contains("angelica_Vertex"), "should become angelica_Vertex");
assertTrue(result.contains("in vec4 angelica_Vertex"), "should have in declaration");
assertTrue(result.contains("location = 0"), "should use attribute location 0");
@ParameterizedTest(name = "{0}")
@MethodSource("vertexAttributes")
void vertexAttributeBuiltinsGetFixedLocations(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
@Test
void testVertexAttributeGlColorInVertexShader() {
// gl_Color in a vertex shader is the per-vertex color attribute, not the fragment varying
String src = """
#version 120
varying vec4 v_Color;
void main() {
v_Color = gl_Color;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("angelica_Color"), "gl_Color -> angelica_Color (vertex attribute)");
assertTrue(result.contains("in vec4 angelica_Color"), "should have in declaration");
assertTrue(result.contains("location = 1"), "should use attribute location 1");
assertFalse(result.contains("gl_Color"), "gl_Color should not remain");
static Stream<Arguments> texCoordVaryings() {
return Stream.of(
Arguments.of("vertex shader declares an out", false, """
#version 120
void main() {
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("out vec4 angelica_TexCoord0"), strings("gl_TexCoord")),
Arguments.of("fragment shader declares an in", true, """
#version 120
void main() {
vec2 uv = gl_TexCoord[0].xy;
gl_FragColor = vec4(uv, 0.0, 1.0);
}
""", strings("in vec4 angelica_TexCoord0"), strings("gl_TexCoord")),
Arguments.of("one varying per index", false, """
#version 120
void main() {
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[1] = gl_MultiTexCoord1;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""", strings("out vec4 angelica_TexCoord0", "out vec4 angelica_TexCoord1"), strings("gl_TexCoord")));
}
@Test
void testVertexAttributeMultiTexCoord0() {
String src = """
#version 120
void main() {
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_MultiTexCoord0"), "gl_MultiTexCoord0 should be replaced");
assertTrue(result.contains("angelica_MultiTexCoord0"), "should become angelica_MultiTexCoord0");
assertTrue(result.contains("in vec4 angelica_MultiTexCoord0"), "should have in declaration");
assertTrue(result.contains("location = 2"), "should use attribute location 2");
}
@Test
void testVertexAttributeMultiTexCoord1() {
String src = """
#version 120
void main() {
vec4 lm = gl_TextureMatrix[1] * gl_MultiTexCoord1;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_MultiTexCoord1"), "gl_MultiTexCoord1 should be replaced");
assertTrue(result.contains("angelica_MultiTexCoord1"), "should become angelica_MultiTexCoord1");
assertTrue(result.contains("location = 3"), "should use attribute location 3");
}
@Test
void testVertexAttributeGlNormal() {
String src = """
#version 120
void main() {
vec3 n = gl_NormalMatrix * gl_Normal;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_Normal"), "gl_Normal should be replaced");
assertTrue(result.contains("angelica_Normal"), "should become angelica_Normal");
assertTrue(result.contains("in vec3 angelica_Normal"), "should have in vec3 declaration");
assertTrue(result.contains("location = 4"), "should use attribute location 4");
}
@Test
void testTexCoordVaryingVertexShader() {
String src = """
#version 120
void main() {
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertFalse(result.contains("gl_TexCoord"), "gl_TexCoord should be replaced");
assertTrue(result.contains("angelica_TexCoord0"), "should become angelica_TexCoord0");
assertTrue(result.contains("out vec4 angelica_TexCoord0"), "vertex shader should have 'out' declaration");
}
@Test
void testTexCoordVaryingFragmentShader() {
String src = """
#version 120
void main() {
vec2 uv = gl_TexCoord[0].xy;
gl_FragColor = vec4(uv, 0.0, 1.0);
}
""";
String result = CompatShaderTransformer.transform(src, true);
assertFalse(result.contains("gl_TexCoord"), "gl_TexCoord should be replaced");
assertTrue(result.contains("angelica_TexCoord0"), "should become angelica_TexCoord0");
assertTrue(result.contains("in vec4 angelica_TexCoord0"), "fragment shader should have 'in' declaration");
}
@Test
void testTexCoordMultipleIndices() {
String src = """
#version 120
void main() {
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[1] = gl_MultiTexCoord1;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertTrue(result.contains("out vec4 angelica_TexCoord0"), "should have TexCoord0 out");
assertTrue(result.contains("out vec4 angelica_TexCoord1"), "should have TexCoord1 out");
assertFalse(result.contains("gl_TexCoord"), "no gl_TexCoord should remain");
@ParameterizedTest(name = "{0}")
@MethodSource("texCoordVaryings")
void texCoordBecomesPerIndexVaryings(String name, boolean fragment, String src, String[] present, String[] absent) {
assertRewrite(fragment, src, present, absent);
}
@Test
@@ -533,7 +416,6 @@ class CompatShaderTransformerTest {
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");
assertTrue(result.contains("angelica_SceneColor"), "should reference angelica_SceneColor");
}
@Test
@@ -9,6 +9,7 @@ import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.assertMatrixEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -64,12 +65,6 @@ class DisplayListCompilationParityTest {
return buf;
}
private static void assertMatrixEquals(FloatBuffer expected, FloatBuffer actual, String message) {
for (int i = 0; i < 16; i++) {
assertEquals(expected.get(i), actual.get(i), 0.001f, message + " (element " + i + ")");
}
}
@BeforeEach
void setup() {
if (DisplayListManager.isRecording()) {
@@ -3,11 +3,9 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.matrix;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -48,12 +46,6 @@ class DisplayListCompilationTest {
GLStateManager.glLoadIdentity();
}
private static FloatBuffer matrix(int pname) {
final FloatBuffer buf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(pname, buf);
return buf;
}
@Test
void glsm_compileAndExecute_matrixModeWithTransforms() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
@@ -102,69 +94,4 @@ class DisplayListCompilationTest {
assertFalse(DisplayListManager.isRecording(), "frame-begin backstop must clear a leaked open list");
}
@Test
void rawGL_compile_immediateModeWithTransforms() {
GL11.glLoadIdentity();
GL11.glTranslatef(100.0f, 0.0f, 0.0f);
testList = GL11.glGenLists(1);
GL11.glNewList(testList, GL11.GL_COMPILE);
GL11.glPushMatrix();
GL11.glTranslatef(50.0f, 25.0f, 0.0f);
GL11.glScalef(0.5f, 0.5f, 1.0f);
GL11.glBegin(GL11.GL_QUADS);
GL11.glVertex3f(0.0f, 0.0f, 0.0f);
GL11.glVertex3f(16.0f, 0.0f, 0.0f);
GL11.glVertex3f(16.0f, 16.0f, 0.0f);
GL11.glVertex3f(0.0f, 16.0f, 0.0f);
GL11.glEnd();
GL11.glPopMatrix();
GL11.glEndList();
final FloatBuffer afterCompile = BufferUtils.createFloatBuffer(16);
GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, afterCompile);
assertEquals(100.0f, afterCompile.get(12), 0.001f, "After GL_COMPILE: matrix unchanged");
GL11.glCallList(testList);
final FloatBuffer afterPlayback = BufferUtils.createFloatBuffer(16);
GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, afterPlayback);
assertEquals(100.0f, afterPlayback.get(12), 0.001f, "After playback: base position preserved");
assertEquals(1.0f, afterPlayback.get(0), 0.001f, "After playback: no scale (popped)");
GL11.glLoadIdentity();
}
@Test
void rawGL_compileAndExecute_immediateModeExecutesImmediately() {
GL11.glLoadIdentity();
testList = GL11.glGenLists(1);
GL11.glNewList(testList, GL11.GL_COMPILE_AND_EXECUTE);
GL11.glScalef(0.5f, 0.5f, 0.5f);
final FloatBuffer during = BufferUtils.createFloatBuffer(16);
GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, during);
assertEquals(0.5f, during.get(0), 0.001f, "During COMPILE_AND_EXECUTE: scale applied");
GL11.glBegin(GL11.GL_QUADS);
GL11.glVertex3f(0.0f, 0.0f, 0.0f);
GL11.glVertex3f(1.0f, 0.0f, 0.0f);
GL11.glVertex3f(1.0f, 1.0f, 0.0f);
GL11.glVertex3f(0.0f, 1.0f, 0.0f);
GL11.glEnd();
GL11.glEndList();
final FloatBuffer after = BufferUtils.createFloatBuffer(16);
GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, after);
assertEquals(0.5f, after.get(0), 0.001f, "After COMPILE_AND_EXECUTE: scale remains");
GL11.glLoadIdentity();
}
}
@@ -1,16 +1,15 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import org.junit.jupiter.api.Test;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
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.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -39,25 +38,14 @@ class DisplayListIdAllocatorTest {
}
@Test
void zeroNeverReturned() {
DisplayListIDAllocator alloc = new DisplayListIDAllocator(1);
// Allocate enough IDs to fill the first word (32 IDs). None should be 0.
for (int i = 0; i < 31; i++) {
int id = alloc.allocRange(1);
assertNotEquals(0, id, "ID 0 must never be returned");
assertTrue(id > 0, "All returned IDs must be positive");
void singleAllocsAreSequentialFromOne() {
// Sequential from 1 across several words: 0 is never handed out and no id repeats
final DisplayListIDAllocator alloc = new DisplayListIDAllocator(1);
for (int expected = 1; expected <= 500; expected++) {
assertEquals(expected, alloc.allocRange(1));
}
}
@Test
void singleAllocReturnsSequential() {
DisplayListIDAllocator alloc = new DisplayListIDAllocator();
// First alloc should be 1 (0 is reserved)
assertEquals(1, alloc.allocRange(1));
assertEquals(2, alloc.allocRange(1));
assertEquals(3, alloc.allocRange(1));
}
@Test
void isAllocatedTracksState() {
DisplayListIDAllocator alloc = new DisplayListIDAllocator();
@@ -159,9 +147,9 @@ class DisplayListIdAllocatorTest {
@Test
void freeZeroIsNoOp() {
DisplayListIDAllocator alloc = new DisplayListIDAllocator();
// Should not throw
alloc.free(0);
alloc.free(-1);
assertEquals(1, alloc.allocRange(1), "freeing the reserved id 0 must not make it allocatable");
}
@Test
@@ -205,17 +193,6 @@ class DisplayListIdAllocatorTest {
}
}
@Test
void noDoubleAllocation() {
DisplayListIDAllocator alloc = new DisplayListIDAllocator();
// Allocate many IDs and verify uniqueness
Set<Integer> seen = new HashSet<>();
for (int i = 0; i < 500; i++) {
int id = alloc.allocRange(1);
assertTrue(seen.add(id), "Duplicate ID returned: " + id);
}
}
@Test
void rangeAfterSingleAllocs() {
DisplayListIDAllocator alloc = new DisplayListIDAllocator();
@@ -54,9 +54,7 @@ class DisplayListMatrixRestorationTest extends DisplayListTestFixture {
}
private static float translation(int target) {
FloatBuffer b = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(target, b);
return b.get(12);
return GlTestSupport.matrix(target).get(12);
}
@Test
@@ -1,10 +1,17 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import net.minecraft.client.renderer.Tessellator;
import org.joml.Matrix4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
@@ -62,4 +69,58 @@ public final class DisplayListTestHelper {
assertNotNull(compiled, "Display list should be compiled");
return compiled;
}
/** Calls {@code list} on an identity modelview and returns the resulting matrix. The caller's matrix is restored. */
public static FloatBuffer modelviewAfterCall(int list) {
GLStateManager.glPushMatrix();
try {
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(list);
return GlTestSupport.modelview();
} finally {
GLStateManager.glPopMatrix();
}
}
public static void assertMatrixEquals(Matrix4f expected, FloatBuffer actual, String message) {
final FloatBuffer expectedBuf = BufferUtils.createFloatBuffer(16);
expected.get(expectedBuf);
for (int i = 0; i < 16; i++) {
assertEquals(expectedBuf.get(i), actual.get(i), 0.0001f, message + " (element " + i + ")");
}
}
public static void assertModelviewAfterCall(int list, Matrix4f expected, String message) {
assertMatrixEquals(expected, modelviewAfterCall(list), message);
}
/** Immediate-mode quad from the origin to (size, size) at z = 0. */
public static void immediateQuad(float size) {
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glVertex3f(0.0f, 0.0f, 0.0f);
GLStateManager.glVertex3f(size, 0.0f, 0.0f);
GLStateManager.glVertex3f(size, size, 0.0f);
GLStateManager.glVertex3f(0.0f, size, 0.0f);
GLStateManager.glEnd();
}
/** Textured quad written straight into the vanilla Tessellator's raw buffer and handed to the draw interceptor. */
public static void rawQuad(float size) {
final Tessellator tess = Tessellator.instance;
final float[][] corners = { { 0.0f, 0.0f }, { size, 0.0f }, { size, size }, { 0.0f, size } };
tess.rawBuffer = new int[4 * 8];
for (int i = 0; i < 4; i++) {
final int base = i * 8;
tess.rawBuffer[base] = Float.floatToRawIntBits(corners[i][0]);
tess.rawBuffer[base + 1] = Float.floatToRawIntBits(corners[i][1]);
tess.rawBuffer[base + 3] = Float.floatToRawIntBits(i == 1 || i == 2 ? 1.0f : 0.0f);
tess.rawBuffer[base + 4] = Float.floatToRawIntBits(i >= 2 ? 1.0f : 0.0f);
}
tess.isDrawing = true;
tess.drawMode = GL11.GL_QUADS;
tess.hasTexture = true;
tess.rawBufferIndex = 4 * 8;
tess.vertexCount = 4;
TessellatorManager.interceptDraw(tess);
}
}
@@ -3,12 +3,16 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import java.nio.FloatBuffer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -97,68 +101,23 @@ class FeedbackManagerTest {
assertEquals(42.0f, buf.get(1), "Second token should be the pass-through value");
}
@Test
void testTriangleFeedback_GL2D() {
final FloatBuffer buf = BufferUtils.createFloatBuffer(128);
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
// Identity MV/proj, viewport (0,0,800,600)
GLStateManager.getModelViewMatrix().identity();
GLStateManager.getProjectionMatrix().identity();
GLStateManager.getViewportState().setViewPort(0, 0, 800, 600);
GLStateManager.getViewportState().setDepthRange(0.0, 1.0);
// Upload 3 vertices forming a triangle
final int vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
// NDC vertices: (0,0,0), (1,0,0), (0,1,0)
// With identity MVP and viewport 800x600:
// NDC(0,0) → window(400, 300)
// NDC(1,0) → window(800, 300)
// NDC(0,1) → window(400, 600)
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 count = FeedbackManager.glRenderMode(GL11.GL_RENDER);
// GL_2D: POLYGON_TOKEN, 3, x0, y0, x1, y1, x2, y2 = 8 floats
assertEquals(8, count, "Triangle in GL_2D should produce 8 floats");
assertEquals(GL11.GL_POLYGON_TOKEN, buf.get(0), 0.001f);
assertEquals(3.0f, buf.get(1), 0.001f);
// Vertex 0: NDC(0,0) → window(400, 300)
assertEquals(400.0f, buf.get(2), 0.01f);
assertEquals(300.0f, buf.get(3), 0.01f);
// Vertex 1: NDC(1,0) → window(800, 300)
assertEquals(800.0f, buf.get(4), 0.01f);
assertEquals(300.0f, buf.get(5), 0.01f);
// Vertex 2: NDC(0,1) → window(400, 600)
assertEquals(400.0f, buf.get(6), 0.01f);
assertEquals(600.0f, buf.get(7), 0.01f);
// Cleanup
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteVertexArrays(vao);
// Identity MVP on an 800x600 viewport: NDC (0,0) -> window (400,300), (1,0) -> (800,300), (0,1) -> (400,600)
static Stream<Arguments> primitives() {
return Stream.of(
Arguments.of("triangle", GL11.GL_TRIANGLES,
new float[] { 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f, 0f },
new float[] { GL11.GL_POLYGON_TOKEN, 3f, 400f, 300f, 800f, 300f, 400f, 600f }),
Arguments.of("polygon", GL11.GL_POLYGON,
new float[] { 0f, 0f, 0f, 1f, 0f, 0f, 1f, 1f, 0f, 0f, 1f, 0f },
new float[] { GL11.GL_POLYGON_TOKEN, 4f, 400f, 300f, 800f, 300f, 800f, 600f, 400f, 600f }),
Arguments.of("quad strip", GL11.GL_QUAD_STRIP,
new float[] { 0f, 0f, 0f, 0f, 1f, 0f, 1f, 0f, 0f, 1f, 1f, 0f },
new float[] { GL11.GL_POLYGON_TOKEN, 4f, 400f, 300f, 400f, 600f, 800f, 600f, 800f, 300f }));
}
@Test
void testPolygonFeedback_GL2D() {
@ParameterizedTest(name = "{0}")
@MethodSource("primitives")
void testPrimitiveFeedback_GL2D(String name, int mode, float[] verts, float[] expected) {
final FloatBuffer buf = BufferUtils.createFloatBuffer(128);
FeedbackManager.glFeedbackBuffer(GL11.GL_2D, buf);
FeedbackManager.glRenderMode(GL11.GL_FEEDBACK);
@@ -171,12 +130,6 @@ class FeedbackManagerTest {
final int vao = GL30.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
final float[] verts = {
0f, 0f, 0f,
1f, 0f, 0f,
1f, 1f, 0f,
0f, 1f, 0f
};
final FloatBuffer vertBuf = BufferUtils.createFloatBuffer(verts.length);
vertBuf.put(verts).flip();
@@ -186,72 +139,15 @@ class FeedbackManagerTest {
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glEnableVertexAttribArray(0);
FeedbackManager.processDrawArrays(GL11.GL_POLYGON, 0, 4);
FeedbackManager.processDrawArrays(mode, 0, verts.length / 3);
final int count = FeedbackManager.glRenderMode(GL11.GL_RENDER);
assertEquals(10, count, "Polygon in GL_2D should produce 10 floats");
assertEquals(GL11.GL_POLYGON_TOKEN, buf.get(0), 0.001f);
assertEquals(4.0f, buf.get(1), 0.001f);
assertEquals(400.0f, buf.get(2), 0.01f);
assertEquals(300.0f, buf.get(3), 0.01f);
assertEquals(800.0f, buf.get(4), 0.01f);
assertEquals(300.0f, buf.get(5), 0.01f);
assertEquals(800.0f, buf.get(6), 0.01f);
assertEquals(600.0f, buf.get(7), 0.01f);
assertEquals(400.0f, buf.get(8), 0.01f);
assertEquals(600.0f, buf.get(9), 0.01f);
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteVertexArrays(vao);
}
@Test
void testQuadStripFeedback_GL2D() {
final FloatBuffer buf = BufferUtils.createFloatBuffer(128);
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 float[] verts = {
0f, 0f, 0f,
0f, 1f, 0f,
1f, 0f, 0f,
1f, 1f, 0f
};
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(GL11.GL_QUAD_STRIP, 0, 4);
final int count = FeedbackManager.glRenderMode(GL11.GL_RENDER);
assertEquals(10, count, "Quad strip in GL_2D should produce 10 floats");
assertEquals(GL11.GL_POLYGON_TOKEN, buf.get(0), 0.001f);
assertEquals(4.0f, buf.get(1), 0.001f);
assertEquals(400.0f, buf.get(2), 0.01f);
assertEquals(300.0f, buf.get(3), 0.01f);
assertEquals(400.0f, buf.get(4), 0.01f);
assertEquals(600.0f, buf.get(5), 0.01f);
assertEquals(800.0f, buf.get(6), 0.01f);
assertEquals(600.0f, buf.get(7), 0.01f);
assertEquals(800.0f, buf.get(8), 0.01f);
assertEquals(300.0f, buf.get(9), 0.01f);
// 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);
@@ -22,7 +22,6 @@ class GLSM_BlendFuncUnknownState_GLTest {
@BeforeEach
void requireBufferBlending() {
assumeTrue(RenderSystem.supportsBufferBlending());
drainGlErrors();
}
@AfterEach
@@ -30,11 +29,6 @@ class GLSM_BlendFuncUnknownState_GLTest {
if (!RenderSystem.supportsBufferBlending()) return;
GLStateManager.tryBlendFuncSeparate(GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ZERO, GL11.GL_ZERO);
GLStateManager.tryBlendFuncSeparate(GL11.GL_ONE, GL11.GL_ZERO, GL11.GL_ONE, GL11.GL_ZERO);
drainGlErrors();
}
private static void drainGlErrors() {
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
private static void seedVanillaSeparate() {
@@ -59,7 +53,6 @@ class GLSM_BlendFuncUnknownState_GLTest {
assertBufferBlend(buffer, VANILLA_SRC_RGB, VANILLA_DST_RGB, VANILLA_SRC_ALPHA, VANILLA_DST_ALPHA);
}
assertFalse(GLStateManager.getBlendState().isFuncUnknown(), "forward must clear the unknown flag");
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
@@ -3,11 +3,13 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import static com.gtnewhorizons.angelica.util.GLSMUtil.resetGLState;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
@@ -51,46 +53,28 @@ class GLSM_BlendUnknownState_UnitTest {
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND), "GL blend should be re-enabled after setUnknownState");
}
@Test
void testDisableBufferBlendInvalidatesCache() {
GLStateManager.enableBlend();
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND));
@ParameterizedTest(name = "global blend enabled={0}")
@ValueSource(booleans = {true, false})
void perBufferBlendToggleInvalidatesCache(boolean enabled) {
setBlend(enabled);
assertEquals(enabled, GL11.glIsEnabled(GL11.GL_BLEND));
// Use RenderSystem which should call setUnknownState internally
RenderSystem.disableBufferBlend(0);
// Flips buffer 0 behind the cache and marks it unknown, so the repeated global call must reach the driver
if (enabled) {
RenderSystem.disableBufferBlend(0);
} else {
RenderSystem.enableBufferBlend(0);
}
// Now re-enable via GLSM — should issue the GL call
GLStateManager.enableBlend();
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND), "Blend should be re-enabled after disableBufferBlend");
setBlend(enabled);
assertEquals(enabled, GL11.glIsEnabled(GL11.GL_BLEND), "global blend state must be re-issued after a per-buffer toggle");
}
@Test
void testEnableBufferBlendInvalidatesCache() {
// Start with blend disabled
GLStateManager.disableBlend();
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND));
// Per-buffer enable bypasses cache
RenderSystem.enableBufferBlend(0);
// Disable via GLSM — should issue the GL call despite cache saying disabled
GLStateManager.disableBlend();
assertFalse(GL11.glIsEnabled(GL11.GL_BLEND), "Blend should be disabled after enableBufferBlend + disableBlend");
}
@Test
void testUnknownStateClearedAfterSetEnabled() {
GLStateManager.enableBlend();
// Mark unknown
GLStateManager.getBlendMode().setUnknownState();
// First setEnabled clears the unknown flag
GLStateManager.enableBlend();
// Second setEnabled should use normal caching (no redundant GL call)
// Verify state is consistent
assertTrue(GL11.glIsEnabled(GL11.GL_BLEND));
assertTrue(GLStateManager.getBlendMode().isEnabled());
private static void setBlend(boolean enabled) {
if (enabled) {
GLStateManager.enableBlend();
} else {
GLStateManager.disableBlend();
}
}
}
@@ -0,0 +1,672 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL33;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizons.angelica.glsm.LegacyQueryFixture.CLIENT_ARRAY_CAPS;
import static com.gtnewhorizons.angelica.glsm.LegacyQueryFixture.assertEnableRoundTrip;
import static com.gtnewhorizons.angelica.glsm.LegacyQueryFixture.drainClientAttribStack;
import static com.gtnewhorizons.angelica.glsm.LegacyQueryFixture.hex;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeFalse;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
/** Conventional client arrays and the client attrib stack, emulated on a core-profile vertex array object. */
@GLCoreTest
public class GLSM_ClientArrays_GLTest {
private int savedVao;
private int vao;
private int vbo;
@BeforeEach
void bindFreshVertexArray() {
savedVao = GLStateManager.getBoundVAO();
vao = GLStateManager.glGenVertexArrays();
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindVertexArray(vao);
}
@AfterEach
void deleteVertexArray() {
GLStateManager.glBindVertexArray(savedVao);
GLStateManager.glDeleteVertexArrays(vao);
GLStateManager.glDeleteBuffers(vbo);
}
private static boolean attribHasSize3(Usage usage) {
final VAOManager.Attrib attrib = GLStateManager.ctx().vaos.attrib(usage.getAttributeLocation());
return attrib != null && attrib.size == 3;
}
private static boolean positionArrayEnabled() {
final VAOManager.Attrib position = GLStateManager.ctx().vaos.attrib(Usage.POSITION.getAttributeLocation());
return position != null && position.enabled;
}
private static boolean colorArrayEnabled() {
return (GLStateManager.ctx().vaos.getVertexFlags() & VertexFlags.COLOR_BIT) != 0;
}
private static int driverAttrib(int index, int pname) {
final IntBuffer out = BufferUtils.createIntBuffer(16);
GL20.glGetVertexAttrib(index, pname, out);
return out.get(0);
}
private static void assertClientArray(String name, int sizePname, int size, int typePname, int type, int stridePname, int stride, int bindingPname, int binding) {
if (sizePname != 0) assertEquals(size, GLStateManager.glGetInteger(sizePname), name + " size");
if (typePname != 0) assertEquals(type, GLStateManager.glGetInteger(typePname), name + " type");
assertEquals(stride, GLStateManager.glGetInteger(stridePname), name + " stride");
assertEquals(binding, GLStateManager.glGetInteger(bindingPname), name + " buffer binding");
}
private static void assertVertexArray(String name, int size, int type, int stride, int binding) {
assertClientArray(name, GL11.GL_VERTEX_ARRAY_SIZE, size, GL11.GL_VERTEX_ARRAY_TYPE, type, GL11.GL_VERTEX_ARRAY_STRIDE, stride, GL15.GL_VERTEX_ARRAY_BUFFER_BINDING, binding);
}
private static void assertTexCoordArray(String name, int size, int type, int stride, int binding) {
assertClientArray(name, GL11.GL_TEXTURE_COORD_ARRAY_SIZE, size, GL11.GL_TEXTURE_COORD_ARRAY_TYPE, type, GL11.GL_TEXTURE_COORD_ARRAY_STRIDE, stride,
GL15.GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING, binding);
}
@Test
void vertexArrayStateRestoredOnlyWithVertexArrayBit() {
final int position = Usage.POSITION.getAttributeLocation();
try {
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
final int flagsBefore = GLStateManager.ctx().vaos.getVertexFlags();
assertFalse(positionArrayEnabled());
assertFalse(colorArrayEnabled());
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
assertTrue(positionArrayEnabled());
assertTrue(colorArrayEnabled());
GLStateManager.glPopClientAttrib();
assertFalse(positionArrayEnabled(), "pop disables an array enabled inside the push");
assertEquals(GL11.GL_FALSE, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
assertFalse(colorArrayEnabled());
assertEquals(flagsBefore, GLStateManager.ctx().vaos.getVertexFlags());
assertEquals(0, GLStateManager.getClientActiveTextureUnit());
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glPopClientAttrib();
assertTrue(positionArrayEnabled(), "pop re-enables an array disabled inside the push");
assertEquals(GL11.GL_TRUE, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glPopClientAttrib();
assertTrue(positionArrayEnabled(), "a pixel-store push must not restore array enables");
assertTrue(colorArrayEnabled());
assertEquals(1, GLStateManager.getClientActiveTextureUnit());
} finally {
drainClientAttribStack();
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glDisableClientState(GL11.GL_COLOR_ARRAY);
}
}
@Test
void clientActiveTextureRoundTrips() {
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
assertEquals(GLStateManager.MAX_TEXTURE_COORDS, GLStateManager.glGetInteger(GL20.GL_MAX_TEXTURE_COORDS));
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
assertEquals(GL13.GL_TEXTURE1, GLStateManager.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE));
GLStateManager.glClientActiveTexture(0);
assertEquals(GL13.GL_TEXTURE1, GLStateManager.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE));
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0 + GLStateManager.MAX_TEXTURE_UNITS);
assertEquals(GL13.GL_TEXTURE1, GLStateManager.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE));
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0 + GLStateManager.MAX_TEXTURE_COORDS);
assertEquals(GL13.GL_TEXTURE1, GLStateManager.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE), "units past GL_MAX_TEXTURE_COORDS are rejected");
assertEquals(1, GLStateManager.getClientActiveTextureUnit());
GLStateManager.glTexCoordPointer(2, GL11.GL_FLOAT, 0, 0L);
final VAOManager.Attrib attrib = GLStateManager.ctx().vaos.attrib(Usage.SECONDARY_UV.getAttributeLocation());
assertNotNull(attrib);
assertEquals(2, attrib.size);
assumeTrue(GLStateManager.MAX_TEXTURE_COORDS > 5);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE5);
assertEquals(GL13.GL_TEXTURE5, GLStateManager.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE));
GLStateManager.glTexCoordPointer(3, GL11.GL_FLOAT, 0, 0L);
assertEquals(3, GLStateManager.glGetInteger(GL11.GL_TEXTURE_COORD_ARRAY_SIZE));
assertFalse(attribHasSize3(Usage.PRIMARY_UV));
assertFalse(attribHasSize3(Usage.SECONDARY_UV));
} finally {
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
@Test
void conventionalPointerSurvivesGenericOverride() {
final int position = Usage.POSITION.getAttributeLocation();
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glVertexAttribPointer(position, 2, GL11.GL_FLOAT, false, 8, 4L);
assertEquals(3, VAOManager.get(position).size, "disabled generic pointer must not displace an enabled conventional array");
assertEquals(3, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_SIZE));
GLStateManager.glEnableVertexAttribArray(position);
assertEquals(2, VAOManager.get(position).size, "enabled generic attribute wins");
assertTrue(VAOManager.get(position).genericPointer);
assertEquals(2, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_SIZE));
assertEquals(8, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_STRIDE));
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glDisableVertexAttribArray(position);
assertEquals(3, VAOManager.get(position).size, "conventional pointer must come back when the generic attribute is disabled");
assertEquals(12, VAOManager.get(position).stride);
assertFalse(VAOManager.get(position).genericPointer);
assertEquals(3, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_SIZE));
assertEquals(12, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_STRIDE));
assertEquals(vbo, driverAttrib(position, GL15.GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING));
assertEquals(GL11.GL_TRUE, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED), "conventional array stays enabled at draw level");
assertTrue(GLStateManager.glIsEnabled(GL11.GL_VERTEX_ARRAY));
assertEquals(0, GLStateManager.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING), "repoint must restore the array buffer binding");
} finally {
GLStateManager.glDisableVertexAttribArray(position);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
@Test
void conventionalPointerSetUnderEnabledGenericAppliesOnceGenericIsDisabled() {
final int position = Usage.POSITION.getAttributeLocation();
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexAttribPointer(position, 2, GL11.GL_FLOAT, false, 8, 0L);
GLStateManager.glEnableVertexAttribArray(position);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
assertEquals(2, VAOManager.get(position).size);
assertEquals(2, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_SIZE));
GLStateManager.glDisableVertexAttribArray(position);
assertEquals(3, VAOManager.get(position).size);
assertEquals(3, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_SIZE));
assertEquals(GL11.GL_TRUE, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
assertEquals(GL11.GL_FALSE, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
assertFalse(positionArrayEnabled());
} finally {
GLStateManager.glDisableVertexAttribArray(position);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
@Test
void drawVertexFlagsDoNotAnswerClientArrayEnables() {
try {
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
VAOManager.setCurrentVertexFlags(VertexFlags.COLOR_BIT | VertexFlags.NORMAL_BIT | VertexFlags.TEXTURE_BIT);
assertFalse(GLStateManager.glIsEnabled(GL11.GL_COLOR_ARRAY));
assertFalse(GLStateManager.glIsEnabled(GL11.GL_NORMAL_ARRAY));
assertFalse(GLStateManager.glIsEnabled(GL11.GL_TEXTURE_COORD_ARRAY));
GLStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY);
VAOManager.setCurrentVertexFlags(0);
assertTrue(GLStateManager.glIsEnabled(GL11.GL_COLOR_ARRAY));
} finally {
GLStateManager.glDisableClientState(GL11.GL_COLOR_ARRAY);
VAOManager.setCurrentVertexFlags(0);
}
}
@Test
void clientArrayPointerQueriesAreAnsweredFromCache() {
final int vaoB = GLStateManager.glGenVertexArrays();
final IntBuffer ints = BufferUtils.createIntBuffer(16);
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
assertVertexArray("default vertex", 4, GL11.GL_FLOAT, 0, 0);
assertClientArray("default normal", 0, 0, GL11.GL_NORMAL_ARRAY_TYPE, GL11.GL_FLOAT, GL11.GL_NORMAL_ARRAY_STRIDE, 0, GL15.GL_NORMAL_ARRAY_BUFFER_BINDING, 0);
assertClientArray("default color", GL11.GL_COLOR_ARRAY_SIZE, 4, GL11.GL_COLOR_ARRAY_TYPE, GL11.GL_FLOAT, GL11.GL_COLOR_ARRAY_STRIDE, 0, GL15.GL_COLOR_ARRAY_BUFFER_BINDING, 0);
assertClientArray("default secondary color", GL14.GL_SECONDARY_COLOR_ARRAY_SIZE, 3, GL14.GL_SECONDARY_COLOR_ARRAY_TYPE, GL11.GL_FLOAT,
GL14.GL_SECONDARY_COLOR_ARRAY_STRIDE, 0, GL15.GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING, 0);
assertClientArray("default fog coord", 0, 0, GL14.GL_FOG_COORDINATE_ARRAY_TYPE, GL11.GL_FLOAT, GL14.GL_FOG_COORDINATE_ARRAY_STRIDE, 0,
GL15.GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING, 0);
assertClientArray("default index", 0, 0, GL11.GL_INDEX_ARRAY_TYPE, GL11.GL_FLOAT, GL11.GL_INDEX_ARRAY_STRIDE, 0, GL15.GL_INDEX_ARRAY_BUFFER_BINDING, 0);
assertClientArray("default edge flag", 0, 0, 0, 0, GL11.GL_EDGE_FLAG_ARRAY_STRIDE, 0, GL15.GL_EDGE_FLAG_ARRAY_BUFFER_BINDING, 0);
assertTexCoordArray("default texcoord", 4, GL11.GL_FLOAT, 0, 0);
assertNull(GLStateManager.glGetPointer(GL11.GL_VERTEX_ARRAY_POINTER, 4));
GLStateManager.glVertexPointer(3, GL11.GL_SHORT, 20, 8L);
GLStateManager.glNormalPointer(GL11.GL_BYTE, 16, 4L);
GLStateManager.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 16, 12L);
GLStateManager.glSecondaryColorPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glFogCoordPointer(GL11.GL_FLOAT, 4, 0L);
GLStateManager.glEdgeFlagPointer(7, 0L);
GLStateManager.glTexCoordPointer(2, GL11.GL_FLOAT, 8, 0L);
assertVertexArray("vertex", 3, GL11.GL_SHORT, 20, vbo);
assertClientArray("normal", 0, 0, GL11.GL_NORMAL_ARRAY_TYPE, GL11.GL_BYTE, GL11.GL_NORMAL_ARRAY_STRIDE, 16, GL15.GL_NORMAL_ARRAY_BUFFER_BINDING, vbo);
assertClientArray("color", GL11.GL_COLOR_ARRAY_SIZE, 4, GL11.GL_COLOR_ARRAY_TYPE, GL11.GL_UNSIGNED_BYTE, GL11.GL_COLOR_ARRAY_STRIDE, 16,
GL15.GL_COLOR_ARRAY_BUFFER_BINDING, vbo);
assertClientArray("secondary color", GL14.GL_SECONDARY_COLOR_ARRAY_SIZE, 3, GL14.GL_SECONDARY_COLOR_ARRAY_TYPE, GL11.GL_FLOAT,
GL14.GL_SECONDARY_COLOR_ARRAY_STRIDE, 12, GL15.GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING, vbo);
assertClientArray("fog coord", 0, 0, GL14.GL_FOG_COORDINATE_ARRAY_TYPE, GL11.GL_FLOAT, GL14.GL_FOG_COORDINATE_ARRAY_STRIDE, 4,
GL15.GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING, vbo);
assertClientArray("edge flag", 0, 0, 0, 0, GL11.GL_EDGE_FLAG_ARRAY_STRIDE, 7, GL15.GL_EDGE_FLAG_ARRAY_BUFFER_BINDING, vbo);
assertTexCoordArray("texcoord unit 0", 2, GL11.GL_FLOAT, 8, vbo);
assertEquals(3.0f, GLStateManager.glGetFloat(GL11.GL_VERTEX_ARRAY_SIZE));
GLStateManager.glGetInteger(GL11.GL_VERTEX_ARRAY_STRIDE, ints);
assertEquals(20, ints.get(0));
assertEquals(8L, MemoryUtilities.memAddress(GLStateManager.glGetPointer(GL11.GL_VERTEX_ARRAY_POINTER, 4)));
assertEquals(12L, MemoryUtilities.memAddress(GLStateManager.glGetPointer(GL11.GL_COLOR_ARRAY_POINTER, 4)));
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0 + GLStateManager.MAX_TEXTURE_COORDS - 1);
assertTexCoordArray("unset texcoord unit", 4, GL11.GL_FLOAT, 0, 0);
GLStateManager.glTexCoordPointer(3, GL11.GL_SHORT, 6, 0L);
assertTexCoordArray("last texcoord unit", 3, GL11.GL_SHORT, 6, vbo);
assertFalse(attribHasSize3(Usage.PRIMARY_UV));
assertFalse(attribHasSize3(Usage.SECONDARY_UV));
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
assertTexCoordArray("texcoord unit 0 again", 2, GL11.GL_FLOAT, 8, vbo);
GLStateManager.glBindVertexArray(vaoB);
assertVertexArray("other vao", 4, GL11.GL_FLOAT, 0, 0);
final FloatBuffer client = BufferUtils.createFloatBuffer(12);
GLStateManager.glVertexPointer(2, 0, client);
assertVertexArray("client pointer", 2, GL11.GL_FLOAT, 0, 0);
assertEquals(MemoryUtilities.memAddress(client), MemoryUtilities.memAddress(GLStateManager.glGetPointer(GL11.GL_VERTEX_ARRAY_POINTER, 4)));
GLStateManager.glBindVertexArray(vao);
assertVertexArray("first vao again", 3, GL11.GL_SHORT, 20, vbo);
} finally {
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glDeleteVertexArrays(vaoB);
}
}
@Test
void interleavedArraysSetClientStateWithoutAnActiveFfpProgram() {
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
assumeFalse(GLStateManager.ctx().ffp.isActive());
GLStateManager.glInterleavedArrays(GL11.GL_C4UB_V3F, 0, 0L);
assertTrue(GLStateManager.glIsEnabled(GL11.GL_VERTEX_ARRAY));
assertTrue(GLStateManager.glIsEnabled(GL11.GL_COLOR_ARRAY));
assertFalse(GLStateManager.glIsEnabled(GL11.GL_NORMAL_ARRAY));
assertVertexArray("interleaved vertex", 3, GL11.GL_FLOAT, 16, vbo);
assertEquals(GL11.GL_UNSIGNED_BYTE, GLStateManager.glGetInteger(GL11.GL_COLOR_ARRAY_TYPE));
} finally {
for (final int cap : CLIENT_ARRAY_CAPS) {
GLStateManager.glDisableClientState(cap);
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
@Test
void clientVertexArrayBitRestoresPointersEnablesAndBindings() {
final int vaoB = GLStateManager.glGenVertexArrays();
final int vbo2 = GLStateManager.glGenBuffers();
final int ebo = GLStateManager.glGenBuffers();
final int position = Usage.POSITION.getAttributeLocation();
final int color = Usage.COLOR.getAttributeLocation();
final int normal = Usage.NORMAL.getAttributeLocation();
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glColorPointer(4, GL11.GL_UNSIGNED_BYTE, 16, 12L);
GLStateManager.glFogCoordPointer(GL11.GL_FLOAT, 4, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo2);
GLStateManager.glVertexPointer(2, GL11.GL_SHORT, 4, 4L);
GLStateManager.glFogCoordPointer(GL11.GL_FLOAT, 8, 0L);
GLStateManager.glDisableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glEnableClientState(GL11.GL_NORMAL_ARRAY);
GLStateManager.glVertexAttribPointer(5, 1, GL11.GL_FLOAT, false, 4, 0L);
GLStateManager.glEnableVertexAttribArray(5);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glPopClientAttrib();
assertEquals(vao, GLStateManager.getBoundVAO());
assertEquals(vbo, GLStateManager.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING));
assertEquals(ebo, GLStateManager.glGetInteger(GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING));
assertEquals(ebo, GL11.glGetInteger(GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING));
assertEquals(GL13.GL_TEXTURE0, GLStateManager.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE));
assertVertexArray("restored vertex", 3, GL11.GL_FLOAT, 12, vbo);
assertEquals(4, GLStateManager.glGetInteger(GL14.GL_FOG_COORDINATE_ARRAY_STRIDE));
assertEquals(vbo, GLStateManager.glGetInteger(GL15.GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING));
assertTrue(GLStateManager.glIsEnabled(GL11.GL_COLOR_ARRAY));
assertFalse(GLStateManager.glIsEnabled(GL11.GL_NORMAL_ARRAY));
assertTrue(colorArrayEnabled());
assertEquals(3, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_SIZE));
assertEquals(GL11.GL_FLOAT, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_TYPE));
assertEquals(12, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_STRIDE));
assertEquals(vbo, driverAttrib(position, GL15.GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING));
assertEquals(GL11.GL_TRUE, driverAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
assertEquals(GL11.GL_TRUE, driverAttrib(color, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
assertEquals(GL11.GL_FALSE, driverAttrib(normal, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
assertEquals(GL11.GL_FALSE, driverAttrib(5, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glDeleteVertexArrays(vaoB);
GLStateManager.glPopClientAttrib();
assertEquals(vao, GLStateManager.getBoundVAO(), "a deleted VAO must not be restored");
assertVertexArray("after dead-vao pop", 3, GL11.GL_FLOAT, 12, vbo);
} finally {
drainClientAttribStack();
GLStateManager.glBindVertexArray(vao);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
for (final int cap : CLIENT_ARRAY_CAPS) {
GLStateManager.glDisableClientState(cap);
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glDeleteVertexArrays(vaoB);
GLStateManager.glDeleteBuffers(vbo2);
GLStateManager.glDeleteBuffers(ebo);
}
}
@Test
void enabledGenericAttributeWinsWithoutAPointer() {
final int position = Usage.POSITION.getAttributeLocation();
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
assertFalse(VAOManager.get(position).genericPointer);
GLStateManager.glEnableVertexAttribArray(position);
assertTrue(VAOManager.get(position).genericPointer, "generic 0 supersedes position on its enable bit alone");
GLStateManager.glDisableVertexAttribArray(position);
assertFalse(VAOManager.get(position).genericPointer);
assertEquals(3, VAOManager.get(position).size);
} finally {
GLStateManager.glDisableVertexAttribArray(position);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
@Test
void deletedBufferIsDetachedFromTheBoundVertexArray() {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 4L);
GLStateManager.glFogCoordPointer(GL11.GL_FLOAT, 4, 0L);
assertEquals(vbo, GLStateManager.glGetInteger(GL15.GL_VERTEX_ARRAY_BUFFER_BINDING));
GLStateManager.glDeleteBuffers(vbo);
assertEquals(0, GLStateManager.glGetInteger(GL15.GL_VERTEX_ARRAY_BUFFER_BINDING));
assertEquals(0, GLStateManager.glGetInteger(GL15.GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING));
assertEquals(12, GLStateManager.glGetInteger(GL11.GL_VERTEX_ARRAY_STRIDE), "offset and stride survive the detach");
assertEquals(3, GLStateManager.glGetInteger(GL11.GL_VERTEX_ARRAY_SIZE));
}
@Test
void shadowedPointerIsNotRepointedAtAReusedBufferName() {
final int other = GLStateManager.glGenVertexArrays();
final int conventional = GLStateManager.glGenBuffers();
final int generic = GLStateManager.glGenBuffers();
final int position = Usage.POSITION.getAttributeLocation();
int reused = 0;
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, conventional);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, generic);
GLStateManager.glVertexAttribPointer(position, 2, GL11.GL_FLOAT, false, 8, 0L);
GLStateManager.glEnableVertexAttribArray(position);
GLStateManager.glBindVertexArray(other);
GLStateManager.glDeleteBuffers(conventional);
reused = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, reused);
assumeTrue(reused == conventional, "driver did not reuse the buffer name");
GLStateManager.glBindVertexArray(vao);
GLStateManager.glDisableVertexAttribArray(position);
assertEquals(generic, driverAttrib(position, GL15.GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING), "a pointer set before the delete must not attach to the new buffer");
} finally {
GLStateManager.glBindVertexArray(vao);
GLStateManager.glDisableVertexAttribArray(position);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glDeleteVertexArrays(other);
GLStateManager.glDeleteBuffers(generic);
if (reused != 0) GLStateManager.glDeleteBuffers(reused);
}
}
@Test
void popKeepsCurrentArraysWhenTheSavedArrayBufferWasDeleted() {
final int vbo2 = GLStateManager.glGenBuffers();
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo2);
GLStateManager.glVertexPointer(2, GL11.GL_SHORT, 4, 0L);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glDeleteBuffers(vbo);
GLStateManager.glPopClientAttrib();
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(GL13.GL_TEXTURE0, GLStateManager.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE));
} finally {
drainClientAttribStack();
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glDeleteBuffers(vbo2);
}
}
@Test
void clientAttribStackIsNotCompiledIntoDisplayLists() {
final int other = GLStateManager.glGenVertexArrays();
final int list = GLStateManager.glGenLists(1);
try {
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glPopClientAttrib();
GLStateManager.glEndList();
GLStateManager.glBindVertexArray(other);
GLStateManager.glCallList(list);
assertEquals(other, GLStateManager.getBoundVAO(), "replaying the list must not rebind the vertex array that was bound while compiling");
} finally {
drainClientAttribStack();
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteVertexArrays(other);
}
}
@Test
void popRestoresDivisorAndStateWithoutAnAttributeLocation() {
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexAttribPointer(5, 1, GL11.GL_FLOAT, false, 4, 0L);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glPopClientAttrib();
assertEquals(0, driverAttrib(5, GL33.GL_VERTEX_ATTRIB_ARRAY_DIVISOR));
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glVertexAttribDivisor(5, 3);
assertEquals(3, driverAttrib(5, GL33.GL_VERTEX_ATTRIB_ARRAY_DIVISOR));
GLStateManager.glPopClientAttrib();
assertEquals(0, driverAttrib(5, GL33.GL_VERTEX_ATTRIB_ARRAY_DIVISOR));
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glEnableClientState(GL14.GL_FOG_COORDINATE_ARRAY);
GLStateManager.glPopClientAttrib();
assertFalse(GLStateManager.glIsEnabled(GL14.GL_FOG_COORDINATE_ARRAY));
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glEnableVertexAttribArray(5);
GLStateManager.glPopClientAttrib();
assertEquals(GL11.GL_FALSE, driverAttrib(5, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED));
} finally {
drainClientAttribStack();
GLStateManager.glDisableVertexAttribArray(5);
GLStateManager.glDisableClientState(GL14.GL_FOG_COORDINATE_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
@Test
void interleavedArraysFromAClientBufferRegisterClientPointers() {
final FloatBuffer data = BufferUtils.createFloatBuffer(64);
final int position = Usage.POSITION.getAttributeLocation();
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
data.position(2);
GLStateManager.glInterleavedArrays(GL11.GL_C4UB_V3F, 0, data);
final long base = MemoryUtilities.memAddress(data);
assertNotNull(VAOManager.get(position).clientPointer);
assertEquals(base + 4, MemoryUtilities.memAddress(VAOManager.get(position).clientPointer));
assertEquals(base + 4, MemoryUtilities.memAddress(GLStateManager.glGetPointer(GL11.GL_VERTEX_ARRAY_POINTER, 4)));
assertEquals(base, MemoryUtilities.memAddress(GLStateManager.glGetPointer(GL11.GL_COLOR_ARRAY_POINTER, 4)));
assertEquals(0, GLStateManager.glGetInteger(GL15.GL_VERTEX_ARRAY_BUFFER_BINDING));
assertEquals(16, GLStateManager.glGetInteger(GL11.GL_VERTEX_ARRAY_STRIDE));
assertTrue(GLStateManager.ctx().vaos.hasAnyClientSideEnabledAttrib());
} finally {
for (final int cap : CLIENT_ARRAY_CAPS) {
GLStateManager.glDisableClientState(cap);
}
}
}
@Test
void enableAndDisableRouteClientArrayCapsToClientState() {
for (final int cap : CLIENT_ARRAY_CAPS) {
GLStateManager.glEnable(cap);
assertTrue(GLStateManager.glIsEnabled(cap), hex(cap));
GLStateManager.glDisable(cap);
assertFalse(GLStateManager.glIsEnabled(cap), hex(cap));
}
GLStateManager.glEnable(GL11.GL_VERTEX_ARRAY);
assertTrue(positionArrayEnabled());
GLStateManager.glDisable(GL11.GL_VERTEX_ARRAY);
}
@Test
void illegalPointerArgumentsAreIgnored() {
try {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
GLStateManager.glNormalPointer(GL11.GL_FLOAT, 12, 0L);
GLStateManager.glVertexPointer(1, GL11.GL_FLOAT, 4, 0L);
GLStateManager.glVertexPointer(3, GL11.GL_UNSIGNED_BYTE, 3, 0L);
GLStateManager.glNormalPointer(GL11.GL_UNSIGNED_BYTE, 3, 0L);
assertVertexArray("vertex after illegal calls", 3, GL11.GL_FLOAT, 12, vbo);
assertEquals(GL11.GL_FLOAT, GLStateManager.glGetInteger(GL11.GL_NORMAL_ARRAY_TYPE));
GLStateManager.glColorPointer(GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, 4, 0L);
assertEquals(GL12.GL_BGRA, GLStateManager.glGetInteger(GL11.GL_COLOR_ARRAY_SIZE));
GLStateManager.glColorPointer(GL12.GL_BGRA, GL11.GL_FLOAT, 16, 0L);
assertEquals(GL11.GL_UNSIGNED_BYTE, GLStateManager.glGetInteger(GL11.GL_COLOR_ARRAY_TYPE));
GLStateManager.glVertexAttribPointer(VAOManager.MAX_ATTRIBS, 3, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(VAOManager.MAX_ATTRIBS);
GLStateManager.glDisableVertexAttribArray(VAOManager.MAX_ATTRIBS);
GLStateManager.glVertexAttribPointer(5, 5, GL11.GL_FLOAT, false, 0, 0L);
assertNull(VAOManager.get(5));
} finally {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
}
@Test
void clientArrayEnablesAreAnsweredFromCache() {
final int vaoB = GLStateManager.glGenVertexArrays();
try {
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
for (final int cap : CLIENT_ARRAY_CAPS) {
assertFalse(GLStateManager.glIsEnabled(cap), "default isEnabled " + hex(cap));
assertEnableRoundTrip(cap, true);
}
GLStateManager.glEnableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
assertFalse(GLStateManager.glIsEnabled(GL11.GL_TEXTURE_COORD_ARRAY), "texcoord array enable is per client texture unit");
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
assertTrue(GLStateManager.glIsEnabled(GL11.GL_TEXTURE_COORD_ARRAY));
GLStateManager.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GLStateManager.glEnableClientState(GL11.GL_INDEX_ARRAY);
GLStateManager.glBindVertexArray(vaoB);
assertFalse(GLStateManager.glIsEnabled(GL11.GL_INDEX_ARRAY), "array enables are per vertex array object");
GLStateManager.glBindVertexArray(vao);
assertTrue(GLStateManager.glIsEnabled(GL11.GL_INDEX_ARRAY));
GLStateManager.glDisableClientState(GL11.GL_INDEX_ARRAY);
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glEnableVertexAttribArray(0);
assertFalse(GLStateManager.glIsEnabled(GL11.GL_VERTEX_ARRAY), "an enabled generic attribute 0 is not GL_VERTEX_ARRAY");
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(vao);
} finally {
GLStateManager.glBindVertexArray(vaoB);
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
for (final int cap : CLIENT_ARRAY_CAPS) {
GLStateManager.glDisableClientState(cap);
}
GLStateManager.glDeleteVertexArrays(vaoB);
}
}
}
@@ -76,7 +76,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
assertFalse(GLStateManager.glIsEnabled(cap));
} finally {
GLStateManager.glDisable(cap);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@@ -91,7 +90,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
assertFalse(GLStateManager.glIsEnabled(cap));
} finally {
GLStateManager.glDisable(cap);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@@ -105,7 +103,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
assertSecondaryColor(0.25F, 0.5F, 0.75F);
} finally {
GLStateManager.glSecondaryColor3f(0.0F, 0.0F, 0.0F);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@@ -121,7 +118,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
assertSecondaryColor(0.125F, 0.25F, 0.375F);
} finally {
GLStateManager.glSecondaryColor3f(0.0F, 0.0F, 0.0F);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@@ -135,7 +131,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
assertFalse(GLStateManager.glIsEnabled(GL14.GL_COLOR_SUM));
} finally {
GLStateManager.glDisable(GL14.GL_COLOR_SUM);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@@ -156,7 +151,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
} finally {
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glSecondaryColor3f(0.0F, 0.0F, 0.0F);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
@@ -176,7 +170,6 @@ public class GLSM_CoreRemovedCaps_GLTest {
} finally {
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDisable(GL11.GL_LINE_STIPPLE);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
}
}
@@ -0,0 +1,62 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL30;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
public class GLSM_DeferredTextureDelete_GLTest {
private static int immutableTexture() {
assumeTrue(GLStateManager.capabilities.OpenGL42, "no glTexStorage2D on this context");
final int tex = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glTexStorage2D(GL11.GL_TEXTURE_2D, 1, GL30.GL_R8, 4, 4);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
return tex;
}
@Test
void deletingAnImmutableTextureRaisesNoError() {
final int tex = immutableTexture();
GLStateManager.glDeleteTextures(tex);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void deletingARescuedImmutableTextureRaisesNoError() {
final int tex = immutableTexture();
GLStateManager.glDeleteTextures(tex);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glDeleteTextures(tex);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void deletingA2DTextureWhileA3DTextureIsBoundLeavesNothingBoundTo2D() {
final int t2d = GLStateManager.glGenTextures();
final int t3d = GLStateManager.glGenTextures();
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, t2d);
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, t3d);
GLStateManager.glDeleteTextures(t2d);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
assertEquals(0, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "the deleted texture must not stay bound");
assertEquals(t3d, GL11.glGetInteger(GL12.GL_TEXTURE_BINDING_3D));
} finally {
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, 0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glDeleteTextures(t3d);
}
}
}
@@ -6,18 +6,12 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.floats;
import static org.junit.jupiter.api.Assertions.assertEquals;
@GLCoreTest
public class GLSM_DirtyTracking_UnitTest {
static final FloatBuffer f4b = ByteBuffer.allocateDirect(4 << 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
static final FloatBuffer f3b = ByteBuffer.allocateDirect(3 << 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
@BeforeEach
void pushTrackedState() {
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT | GL11.GL_FOG_BIT | GL11.GL_CURRENT_BIT);
@@ -30,38 +24,31 @@ public class GLSM_DirtyTracking_UnitTest {
@Test
void materialBumpsOnlyOnChange() {
newf4b(0.1F, 0.2F, 0.3F, 0.4F);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, floats(0.1F, 0.2F, 0.3F, 0.4F));
final int gen = GLStateManager.getLightingGeneration();
newf4b(0.1F, 0.2F, 0.3F, 0.4F);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, floats(0.1F, 0.2F, 0.3F, 0.4F));
assertEquals(gen, GLStateManager.getLightingGeneration(), "identical material must not bump lightingGeneration");
newf4b(0.9F, 0.2F, 0.3F, 0.4F);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, floats(0.9F, 0.2F, 0.3F, 0.4F));
assertEquals(gen + 1, GLStateManager.getLightingGeneration(), "changed material must bump lightingGeneration once");
}
@Test
void lightModelBumpsOnlyOnChange() {
newf4b(0.5F, 0.5F, 0.5F, 1.0F);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, f4b);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, floats(0.5F, 0.5F, 0.5F, 1.0F));
final int gen = GLStateManager.getLightingGeneration();
newf4b(0.5F, 0.5F, 0.5F, 1.0F);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, f4b);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, floats(0.5F, 0.5F, 0.5F, 1.0F));
assertEquals(gen, GLStateManager.getLightingGeneration(), "identical light model ambient must not bump");
}
@Test
void lightColorBumpsOnlyOnChange() {
newf4b(0.25F, 0.5F, 0.75F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, floats(0.25F, 0.5F, 0.75F, 1.0F));
final int gen = GLStateManager.getLightingGeneration();
newf4b(0.25F, 0.5F, 0.75F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, floats(0.25F, 0.5F, 0.75F, 1.0F));
assertEquals(gen, GLStateManager.getLightingGeneration(), "identical light diffuse must not bump");
}
@@ -72,17 +59,14 @@ public class GLSM_DirtyTracking_UnitTest {
GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(1.0F, 2.0F, 3.0F);
newf4b(0.1F, 0.2F, 0.3F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, floats(0.1F, 0.2F, 0.3F, 1.0F));
final int gen = GLStateManager.getLightingGeneration();
newf4b(0.1F, 0.2F, 0.3F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, floats(0.1F, 0.2F, 0.3F, 1.0F));
assertEquals(gen, GLStateManager.getLightingGeneration(), "same position under same modelview must not bump");
GLStateManager.glTranslatef(5.0F, 0.0F, 0.0F);
newf4b(0.1F, 0.2F, 0.3F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, floats(0.1F, 0.2F, 0.3F, 1.0F));
assertEquals(gen + 1, GLStateManager.getLightingGeneration(), "same raw position under changed modelview must bump");
GLStateManager.glPopMatrix();
@@ -128,18 +112,15 @@ public class GLSM_DirtyTracking_UnitTest {
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
newf3b(0.0F, 0.0F, -1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f3b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, floats(0.0F, 0.0F, -1.0F));
final int gen = GLStateManager.getLightingGeneration();
GLStateManager.glTranslatef(5.0F, 0.0F, 0.0F);
newf3b(0.0F, 0.0F, -1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f3b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, floats(0.0F, 0.0F, -1.0F));
assertEquals(gen, GLStateManager.getLightingGeneration(), "spot direction under translation-only must not bump");
GLStateManager.glRotatef(90.0F, 0.0F, 1.0F, 0.0F);
newf3b(0.0F, 0.0F, -1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f3b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, floats(0.0F, 0.0F, -1.0F));
assertEquals(gen + 1, GLStateManager.getLightingGeneration(), "same raw spot direction under changed 3x3 must bump");
GLStateManager.glPopMatrix();
@@ -158,16 +139,4 @@ public class GLSM_DirtyTracking_UnitTest {
assertEquals(0.0F, diffuse.y, "green channel must be 0");
assertEquals(1.0F, diffuse.z, "blue channel must be 1");
}
static void newf4b(float x, float y, float z, float w) {
f4b.clear();
f4b.put(x).put(y).put(z).put(w);
f4b.flip();
}
static void newf3b(float x, float y, float z) {
f3b.clear();
f3b.put(x).put(y).put(z);
f3b.flip();
}
}
@@ -2,6 +2,8 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
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 static com.gtnewhorizons.angelica.util.GLSMUtil.verifyState;
@@ -28,106 +30,26 @@ class GLSM_DisplayList_FormatOptimization_Test {
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
}
@Test
void testPositionOnlyFormat() {
// Create a display list with only position data (no color, texture, brightness, or normals)
// Use GLStateManager for all calls to ensure they go through GLSM (test env doesn't redirect GL11)
private static final float[][] CORNERS = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
private static final float[][] COLORS = {{1, 1, 1, 1}, {1, 0, 0, 1}, {0, 1, 0, 1}, {0, 0, 1, 1}};
@ParameterizedTest(name = "normals={0}")
@ValueSource(booleans = {false, true})
void colorTextureFormatLeavesLastColorCurrent(boolean normals) {
displayList = GL11.glGenLists(1);
GLStateManager.glNewList(displayList, GL11.GL_COMPILE);
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);
for (int i = 0; i < 4; i++) {
if (normals) GLStateManager.glNormal3f(0, 0, 1);
GLStateManager.glColor4f(COLORS[i][0], COLORS[i][1], COLORS[i][2], COLORS[i][3]);
GLStateManager.glTexCoord2f(CORNERS[i][0], CORNERS[i][1]);
GLStateManager.glVertex3f(CORNERS[i][0], CORNERS[i][1], 0);
}
GLStateManager.glEnd();
GLStateManager.glEndList();
// Call the list to ensure it doesn't crash (verifies compilation succeeded)
GLStateManager.glCallList(displayList);
}
@Test
void testPositionTextureFormat() {
// Create a display list with position and texture coordinates
displayList = GL11.glGenLists(1);
GLStateManager.glNewList(displayList, GL11.GL_COMPILE);
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glTexCoord2f(0, 0);
GLStateManager.glVertex3f(0, 0, 0);
GLStateManager.glTexCoord2f(1, 0);
GLStateManager.glVertex3f(1, 0, 0);
GLStateManager.glTexCoord2f(1, 1);
GLStateManager.glVertex3f(1, 1, 0);
GLStateManager.glTexCoord2f(0, 1);
GLStateManager.glVertex3f(0, 1, 0);
GLStateManager.glEnd();
GLStateManager.glEndList();
// Call the list to ensure it doesn't crash (verifies compilation succeeded)
GLStateManager.glCallList(displayList);
}
@Test
void testPositionColorTextureFormat() {
// Create a display list with position, color, and texture
displayList = GL11.glGenLists(1);
GLStateManager.glNewList(displayList, GL11.GL_COMPILE);
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glColor4f(1, 1, 1, 1);
GLStateManager.glTexCoord2f(0, 0);
GLStateManager.glVertex3f(0, 0, 0);
GLStateManager.glColor4f(1, 0, 0, 1);
GLStateManager.glTexCoord2f(1, 0);
GLStateManager.glVertex3f(1, 0, 0);
GLStateManager.glColor4f(0, 1, 0, 1);
GLStateManager.glTexCoord2f(1, 1);
GLStateManager.glVertex3f(1, 1, 0);
GLStateManager.glColor4f(0, 0, 1, 1);
GLStateManager.glTexCoord2f(0, 1);
GLStateManager.glVertex3f(0, 1, 0);
GLStateManager.glEnd();
GLStateManager.glEndList();
// Call the list and verify color is restored to last value (blue)
GLStateManager.glCallList(displayList);
verifyState(GL11.GL_CURRENT_COLOR, new float[]{0f, 0f, 1f, 1f},
"After playback, color should be blue (last color in immediate mode block)");
}
@Test
void testFullFormat() {
// Create a display list with all attributes: position, color, texture, and normal
displayList = GL11.glGenLists(1);
GLStateManager.glNewList(displayList, GL11.GL_COMPILE);
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glNormal3f(0, 0, 1);
GLStateManager.glColor4f(1, 1, 1, 1);
GLStateManager.glTexCoord2f(0, 0);
GLStateManager.glVertex3f(0, 0, 0);
GLStateManager.glNormal3f(0, 0, 1);
GLStateManager.glColor4f(1, 0, 0, 1);
GLStateManager.glTexCoord2f(1, 0);
GLStateManager.glVertex3f(1, 0, 0);
GLStateManager.glNormal3f(0, 0, 1);
GLStateManager.glColor4f(0, 1, 0, 1);
GLStateManager.glTexCoord2f(1, 1);
GLStateManager.glVertex3f(1, 1, 0);
GLStateManager.glNormal3f(0, 0, 1);
GLStateManager.glColor4f(0, 0, 1, 1);
GLStateManager.glTexCoord2f(0, 1);
GLStateManager.glVertex3f(0, 1, 0);
GLStateManager.glEnd();
GLStateManager.glEndList();
// Call the list and verify color is restored to last value (blue)
GLStateManager.glCallList(displayList);
verifyState(GL11.GL_CURRENT_COLOR, new float[]{0f, 0f, 1f, 1f},
"After playback, color should be blue (last color in immediate mode block)");
@@ -2,6 +2,8 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
@@ -15,6 +17,23 @@ public class GLSM_DisplayList_PixelStore_Test {
private static final int SRC = 8;
private static final int SUB = 4;
enum Upload {
TEX_SUB_IMAGE_2D {
@Override
void run(ByteBuffer src) {
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
}
},
TEX_IMAGE_2D {
@Override
void run(ByteBuffer src) {
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SUB, SUB, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
}
};
abstract void run(ByteBuffer src);
}
@AfterEach
void resetUnpackState() {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
@@ -96,8 +115,9 @@ public class GLSM_DisplayList_PixelStore_Test {
GLStateManager.glDeleteLists(list, 1);
}
@Test
void stridedTexSubImage2DCompile() {
@ParameterizedTest
@EnumSource(Upload.class)
void stridedUploadCompile(Upload upload) {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SRC);
@@ -105,7 +125,7 @@ public class GLSM_DisplayList_PixelStore_Test {
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
upload.run(src);
GLStateManager.glEndList();
assertEquals(0, red(readback(SUB), SUB, 0, 0), "GL_COMPILE must not execute the upload");
@@ -146,58 +166,15 @@ public class GLSM_DisplayList_PixelStore_Test {
GLStateManager.glDeleteTextures(tex);
}
@Test
void stridedTexImage2DCompile() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SRC);
setStridedUnpack();
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SUB, SUB, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
setUnpack(0, 0, 0);
GLStateManager.glCallList(list);
assertStridedSubImage(readback(SUB));
assertUnpack(0, 0, 0);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void defaultRecordedTexSubImage2DReplaysWithDefaults() {
@ParameterizedTest
@EnumSource(Upload.class)
void defaultRecordedUploadReplaysWithDefaults(Upload upload) {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SUB);
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
setStridedUnpack();
GLStateManager.glCallList(list);
assertTightSubImage(readback(SUB));
assertEquals(1, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT), "live state must survive replay");
assertUnpack(SRC, 1, 2);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void defaultRecordedTexImage2DReplaysWithDefaults() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SUB);
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SUB, SUB, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
upload.run(src);
GLStateManager.glEndList();
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
@@ -1,14 +1,14 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.client.renderer.Tessellator;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import static com.gtnewhorizons.angelica.glsm.DisplayListTestHelper.immediateQuad;
import static com.gtnewhorizons.angelica.glsm.DisplayListTestHelper.rawQuad;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -27,34 +27,6 @@ class GLSM_DisplayList_RawTessellatorTransform_Test {
GLStateManager.glLoadIdentity();
}
private static void rawQuad(float size) {
final Tessellator tess = Tessellator.instance;
final float[][] corners = { { 0.0f, 0.0f }, { size, 0.0f }, { size, size }, { 0.0f, size } };
tess.rawBuffer = new int[4 * 8];
for (int i = 0; i < 4; i++) {
final int base = i * 8;
tess.rawBuffer[base] = Float.floatToRawIntBits(corners[i][0]);
tess.rawBuffer[base + 1] = Float.floatToRawIntBits(corners[i][1]);
tess.rawBuffer[base + 3] = Float.floatToRawIntBits(i == 1 || i == 2 ? 1.0f : 0.0f);
tess.rawBuffer[base + 4] = Float.floatToRawIntBits(i >= 2 ? 1.0f : 0.0f);
}
tess.isDrawing = true;
tess.drawMode = GL11.GL_QUADS;
tess.hasTexture = true;
tess.rawBufferIndex = 4 * 8;
tess.vertexCount = 4;
TessellatorManager.interceptDraw(tess);
}
private static void immediateQuad(float size) {
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glVertex3f(0.0f, 0.0f, 0.0f);
GLStateManager.glVertex3f(size, 0.0f, 0.0f);
GLStateManager.glVertex3f(size, size, 0.0f);
GLStateManager.glVertex3f(0.0f, size, 0.0f);
GLStateManager.glEnd();
}
@Test
void pendingTransformIsEmittedBeforeARawDraw() {
testList = GLStateManager.glGenLists(1);
@@ -1,19 +1,25 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
import net.minecraft.client.renderer.Tessellator;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntList;
import org.joml.Matrix4f;
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 org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import java.util.stream.Stream;
import static com.gtnewhorizons.angelica.glsm.DisplayListTestHelper.assertMatrixEquals;
import static com.gtnewhorizons.angelica.glsm.DisplayListTestHelper.immediateQuad;
import static com.gtnewhorizons.angelica.glsm.DisplayListTestHelper.modelviewAfterCall;
import static com.gtnewhorizons.angelica.glsm.DisplayListTestHelper.rawQuad;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -70,12 +76,7 @@ class GLSM_DisplayList_TransformCollapsing_Test {
"No individual transform commands should remain in optimized list");
// Verify behavioral correctness: matrix state should match expected
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
final FloatBuffer actualBuf = modelviewAfterCall(testList);
Matrix4f expected = new Matrix4f()
.translate(1.0f, 2.0f, 3.0f)
@@ -84,15 +85,7 @@ class GLSM_DisplayList_TransformCollapsing_Test {
.translate(-0.5f, -0.5f, -0.5f)
.rotate((float) Math.toRadians(30.0f), 1.0f, 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 after collapsed transforms", i));
}
GLStateManager.glPopMatrix();
assertMatrixEquals(expected, actualBuf, "mismatch after collapsed transforms");
}
@Test
@@ -121,12 +114,7 @@ class GLSM_DisplayList_TransformCollapsing_Test {
assertEquals(1, popCount, "Should have exactly 1 PopMatrix");
// Verify behavioral correctness
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
final FloatBuffer actualBuf = modelviewAfterCall(testList);
// Expected: translate(10,0,0) then translate(0,3,0) = translate(10,3,0)
// The inner push/pop isolates the (5,0,0) translate and rotation
@@ -134,15 +122,7 @@ class GLSM_DisplayList_TransformCollapsing_Test {
.translate(10.0f, 0.0f, 0.0f)
.translate(0.0f, 3.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 - push/pop should isolate inner transforms", i));
}
GLStateManager.glPopMatrix();
assertMatrixEquals(expected, actualBuf, "mismatch - push/pop should isolate inner transforms");
}
@Test
@@ -164,12 +144,7 @@ class GLSM_DisplayList_TransformCollapsing_Test {
GLStateManager.glEndList();
// Verify behavioral correctness
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
final FloatBuffer actualBuf = modelviewAfterCall(testList);
// Final state: translate(1,0,0) + translate(5,0,0) = translate(6,0,0)
// Level 1 and 2 transforms are isolated by push/pop
@@ -182,8 +157,6 @@ class GLSM_DisplayList_TransformCollapsing_Test {
assertEquals(expectedBuf.get(12), actualBuf.get(12), 0.0001f,
"X translation should be 6.0 (1+5, inner transforms isolated)");
GLStateManager.glPopMatrix(); // Balance the push from line 185
}
@Test
@@ -219,12 +192,7 @@ class GLSM_DisplayList_TransformCollapsing_Test {
"MultMatrix should be emitted BEFORE CallList (transform flush at barrier)");
// Verify behavioral correctness
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
final FloatBuffer actualBuf = modelviewAfterCall(testList);
// Build expected: translate(10,0,0) * rotate(90,Y) * translate(100,0,0) * translate(1,0,0)
// After 90-degree Y rotation, X axis points into -Z, so nested translate goes along -Z
@@ -234,15 +202,7 @@ class GLSM_DisplayList_TransformCollapsing_Test {
.translate(100.0f, 0.0f, 0.0f)
.translate(1.0f, 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 - nested list should see parent's transformed state", i));
}
GLStateManager.glPopMatrix();
assertMatrixEquals(expected, actualBuf, "mismatch - nested list should see parent's transformed state");
}
@Test
@@ -271,63 +231,33 @@ class GLSM_DisplayList_TransformCollapsing_Test {
"Identity-equivalent transforms should not emit MultMatrix");
}
@Test
void testTransformBetweenDrawsCreatesSeparateBatches() {
// This test verifies that draws with different transforms are not batched together
// Note: Without actual Tessellator integration, we can only verify command structure
testList = GL11.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
// Simulate: Transform A, Draw, Transform B, Draw
// The draws should NOT be in the same batch
GLStateManager.glTranslatef(1.0f, 0.0f, 0.0f);
// (Would have a draw here in real usage)
GLStateManager.glTranslatef(2.0f, 0.0f, 0.0f);
// (Would have another draw here)
GLStateManager.glEndList();
// Verify the transforms are accumulated correctly
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
// Total translation should be 3.0 (1+2)
assertEquals(3.0f, actualBuf.get(12), 0.0001f,
"Consecutive translates should accumulate");
GLStateManager.glPopMatrix();
static Stream<Arguments> discardedByLoadIdentity() {
return Stream.of(
Arguments.of("accumulated translate", (Runnable) () -> GLStateManager.glTranslatef(100.0f, 100.0f, 100.0f)),
Arguments.of("loaded matrix", (Runnable) () -> {
final FloatBuffer loadBuf = BufferUtils.createFloatBuffer(16);
new Matrix4f().translate(50.0f, 60.0f, 70.0f).rotate((float) Math.toRadians(45.0f), 1.0f, 1.0f, 0.0f).get(loadBuf);
GLStateManager.glLoadMatrix(loadBuf);
}));
}
@Test
void testLoadIdentityResetsAccumulation() {
// LoadIdentity should reset accumulated transforms
@ParameterizedTest(name = "{0}")
@MethodSource("discardedByLoadIdentity")
void testLoadIdentityDiscardsEarlierTransforms(String name, Runnable earlier) {
testList = GL11.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
GLStateManager.glTranslatef(100.0f, 100.0f, 100.0f); // Big translate
GLStateManager.glLoadIdentity(); // Reset
GLStateManager.glTranslatef(1.0f, 2.0f, 3.0f); // New translate
earlier.run();
GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(1.0f, 2.0f, 3.0f);
GLStateManager.glEndList();
// Verify: final state should only have the second translate
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
final FloatBuffer actualBuf = modelviewAfterCall(testList);
assertEquals(1.0f, actualBuf.get(12), 0.0001f, "X should be 1.0 (LoadIdentity reset)");
assertEquals(2.0f, actualBuf.get(13), 0.0001f, "Y should be 2.0 (LoadIdentity reset)");
assertEquals(3.0f, actualBuf.get(14), 0.0001f, "Z should be 3.0 (LoadIdentity reset)");
GLStateManager.glPopMatrix();
}
@Test
@@ -359,121 +289,14 @@ class GLSM_DisplayList_TransformCollapsing_Test {
"Translate + MultMatrix + Translate should collapse to 1 MultMatrix");
// Verify behavioral correctness
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
final FloatBuffer actualBuf = modelviewAfterCall(testList);
Matrix4f expected = new Matrix4f()
.translate(1.0f, 0.0f, 0.0f)
.rotate((float) Math.toRadians(90.0f), 0.0f, 1.0f, 0.0f)
.translate(2.0f, 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 MultMatrix accumulation", i));
}
GLStateManager.glPopMatrix();
}
// ==================== Batcher Flush Ordering Tests ====================
@Test
void testDrawsBeforePushMatrixAreEmittedBeforePush() {
// This test verifies that draws occurring before a PushMatrix are flushed
// before the PushMatrix command, not delayed until later.
// This was a bug where the batcher wasn't flushed at PushMatrix.
// Create a list with: Transform, (implicit draw would go here), PushMatrix, Transform, PopMatrix
testList = GL11.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
// Outer transform
GLStateManager.glTranslatef(1.0f, 0.0f, 0.0f);
// Note: If there was a draw here, it should be emitted BEFORE PushMatrix
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(5.0f, 0.0f, 0.0f); // Inner transform
GLStateManager.glPopMatrix();
// Final transform after pop
GLStateManager.glTranslatef(2.0f, 0.0f, 0.0f);
GLStateManager.glEndList();
// Verify the command structure
CompiledDisplayList compiled = DisplayListManager.getDisplayList(testList);
assertNotNull(compiled, "Display list should be compiled");
IntList opcodes = compiled.getCommandOpcodes();
// Find indices of key commands
int pushIndex = opcodes.indexOf(GLCommand.PUSH_MATRIX);
int popIndex = opcodes.indexOf(GLCommand.POP_MATRIX);
// Count MultMatrix commands before first PushMatrix
int multMatrixBeforePushCount = 0;
for (int i = 0; i < opcodes.size() && i < pushIndex; i++) {
if (opcodes.get(i) == GLCommand.TRANSLATE) {
multMatrixBeforePushCount++;
}
}
assertTrue(pushIndex >= 0, "Should have PushMatrix command");
assertTrue(popIndex >= 0, "Should have PopMatrix command");
assertTrue(pushIndex < popIndex, "PushMatrix should come before PopMatrix");
// The outer translate(1,0,0) should be emitted as MultMatrix before PushMatrix
assertTrue(multMatrixBeforePushCount >= 1,
"Transform before PushMatrix should be emitted before the Push");
// Verify behavioral correctness: final state should be translate(1,0,0) + translate(2,0,0) = translate(3,0,0)
// The inner translate(5,0,0) is isolated by push/pop
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
assertEquals(3.0f, actualBuf.get(12), 0.0001f,
"X translation should be 3.0 (1+2, inner transform isolated by push/pop)");
GLStateManager.glPopMatrix();
}
@Test
void testDrawsBeforePopMatrixAreEmittedBeforePop() {
// Verify draws inside a push/pop block are emitted before the pop
testList = GL11.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(10.0f, 0.0f, 0.0f);
// Draws here should be emitted before PopMatrix
GLStateManager.glPopMatrix();
GLStateManager.glEndList();
CompiledDisplayList compiled = DisplayListManager.getDisplayList(testList);
IntList opcodes = compiled.getCommandOpcodes();
int pushIndex = opcodes.indexOf(GLCommand.PUSH_MATRIX);
int popIndex = opcodes.indexOf(GLCommand.POP_MATRIX);
assertTrue(pushIndex >= 0, "Should have PushMatrix");
assertTrue(popIndex >= 0, "Should have PopMatrix");
assertTrue(pushIndex < popIndex, "Push should come before Pop");
// The inner translate should be emitted between push and pop (or collapsed to residual)
// At minimum, the structure should be valid
assertTrue(popIndex > pushIndex, "PopMatrix should follow PushMatrix");
assertMatrixEquals(expected, actualBuf, "mismatch for MultMatrix accumulation");
}
// ==================== LoadMatrix Tests ====================
@@ -509,22 +332,9 @@ class GLSM_DisplayList_TransformCollapsing_Test {
"Should have exactly 1 LoadMatrix command (can't be absorbed)");
// Verify behavioral correctness: matrix should equal the loaded matrix
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
final FloatBuffer actualBuf = modelviewAfterCall(testList);
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
FloatBuffer expectedBuf = BufferUtils.createFloatBuffer(16);
loadedMatrix.get(expectedBuf);
for (int i = 0; i < 16; i++) {
assertEquals(expectedBuf.get(i), actualBuf.get(i), 0.0001f,
String.format("Matrix[%d] should match loaded matrix (not multiplied)", i));
}
GLStateManager.glPopMatrix();
assertMatrixEquals(loadedMatrix, actualBuf, "should match loaded matrix (not multiplied)");
}
@Test
@@ -547,62 +357,11 @@ class GLSM_DisplayList_TransformCollapsing_Test {
GLStateManager.glEndList();
// Verify behavioral correctness
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
final FloatBuffer actualBuf = modelviewAfterCall(testList);
// Expected: loadedMatrix * translate(10,0,0)
Matrix4f expected = new Matrix4f(loadedMatrix).translate(10.0f, 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] - transform after LoadMatrix should be relative to loaded matrix", i));
}
GLStateManager.glPopMatrix();
}
@Test
void testLoadMatrixThenLoadIdentity() {
// LoadMatrix followed by LoadIdentity should result in identity
testList = GL11.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
// Load a complex matrix
Matrix4f loadedMatrix = new Matrix4f()
.translate(50.0f, 60.0f, 70.0f)
.rotate((float) Math.toRadians(45.0f), 1.0f, 1.0f, 0.0f);
FloatBuffer loadBuf = BufferUtils.createFloatBuffer(16);
loadedMatrix.get(loadBuf);
loadBuf.rewind();
GLStateManager.glLoadMatrix(loadBuf);
// Then reset to identity
GLStateManager.glLoadIdentity();
// Then apply a simple translate
GLStateManager.glTranslatef(1.0f, 2.0f, 3.0f);
GLStateManager.glEndList();
// Verify: final state should just be translate(1,2,3)
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
assertEquals(1.0f, actualBuf.get(12), 0.0001f, "X should be 1.0");
assertEquals(2.0f, actualBuf.get(13), 0.0001f, "Y should be 2.0");
assertEquals(3.0f, actualBuf.get(14), 0.0001f, "Z should be 3.0");
GLStateManager.glPopMatrix();
assertMatrixEquals(expected, actualBuf, "transform after LoadMatrix should be relative to loaded matrix");
}
@Test
@@ -628,17 +387,10 @@ class GLSM_DisplayList_TransformCollapsing_Test {
GLStateManager.glEndList();
// Verify: final state should be matrix2
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
FloatBuffer actualBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, actualBuf);
final FloatBuffer actualBuf = modelviewAfterCall(testList);
assertEquals(0.0f, actualBuf.get(12), 0.0001f, "X should be 0.0 (from second LoadMatrix)");
assertEquals(20.0f, actualBuf.get(13), 0.0001f, "Y should be 20.0 (from second LoadMatrix)");
GLStateManager.glPopMatrix();
}
// ==================== Immediate Mode with Transforms Tests ====================
@@ -652,12 +404,7 @@ class GLSM_DisplayList_TransformCollapsing_Test {
GLStateManager.glPushMatrix();
GLStateManager.glScalef(0.25f, 0.25f, 0.25f);
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glVertex3f(0.0f, 0.0f, 0.0f);
GLStateManager.glVertex3f(2.0f, 0.0f, 0.0f);
GLStateManager.glVertex3f(2.0f, 2.0f, 0.0f);
GLStateManager.glVertex3f(0.0f, 2.0f, 0.0f);
GLStateManager.glEnd();
immediateQuad(2.0f);
GLStateManager.glPopMatrix();
GLStateManager.glEndList();
@@ -683,12 +430,7 @@ class GLSM_DisplayList_TransformCollapsing_Test {
GLStateManager.glTranslatef(100.0f, 50.0f, 0.0f);
GLStateManager.glScalef(0.5f, 0.5f, 1.0f);
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glVertex3f(0.0f, 0.0f, 0.0f);
GLStateManager.glVertex3f(16.0f, 0.0f, 0.0f);
GLStateManager.glVertex3f(16.0f, 16.0f, 0.0f);
GLStateManager.glVertex3f(0.0f, 16.0f, 0.0f);
GLStateManager.glEnd();
immediateQuad(16.0f);
GLStateManager.glEndList();
@@ -700,20 +442,13 @@ class GLSM_DisplayList_TransformCollapsing_Test {
assertEquals(0, counts.getOrDefault(GLCommand.SCALE, 0), "No individual Scale");
// Verify matrix values after playback
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
FloatBuffer matrixBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, matrixBuf);
final FloatBuffer matrixBuf = modelviewAfterCall(testList);
assertEquals(0.5f, matrixBuf.get(0), 0.0001f, "Scale X");
assertEquals(0.5f, matrixBuf.get(5), 0.0001f, "Scale Y");
assertEquals(1.0f, matrixBuf.get(10), 0.0001f, "Scale Z");
assertEquals(100.0f, matrixBuf.get(12), 0.0001f, "Translate X");
assertEquals(50.0f, matrixBuf.get(13), 0.0001f, "Translate Y");
GLStateManager.glPopMatrix();
}
// ==================== Cross-Mode Transform Collapsing Tests ====================
@@ -806,12 +541,7 @@ class GLSM_DisplayList_TransformCollapsing_Test {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
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.glEnd();
immediateQuad(1.0f);
GLStateManager.glEndList();
@@ -826,16 +556,9 @@ class GLSM_DisplayList_TransformCollapsing_Test {
assertTrue(opcodes.indexOf(GLCommand.MULT_MATRIX) > opcodes.indexOf(GLCommand.DRAW_RANGE),
"MultMatrix after DrawRange for mode " + mode);
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
FloatBuffer matrixBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, matrixBuf);
final FloatBuffer matrixBuf = modelviewAfterCall(testList);
assertEquals(0.5f, matrixBuf.get(0), 0.0001f, "Scale X for mode " + mode);
assertEquals(0.5f, matrixBuf.get(5), 0.0001f, "Scale Y for mode " + mode);
GLStateManager.glPopMatrix();
}
}
@@ -846,38 +569,16 @@ class GLSM_DisplayList_TransformCollapsing_Test {
GLStateManager.glScalef(0.5f, 0.5f, 0.5f);
final Tessellator tess = Tessellator.instance;
final float[][] corners = { { 0.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f } };
tess.rawBuffer = new int[4 * 8];
for (int i = 0; i < 4; i++) {
final int base = i * 8;
tess.rawBuffer[base] = Float.floatToRawIntBits(corners[i][0]);
tess.rawBuffer[base + 1] = Float.floatToRawIntBits(corners[i][1]);
tess.rawBuffer[base + 3] = Float.floatToRawIntBits(i == 1 || i == 2 ? 1.0f : 0.0f);
tess.rawBuffer[base + 4] = Float.floatToRawIntBits(i >= 2 ? 1.0f : 0.0f);
}
tess.isDrawing = true;
tess.drawMode = GL11.GL_QUADS;
tess.hasTexture = true;
tess.rawBufferIndex = 4 * 8;
tess.vertexCount = 4;
TessellatorManager.interceptDraw(tess);
rawQuad(1.0f);
GLStateManager.glEndList();
CompiledDisplayList compiled = DisplayListManager.getDisplayList(testList);
assertNotNull(compiled);
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
FloatBuffer matrixBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, matrixBuf);
final FloatBuffer matrixBuf = modelviewAfterCall(testList);
assertEquals(0.5f, matrixBuf.get(0), 0.0001f, "Scale X after playback");
assertEquals(0.5f, matrixBuf.get(5), 0.0001f, "Scale Y after playback");
GLStateManager.glPopMatrix();
}
// ==================== Optimizer Push/Pop Reset Tests ====================
@@ -898,12 +599,7 @@ class GLSM_DisplayList_TransformCollapsing_Test {
GLStateManager.glTranslatef(10.0f, 0.0f, 0.0f);
GLStateManager.glRotatef(45.0f, 0.0f, 1.0f, 0.0f);
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.glEnd();
immediateQuad(1.0f);
GLStateManager.glPopMatrix();
GLStateManager.glEndList();
@@ -930,18 +626,11 @@ class GLSM_DisplayList_TransformCollapsing_Test {
assertTrue(drawIndex < popIndex, "DRAW before POP");
// Verify final matrix after pop = T1
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(testList);
FloatBuffer matrixBuf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, matrixBuf);
final FloatBuffer matrixBuf = modelviewAfterCall(testList);
assertEquals(2.0f, matrixBuf.get(0), 0.0001f, "Scale X after pop");
assertEquals(2.0f, matrixBuf.get(5), 0.0001f, "Scale Y after pop");
assertEquals(5.0f, matrixBuf.get(12), 0.0001f, "Translate X after pop");
GLStateManager.glPopMatrix();
}
@Test
@@ -1,7 +1,16 @@
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.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
import java.nio.FloatBuffer;
import java.util.function.DoubleSupplier;
import java.util.stream.Stream;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyState;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -9,128 +18,77 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
@GLCompatTest
class GLSM_DisplayList_UnitTest {
@Test
void testDisplayListCompileCall() {
verifyState(GL11.GL_LIGHTING, false, "GL_LIGHTING Initial State");
int list = GL11.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glEnable(GL11.GL_LIGHTING);
GLStateManager.glEndList();
verifyState(GL11.GL_LIGHTING, false, "GL_LIGHTING Post Display List Compile");
GLStateManager.glCallList(list);
verifyState(GL11.GL_LIGHTING, true, "GL_LIGHTING Post Display List Call");
// Reset state that we changed during test
GLStateManager.glDisable(GL11.GL_LIGHTING);
/** One recordable state command: {@code apply} moves the driver value read by {@code driver} to {@code applied}. */
private static Arguments state(String name, Runnable apply, Runnable reset, DoubleSupplier driver, double applied) {
return Arguments.of(name, apply, reset, driver, applied);
}
@Test
void testDisplayListCompileExecute() {
verifyState(GL11.GL_LIGHTING, false, "GL_LIGHTING Initial State");
int list = GL11.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
GLStateManager.glEnable(GL11.GL_LIGHTING);
GLStateManager.glEndList();
verifyState(GL11.GL_LIGHTING, true, "GL_LIGHTING Post Display List Compile and Execute");
// Reset state that we changed during test
GLStateManager.glDisable(GL11.GL_LIGHTING);
private static double lighting() {
final boolean enabled = GLStateManager.glIsEnabled(GL11.GL_LIGHTING);
verifyState(GL11.GL_LIGHTING, enabled, "driver must agree with the GLSM cache");
return enabled ? 1.0 : 0.0;
}
// ==================== CullFace Tests ====================
@Test
void testCullFaceInDisplayList() {
// Get initial cull face mode
int initialMode = GL11.glGetInteger(GL11.GL_CULL_FACE_MODE);
int list = GL11.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glCullFace(GL11.GL_FRONT);
GLStateManager.glEndList();
// After compile, mode should be unchanged
assertEquals(initialMode, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "CullFace mode should be unchanged after GL_COMPILE");
// Call the display list
GLStateManager.glCallList(list);
// Now mode should be GL_FRONT
assertEquals(GL11.GL_FRONT, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "CullFace mode should be GL_FRONT after calling display list");
// Cleanup
GLStateManager.glCullFace(GL11.GL_BACK); // Reset to default
GLStateManager.glDeleteLists(list, 1);
private static double blendColorRed() {
final FloatBuffer color = BufferUtils.createFloatBuffer(16);
GL11.glGetFloat(GL14.GL_BLEND_COLOR, color);
return color.get(0);
}
@Test
void testCullFaceCompileAndExecute() {
// Get initial cull face mode
int initialMode = GL11.glGetInteger(GL11.GL_CULL_FACE_MODE);
int list = GL11.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
GLStateManager.glCullFace(GL11.GL_FRONT_AND_BACK);
GLStateManager.glEndList();
// After compile and execute, mode should be changed
assertEquals(GL11.GL_FRONT_AND_BACK, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "CullFace mode should be GL_FRONT_AND_BACK after GL_COMPILE_AND_EXECUTE");
// Cleanup
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.glDeleteLists(list, 1);
static Stream<Arguments> states() {
return Stream.of(
state("glEnable(GL_LIGHTING)", () -> GLStateManager.glEnable(GL11.GL_LIGHTING), () -> GLStateManager.glDisable(GL11.GL_LIGHTING),
GLSM_DisplayList_UnitTest::lighting, 1.0),
state("glCullFace", () -> GLStateManager.glCullFace(GL11.GL_FRONT), () -> GLStateManager.glCullFace(GL11.GL_BACK),
() -> GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), GL11.GL_FRONT),
state("glLogicOp", () -> GLStateManager.glLogicOp(GL11.GL_XOR), () -> GLStateManager.glLogicOp(GL11.GL_COPY),
() -> GL11.glGetInteger(GL11.GL_LOGIC_OP_MODE), GL11.GL_XOR),
state("glClearDepth", () -> GLStateManager.glClearDepth(0.5), () -> GLStateManager.glClearDepth(1.0),
() -> GL11.glGetDouble(GL11.GL_DEPTH_CLEAR_VALUE), 0.5),
state("glBlendColor", () -> GLStateManager.glBlendColor(0.5f, 0.4f, 0.6f, 0.8f), () -> GLStateManager.glBlendColor(0f, 0f, 0f, 0f),
GLSM_DisplayList_UnitTest::blendColorRed, 0.5),
state("glFrontFace", () -> GLStateManager.glFrontFace(GL11.GL_CW), () -> GLStateManager.glFrontFace(GL11.GL_CCW),
() -> GL11.glGetInteger(GL11.GL_FRONT_FACE), GL11.GL_CW),
state("glHint", () -> GLStateManager.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_NICEST), () -> GLStateManager.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_DONT_CARE),
() -> GL11.glGetInteger(GL11.GL_LINE_SMOOTH_HINT), GL11.GL_NICEST));
}
// ==================== LogicOp Tests ====================
@ParameterizedTest(name = "{0}")
@MethodSource("states")
void compiledStateChangeAppliesOnlyWhenTheListIsCalled(String name, Runnable apply, Runnable reset, DoubleSupplier driver, double applied) {
final double initial = driver.getAsDouble();
final int list = GL11.glGenLists(1);
try {
GLStateManager.glNewList(list, GL11.GL_COMPILE);
apply.run();
GLStateManager.glEndList();
assertEquals(initial, driver.getAsDouble(), 0.0001, name + " must not execute under GL_COMPILE");
@Test
void testLogicOpInDisplayList() {
// Get initial logic op
int initialOp = GL11.glGetInteger(GL11.GL_LOGIC_OP_MODE);
int list = GL11.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glLogicOp(GL11.GL_XOR);
GLStateManager.glEndList();
// After compile, op should be unchanged
assertEquals(initialOp, GL11.glGetInteger(GL11.GL_LOGIC_OP_MODE), "LogicOp should be unchanged after GL_COMPILE");
// Call the display list
GLStateManager.glCallList(list);
// Now op should be GL_XOR
assertEquals(GL11.GL_XOR, GL11.glGetInteger(GL11.GL_LOGIC_OP_MODE), "LogicOp should be GL_XOR after calling display list");
// Cleanup
GLStateManager.glLogicOp(GL11.GL_COPY); // Reset to default
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glCallList(list);
assertEquals(applied, driver.getAsDouble(), 0.0001, name + " must apply when the list is called");
} finally {
reset.run();
GLStateManager.glDeleteLists(list, 1);
}
}
@Test
void testLogicOpCompileAndExecute() {
int list = GL11.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
GLStateManager.glLogicOp(GL11.GL_INVERT);
GLStateManager.glEndList();
// After compile and execute, op should be changed
assertEquals(GL11.GL_INVERT, GL11.glGetInteger(GL11.GL_LOGIC_OP_MODE), "LogicOp should be GL_INVERT after GL_COMPILE_AND_EXECUTE");
// Cleanup
GLStateManager.glLogicOp(GL11.GL_COPY);
GLStateManager.glDeleteLists(list, 1);
@ParameterizedTest(name = "{0}")
@MethodSource("states")
void compileAndExecuteAppliesStateChangeImmediately(String name, Runnable apply, Runnable reset, DoubleSupplier driver, double applied) {
final int list = GL11.glGenLists(1);
try {
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
apply.run();
GLStateManager.glEndList();
assertEquals(applied, driver.getAsDouble(), 0.0001, name + " must apply under GL_COMPILE_AND_EXECUTE");
} finally {
reset.run();
GLStateManager.glDeleteLists(list, 1);
}
}
@Test
void testMultipleStateChangesInDisplayList() {
// Test that multiple state commands work together
int list = GL11.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glCullFace(GL11.GL_FRONT);
@@ -138,173 +96,15 @@ class GLSM_DisplayList_UnitTest {
GLStateManager.glEnable(GL11.GL_LIGHTING);
GLStateManager.glEndList();
// Call the list
GLStateManager.glCallList(list);
// Verify all states changed
assertEquals(GL11.GL_FRONT, GL11.glGetInteger(GL11.GL_CULL_FACE_MODE), "CullFace should be GL_FRONT");
assertEquals(GL11.GL_AND, GL11.glGetInteger(GL11.GL_LOGIC_OP_MODE), "LogicOp should be GL_AND");
verifyState(GL11.GL_LIGHTING, true, "GL_LIGHTING should be enabled");
// Cleanup
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.glLogicOp(GL11.GL_COPY);
GLStateManager.glDisable(GL11.GL_LIGHTING);
GLStateManager.glDeleteLists(list, 1);
}
// ==================== ClearDepth Tests ====================
@Test
void testClearDepthInDisplayList() {
// Get initial clear depth
double initialDepth = GL11.glGetDouble(GL11.GL_DEPTH_CLEAR_VALUE);
int list = GL11.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glClearDepth(0.5);
GLStateManager.glEndList();
// After compile, depth should be unchanged
assertEquals(initialDepth, GL11.glGetDouble(GL11.GL_DEPTH_CLEAR_VALUE), 0.0001, "ClearDepth should be unchanged after GL_COMPILE");
// Call the display list
GLStateManager.glCallList(list);
// Now depth should be 0.5
assertEquals(0.5, GL11.glGetDouble(GL11.GL_DEPTH_CLEAR_VALUE), 0.0001, "ClearDepth should be 0.5 after calling display list");
// Cleanup
GLStateManager.glClearDepth(1.0); // Reset to default
GLStateManager.glDeleteLists(list, 1);
}
@Test
void testClearDepthCompileAndExecute() {
int list = GL11.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
GLStateManager.glClearDepth(0.25);
GLStateManager.glEndList();
// After compile and execute, depth should be changed
assertEquals(0.25, GL11.glGetDouble(GL11.GL_DEPTH_CLEAR_VALUE), 0.0001, "ClearDepth should be 0.25 after GL_COMPILE_AND_EXECUTE");
// Cleanup
GLStateManager.glClearDepth(1.0);
GLStateManager.glDeleteLists(list, 1);
}
// ==================== BlendColor Tests ====================
@Test
void testBlendColorInDisplayList() {
int list = GL11.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glBlendColor(0.2f, 0.4f, 0.6f, 0.8f);
GLStateManager.glEndList();
// Call the display list - should not throw
GLStateManager.glCallList(list);
// Cleanup
GLStateManager.glBlendColor(0f, 0f, 0f, 0f);
GLStateManager.glDeleteLists(list, 1);
}
@Test
void testBlendColorCompileAndExecute() {
int list = GL11.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
GLStateManager.glBlendColor(0.5f, 0.5f, 0.5f, 1.0f);
GLStateManager.glEndList();
// Should not throw - display list was executed
GLStateManager.glCallList(list);
// Cleanup
GLStateManager.glBlendColor(0f, 0f, 0f, 0f);
GLStateManager.glDeleteLists(list, 1);
}
// ==================== FrontFace Tests ====================
@Test
void testFrontFaceInDisplayList() {
// Get initial front face
int initialMode = GL11.glGetInteger(GL11.GL_FRONT_FACE);
int list = GL11.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glFrontFace(GL11.GL_CW);
GLStateManager.glEndList();
// After compile, mode should be unchanged
assertEquals(initialMode, GL11.glGetInteger(GL11.GL_FRONT_FACE), "FrontFace should be unchanged after GL_COMPILE");
// Call the display list
GLStateManager.glCallList(list);
// Now mode should be GL_CW
assertEquals(GL11.GL_CW, GL11.glGetInteger(GL11.GL_FRONT_FACE), "FrontFace should be GL_CW after calling display list");
// Cleanup
GLStateManager.glFrontFace(GL11.GL_CCW); // Reset to default
GLStateManager.glDeleteLists(list, 1);
}
@Test
void testFrontFaceCompileAndExecute() {
int list = GL11.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
GLStateManager.glFrontFace(GL11.GL_CW);
GLStateManager.glEndList();
// After compile and execute, mode should be changed
assertEquals(GL11.GL_CW, GL11.glGetInteger(GL11.GL_FRONT_FACE), "FrontFace should be GL_CW after GL_COMPILE_AND_EXECUTE");
// Cleanup
GLStateManager.glFrontFace(GL11.GL_CCW);
GLStateManager.glDeleteLists(list, 1);
}
// ==================== Hint Tests ====================
@Test
void testHintInDisplayList() {
// Get initial hint value
int initialHint = GL11.glGetInteger(GL11.GL_LINE_SMOOTH_HINT);
int list = GL11.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_NICEST);
GLStateManager.glEndList();
// After compile, hint should be unchanged
assertEquals(initialHint, GL11.glGetInteger(GL11.GL_LINE_SMOOTH_HINT), "Hint should be unchanged after GL_COMPILE");
// Call the display list
GLStateManager.glCallList(list);
// Now hint should be GL_NICEST
assertEquals(GL11.GL_NICEST, GL11.glGetInteger(GL11.GL_LINE_SMOOTH_HINT), "Hint should be GL_NICEST after calling display list");
// Cleanup
GLStateManager.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_DONT_CARE); // Reset to default
GLStateManager.glDeleteLists(list, 1);
}
@Test
void testHintCompileAndExecute() {
int list = GL11.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
GLStateManager.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_FASTEST);
GLStateManager.glEndList();
// After compile and execute, hint should be changed
assertEquals(GL11.GL_FASTEST, GL11.glGetInteger(GL11.GL_LINE_SMOOTH_HINT), "Hint should be GL_FASTEST after GL_COMPILE_AND_EXECUTE");
// Cleanup
GLStateManager.glHint(GL11.GL_LINE_SMOOTH_HINT, GL11.GL_DONT_CARE);
GLStateManager.glDeleteLists(list, 1);
}
}
@@ -3,12 +3,16 @@ package com.gtnewhorizons.angelica.glsm;
import org.joml.Matrix4f;
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.MethodSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -20,37 +24,18 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
@GLCompatTest
public class GLSM_GLU_UnitTest {
private static final FloatBuffer buffer = BufferUtils.createFloatBuffer(16);
private Matrix4f getModelviewMatrix() {
buffer.clear();
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, buffer);
return new Matrix4f(buffer);
return new Matrix4f(GlTestSupport.modelview());
}
private Matrix4f getProjectionMatrix() {
buffer.clear();
GLStateManager.glGetFloat(GL11.GL_PROJECTION_MATRIX, buffer);
return new Matrix4f(buffer);
return new Matrix4f(GlTestSupport.matrix(GL11.GL_PROJECTION_MATRIX));
}
private void assertMatrixEquals(Matrix4f expected, Matrix4f actual, String message) {
assertTrue(expected.equals(actual, 0.001f), () -> message + "\nExpected:\n" + expected + "\nActual:\n" + actual);
}
@Test
void gluPerspective_matchesJOML() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.gluPerspective(60.0f, 1.5f, 0.1f, 1000.0f);
Matrix4f expected = new Matrix4f().perspective((float) Math.toRadians(60.0f), 1.5f, 0.1f, 1000.0f);
assertMatrixEquals(expected, getProjectionMatrix(), "gluPerspective standard");
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
@Test
void gluPerspective_accumulates() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
@@ -67,40 +52,6 @@ public class GLSM_GLU_UnitTest {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
@Test
void gluLookAt_matchesJOML() {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.gluLookAt(
0.0f, 0.0f, 5.0f, // eye
0.0f, 0.0f, 0.0f, // center
0.0f, 1.0f, 0.0f); // up
Matrix4f expected = new Matrix4f().lookAt(
0.0f, 0.0f, 5.0f,
0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f);
assertMatrixEquals(expected, getModelviewMatrix(), "gluLookAt simple");
}
@Test
void gluLookAt_offAxis() {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.gluLookAt(
3.0f, 4.0f, 5.0f,
1.0f, 2.0f, 0.0f,
0.0f, 1.0f, 0.0f);
Matrix4f expected = new Matrix4f().lookAt(
3.0f, 4.0f, 5.0f,
1.0f, 2.0f, 0.0f,
0.0f, 1.0f, 0.0f);
assertMatrixEquals(expected, getModelviewMatrix(), "gluLookAt off-axis");
}
@Test
void gluLookAt_accumulates() {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
@@ -208,25 +159,23 @@ public class GLSM_GLU_UnitTest {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
@Test
void gluErrorString_glErrors() {
assertEquals("No error", GLStateManager.gluErrorString(GL11.GL_NO_ERROR));
assertEquals("Invalid enum", GLStateManager.gluErrorString(GL11.GL_INVALID_ENUM));
assertEquals("Invalid value", GLStateManager.gluErrorString(GL11.GL_INVALID_VALUE));
assertEquals("Invalid operation", GLStateManager.gluErrorString(GL11.GL_INVALID_OPERATION));
assertEquals("Out of memory", GLStateManager.gluErrorString(GL11.GL_OUT_OF_MEMORY));
static Stream<Arguments> errorStrings() {
return Stream.of(
Arguments.of(GL11.GL_NO_ERROR, "No error"),
Arguments.of(GL11.GL_INVALID_ENUM, "Invalid enum"),
Arguments.of(GL11.GL_INVALID_VALUE, "Invalid value"),
Arguments.of(GL11.GL_INVALID_OPERATION, "Invalid operation"),
Arguments.of(GL11.GL_OUT_OF_MEMORY, "Out of memory"),
Arguments.of(100900, "Invalid enum (glu)"),
Arguments.of(100901, "Invalid value (glu)"),
Arguments.of(100902, "Out of memory (glu)"),
Arguments.of(99999, "Unknown error: 99999"));
}
@Test
void gluErrorString_gluErrors() {
assertEquals("Invalid enum (glu)", GLStateManager.gluErrorString(100900));
assertEquals("Invalid value (glu)", GLStateManager.gluErrorString(100901));
assertEquals("Out of memory (glu)", GLStateManager.gluErrorString(100902));
}
@Test
void gluErrorString_unknownCode() {
assertEquals("Unknown error: 99999", GLStateManager.gluErrorString(99999));
@ParameterizedTest(name = "{1}")
@MethodSource("errorStrings")
void gluErrorString(int code, String expected) {
assertEquals(expected, GLStateManager.gluErrorString(code));
}
@Test
@@ -2,11 +2,13 @@ package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.states.ImageUnitBinding;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.ContextCapabilities;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL42;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.drainErrors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -16,17 +18,12 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
public class GLSM_ImageUnitBindingCache_UnitTest {
private static void drainErrors() {
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
private static void unbind(int unit) {
GLStateManager.glBindImageTexture(unit, 0, 0, false, 0, GL15.GL_READ_ONLY, 0);
GLStateManager.glBindImageTexture(unit, 0, 0, false, 0, GL15.GL_READ_ONLY, GL30.GL_R8);
}
@Test
void bindThenQueryReturnsFullTuple() {
drainErrors();
final int tex = GL11.glGenTextures();
try {
GLStateManager.glBindImageTexture(2, tex, 1, false, 3, GL15.GL_READ_ONLY, GL30.GL_RGBA32UI);
@@ -40,13 +37,11 @@ public class GLSM_ImageUnitBindingCache_UnitTest {
} finally {
unbind(2);
GLStateManager.glDeleteTextures(tex);
drainErrors();
}
}
@Test
void saveRestoreRoundTripsTheWholeTuple() {
drainErrors();
final int a = GL11.glGenTextures();
final int b = GL11.glGenTextures();
try {
@@ -55,7 +50,7 @@ public class GLSM_ImageUnitBindingCache_UnitTest {
assertNotNull(live);
final ImageUnitBinding saved = live.copy();
GLStateManager.glBindImageTexture(0, b, 0, true, 0, GL15.GL_WRITE_ONLY, 0);
GLStateManager.glBindImageTexture(0, b, 0, true, 0, GL15.GL_WRITE_ONLY, GL30.GL_R8);
assertEquals(b, GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_NAME, 0));
GLStateManager.glBindImageTexture(0, saved.getTexture(), saved.getLevel(), saved.isLayered(), saved.getLayer(), saved.getAccess(), saved.getFormat());
@@ -68,30 +63,26 @@ public class GLSM_ImageUnitBindingCache_UnitTest {
unbind(0);
GLStateManager.glDeleteTextures(a);
GLStateManager.glDeleteTextures(b);
drainErrors();
}
}
@Test
void resetToZeroIsNotARestore() {
drainErrors();
final int tex = GL11.glGenTextures();
try {
GLStateManager.glBindImageTexture(0, tex, 1, true, 0, GL15.GL_READ_WRITE, GL30.GL_R32UI);
GLStateManager.glBindImageTexture(0, 0, 0, true, 0, GL15.GL_WRITE_ONLY, 0);
GLStateManager.glBindImageTexture(0, 0, 0, true, 0, GL15.GL_WRITE_ONLY, GL30.GL_R8);
assertEquals(0, GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_NAME, 0));
assertEquals(0, GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_LEVEL, 0));
} finally {
unbind(0);
GLStateManager.glDeleteTextures(tex);
drainErrors();
}
}
@Test
void deletingTheBoundTextureClearsTheImageUnit() {
drainErrors();
final int tex = GL11.glGenTextures();
try {
GLStateManager.glBindImageTexture(1, tex, 0, true, 0, GL15.GL_READ_WRITE, GL30.GL_R32UI);
@@ -101,13 +92,11 @@ public class GLSM_ImageUnitBindingCache_UnitTest {
assertEquals(0, GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_NAME, 1), "GL unbinds a deleted texture from every image unit");
} finally {
unbind(1);
drainErrors();
}
}
@Test
void unitsAreIndependentAndUnitZeroIsNotSpecial() {
drainErrors();
final int a = GL11.glGenTextures();
final int b = GL11.glGenTextures();
try {
@@ -123,13 +112,11 @@ public class GLSM_ImageUnitBindingCache_UnitTest {
unbind(3);
GLStateManager.glDeleteTextures(a);
GLStateManager.glDeleteTextures(b);
drainErrors();
}
}
@Test
void outOfRangeUnitDoesNotCorruptNeighbours() {
drainErrors();
final int tex = GL11.glGenTextures();
try {
GLStateManager.glBindImageTexture(0, tex, 0, true, 0, GL15.GL_READ_ONLY, GL30.GL_R32UI);
@@ -137,11 +124,14 @@ public class GLSM_ImageUnitBindingCache_UnitTest {
assertFalse(beyond == 0, "unit 0 must resolve for this test to mean anything");
GLStateManager.glBindImageTexture(beyond, tex, 0, true, 0, GL15.GL_READ_ONLY, GL30.GL_R32UI);
// Without image load/store the backend drops the call, so the driver never sees the unit.
final ContextCapabilities caps = GLStateManager.capabilities;
final boolean reachesDriver = caps.OpenGL42 || caps.GL_ARB_shader_image_load_store || caps.GL_EXT_shader_image_load_store;
assertEquals(reachesDriver ? GL11.GL_INVALID_VALUE : GL11.GL_NO_ERROR, GL11.glGetError(), "driver must reject the out-of-range unit");
assertEquals(tex, GLStateManager.glGetInteger(GL42.GL_IMAGE_BINDING_NAME, 0));
} finally {
unbind(0);
GLStateManager.glDeleteTextures(tex);
drainErrors();
}
}
@@ -151,9 +141,9 @@ public class GLSM_ImageUnitBindingCache_UnitTest {
drainErrors();
final int tex = GL11.glGenTextures();
try {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GL42.glTexStorage2D(GL11.GL_TEXTURE_2D, 1, GL30.GL_R32UI, 4, 4);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glTexStorage2D(GL11.GL_TEXTURE_2D, 1, GL30.GL_R32UI, 4, 4);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glBindImageTexture(1, tex, 0, false, 0, GL15.GL_READ_ONLY, GL30.GL_R32UI);
drainErrors();
@@ -164,7 +154,6 @@ public class GLSM_ImageUnitBindingCache_UnitTest {
} finally {
unbind(1);
GLStateManager.glDeleteTextures(tex);
drainErrors();
}
}
}
@@ -1,22 +1,34 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.ffp.VertexKey;
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
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.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.stream.Stream;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.drainErrors;
import static com.gtnewhorizons.angelica.glsm.LegacyQueryFixture.HINT_TARGETS;
import static com.gtnewhorizons.angelica.glsm.LegacyQueryFixture.assertEnableRoundTrip;
import static com.gtnewhorizons.angelica.glsm.LegacyQueryFixture.drainClientAttribStack;
import static com.gtnewhorizons.angelica.glsm.LegacyQueryFixture.hex;
import static com.gtnewhorizons.angelica.glsm.LegacyQueryFixture.legacyPnames;
import static com.gtnewhorizons.angelica.glsm.LegacyQueryFixture.popAttribTo;
import static com.gtnewhorizons.angelica.glsm.LegacyQueryFixture.setEnabled;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -27,44 +39,12 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
public class GLSM_LegacyStackQueries_GLTest {
private static final int[] SINGLE_VALUE_QUERIES = {
GL11.GL_ATTRIB_STACK_DEPTH,
GL11.GL_CLIENT_ATTRIB_STACK_DEPTH,
GL11.GL_TEXTURE_STACK_DEPTH,
GL11.GL_MAX_ATTRIB_STACK_DEPTH,
GL11.GL_MAX_CLIENT_ATTRIB_STACK_DEPTH,
GL11.GL_MAX_MODELVIEW_STACK_DEPTH,
GL11.GL_MAX_PROJECTION_STACK_DEPTH,
GL11.GL_MAX_TEXTURE_STACK_DEPTH,
GL13.GL_MAX_TEXTURE_UNITS,
GL11.GL_MAX_LIGHTS,
GL11.GL_STENCIL_BITS,
GL11.GL_DEPTH_BITS,
};
private static final int[] LEGACY_PNAMES = legacyPnames();
private static boolean positionArrayEnabled() {
final VAOManager.Attrib position = GLStateManager.ctx().vaos.attrib(Usage.POSITION.getAttributeLocation());
return position != null && position.enabled;
}
private static boolean colorArrayEnabled() {
return (GLStateManager.ctx().vaos.getVertexFlags() & VertexFlags.COLOR_BIT) != 0;
}
private static void drainErrors() {
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
private static void drainClientAttribStack() {
while (GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH) > 0) {
GLStateManager.glPopClientAttrib();
}
}
private static void drainAttribStack() {
while (GLStateManager.getAttribDepth() > 0) {
GLStateManager.glPopAttrib();
}
private static void assumeDriverRejectsLegacyPnames() {
drainErrors();
GL11.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH);
assumeTrue(GL11.glGetError() != GL11.GL_NO_ERROR, "driver accepts GL_ATTRIB_STACK_DEPTH, so a clean error state proves nothing");
}
private static int pushToLimit(int depthPname, int maxPname) {
@@ -95,9 +75,7 @@ public class GLSM_LegacyStackQueries_GLTest {
assertEquals(base, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
assertEquals(GLStateManager.MAX_ATTRIB_STACK_DEPTH, GLStateManager.glGetInteger(GL11.GL_MAX_ATTRIB_STACK_DEPTH));
} finally {
while (GLStateManager.getAttribDepth() > base) {
GLStateManager.glPopAttrib();
}
popAttribTo(base);
}
}
@@ -150,42 +128,24 @@ public class GLSM_LegacyStackQueries_GLTest {
}
}
@Test
void maxModelviewStackDepthIsExactlyReachable() {
try {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_MODELVIEW_STACK_DEPTH));
assertEquals(GLStateManager.glGetInteger(GL11.GL_MAX_MODELVIEW_STACK_DEPTH), pushToLimit(GL11.GL_MODELVIEW_STACK_DEPTH, GL11.GL_MAX_MODELVIEW_STACK_DEPTH));
} finally {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
popToBase(GL11.GL_MODELVIEW_STACK_DEPTH);
}
static Stream<Arguments> matrixStacks() {
return Stream.of(
Arguments.of("modelview", GL11.GL_MODELVIEW, GL11.GL_MODELVIEW_STACK_DEPTH, GL11.GL_MAX_MODELVIEW_STACK_DEPTH),
Arguments.of("projection", GL11.GL_PROJECTION, GL11.GL_PROJECTION_STACK_DEPTH, GL11.GL_MAX_PROJECTION_STACK_DEPTH),
Arguments.of("texture", GL11.GL_TEXTURE, GL11.GL_TEXTURE_STACK_DEPTH, GL11.GL_MAX_TEXTURE_STACK_DEPTH));
}
@Test
void maxProjectionStackDepthIsExactlyReachable() {
try {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_PROJECTION_STACK_DEPTH));
assertEquals(GLStateManager.glGetInteger(GL11.GL_MAX_PROJECTION_STACK_DEPTH), pushToLimit(GL11.GL_PROJECTION_STACK_DEPTH, GL11.GL_MAX_PROJECTION_STACK_DEPTH));
} finally {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
popToBase(GL11.GL_PROJECTION_STACK_DEPTH);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
}
@Test
void maxTextureStackDepthIsExactlyReachable() {
@ParameterizedTest(name = "{0}")
@MethodSource("matrixStacks")
void maxMatrixStackDepthIsExactlyReachable(String name, int mode, int depthPname, int maxPname) {
try {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_TEXTURE_STACK_DEPTH));
assertEquals(GLStateManager.glGetInteger(GL11.GL_MAX_TEXTURE_STACK_DEPTH), pushToLimit(GL11.GL_TEXTURE_STACK_DEPTH, GL11.GL_MAX_TEXTURE_STACK_DEPTH));
GLStateManager.glMatrixMode(mode);
assertEquals(1, GLStateManager.glGetInteger(depthPname));
assertEquals(GLStateManager.glGetInteger(maxPname), pushToLimit(depthPname, maxPname));
} finally {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
popToBase(GL11.GL_TEXTURE_STACK_DEPTH);
GLStateManager.glMatrixMode(mode);
popToBase(depthPname);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
}
@@ -203,14 +163,12 @@ public class GLSM_LegacyStackQueries_GLTest {
final int savedRead = glCtx.readFramebuffer;
try {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
drainErrors();
assertEquals(
GL30.glGetFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_STENCIL, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE),
GLStateManager.glGetInteger(GL11.GL_STENCIL_BITS));
assertEquals(
GL30.glGetFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_DEPTH, GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE),
GLStateManager.glGetInteger(GL11.GL_DEPTH_BITS));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, savedDraw);
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, savedRead);
@@ -226,7 +184,6 @@ public class GLSM_LegacyStackQueries_GLTest {
final int depthOnly = GLStateManager.glGenRenderbuffers();
final int depthStencil = GLStateManager.glGenRenderbuffers();
try {
drainErrors();
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depthOnly);
GLStateManager.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL14.GL_DEPTH_COMPONENT24, 16, 16);
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depthStencil);
@@ -247,7 +204,6 @@ public class GLSM_LegacyStackQueries_GLTest {
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, depthStencil);
assertEquals(8, GLStateManager.glGetInteger(GL11.GL_STENCIL_BITS));
assertEquals(24, GLStateManager.glGetInteger(GL11.GL_DEPTH_BITS));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, savedDraw);
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, savedRead);
@@ -257,33 +213,6 @@ public class GLSM_LegacyStackQueries_GLTest {
}
}
@Test
void intBufferOverloadMatchesAndDriverIsNotAsked() {
final int base = GLStateManager.getAttribDepth();
final IntBuffer buf = BufferUtils.createIntBuffer(16);
try {
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
drainErrors();
GL11.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH);
assumeTrue(GL11.glGetError() != GL11.GL_NO_ERROR, "driver accepts GL_ATTRIB_STACK_DEPTH, so a clean error state proves nothing");
for (final int pname : SINGLE_VALUE_QUERIES) {
final int direct = GLStateManager.glGetInteger(pname);
buf.put(0, -12345);
GLStateManager.glGetInteger(pname, buf);
assertEquals(direct, buf.get(0), "IntBuffer overload for 0x" + Integer.toHexString(pname));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "driver error after querying 0x" + Integer.toHexString(pname));
}
} finally {
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
drainClientAttribStack();
while (GLStateManager.getAttribDepth() > base) {
GLStateManager.glPopAttrib();
}
}
}
@Test
void attribDepthUnchangedWhileCompilingDisplayList() {
final int base = GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH);
@@ -306,14 +235,12 @@ public class GLSM_LegacyStackQueries_GLTest {
@Test
void popAttribOnEmptyStackIsIgnored() {
drainAttribStack();
drainErrors();
popAttribTo(0);
assertEquals(0, GLStateManager.getAttribDepth());
assertDoesNotThrow(GLStateManager::glPopAttrib);
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_ATTRIB_STACK_DEPTH));
assertDoesNotThrow(GLStateManager::glPopAttrib);
assertEquals(0, GLStateManager.getAttribDepth());
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
@@ -322,8 +249,11 @@ public class GLSM_LegacyStackQueries_GLTest {
final int unpackBuffer = GLStateManager.glGenBuffers();
final int packBuffer = GLStateManager.glGenBuffers();
try {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
// Pack and unpack start from different values, so a pop that crosses them is caught
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 2);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 8);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 3);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 5);
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 0);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, 0);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
@@ -332,31 +262,41 @@ public class GLSM_LegacyStackQueries_GLTest {
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 2);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 6);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 7);
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 7);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, unpackBuffer);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, packBuffer);
assertEquals(1, glCtx.pixelUnpackState.alignment());
assertEquals(2, glCtx.pixelPackState.alignment());
assertEquals(4, glCtx.pixelPackState.alignment());
assertEquals(6, glCtx.pixelUnpackState.skipRows());
assertEquals(7, glCtx.pixelPackState.skipRows());
assertEquals(7, glCtx.pixelPackState.rowLength());
assertEquals(2, GL11.glGetInteger(GL11.GL_PACK_ALIGNMENT));
assertEquals(4, GL11.glGetInteger(GL11.GL_PACK_ALIGNMENT));
assertEquals(6, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_ROWS));
assertEquals(7, GL11.glGetInteger(GL11.GL_PACK_SKIP_ROWS));
GLStateManager.glPopClientAttrib();
assertSame(unpackBefore, glCtx.pixelUnpackState);
assertSame(packBefore, glCtx.pixelPackState);
assertEquals(4, GLStateManager.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
assertEquals(2, GLStateManager.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
assertEquals(8, GLStateManager.glGetInteger(GL11.GL_PACK_ALIGNMENT));
assertEquals(0, GLStateManager.glGetInteger(GL21.GL_PIXEL_UNPACK_BUFFER_BINDING));
assertEquals(0, GLStateManager.glGetInteger(GL21.GL_PIXEL_PACK_BUFFER_BINDING));
assertEquals(4, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
assertEquals(4, GL11.glGetInteger(GL11.GL_PACK_ALIGNMENT));
assertEquals(2, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
assertEquals(8, GL11.glGetInteger(GL11.GL_PACK_ALIGNMENT));
assertEquals(3, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_ROWS));
assertEquals(5, GL11.glGetInteger(GL11.GL_PACK_SKIP_ROWS));
assertEquals(0, GL11.glGetInteger(GL11.GL_PACK_ROW_LENGTH));
assertEquals(0, GL11.glGetInteger(GL21.GL_PIXEL_UNPACK_BUFFER_BINDING));
assertEquals(0, GL11.glGetInteger(GL21.GL_PIXEL_PACK_BUFFER_BINDING));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
drainClientAttribStack();
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 0);
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 0);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_UNPACK_BUFFER, 0);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
@@ -428,122 +368,6 @@ public class GLSM_LegacyStackQueries_GLTest {
}
}
@Test
void packAndUnpackRestoreToTheirOwnPriorValues() {
final GLContextState glCtx = GLStateManager.ctx();
try {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 2);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 8);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 3);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 5);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 6);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 7);
assertEquals(1, glCtx.pixelUnpackState.alignment());
assertEquals(4, glCtx.pixelPackState.alignment());
assertEquals(6, glCtx.pixelUnpackState.skipRows());
assertEquals(7, glCtx.pixelPackState.skipRows());
assertEquals(6, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_ROWS));
assertEquals(7, GL11.glGetInteger(GL11.GL_PACK_SKIP_ROWS));
GLStateManager.glPopClientAttrib();
assertEquals(2, glCtx.pixelUnpackState.alignment());
assertEquals(8, glCtx.pixelPackState.alignment());
assertEquals(3, glCtx.pixelUnpackState.skipRows());
assertEquals(5, glCtx.pixelPackState.skipRows());
assertEquals(2, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT));
assertEquals(8, GL11.glGetInteger(GL11.GL_PACK_ALIGNMENT));
assertEquals(3, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_ROWS));
assertEquals(5, GL11.glGetInteger(GL11.GL_PACK_SKIP_ROWS));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
drainClientAttribStack();
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, 0);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 0);
}
}
@Test
void vertexArrayEnableRestoredOnlyWithVertexArrayBit() {
final int savedVao = GLStateManager.getBoundVAO();
final int vao = GLStateManager.glGenVertexArrays();
final IntBuffer enabled = BufferUtils.createIntBuffer(16);
final int position = Usage.POSITION.getAttributeLocation();
try {
GLStateManager.glBindVertexArray(vao);
drainErrors();
assertFalse(positionArrayEnabled());
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
assertTrue(positionArrayEnabled());
GLStateManager.glPopClientAttrib();
assertFalse(positionArrayEnabled());
GL20.glGetVertexAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED, enabled);
assertEquals(GL11.GL_FALSE, enabled.get(0));
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glPopClientAttrib();
assertTrue(positionArrayEnabled());
GL20.glGetVertexAttrib(position, GL20.GL_VERTEX_ATTRIB_ARRAY_ENABLED, enabled);
assertEquals(GL11.GL_TRUE, enabled.get(0));
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glPopClientAttrib();
assertTrue(positionArrayEnabled());
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
drainClientAttribStack();
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindVertexArray(savedVao);
GLStateManager.glDeleteVertexArrays(vao);
}
}
@Test
void vertexArrayStateRestoredOnlyWithVertexArrayBit() {
final int savedVao = GLStateManager.getBoundVAO();
final int vao = GLStateManager.glGenVertexArrays();
try {
GLStateManager.glBindVertexArray(vao);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
final int flagsBefore = GLStateManager.ctx().vaos.getVertexFlags();
assertFalse(colorArrayEnabled());
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
GLStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
assertTrue(colorArrayEnabled());
GLStateManager.glPopClientAttrib();
assertFalse(colorArrayEnabled());
assertEquals(flagsBefore, GLStateManager.ctx().vaos.getVertexFlags());
assertEquals(0, GLStateManager.getClientActiveTextureUnit());
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
GLStateManager.glEnableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glPopClientAttrib();
assertTrue(colorArrayEnabled());
assertEquals(1, GLStateManager.getClientActiveTextureUnit());
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
drainClientAttribStack();
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glDisableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glBindVertexArray(savedVao);
GLStateManager.glDeleteVertexArrays(vao);
}
}
@Test
void intLightGettersConvertLikeMesa() {
final FloatBuffer savedDiffuse = BufferUtils.createFloatBuffer(16);
@@ -570,7 +394,6 @@ public class GLSM_LegacyStackQueries_GLTest {
GLStateManager.glLight(GL11.GL_LIGHT1, GL11.GL_SPOT_DIRECTION, values);
GLStateManager.glLightf(GL11.GL_LIGHT1, GL11.GL_SPOT_CUTOFF, 45.75f);
GLStateManager.glLightf(GL11.GL_LIGHT1, GL11.GL_LINEAR_ATTENUATION, 2.5f);
drainErrors();
ints.position(2);
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_DIFFUSE, ints);
@@ -606,7 +429,6 @@ public class GLSM_LegacyStackQueries_GLTest {
assertEquals(-777, ints.get(0));
GLStateManager.glGetLight(GL11.GL_LIGHT1, GL11.GL_SHININESS, ints);
assertEquals(-777, ints.get(0));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glLoadIdentity();
savedDiffuse.limit(4);
@@ -637,7 +459,6 @@ public class GLSM_LegacyStackQueries_GLTest {
GLStateManager.glMaterialf(GL11.GL_FRONT, GL11.GL_SHININESS, 12.6f);
values.put(0, 2.5f).put(1, -2.5f).put(2, 7.4f).put(3, 0.0f);
GLStateManager.glMaterial(GL11.GL_BACK, GL11.GL_COLOR_INDEXES, values);
drainErrors();
ints.position(1);
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR, ints);
@@ -665,7 +486,6 @@ public class GLSM_LegacyStackQueries_GLTest {
assertEquals(-777, ints.get(0));
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_SPOT_CUTOFF, ints);
assertEquals(-777, ints.get(0));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
savedSpecular.limit(4);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_SPECULAR, savedSpecular);
@@ -691,7 +511,6 @@ public class GLSM_LegacyStackQueries_GLTest {
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE1, 240.0f, 16.0f);
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE2, 0.75f, 0.125f);
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE3, 0.375f, 0.625f);
drainErrors();
final float[][] expected = { { 0.25f, 0.5f }, { 240.0f, 16.0f }, { 0.75f, 0.125f }, { 0.375f, 0.625f } };
for (int unit = 0; unit < 4; unit++) {
@@ -702,7 +521,6 @@ public class GLSM_LegacyStackQueries_GLTest {
assertEquals(0.0f, buf.get(2), "r of unit " + unit);
assertEquals(1.0f, buf.get(3), "q of unit " + unit);
}
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
for (int unit = 0; unit < 4; unit++) {
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0 + unit, saved[unit][0], saved[unit][1]);
@@ -719,7 +537,6 @@ public class GLSM_LegacyStackQueries_GLTest {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0, 0.25f, 0.5f);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
drainErrors();
for (int i = 0; i < 4; i++) {
buf.put(i, -7.0f);
}
@@ -728,10 +545,481 @@ public class GLSM_LegacyStackQueries_GLTest {
assertEquals(0.0f, buf.get(1));
assertEquals(0.0f, buf.get(2));
assertEquals(1.0f, buf.get(3));
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0, 0.0f, 0.0f);
}
}
@Test
void legacyQueriesNeverReachTheDriver() {
final int base = GLStateManager.getAttribDepth();
final IntBuffer ints = BufferUtils.createIntBuffer(16);
final FloatBuffer floats = BufferUtils.createFloatBuffer(16);
final DoubleBuffer doubles = BufferUtils.createDoubleBuffer(16);
final ByteBuffer bytes = BufferUtils.createByteBuffer(16);
final int[] intArray = new int[16];
final float[] floatArray = new float[16];
final double[] doubleArray = new double[16];
try {
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glPushClientAttrib(GL11.GL_CLIENT_PIXEL_STORE_BIT);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
assumeDriverRejectsLegacyPnames();
for (final int pname : LEGACY_PNAMES) {
drainErrors();
final int scalar = GLStateManager.glGetInteger(pname);
GLStateManager.glGetFloat(pname);
GLStateManager.glGetBoolean(pname);
GLStateManager.glIsEnabled(pname);
ints.clear();
ints.put(0, -12345);
GLStateManager.glGetInteger(pname, ints);
floats.clear();
GLStateManager.glGetFloat(pname, floats);
doubles.clear();
GLStateManager.glGetDouble(pname, doubles);
bytes.clear();
GLStateManager.glGetBoolean(pname, bytes);
intArray[0] = -12345;
GLStateManager.glGetInteger(pname, intArray);
GLStateManager.glGetFloat(pname, floatArray);
GLStateManager.glGetDouble(pname, doubleArray);
if (LegacyQueries.count(LegacyQueries.desc(pname)) == 1) {
assertEquals(scalar, ints.get(0), "IntBuffer overload for " + hex(pname));
assertEquals(scalar, intArray[0], "int[] overload for " + hex(pname));
}
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "driver error after querying " + hex(pname));
}
} finally {
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
drainClientAttribStack();
popAttribTo(base);
}
}
@Test
void typedEntryPointsAgreeUnderMesaConversions() {
final IntBuffer ints = BufferUtils.createIntBuffer(16);
final FloatBuffer floats = BufferUtils.createFloatBuffer(16);
final DoubleBuffer doubles = BufferUtils.createDoubleBuffer(16);
final FloatBuffer savedColor = BufferUtils.createFloatBuffer(16);
final FloatBuffer savedNormal = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_CURRENT_COLOR, savedColor);
GLStateManager.glGetFloat(GL11.GL_CURRENT_NORMAL, savedNormal);
final int savedFunc = GLStateManager.glGetInteger(GL11.GL_ALPHA_TEST_FUNC);
final float savedRef = GLStateManager.glGetFloat(GL11.GL_ALPHA_TEST_REF);
final float savedStart = GLStateManager.glGetFloat(GL11.GL_FOG_START);
final float savedEnd = GLStateManager.glGetFloat(GL11.GL_FOG_END);
final float savedDensity = GLStateManager.glGetFloat(GL11.GL_FOG_DENSITY);
final boolean savedLighting = GLStateManager.glIsEnabled(GL11.GL_LIGHTING);
final boolean savedFog = GLStateManager.glIsEnabled(GL11.GL_FOG);
final int savedShade = GLStateManager.glGetInteger(GL11.GL_SHADE_MODEL);
final int savedHint = GLStateManager.glGetInteger(GL11.GL_FOG_HINT);
final float savedMinSize = GLStateManager.glGetFloat(GL14.GL_POINT_SIZE_MIN);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
try {
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.5f);
GLStateManager.glFogf(GL11.GL_FOG_START, -1.5f);
GLStateManager.glFogf(GL11.GL_FOG_END, 2.5f);
GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 0.25f);
GLStateManager.glColor4f(0.25f, 0.5f, 0.75f, 1f);
GLStateManager.glNormal3f(0f, -0.5f, 1f);
GLStateManager.glEnable(GL11.GL_LIGHTING);
GLStateManager.glEnable(GL11.GL_FOG);
GLStateManager.glShadeModel(GL11.GL_FLAT);
GLStateManager.glHint(GL11.GL_FOG_HINT, GL11.GL_NICEST);
GLStateManager.glPointParameterf(GL14.GL_POINT_SIZE_MIN, 2.5f);
GLStateManager.glTranslatef(1.5f, -2.5f, 3f);
for (final int pname : LEGACY_PNAMES) {
final int desc = LegacyQueries.desc(pname);
final int type = LegacyQueries.type(desc);
final int count = LegacyQueries.count(desc);
final String name = hex(pname);
if (count == 1) {
if (type == LegacyQueries.BOOL) {
final boolean b = GLStateManager.glGetBoolean(pname);
assertEquals(b ? 1 : 0, GLStateManager.glGetInteger(pname), "int of bool " + name);
assertEquals(b ? 1.0f : 0.0f, GLStateManager.glGetFloat(pname), "float of bool " + name);
} else if (type == LegacyQueries.INT) {
final int i = GLStateManager.glGetInteger(pname);
assertEquals((float) i, GLStateManager.glGetFloat(pname), "float of int " + name);
assertEquals(i != 0, GLStateManager.glGetBoolean(pname), "bool of int " + name);
} else {
final float f = GLStateManager.glGetFloat(pname);
assertEquals(LegacyQueries.toInt(type, f), GLStateManager.glGetInteger(pname), "int of float " + name);
assertEquals(f != 0.0f, GLStateManager.glGetBoolean(pname), "bool of float " + name);
}
} else {
floats.clear();
GLStateManager.glGetFloat(pname, floats);
ints.clear();
GLStateManager.glGetInteger(pname, ints);
doubles.clear();
GLStateManager.glGetDouble(pname, doubles);
for (int i = 0; i < count; i++) {
final float f = floats.get(i);
assertEquals(LegacyQueries.toInt(type, f), ints.get(i), "int element " + i + " of " + name);
assertEquals((double) f, doubles.get(i), "double element " + i + " of " + name);
}
assertEquals(floats.get(0), GLStateManager.glGetFloat(pname), "scalar float of " + name);
}
}
} finally {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPopMatrix();
GLStateManager.glPointParameterf(GL14.GL_POINT_SIZE_MIN, savedMinSize);
GLStateManager.glHint(GL11.GL_FOG_HINT, savedHint);
GLStateManager.glShadeModel(savedShade);
setEnabled(GL11.GL_FOG, savedFog);
setEnabled(GL11.GL_LIGHTING, savedLighting);
GLStateManager.glNormal3f(savedNormal.get(0), savedNormal.get(1), savedNormal.get(2));
GLStateManager.glColor4f(savedColor.get(0), savedColor.get(1), savedColor.get(2), savedColor.get(3));
GLStateManager.glFogf(GL11.GL_FOG_DENSITY, savedDensity);
GLStateManager.glFogf(GL11.GL_FOG_END, savedEnd);
GLStateManager.glFogf(GL11.GL_FOG_START, savedStart);
GLStateManager.glAlphaFunc(savedFunc, savedRef);
}
}
private static float driverMaxPointSize() {
final FloatBuffer range = BufferUtils.createFloatBuffer(16);
GL11.glGetFloat(GL11.GL_POINT_SIZE_RANGE, range);
final float smooth = range.get(1);
// A rejected query leaves the buffer alone, so the smooth maximum stands in for the missing aliased range
GL11.glGetFloat(GL12.GL_ALIASED_POINT_SIZE_RANGE, range);
drainErrors();
return Math.max(smooth, range.get(1));
}
@Test
void edgeFlagIsCompiledIntoDisplayLists() {
final int list = GLStateManager.glGenLists(1);
try {
GLStateManager.glEdgeFlag(true);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glEdgeFlag(false);
GLStateManager.glEndList();
assertTrue(GLStateManager.glGetBoolean(GL11.GL_EDGE_FLAG), "compiling must not change the live edge flag");
GLStateManager.glCallList(list);
assertFalse(GLStateManager.glGetBoolean(GL11.GL_EDGE_FLAG));
} finally {
GLStateManager.glEdgeFlag(true);
GLStateManager.glDeleteLists(list, 1);
}
}
@Test
void textureBitRestoresPerUnitTextureEnables() {
final int base = GLStateManager.getAttribDepth();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final boolean cube = GLStateManager.glIsEnabled(GL13.GL_TEXTURE_CUBE_MAP);
final boolean texture2d = GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D);
try {
GLStateManager.glPushAttrib(GL11.GL_TEXTURE_BIT);
setEnabled(GL13.GL_TEXTURE_CUBE_MAP, !cube);
setEnabled(GL11.GL_TEXTURE_2D, !texture2d);
GLStateManager.glPopAttrib();
assertEquals(cube, GLStateManager.glIsEnabled(GL13.GL_TEXTURE_CUBE_MAP));
assertEquals(texture2d, GLStateManager.glIsEnabled(GL11.GL_TEXTURE_2D));
} finally {
popAttribTo(base);
setEnabled(GL13.GL_TEXTURE_CUBE_MAP, cube);
setEnabled(GL11.GL_TEXTURE_2D, texture2d);
}
}
@Test
void crossTypeQueriesConvert() {
final FloatBuffer savedColor = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_CURRENT_COLOR, savedColor);
final int savedFunc = GLStateManager.glGetInteger(GL11.GL_ALPHA_TEST_FUNC);
final float savedRef = GLStateManager.glGetFloat(GL11.GL_ALPHA_TEST_REF);
final float savedStart = GLStateManager.glGetFloat(GL11.GL_FOG_START);
final boolean savedLighting = GLStateManager.glIsEnabled(GL11.GL_LIGHTING);
final IntBuffer ints = BufferUtils.createIntBuffer(16);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
try {
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.5f);
assertEquals(1073741823, GLStateManager.glGetInteger(GL11.GL_ALPHA_TEST_REF));
assertEquals((float) GLStateManager.glGetInteger(GL11.GL_FOG_MODE), GLStateManager.glGetFloat(GL11.GL_FOG_MODE));
GLStateManager.glFogf(GL11.GL_FOG_START, -1.5f);
assertEquals(-2, GLStateManager.glGetInteger(GL11.GL_FOG_START));
GLStateManager.glEnable(GL11.GL_LIGHTING);
assertEquals(1, GLStateManager.glGetInteger(GL11.GL_LIGHTING));
GLStateManager.glDisable(GL11.GL_LIGHTING);
assertEquals(0, GLStateManager.glGetInteger(GL11.GL_LIGHTING));
GLStateManager.glColor4f(1f, 0.5f, 0f, 1f);
ints.clear();
GLStateManager.glGetInteger(GL11.GL_CURRENT_COLOR, ints);
assertEquals(2147483647, ints.get(0));
assertEquals(1073741823, ints.get(1));
assertEquals(0, ints.get(2));
assertEquals(2147483647, ints.get(3));
GLStateManager.glLoadIdentity();
GLStateManager.glGetInteger(GL11.GL_MODELVIEW_MATRIX, ints);
assertEquals(2147483647, ints.get(0), "matrix elements scale like normalized floats");
assertEquals(0, ints.get(1));
assertEquals(2147483647, ints.get(15));
assertEquals(2147483647, GLStateManager.glGetInteger(GL11.GL_MODELVIEW_MATRIX));
assertEquals(1.0f, GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX));
} finally {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPopMatrix();
GLStateManager.glFogf(GL11.GL_FOG_START, savedStart);
GLStateManager.glColor4f(savedColor.get(0), savedColor.get(1), savedColor.get(2), savedColor.get(3));
GLStateManager.glAlphaFunc(savedFunc, savedRef);
setEnabled(GL11.GL_LIGHTING, savedLighting);
}
}
@Test
void conversionHelpersMatchMesa() {
assertEquals(1, LegacyQueries.lround(0.5f));
assertEquals(-1, LegacyQueries.lround(-0.5f));
assertEquals(2, LegacyQueries.lround(2.4f));
assertEquals(-3, LegacyQueries.lround(-2.6f));
assertEquals(Integer.MAX_VALUE, LegacyQueries.floatToInt(1f));
assertEquals(0, LegacyQueries.floatToInt(0f));
assertEquals(-Integer.MAX_VALUE, LegacyQueries.floatToInt(-1f));
}
@Test
void queriesWriteAtBufferPosition() {
final IntBuffer ints = BufferUtils.createIntBuffer(32);
final FloatBuffer floats = BufferUtils.createFloatBuffer(32);
final DoubleBuffer doubles = BufferUtils.createDoubleBuffer(32);
final FloatBuffer reference = BufferUtils.createFloatBuffer(32);
final FloatBuffer savedColor = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_CURRENT_COLOR, savedColor);
final float savedStart = GLStateManager.glGetFloat(GL11.GL_FOG_START);
try {
for (int i = 0; i < 32; i++) {
ints.put(i, -777);
floats.put(i, -777f);
doubles.put(i, -777.0);
}
ints.position(4);
GLStateManager.glGetInteger(GL11.GL_MATRIX_MODE, ints);
assertEquals(GLStateManager.glGetInteger(GL11.GL_MATRIX_MODE), ints.get(4));
assertEquals(-777, ints.get(0));
floats.position(4);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, floats);
reference.clear();
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, reference);
for (int i = 0; i < 16; i++) {
assertEquals(reference.get(i), floats.get(4 + i), "modelview element " + i);
}
assertEquals(-777f, floats.get(0));
for (int i = 0; i < 32; i++) {
ints.put(i, -777);
}
GLStateManager.glColor4f(0.25f, 0.5f, 0.75f, 1f);
ints.position(4);
GLStateManager.glGetInteger(GL11.GL_CURRENT_COLOR, ints);
assertEquals(536870911, ints.get(4));
assertEquals(1073741823, ints.get(5));
assertEquals(1610612735, ints.get(6));
assertEquals(2147483647, ints.get(7));
assertEquals(-777, ints.get(0));
GLStateManager.glFogf(GL11.GL_FOG_START, 3.5f);
doubles.position(4);
GLStateManager.glGetDouble(GL11.GL_FOG_START, doubles);
assertEquals(3.5, doubles.get(4));
assertEquals(-777.0, doubles.get(0));
for (int i = 0; i < 32; i++) {
floats.put(i, -777f);
}
floats.position(4);
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_DIFFUSE, floats);
reference.clear();
GLStateManager.glGetMaterial(GL11.GL_FRONT, GL11.GL_DIFFUSE, reference);
for (int i = 0; i < 4; i++) {
assertEquals(reference.get(i), floats.get(4 + i), "diffuse element " + i);
}
assertEquals(-777f, floats.get(0));
} finally {
GLStateManager.glFogf(GL11.GL_FOG_START, savedStart);
GLStateManager.glColor4f(savedColor.get(0), savedColor.get(1), savedColor.get(2), savedColor.get(3));
}
}
@Test
void hintsRoundTripAndFollowHintBit() {
final int base = GLStateManager.getAttribDepth();
final int[] saved = new int[HINT_TARGETS.length];
for (int i = 0; i < HINT_TARGETS.length; i++) {
saved[i] = GLStateManager.glGetInteger(HINT_TARGETS[i]);
}
try {
for (final int target : HINT_TARGETS) {
final String name = hex(target);
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);
GLStateManager.glHint(target, 12345);
assertEquals(GL11.GL_NICEST, GLStateManager.glGetInteger(target), "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));
}
GLStateManager.glPopAttrib();
for (final int target : HINT_TARGETS) {
assertEquals(GL11.GL_NICEST, GLStateManager.glGetInteger(target), "restored " + hex(target));
}
assertEquals(GL11.GL_RENDER, GLStateManager.glGetInteger(GL11.GL_RENDER_MODE));
} finally {
popAttribTo(base);
for (int i = 0; i < HINT_TARGETS.length; i++) {
GLStateManager.glHint(HINT_TARGETS[i], saved[i]);
}
}
}
@Test
void pointParametersRoundTripAndFollowPointBit() {
final int base = GLStateManager.getAttribDepth();
final FloatBuffer attenuation = BufferUtils.createFloatBuffer(16);
final FloatBuffer savedAttenuation = BufferUtils.createFloatBuffer(16);
final float savedMin = GLStateManager.glGetFloat(GL14.GL_POINT_SIZE_MIN);
final float savedMax = GLStateManager.glGetFloat(GL14.GL_POINT_SIZE_MAX);
final float savedFade = GLStateManager.glGetFloat(GL14.GL_POINT_FADE_THRESHOLD_SIZE);
final int savedOrigin = GLStateManager.glGetInteger(GL20.GL_POINT_SPRITE_COORD_ORIGIN);
GLStateManager.glGetFloat(GL14.GL_POINT_DISTANCE_ATTENUATION, savedAttenuation);
GLStateManager.glPushAttrib(GL11.GL_POINT_BIT);
try {
assertEquals(0f, savedMin);
assertEquals(driverMaxPointSize(), savedMax);
assertEquals(1f, savedFade);
assertEquals(1f, savedAttenuation.get(0));
assertEquals(0f, savedAttenuation.get(1));
assertEquals(0f, savedAttenuation.get(2));
assertEquals(GL20.GL_UPPER_LEFT, savedOrigin);
GLStateManager.glPointParameterf(GL14.GL_POINT_SIZE_MIN, 0.5f);
GLStateManager.glPointParameterf(GL14.GL_POINT_SIZE_MAX, 2.5f);
GLStateManager.glPointParameterf(GL14.GL_POINT_FADE_THRESHOLD_SIZE, 4.5f);
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));
GLStateManager.glPointParameterf(GL14.GL_POINT_SIZE_MIN, -1f);
assertEquals(0.5f, GLStateManager.glGetFloat(GL14.GL_POINT_SIZE_MIN));
attenuation.put(0, 0.5f).put(1, 0.25f).put(2, 0.125f);
attenuation.position(0).limit(3);
GLStateManager.glPointParameter(GL14.GL_POINT_DISTANCE_ATTENUATION, attenuation);
attenuation.clear();
for (int i = 0; i < 16; i++) {
attenuation.put(i, -777f);
}
GLStateManager.glGetFloat(GL14.GL_POINT_DISTANCE_ATTENUATION, attenuation);
assertEquals(0.5f, attenuation.get(0));
assertEquals(0.25f, attenuation.get(1));
assertEquals(0.125f, attenuation.get(2));
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));
GLStateManager.glPushAttrib(GL11.GL_POINT_BIT);
GLStateManager.glPointParameterf(GL14.GL_POINT_SIZE_MIN, 1f);
GLStateManager.glPointParameterf(GL14.GL_POINT_SIZE_MAX, 3f);
GLStateManager.glPointParameterf(GL14.GL_POINT_FADE_THRESHOLD_SIZE, 2f);
attenuation.put(0, 1f).put(1, 1f).put(2, 1f);
attenuation.position(0).limit(3);
GLStateManager.glPointParameter(GL14.GL_POINT_DISTANCE_ATTENUATION, attenuation);
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));
GLStateManager.glPointParameterf(GL14.GL_POINT_SIZE_MIN, Float.NaN);
assertTrue(Float.isNaN(GLStateManager.glGetFloat(GL14.GL_POINT_SIZE_MIN)));
GLStateManager.glPopAttrib();
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(GL20.GL_LOWER_LEFT, GLStateManager.glGetInteger(GL20.GL_POINT_SPRITE_COORD_ORIGIN));
attenuation.clear();
GLStateManager.glGetFloat(GL14.GL_POINT_DISTANCE_ATTENUATION, attenuation);
assertEquals(0.5f, attenuation.get(0));
assertEquals(0.25f, attenuation.get(1));
assertEquals(0.125f, attenuation.get(2));
} finally {
popAttribTo(base);
assertEquals(savedMax, GLStateManager.glGetFloat(GL14.GL_POINT_SIZE_MAX));
}
}
@Test
void cacheOnlyEnablesRoundTrip() {
final int base = GLStateManager.getAttribDepth();
final boolean savedSprite = GLStateManager.glIsEnabled(GL20.GL_POINT_SPRITE);
final boolean savedTwoSide = GLStateManager.glIsEnabled(GL20.GL_VERTEX_PROGRAM_TWO_SIDE);
final boolean savedEdge = GLStateManager.glGetBoolean(GL11.GL_EDGE_FLAG);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final boolean savedCube0 = GLStateManager.glIsEnabled(GL13.GL_TEXTURE_CUBE_MAP);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
final boolean savedCube1 = GLStateManager.glIsEnabled(GL13.GL_TEXTURE_CUBE_MAP);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
try {
assertEnableRoundTrip(GL20.GL_POINT_SPRITE, false);
assertEnableRoundTrip(GL20.GL_VERTEX_PROGRAM_TWO_SIDE, false);
assertEnableRoundTrip(GL13.GL_TEXTURE_CUBE_MAP, false);
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glEnable(GL20.GL_POINT_SPRITE);
GLStateManager.glEnable(GL20.GL_VERTEX_PROGRAM_TWO_SIDE);
GLStateManager.glEnable(GL13.GL_TEXTURE_CUBE_MAP);
assertTrue(GLStateManager.glIsEnabled(GL20.GL_POINT_SPRITE));
assertTrue(GLStateManager.glIsEnabled(GL20.GL_VERTEX_PROGRAM_TWO_SIDE));
assertTrue(GLStateManager.glIsEnabled(GL13.GL_TEXTURE_CUBE_MAP));
GLStateManager.glPopAttrib();
assertFalse(GLStateManager.glIsEnabled(GL20.GL_POINT_SPRITE));
assertFalse(GLStateManager.glIsEnabled(GL20.GL_VERTEX_PROGRAM_TWO_SIDE));
assertFalse(GLStateManager.glIsEnabled(GL13.GL_TEXTURE_CUBE_MAP));
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glDisable(GL13.GL_TEXTURE_CUBE_MAP);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glEnable(GL13.GL_TEXTURE_CUBE_MAP);
assertTrue(GLStateManager.glIsEnabled(GL13.GL_TEXTURE_CUBE_MAP));
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
assertFalse(GLStateManager.glIsEnabled(GL13.GL_TEXTURE_CUBE_MAP));
assertFalse(GLStateManager.glGetBoolean(GL13.GL_TEXTURE_CUBE_MAP));
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
assertTrue(GLStateManager.glGetBoolean(GL13.GL_TEXTURE_CUBE_MAP));
GLStateManager.glDisable(GL13.GL_TEXTURE_CUBE_MAP);
assertTrue(savedEdge);
GLStateManager.glEdgeFlag(false);
assertFalse(GLStateManager.glGetBoolean(GL11.GL_EDGE_FLAG));
GLStateManager.glPushAttrib(GL11.GL_CURRENT_BIT);
GLStateManager.glEdgeFlag(true);
assertTrue(GLStateManager.glGetBoolean(GL11.GL_EDGE_FLAG));
GLStateManager.glPopAttrib();
assertFalse(GLStateManager.glGetBoolean(GL11.GL_EDGE_FLAG));
} finally {
popAttribTo(base);
GLStateManager.glEdgeFlag(savedEdge);
setEnabled(GL20.GL_POINT_SPRITE, savedSprite);
setEnabled(GL20.GL_VERTEX_PROGRAM_TWO_SIDE, savedTwoSide);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
setEnabled(GL13.GL_TEXTURE_CUBE_MAP, savedCube1);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
setEnabled(GL13.GL_TEXTURE_CUBE_MAP, savedCube0);
}
}
}
@@ -4,10 +4,7 @@ import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.floats;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyLightState;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyMaterialState;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyState;
@@ -15,9 +12,6 @@ import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyState;
@GLCompatTest
public class GLSM_Lighting_UnitTest {
static final FloatBuffer f4b = ByteBuffer.allocateDirect(4 << 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
static final FloatBuffer f3b = ByteBuffer.allocateDirect(3 << 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
@Test
void testLightEnablement() {
verifyState(GL11.GL_LIGHT0, false, "GL_LIGHT0 Initial State");
@@ -46,12 +40,10 @@ public class GLSM_Lighting_UnitTest {
verifyLightState(GL11.GL_LIGHT1, GL11.GL_SPECULAR, new float[]{0F, 0F, 0F, 1F}, "GL_LIGHT1 Initial Specular");
verifyLightState(GL11.GL_LIGHT1, GL11.GL_POSITION, new float[]{0F, 0F, 1F, 0F}, "GL_LIGHT1 Initial Position");
newf4b(0.25F, 0.5F, 0.25F, 0.5F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_AMBIENT, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_AMBIENT, floats(0.25F, 0.5F, 0.25F, 0.5F));
verifyLightState(GL11.GL_LIGHT0, GL11.GL_AMBIENT, new float[]{0.25F, 0.5F, 0.25F, 0.5F}, "GL_LIGHT0 Ambient Changed State");
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
newf4b(0.3F, 0.4F, 0.5F, 0.6F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_AMBIENT, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_AMBIENT, floats(0.3F, 0.4F, 0.5F, 0.6F));
verifyLightState(GL11.GL_LIGHT0, GL11.GL_AMBIENT, new float[]{0.3F, 0.4F, 0.5F, 0.6F}, "GL_LIGHT0 attrib push state changed check");
GLStateManager.glPopAttrib();
verifyLightState(GL11.GL_LIGHT0, GL11.GL_AMBIENT, new float[]{0.25F, 0.5F, 0.25F, 0.5F}, "GL_LIGHT0 attrib pop state check");
@@ -64,33 +56,27 @@ public class GLSM_Lighting_UnitTest {
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(25.0F, 25.0F, 25.0F);
GLStateManager.glRotatef(35.0F, 10.0F, 15.0F, 20.0F);
newf4b(0.25F, 0.5F, 0.25F, 0.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, floats(0.25F, 0.5F, 0.25F, 0.0F));
verifyLightState(GL11.GL_LIGHT0, GL11.GL_POSITION, new float[]{0.1090F, 0.5189F, 0.3062F, 0.0F}, "GL_LIGHT0 Position Changed State");
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
newf4b(0.3F, 0.4F, 0.5F, 0.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, floats(0.3F, 0.4F, 0.5F, 0.0F));
verifyLightState(GL11.GL_LIGHT0, GL11.GL_POSITION, new float[]{0.2824F, 0.4200F, 0.4937F, 0.0F}, "GL_LIGHT0 attrib push state changed check");
GLStateManager.glPopAttrib();
verifyLightState(GL11.GL_LIGHT0, GL11.GL_POSITION, new float[]{0.1090F, 0.5189F, 0.3062F, 0.0F}, "GL_LIGHT0 attrib pop state check");
GLStateManager.glPopMatrix();
newf4b(0.25F, 0.5F, 0.25F, 0.5F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, floats(0.25F, 0.5F, 0.25F, 0.5F));
verifyLightState(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, new float[]{0.25F, 0.5F, 0.25F, 0.5F}, "GL_LIGHT0 Diffuse Changed State");
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
newf4b(0.3F, 0.4F, 0.5F, 0.6F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, floats(0.3F, 0.4F, 0.5F, 0.6F));
verifyLightState(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, new float[]{0.3F, 0.4F, 0.5F, 0.6F}, "GL_LIGHT0 attrib push state changed check");
GLStateManager.glPopAttrib();
verifyLightState(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, new float[]{0.25F, 0.5F, 0.25F, 0.5F}, "GL_LIGHT0 attrib pop state check");
newf4b(0.25F, 0.5F, 0.25F, 0.5F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, floats(0.25F, 0.5F, 0.25F, 0.5F));
verifyLightState(GL11.GL_LIGHT0, GL11.GL_SPECULAR, new float[]{0.25F, 0.5F, 0.25F, 0.5F}, "GL_LIGHT0 Specular Changed State");
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
newf4b(0.3F, 0.4F, 0.5F, 0.6F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, floats(0.3F, 0.4F, 0.5F, 0.6F));
verifyLightState(GL11.GL_LIGHT0, GL11.GL_SPECULAR, new float[]{0.3F, 0.4F, 0.5F, 0.6F}, "GL_LIGHT0 attrib push state changed check");
GLStateManager.glPopAttrib();
verifyLightState(GL11.GL_LIGHT0, GL11.GL_SPECULAR, new float[]{0.25F, 0.5F, 0.25F, 0.5F}, "GL_LIGHT0 attrib pop state check");
@@ -99,12 +85,10 @@ public class GLSM_Lighting_UnitTest {
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(25.0F, 25.0F, 25.0F);
GLStateManager.glRotatef(35.0F, 10.0F, 15.0F, 20.0F);
newf4b(0.25F, 0.5F, 0.25F, 0.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, floats(0.25F, 0.5F, 0.25F, 0.0F));
verifyLightState(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, new float[]{0.1090F, 0.5189F, 0.3062F}, "GL_LIGHT0 Spot Direction Changed State");
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
newf4b(0.3F, 0.4F, 0.5F, 0.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f4b);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, floats(0.3F, 0.4F, 0.5F, 0.0F));
verifyLightState(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, new float[]{0.2824F, 0.4200F, 0.4937F}, "GL_LIGHT0 attrib push state changed check");
GLStateManager.glPopAttrib();
verifyLightState(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, new float[]{0.1090F, 0.5189F, 0.3062F}, "GL_LIGHT0 attrib pop state check");
@@ -157,8 +141,7 @@ public class GLSM_Lighting_UnitTest {
void testLightModel() {
verifyState(GL11.GL_LIGHT_MODEL_AMBIENT, new float[]{0.2F, 0.2F, 0.2F, 1.0F}, "GL_LIGHT_MODEL_AMBIENT initial state");
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
newf4b(0.8F, 0.6F, 0.4F, 0.2F);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, f4b);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, floats(0.8F, 0.6F, 0.4F, 0.2F));
verifyState(GL11.GL_LIGHT_MODEL_AMBIENT, new float[]{0.8F, 0.6F, 0.4F, 0.2F}, "GL_LIGHT_MODEL_AMBIENT changed state");
GLStateManager.glPopAttrib();
verifyState(GL11.GL_LIGHT_MODEL_AMBIENT, new float[]{0.2F, 0.2F, 0.2F, 1.0F});
@@ -235,16 +218,14 @@ public class GLSM_Lighting_UnitTest {
void testMaterial() {
verifyMaterialState(GL11.GL_FRONT, GL11.GL_AMBIENT, new float[]{0.2F, 0.2F, 0.2F, 1.0F}, "");
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
newf4b(0.8F, 0.6F, 0.4F, 0.2F);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, floats(0.8F, 0.6F, 0.4F, 0.2F));
verifyMaterialState(GL11.GL_FRONT, GL11.GL_AMBIENT, new float[]{0.8F, 0.6F, 0.4F, 0.2F}, "");
GLStateManager.glPopAttrib();
verifyMaterialState(GL11.GL_FRONT, GL11.GL_AMBIENT, new float[]{0.2F, 0.2F, 0.2F, 1.0F}, "");
verifyMaterialState(GL11.GL_BACK, GL11.GL_AMBIENT, new float[]{0.2F, 0.2F, 0.2F, 1.0F}, "");
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
newf4b(0.8F, 0.6F, 0.4F, 0.2F);
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT, f4b);
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT, floats(0.8F, 0.6F, 0.4F, 0.2F));
verifyMaterialState(GL11.GL_FRONT, GL11.GL_AMBIENT, new float[]{0.8F, 0.6F, 0.4F, 0.2F}, "");
verifyMaterialState(GL11.GL_BACK, GL11.GL_AMBIENT, new float[]{0.8F, 0.6F, 0.4F, 0.2F}, "");
GLStateManager.glPopAttrib();
@@ -254,8 +235,7 @@ public class GLSM_Lighting_UnitTest {
verifyMaterialState(GL11.GL_FRONT, GL11.GL_DIFFUSE, new float[]{0.8F, 0.8F, 0.8F, 1.0F}, "");
verifyMaterialState(GL11.GL_BACK, GL11.GL_DIFFUSE, new float[]{0.8F, 0.8F, 0.8F, 1.0F}, "");
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
newf4b(0.8F, 0.6F, 0.4F, 0.2F);
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE, f4b);
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE, floats(0.8F, 0.6F, 0.4F, 0.2F));
verifyMaterialState(GL11.GL_FRONT, GL11.GL_AMBIENT, new float[]{0.8F, 0.6F, 0.4F, 0.2F}, "");
verifyMaterialState(GL11.GL_BACK, GL11.GL_AMBIENT, new float[]{0.8F, 0.6F, 0.4F, 0.2F}, "");
verifyMaterialState(GL11.GL_FRONT, GL11.GL_DIFFUSE, new float[]{0.8F, 0.6F, 0.4F, 0.2F}, "");
@@ -268,16 +248,14 @@ public class GLSM_Lighting_UnitTest {
verifyMaterialState(GL11.GL_BACK, GL11.GL_SPECULAR, new float[]{0.0F, 0.0F, 0.0F, 1.0F}, "");
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
newf4b(0.8F, 0.6F, 0.4F, 0.2F);
GLStateManager.glMaterial(GL11.GL_BACK, GL11.GL_SPECULAR, f4b);
GLStateManager.glMaterial(GL11.GL_BACK, GL11.GL_SPECULAR, floats(0.8F, 0.6F, 0.4F, 0.2F));
verifyMaterialState(GL11.GL_BACK, GL11.GL_SPECULAR, new float[]{0.8F, 0.6F, 0.4F, 0.2F}, "");
GLStateManager.glPopAttrib();
verifyMaterialState(GL11.GL_BACK, GL11.GL_SPECULAR, new float[]{0.0F, 0.0F, 0.0F, 1.0F}, "");
verifyMaterialState(GL11.GL_BACK, GL11.GL_EMISSION, new float[]{0.0F, 0.0F, 0.0F, 1.0F}, "");
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
newf4b(0.8F, 0.6F, 0.4F, 0.2F);
GLStateManager.glMaterial(GL11.GL_BACK, GL11.GL_EMISSION, f4b);
GLStateManager.glMaterial(GL11.GL_BACK, GL11.GL_EMISSION, floats(0.8F, 0.6F, 0.4F, 0.2F));
verifyMaterialState(GL11.GL_BACK, GL11.GL_EMISSION, new float[]{0.8F, 0.6F, 0.4F, 0.2F}, "");
GLStateManager.glPopAttrib();
verifyMaterialState(GL11.GL_BACK, GL11.GL_EMISSION, new float[]{0.0F, 0.0F, 0.0F, 1.0F}, "");
@@ -291,8 +269,7 @@ public class GLSM_Lighting_UnitTest {
verifyMaterialState(GL11.GL_FRONT, GL11.GL_COLOR_INDEXES, new float[]{0.0F, 1.0F, 1.0F}, "");
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT);
newf4b(0.8F, 0.6F, 0.4F, 0.0F);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_COLOR_INDEXES, f4b);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_COLOR_INDEXES, floats(0.8F, 0.6F, 0.4F, 0.0F));
if (GLStateManager.vendorIsNVIDIA()) {
verifyMaterialState(GL11.GL_FRONT, GL11.GL_COLOR_INDEXES, new float[]{0.8F, 0.4F, 0.6F}, "");
} else {
@@ -302,16 +279,4 @@ public class GLSM_Lighting_UnitTest {
verifyMaterialState(GL11.GL_FRONT, GL11.GL_COLOR_INDEXES, new float[]{0.0F, 1.0F, 1.0F}, "");
}
static void newf4b(float x, float y, float z, float w) {
f4b.clear();
f4b.put(x).put(y).put(z).put(w);
f4b.flip();
}
static void newf3b(float x, float y, float z) {
f3b.clear();
f3b.put(x).put(y).put(z);
f3b.flip();
}
}
@@ -5,6 +5,9 @@ import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.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.GL15;
@@ -12,6 +15,7 @@ import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.function.IntPredicate;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -72,7 +76,6 @@ public class GLSM_LineStipple_GLTest {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
if (vbo != 0) GLStateManager.glDeleteBuffers(vbo);
if (vao != 0) GLStateManager.glDeleteVertexArrays(vao);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
private boolean[] renderLine(float x0, float x1) {
@@ -117,35 +120,29 @@ public class GLSM_LineStipple_GLTest {
GLStateManager.glLineStipple(factor, (short) pattern);
}
@Test
void solidPatternCoversEveryPixel() {
stipple(1, 0xFFFF);
assertArrayEquals(expected(i -> true), renderLine(0f, W));
static Stream<Arguments> patterns() {
return Stream.of(
pattern("solid pattern covers every pixel", true, 1, 0xFFFF, i -> true),
pattern("empty pattern covers nothing", true, 1, 0x0000, i -> false),
pattern("alternating pattern dashes every other pixel", true, 1, 0xAAAA, i -> (i & 1) == 1),
pattern("factor stretches each bit across that many pixels", true, 2, 0xAAAA, i -> ((i / 2) & 1) == 1),
pattern("disabled stipple covers every pixel", false, 1, 0xAAAA, i -> true));
}
@Test
void emptyPatternCoversNothing() {
stipple(1, 0x0000);
assertArrayEquals(expected(i -> false), renderLine(0f, W));
private static Arguments pattern(String name, boolean enabled, int factor, int pattern, IntPredicate on) {
return Arguments.of(name, enabled, factor, pattern, on);
}
@Test
void alternatingPatternDashesEveryOtherPixel() {
stipple(1, 0xAAAA);
assertArrayEquals(expected(i -> (i & 1) == 1), renderLine(0f, W));
}
@Test
void factorStretchesEachBitAcrossThatManyPixels() {
stipple(2, 0xAAAA);
assertArrayEquals(expected(i -> ((i / 2) & 1) == 1), renderLine(0f, W));
}
@Test
void disabledStippleCoversEveryPixel() {
GLStateManager.glLineStipple(1, (short) 0xAAAA);
GLStateManager.glDisable(GL11.GL_LINE_STIPPLE);
assertArrayEquals(expected(i -> true), renderLine(0f, W));
@ParameterizedTest(name = "{0}")
@MethodSource("patterns")
void patternSelectsTheCoveredPixels(String name, boolean enabled, int factor, int pattern, IntPredicate on) {
GLStateManager.glLineStipple(factor, (short) pattern);
if (enabled) {
GLStateManager.glEnable(GL11.GL_LINE_STIPPLE);
} else {
GLStateManager.glDisable(GL11.GL_LINE_STIPPLE);
}
assertArrayEquals(expected(on), renderLine(0f, W));
}
@Test
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import org.joml.Matrix4f;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
@@ -18,23 +19,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
@GLCoreTest
public class GLSM_PerThread_UnitTest {
private static void runThreaded(int n, ThreadBody body) throws Throwable {
private static void runThreaded(int n, ThreadBody body) throws InterruptedException {
final CyclicBarrier barrier = new CyclicBarrier(n);
final Throwable[] errs = new Throwable[n];
final Thread[] ts = new Thread[n];
final TestThreads.Worker[] workers = new TestThreads.Worker[n];
for (int i = 0; i < n; i++) {
final int tid = i;
ts[i] = new Thread(() -> {
try {
body.run(tid, barrier);
} catch (Throwable t) {
errs[tid] = t;
}
}, "glsm-perthread-" + i);
workers[i] = TestThreads.start("glsm-perthread-" + i, () -> body.run(tid, barrier));
}
for (Thread t : ts) t.start();
for (Thread t : ts) t.join();
for (int i = 0; i < n; i++) if (errs[i] != null) throw errs[i];
for (TestThreads.Worker worker : workers) worker.join();
}
interface ThreadBody {
@@ -13,6 +13,7 @@ import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizons.angelica.util.GLSMUtil.resetGLState;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyIsEnabled;
@@ -49,6 +50,24 @@ class GLSM_PushPop_UnitTest {
public static final float[] FLOAT_ARRAY_3_1 = { 1f, 1f, 1f };
public static final float[] FLOAT_ARRAY_GL_SPOT_DIRECTION_DEFAULT = { 0f, 0f, -1f };
private static void toggleAndVerify(List<GLBit> bits) {
for (GLBit bit : bits) {
verifyState(bit.glEnum(), bit.initial(), bit.name() + " Initial State");
if (bit.initial()) {
GLStateManager.glDisable(bit.glEnum());
} else {
GLStateManager.glEnable(bit.glEnum());
}
verifyState(bit.glEnum(), !bit.initial(), bit.name() + " Toggle State");
}
}
private static void verifyReset(List<GLBit> bits) {
for (GLBit bit : bits) {
verifyState(bit.glEnum(), bit.initial(), bit.name() + " Reset State");
}
}
@Test
void testPushPopColorBufferBit() {
@@ -241,19 +260,11 @@ class GLSM_PushPop_UnitTest {
bits.add(new GLBit(GL11.GL_TEXTURE_GEN_Q, "Texture Gen Q", false));
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
bits.forEach(bit -> {
verifyState(bit.glEnum(), bit.initial(), bit.name() + " Initial State");
if(bit.initial()) {
GLStateManager.glDisable(bit.glEnum());
} else {
GLStateManager.glEnable(bit.glEnum());
}
verifyState(bit.glEnum(), !bit.initial(), bit.name() + " Toggle State");
});
toggleAndVerify(bits);
GLStateManager.glPopAttrib();
bits.forEach(bit -> verifyState(bit.glEnum(), bit.initial(), bit.name() + " Reset State"));
verifyReset(bits);
}
@Test
@@ -329,15 +340,7 @@ class GLSM_PushPop_UnitTest {
verifyState(GL11.GL_SHADE_MODEL, GL11.GL_FLAT, "Shade Model");
bits.forEach(bit -> {
verifyState(bit.glEnum(), bit.initial(), bit.name() + " Initial State");
if(bit.initial()) {
GLStateManager.glDisable(bit.glEnum());
} else {
GLStateManager.glEnable(bit.glEnum());
}
verifyState(bit.glEnum(), !bit.initial(), bit.name() + " Toggle State");
});
toggleAndVerify(bits);
GLStateManager.glPopAttrib();
verifyState(GL11.GL_COLOR_MATERIAL_FACE, GL11.GL_FRONT_AND_BACK, "Color Material Face - Reset");
@@ -353,7 +356,7 @@ class GLSM_PushPop_UnitTest {
verifyLightState(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, FLOAT_ARRAY_GL_SPOT_DIRECTION_DEFAULT, "Light Spot Direction - Reset");
verifyState(GL11.GL_SHADE_MODEL, GL11.GL_SMOOTH, "Shade Model - Reset");
bits.forEach(bit -> verifyState(bit.glEnum(), bit.initial(), bit.name() + " Reset State"));
verifyReset(bits);
}
@Test
@@ -388,15 +391,7 @@ class GLSM_PushPop_UnitTest {
verifyState(GL13.GL_ACTIVE_TEXTURE, GL13.GL_TEXTURE1, "Active Texture");
verifyState(GL11.GL_TEXTURE_BINDING_2D, tex2, "Texture Binding - Unit 1");
bits.forEach(bit -> {
verifyState(bit.glEnum(), bit.initial(), bit.name() + " Initial State");
if(bit.initial()) {
GLStateManager.glDisable(bit.glEnum());
} else {
GLStateManager.glEnable(bit.glEnum());
}
verifyState(bit.glEnum(), !bit.initial(), bit.name() + " Toggle State");
});
toggleAndVerify(bits);
GLStateManager.glPopAttrib();
verifyState(GL13.GL_ACTIVE_TEXTURE, GL13.GL_TEXTURE0, "Active Texture Unit 0 - Reset");
@@ -404,7 +399,7 @@ class GLSM_PushPop_UnitTest {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
verifyState(GL11.GL_TEXTURE_BINDING_2D, 0, "Texture Binding Unit 1 - Reset");
bits.forEach(bit -> verifyState(bit.glEnum(), bit.initial(), bit.name() + " Reset State"));
verifyReset(bits);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glDeleteTextures(tex1);
@@ -1156,30 +1151,6 @@ class GLSM_PushPop_UnitTest {
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ZERO);
}
@Test
void testPolygonStateReadBack() {
GLStateManager.glCullFace(GL11.GL_FRONT);
GLStateManager.glFrontFace(GL11.GL_CW);
GLStateManager.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_LINE);
verifyState(GL11.GL_CULL_FACE_MODE, GL11.GL_FRONT, "Cull Face Mode");
verifyState(GL11.GL_FRONT_FACE, GL11.GL_CW, "Front Face");
verifyState(GL11.GL_POLYGON_MODE, new int[] { GL11.GL_LINE, GL11.GL_LINE }, "Polygon Mode");
GLStateManager.glCullFace(GL11.GL_BACK);
GLStateManager.glFrontFace(GL11.GL_CCW);
GLStateManager.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
}
@Test
void testActiveTextureReadBack() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
verifyState(GL13.GL_ACTIVE_TEXTURE, GL13.GL_TEXTURE1, "Active Texture");
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
verifyState(GL13.GL_ACTIVE_TEXTURE, GL13.GL_TEXTURE0, "Active Texture - Reset");
}
@Test
void testUntouchedPushPopRoundTripLeavesStateIdentical() {
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
@@ -6,6 +6,8 @@ import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.drainErrors;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.driverTextureBinding;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -15,14 +17,6 @@ public class GLSM_RetainedState_GLTest {
private static final StateSet ALL = StateSet.forMask(GL11.GL_ALL_ATTRIB_BITS);
private static int driverTextureBinding(int unit) {
final int saved = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit);
final int binding = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
GL13.glActiveTexture(saved);
return binding;
}
private static void baseline() {
GLStateManager.enableCull();
GLStateManager.disableBlend();
@@ -234,7 +228,7 @@ public class GLSM_RetainedState_GLTest {
@Test
void aDeletedProgramIsNotRebound() {
final RetainedState retained = new RetainedState();
final int program = GLSM_StateSet_GLTest.linkTrivialProgram();
final int program = GlTestSupport.program(GlTestSupport.TRIVIAL_VS, GlTestSupport.TRIVIAL_FS);
try {
baseline();
final int d = GLStateManager.pushState(ALL);
@@ -243,11 +237,10 @@ public class GLSM_RetainedState_GLTest {
GLStateManager.popStateTo(d);
GLStateManager.glUseProgram(0);
GLStateManager.glDeleteProgram(program);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
drainErrors();
GLStateManager.applyRetainedState(retained);
assertEquals(0, GLStateManager.getActiveProgram());
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glUseProgram(0);
baseline();
@@ -24,7 +24,6 @@ class GLSM_SamplerBinding_GLTest {
@BeforeEach
void requireSamplerObjects() {
assumeTrue(RenderSystem.supportsSamplerObjects());
drainGlErrors();
}
@AfterEach
@@ -35,11 +34,6 @@ class GLSM_SamplerBinding_GLTest {
GL33.glBindSampler(unit, 0);
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
drainGlErrors();
}
private static void drainGlErrors() {
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
private static int driverBinding(int unit) {
@@ -3,6 +3,8 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
@@ -12,6 +14,7 @@ import org.lwjgl.opengl.SharedDrawable;
import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -43,21 +46,13 @@ public class GLSM_ServerSideState_UnitTest {
private boolean originalSplashComplete;
private Thread originalDrawableGLHolder;
private static void setSplashComplete(boolean value) {
SplashWindow.setSplashComplete(value);
}
private static boolean getSplashComplete() {
return SplashWindow.isSplashComplete();
}
@BeforeEach
void setupSharedDrawable() throws Exception {
originalSplashComplete = getSplashComplete();
originalSplashComplete = SplashWindow.isSplashComplete();
originalDrawableGLHolder = GLStateManager.getDrawableGLHolder();
// Reset splash state to simulate splash screen scenario
setSplashComplete(false);
SplashWindow.setSplashComplete(false);
sharedDrawable = new SharedDrawable(Display.getDrawable());
@@ -149,7 +144,7 @@ public class GLSM_ServerSideState_UnitTest {
// Restore original state
GLStateManager.setDrawableGL(null);
GLStateManager.setDrawableGLHolder(originalDrawableGLHolder);
setSplashComplete(originalSplashComplete);
SplashWindow.setSplashComplete(originalSplashComplete);
if (originalSplashComplete) {
GLStateManager.markSplashComplete("test");
}
@@ -214,46 +209,81 @@ public class GLSM_ServerSideState_UnitTest {
GL11.glDeleteTextures(texShared[0]);
}
/** One texture parameter through its typed GLSM setter and getter, plus the raw driver read. Values travel as float. */
private static final class TexParam {
final String name;
final float initial;
final float changed;
final FloatConsumer set;
final FloatSupplier glsm;
final FloatSupplier driver;
TexParam(String name, float initial, float changed, FloatConsumer set, FloatSupplier glsm, FloatSupplier driver) {
this.name = name;
this.initial = initial;
this.changed = changed;
this.set = set;
this.glsm = glsm;
this.driver = driver;
}
@Override
public String toString() {
return name;
}
}
private interface FloatConsumer {
void accept(float value);
}
private interface FloatSupplier {
float get();
}
static Stream<TexParam> texParams() {
return Stream.of(
new TexParam("glTexParameteri MIN_FILTER", GL11.GL_LINEAR, GL11.GL_NEAREST,
v -> GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, (int) v),
() -> GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER),
() -> GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER)),
new TexParam("glTexParameterf LOD_BIAS", 0.0f, 1.5f,
v -> GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, v),
() -> GLStateManager.glGetTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS),
() -> GL11.glGetTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS)));
}
/**
* Test: SharedDrawable sets texture MIN_FILTER, verify both GLSM and actual GL state are correct on both contexts.
* Test: SharedDrawable sets a texture parameter, verify both GLSM and actual GL state are correct on both contexts.
*/
@Test
void testTexParameterFromSharedDrawable() throws InterruptedException {
@ParameterizedTest
@MethodSource("texParams")
void testTexParameterFromSharedDrawable(TexParam param) throws InterruptedException {
int texId = GL11.glGenTextures();
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
param.set.accept(param.initial);
int initialGLSM = GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
assertEquals(GL11.GL_LINEAR, initialGLSM, "Initial GLSM should be GL_LINEAR");
assertEquals(param.initial, param.glsm.get(), 0.001f, "Initial GLSM value");
assertEquals(param.initial, param.driver.get(), 0.001f, "Initial GL value");
int initialGL = GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
assertEquals(GL11.GL_LINEAR, initialGL, "Initial GL should be GL_LINEAR");
// SharedDrawable: bind and change params to GL_NEAREST via GLSM
executeOnSharedDrawable(() -> {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
int boundTex = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
assertEquals(texId, boundTex, "SharedDrawable should have texId bound");
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
param.set.accept(param.changed);
int glsmValue = GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
assertEquals(GL11.GL_NEAREST, glsmValue, "SharedDrawable GLSM should return GL_NEAREST");
int glValue = GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
assertEquals(GL11.GL_NEAREST, glValue, "SharedDrawable GL should be GL_NEAREST");
assertEquals(param.changed, param.glsm.get(), 0.001f, "SharedDrawable GLSM value");
assertEquals(param.changed, param.driver.get(), 0.001f, "SharedDrawable GL value");
});
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
int mainGLSM = GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
assertEquals(GL11.GL_NEAREST, mainGLSM, "Main thread GLSM should return GL_NEAREST");
int mainGL = GL11.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
assertEquals(GL11.GL_NEAREST, mainGL, "Main thread GL should be GL_NEAREST");
assertEquals(param.changed, param.glsm.get(), 0.001f, "Main thread GLSM value");
assertEquals(param.changed, param.driver.get(), 0.001f, "Main thread GL value");
} finally {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
@@ -358,50 +388,6 @@ public class GLSM_ServerSideState_UnitTest {
}
}
/**
* Test glTexParameterf (float parameters like LOD_BIAS).
*/
@Test
void testTexParameterfFromSharedDrawable() throws InterruptedException {
int texId = GL11.glGenTextures();
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 0.0f);
float initialGLSM = GLStateManager.glGetTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS);
float initialGL = GL11.glGetTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS);
assertEquals(0.0f, initialGLSM, 0.001f, "Initial GLSM LOD_BIAS should be 0.0");
assertEquals(0.0f, initialGL, 0.001f, "Initial GL LOD_BIAS should be 0.0");
// SharedDrawable: change LOD_BIAS
executeOnSharedDrawable(() -> {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
int bound = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
assertEquals(texId, bound, "SharedDrawable should have texId bound");
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS, 1.5f);
float glsmValue = GLStateManager.glGetTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS);
assertEquals(1.5f, glsmValue, 0.001f, "SharedDrawable GLSM should return 1.5");
float glValue = GL11.glGetTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS);
assertEquals(1.5f, glValue, 0.001f, "SharedDrawable GL should be 1.5");
});
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
float finalGLSM = GLStateManager.glGetTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS);
float finalGL = GL11.glGetTexParameterf(GL11.GL_TEXTURE_2D, GL14.GL_TEXTURE_LOD_BIAS);
assertEquals(1.5f, finalGLSM, 0.001f, "Main thread GLSM should return 1.5");
assertEquals(1.5f, finalGL, 0.001f, "Main thread GL should be 1.5");
} finally {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glDeleteTextures(texId);
}
}
/**
* Test glTexImage2D: SharedDrawable uploads texture data, verify dimensions cached correctly.
*/
@@ -478,7 +464,7 @@ public class GLSM_ServerSideState_UnitTest {
GL11.glDeleteTextures(deferredGen);
assertTrue(GL11.glIsTexture(texId), "Delete should stay deferred while the splash is not complete");
setSplashComplete(true);
SplashWindow.setSplashComplete(true);
flushGen = GLStateManager.glGenTextures();
assertFalse(GL11.glIsTexture(texId), "Texture should be gone after the post-splash flush");
assertEquals(0, GLStateManager.getBoundTextureForServerState(), "Flush should scrub the main thread binding");
@@ -5,12 +5,17 @@ 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;
@@ -30,13 +35,23 @@ public class GLSM_SplashCachingCoverage_UnitTest {
void leaveSplashWindow() throws IllegalAccessException {
SplashWindow.leave();
GLStateManager.glDepthFunc(GL11.GL_LESS);
GLStateManager.glCullFace(GL11.GL_BACK);
}
@Test
void depthFuncSetDuringSplashLandsInCache() {
GLStateManager.glDepthFunc(GL11.GL_LEQUAL);
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()));
}
assertEquals(GL11.GL_LEQUAL, GLStateManager.getDepthState().getFunc(), "cache write dropped while caching=false");
@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
@@ -137,11 +152,4 @@ public class GLSM_SplashCachingCoverage_UnitTest {
assertFalse(GLStateManager.takeStateSeedPending(), "seed must be consumed exactly once, not once per frame");
assertFalse(GLStateManager.takeStateSeedPending(), "seed must stay consumed");
}
@Test
void cullFaceSetDuringSplashLandsInCache() {
GLStateManager.glCullFace(GL11.GL_FRONT);
assertEquals(GL11.GL_FRONT, GLStateManager.getPolygonState().getCullFaceMode(), "cache write dropped while caching=false");
}
}
@@ -54,18 +54,14 @@ public class GLSM_SplashCompleteLatch_UnitTest {
@Test
void cachingIsThreadScopedBeforeTheLatchAndGlobalAfter() throws Exception {
final AtomicBoolean cachingOffThread = new AtomicBoolean(true);
final Runnable probe = () -> cachingOffThread.set(GLStateManager.isCachingEnabled());
final TestThreads.Body probe = () -> cachingOffThread.set(GLStateManager.isCachingEnabled());
Thread t = new Thread(probe, "non-holder");
t.start();
t.join();
TestThreads.run("non-holder", probe);
assertFalse(cachingOffThread.get(), "caching must be holder-scoped during the dual-context window");
GLStateManager.markSplashComplete("test");
t = new Thread(probe, "non-holder");
t.start();
t.join();
TestThreads.run("non-holder", probe);
assertTrue(cachingOffThread.get(), "after the latch caching is global - this is what the latch buys");
}
File diff suppressed because it is too large. Load diff
@@ -10,13 +10,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
@GLCoreTest
public class GLSM_TextureBindingTargetCache_UnitTest {
private static void drainErrors() {
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
@Test
void flattenedCacheTracksNon2DTargets() {
drainErrors();
final int t3d = GL11.glGenTextures();
final int t2d = GL11.glGenTextures();
try {
@@ -38,13 +33,11 @@ public class GLSM_TextureBindingTargetCache_UnitTest {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glDeleteTextures(t3d);
GLStateManager.glDeleteTextures(t2d);
drainErrors();
}
}
@Test
void proxyTargetBindIsNeitherForwardedNorCached() {
drainErrors();
final int t2d = GL11.glGenTextures();
try {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE5);
@@ -60,13 +53,11 @@ public class GLSM_TextureBindingTargetCache_UnitTest {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glDeleteTextures(t2d);
drainErrors();
}
}
@Test
void popAttribRestoresRecordedTarget() {
drainErrors();
final int t3d = GL11.glGenTextures();
final int t2d = GL11.glGenTextures();
try {
@@ -86,13 +77,11 @@ public class GLSM_TextureBindingTargetCache_UnitTest {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glDeleteTextures(t3d);
GLStateManager.glDeleteTextures(t2d);
drainErrors();
}
}
@Test
void unbindIsNotElidedAcrossTargets() {
drainErrors();
final int t2d = GL11.glGenTextures();
try {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE4);
@@ -100,11 +89,9 @@ public class GLSM_TextureBindingTargetCache_UnitTest {
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, 0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
assertEquals(0, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "2D unbind after a 3D unbind must reach the driver");
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
} finally {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glDeleteTextures(t2d);
drainErrors();
}
}
}
@@ -79,6 +79,8 @@ public class GLSM_TextureUploadGeneration_GLTest {
final int afterSub = info.getUploadGeneration();
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 1, 0, 0, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, BufferUtils.createByteBuffer(4));
assertEquals(afterSub, info.getUploadGeneration(), "only level 0 is tracked");
// Level 1 was never allocated, so the driver rejects that upload.
assertEquals(GL11.GL_INVALID_OPERATION, GL11.glGetError());
} finally {
GLStateManager.glDeleteTextures(texId);
}
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
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.opengl.GL11;
@@ -15,6 +16,13 @@ import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyState;
@GLCompatTest
public class GLSM_VAO_UnitTest {
@AfterEach
void resetClientArrayState() {
glBindVertexArray(0);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE0);
GLSMUtil.setClientState(GL11.GL_NORMAL_ARRAY, false);
}
@Test
void testDefaultStates() {
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, GLStateManager.ctx().defaultVAO, "GL_VERTEX_ARRAY_BINDING Initial State");
@@ -36,14 +44,14 @@ public class GLSM_VAO_UnitTest {
final int vaoVertexType = GL11.GL_INT;
final int vaoVertexStride = vaoVertexSize * 4;
GL13.glClientActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glClientActiveTexture(GL13.GL_TEXTURE1);
verifyState(GL13.GL_CLIENT_ACTIVE_TEXTURE, GL13.GL_TEXTURE1, "GL_CLIENT_ACTIVE_TEXTURE - Setup");
// Set the vertex attrib to a defined state
int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GL11.glVertexPointer(defaultVertexSize, defaultVertexType, defaultVertexStride, 0);
GLStateManager.glVertexPointer(defaultVertexSize, defaultVertexType, defaultVertexStride, 0L);
GLSMUtil.setClientState(GL11.GL_NORMAL_ARRAY, true);
verifyState(GL11.GL_NORMAL_ARRAY, true, "GL_NORMAL_ARRAY - Enabled");
@@ -74,7 +82,7 @@ public class GLSM_VAO_UnitTest {
GLSMUtil.setClientState(GL11.GL_VERTEX_ARRAY, vaoVertexEnabled);
verifyState(GL11.GL_VERTEX_ARRAY, vaoVertexEnabled, "GL_VERTEX_ARRAY - Enabled");
GL11.glVertexPointer(vaoVertexSize, vaoVertexType, vaoVertexStride, 0);
GLStateManager.glVertexPointer(vaoVertexSize, vaoVertexType, vaoVertexStride, 0L);
verifyState(GL11.GL_VERTEX_ARRAY_SIZE, vaoVertexSize, "GL_VERTEX_ARRAY_SIZE - Set");
verifyState(GL11.GL_VERTEX_ARRAY_TYPE, vaoVertexType, "GL_VERTEX_ARRAY_TYPE - Set");
@@ -117,7 +125,7 @@ public class GLSM_VAO_UnitTest {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
}
@@ -138,7 +146,7 @@ public class GLSM_VAO_UnitTest {
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
}
@@ -159,7 +167,7 @@ public class GLSM_VAO_UnitTest {
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
}
@@ -179,7 +187,7 @@ public class GLSM_VAO_UnitTest {
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
GLStateManager.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
}
@@ -31,6 +31,9 @@ public class GLSM_ValueStackCow_GLTest {
if (fixtureTexA == -1) {
fixtureTexA = GLStateManager.glGenTextures();
fixtureTexB = GLStateManager.glGenTextures();
final int bound = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, fixtureTexB);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, bound);
}
}
@@ -43,4 +43,15 @@ class GLVersionParseTest {
assertFalse(RenderSystem.isContextValid(4, 4, -1, false));
assertTrue(RenderSystem.isContextValid(3, 3, 33, true));
}
@Test
void probeCapAfterRejection() {
assertEquals(41, RenderSystem.probeCapAfterRejection(46, 41));
assertEquals(33, RenderSystem.probeCapAfterRejection(46, 11));
assertEquals(33, RenderSystem.probeCapAfterRejection(46, 21));
assertEquals(32, RenderSystem.probeCapAfterRejection(33, 11));
assertEquals(45, RenderSystem.probeCapAfterRejection(46, 46));
assertEquals(45, RenderSystem.probeCapAfterRejection(46, -1));
assertEquals(33, RenderSystem.probeCapAfterRejection(46, 33));
}
}
@@ -4,12 +4,12 @@ import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
@@ -29,10 +29,10 @@ class ImmediateModeThreadIsolationTest {
final AtomicReference<DirectTessellator> bg1 = new AtomicReference<>();
final AtomicReference<DirectTessellator> bg2 = new AtomicReference<>();
final Thread t1 = new Thread(() -> bg1.set(ImmediateModeRecorder.getInternalTessellator()), "iso-test-1");
final Thread t2 = new Thread(() -> bg2.set(ImmediateModeRecorder.getInternalTessellator()), "iso-test-2");
t1.start(); t2.start();
t1.join(); t2.join();
final TestThreads.Worker t1 = TestThreads.start("iso-test-1", () -> bg1.set(ImmediateModeRecorder.getInternalTessellator()));
final TestThreads.Worker t2 = TestThreads.start("iso-test-2", () -> bg2.set(ImmediateModeRecorder.getInternalTessellator()));
t1.join();
t2.join();
assertNotNull(bg1.get());
assertNotNull(bg2.get());
@@ -43,28 +43,22 @@ class ImmediateModeThreadIsolationTest {
@Test
void concurrentImmediateModeDoesNotCorruptRecording() throws Exception {
final List<Throwable> failures = new CopyOnWriteArrayList<>();
final CountDownLatch start = new CountDownLatch(1);
final AtomicBoolean stop = new AtomicBoolean(false);
final Runnable spam = () -> {
try {
start.await();
while (!stop.get()) {
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();
}
} catch (Throwable t) {
failures.add(t);
final TestThreads.Body spam = () -> {
start.await();
while (!stop.get()) {
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();
}
};
final Thread t1 = new Thread(spam, "iso-spam-1");
final Thread t2 = new Thread(spam, "iso-spam-2");
t1.start(); t2.start();
final TestThreads.Worker t1 = TestThreads.start("iso-spam-1", spam);
final TestThreads.Worker t2 = TestThreads.start("iso-spam-2", spam);
start.countDown();
final List<Integer> lists = new ArrayList<>();
@@ -83,10 +77,10 @@ class ImmediateModeThreadIsolationTest {
}
} finally {
stop.set(true);
t1.join(5000); t2.join(5000);
}
t1.join();
t2.join();
assertTrue(failures.isEmpty(), "background immediate mode threw: " + failures);
for (int list : lists) {
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(list);
assertNotNull(compiled, "list " + list + " should be compiled");
@@ -97,9 +91,7 @@ class ImmediateModeThreadIsolationTest {
@Test
void sweepFreesDeadThreadTessellators() throws Exception {
final Thread t = new Thread(() -> ImmediateModeRecorder.getInternalTessellator(), "iso-sweep");
t.start();
t.join();
TestThreads.run("iso-sweep", ImmediateModeRecorder::getInternalTessellator);
final int before = ImmediateModeRecorder.getThreadTessellatorCount();
assertTrue(before >= 1);
ImmediateModeRecorder.cleanupOrphanTessellators();
@@ -2,17 +2,19 @@ package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.function.IntConsumer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@GLCoreTest
class InstancedQuadDrawGLTest {
@@ -26,7 +28,7 @@ class InstancedQuadDrawGLTest {
@BeforeEach
void setup() {
program = buildProgram();
program = GlTestSupport.program(VS, FS);
GLStateManager.glUseProgram(program);
final FloatBuffer verts = BufferUtils.createFloatBuffer(8);
@@ -42,63 +44,39 @@ class InstancedQuadDrawGLTest {
ebo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, idx, GL15.GL_STATIC_DRAW);
drainErrors();
}
@AfterEach
void cleanup() {
GLStateManager.glUseProgram(0);
if (program != 0) GL20.glDeleteProgram(program);
if (program != 0) GLStateManager.glDeleteProgram(program);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
if (vbo != 0) GLStateManager.glDeleteBuffers(vbo);
if (ebo != 0) GLStateManager.glDeleteBuffers(ebo);
drainErrors();
}
@Test
void drawArraysInstanced_quads_noInvalidEnum() {
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, 4, 3);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "GL_QUADS instanced array draw must be emulated, not rejected");
static Stream<Arguments> draws() {
return Stream.of(
draw("GL_QUADS instanced array draw must be emulated, not rejected", ebo -> GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, 4, 3)),
draw("non-quad instanced array draw passes through cleanly", ebo -> {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 3);
}),
draw("GL_QUADS instanced element draw must be emulated, not rejected", ebo -> {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElementsInstanced(GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_INT, 0L, 3);
}));
}
@Test
void drawArraysInstanced_triangles_fastPathClean() {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 3);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "non-quad instanced array draw passes through cleanly");
private static Arguments draw(String name, IntConsumer body) {
return Arguments.of(name, body);
}
@Test
void drawElementsInstanced_quads_noInvalidEnum() {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElementsInstanced(GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_INT, 0L, 3);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "GL_QUADS instanced element draw must be emulated, not rejected");
}
private static int buildProgram() {
final int vs = compile(GL20.GL_VERTEX_SHADER, VS);
final int fs = compile(GL20.GL_FRAGMENT_SHADER, FS);
final int p = GL20.glCreateProgram();
GL20.glAttachShader(p, vs);
GL20.glAttachShader(p, fs);
GL20.glLinkProgram(p);
assertNotEquals(0, GL20.glGetProgrami(p, GL20.GL_LINK_STATUS), "test shader must link: " + GL20.glGetProgramInfoLog(p, 1024));
GL20.glDeleteShader(vs);
GL20.glDeleteShader(fs);
return p;
}
private static int compile(int type, String src) {
final int s = GL20.glCreateShader(type);
GL20.glShaderSource(s, src);
GL20.glCompileShader(s);
assertNotEquals(0, GL20.glGetShaderi(s, GL20.GL_COMPILE_STATUS), "test shader must compile: " + GL20.glGetShaderInfoLog(s, 1024));
return s;
}
private static void drainErrors() {
while (GL11.glGetError() != GL11.GL_NO_ERROR) { /* clear */ }
@ParameterizedTest(name = "{0}")
@MethodSource("draws")
void instancedDrawRaisesNoGlError(String name, IntConsumer draw) {
draw.accept(ebo);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), name);
}
}
@@ -81,11 +81,6 @@ class QuadConverterTest {
}
}
@Test
void triangulateQuads_zeroQuads_noop() {
QuadConverter.triangulateQuads(0L, GL11.GL_UNSIGNED_INT, 0L, GL11.GL_UNSIGNED_INT, 0);
}
@Test
void triangulateQuads_unsupportedTypes_throw() {
final ByteBuffer src = MemoryUtilities.memAlloc(16);
@@ -240,8 +235,6 @@ class QuadConverterTest {
}
}
// === helpers ===
private static void assertUintIndices(ByteBuffer buf, int... expected) {
for (int i = 0; i < expected.length; i++) {
assertEquals(expected[i], buf.getInt(i * 4), "uint index[" + i + "]");
@@ -1,72 +1,42 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.stream.Stream;
@GLCoreTest
class ReadbackGLTest {
@AfterEach
void noErrors() {
void resetPack() {
ReadbackFixture.resetPack();
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void rgba8() {
ReadbackFixture.rgba8();
static Stream<Arguments> readbacks() {
return Stream.of(
format("rgba8", ReadbackFixture::rgba8),
format("packState", ReadbackFixture::packState),
format("pixelPackBuffer", ReadbackFixture::pixelPackBuffer),
format("rgb8", ReadbackFixture::rgb8),
format("rgba16f", ReadbackFixture::rgba16f),
format("rgba32f", ReadbackFixture::rgba32f),
format("r8", ReadbackFixture::r8),
format("rg16f", ReadbackFixture::rg16f),
format("rgb10a2", ReadbackFixture::rgb10a2),
format("r11g11b10f", ReadbackFixture::r11g11b10f),
format("depthStencil", ReadbackFixture::depthStencil));
}
@Test
void packState() {
ReadbackFixture.packState();
private static Arguments format(String name, Runnable body) {
return Arguments.of(name, body);
}
@Test
void pixelPackBuffer() {
ReadbackFixture.pixelPackBuffer();
}
@Test
void rgb8() {
ReadbackFixture.rgb8();
}
@Test
void rgba16f() {
ReadbackFixture.rgba16f();
}
@Test
void rgba32f() {
ReadbackFixture.rgba32f();
}
@Test
void r8() {
ReadbackFixture.r8();
}
@Test
void rg16f() {
ReadbackFixture.rg16f();
}
@Test
void rgb10a2() {
ReadbackFixture.rgb10a2();
}
@Test
void r11g11b10f() {
ReadbackFixture.r11g11b10f();
}
@Test
void depthStencil() {
ReadbackFixture.depthStencil();
@ParameterizedTest(name = "{0}")
@MethodSource("readbacks")
void readback(String name, Runnable body) {
body.run();
}
}
@@ -9,6 +9,7 @@ import org.lwjgl.opengl.GL13;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.drainErrors;
import static org.junit.jupiter.api.Assertions.assertEquals;
@GLCoreTest
@@ -71,8 +72,7 @@ public class DSAUnsupportedBindingTest {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, t);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, y);
GLStateManager.glBindTexture(GL12.GL_TEXTURE_3D, x);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {
}
drainErrors();
dsa.texParameteri(t, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
@@ -18,7 +18,7 @@ class FFPUniformBlockLayoutGLTest {
void driverOffsetsMatchJavaConstants() {
final VertexKey vk = VertexKey.fromState(false, false, false, false, 0);
final FragmentKey fk = FragmentKey.fromState();
final Program program = Program.create(vk, fk, VertexShaderGenerator.generate(vk), FragmentShaderGenerator.generate(fk), null);
final Program program = Program.create(vk, fk, VertexShaderGenerator.generate(vk), FragmentShaderGenerator.generate(fk));
try {
final int programId = program.getProgramId();
final int blockIndex = GL31.glGetUniformBlockIndex(programId, FFPUniformBlock.BLOCK_NAME);
@@ -42,7 +42,7 @@ class FFPUniformValuesTest {
final VertexKey vk = VertexKey.fromState(false, false, false, false, 0);
final FragmentKey fk = FragmentKey.fromState();
final Program program = Program.create(vk, fk, VertexShaderGenerator.generate(vk), FragmentShaderGenerator.generate(fk), null);
final Program program = Program.create(vk, fk, VertexShaderGenerator.generate(vk), FragmentShaderGenerator.generate(fk));
final Uniforms uniforms = new Uniforms();
try {
GL20.glUseProgram(program.getProgramId());
@@ -7,15 +7,19 @@ import org.joml.Vector3f;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.stream.Stream;
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.COLOR_BIT;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.floats;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -112,12 +116,6 @@ class FFPVertexLightingGLTest {
+ " light1=" + GLStateManager.getLightStates()[1].isEnabled();
}
private static FloatBuffer floats(float... values) {
final FloatBuffer buf = BufferUtils.createFloatBuffer(values.length);
buf.put(values).flip();
return buf;
}
private void buildTriangle() {
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
@@ -156,9 +154,8 @@ class FFPVertexLightingGLTest {
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "FFP draw must not raise a GL error");
final ByteBuffer pixel = BufferUtils.createByteBuffer(4);
GL11.glReadPixels(400, 200, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixel);
return new float[] { (pixel.get(0) & 0xFF) / 255.0f, (pixel.get(1) & 0xFF) / 255.0f, (pixel.get(2) & 0xFF) / 255.0f };
final int[] pixel = FfpFixture.readPixel(400, 200);
return new float[] { pixel[0] / 255.0f, pixel[1] / 255.0f, pixel[2] / 255.0f };
}
private void assertSolverMatchesShader(String context) {
@@ -170,23 +167,20 @@ class FFPVertexLightingGLTest {
}
}
@Test
void faceOnNormalMatchesGeneratedShader() {
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
assertSolverMatchesShader("face-on normal");
static Stream<Arguments> normals() {
return Stream.of(
Arguments.of("face-on normal", 0.0f, 0.0f, 1.0f, Float.POSITIVE_INFINITY),
// A 0.0104-long normal must stay at the 0.4 light model ambient floor rather than pick up 0.6 per light
Arguments.of("sign normal", 0.0f, 0.0f, -0.010416667f, 0.45f),
Arguments.of("tilted normal", 0.3f, 0.8f, 0.5f, Float.POSITIVE_INFINITY));
}
@Test
void signNormalMatchesGeneratedShader() {
GLStateManager.glNormal3f(0.0f, 0.0f, -0.010416667f);
assertSolverMatchesShader("sign normal");
assertTrue(factor.x < 0.45f, "a 0.0104-long normal must stay at the 0.4 light model ambient floor rather than pick up 0.6 per light, got " + factor.x);
}
@Test
void tiltedNormalMatchesGeneratedShader() {
GLStateManager.glNormal3f(0.3f, 0.8f, 0.5f);
assertSolverMatchesShader("tilted normal");
@ParameterizedTest(name = "{0}")
@MethodSource("normals")
void normalMatchesGeneratedShader(String name, float x, float y, float z, float factorCeiling) {
GLStateManager.glNormal3f(x, y, z);
assertSolverMatchesShader(name);
assertTrue(factor.x < factorCeiling, name + ": factor must stay below " + factorCeiling + ", got " + factor.x);
}
@Test
@@ -206,27 +200,31 @@ class FFPVertexLightingGLTest {
assertTrue(factor.x > 1.2f, "normalize restores the unit normal, which must pick up more diffuse than rescale's 0.5-long one, got " + factor.x);
}
@Test
void materialEmissionIsNotRepresentable() {
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_EMISSION, floats(0.1f, 0.1f, 0.1f, 1.0f));
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), "emission is additive, so the lit colour is not vertexColor * factor");
static Stream<Arguments> unrepresentableStates() {
return Stream.of(
unrepresentable("emission is additive, so the lit colour is not vertexColor * factor",
() -> GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_EMISSION, floats(0.1f, 0.1f, 0.1f, 1.0f))),
unrepresentable("specular is additive, so the lit colour is not vertexColor * factor", () -> {
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_SPECULAR, floats(1.0f, 1.0f, 1.0f, 1.0f));
GLStateManager.glMaterialf(GL11.GL_FRONT_AND_BACK, GL11.GL_SHININESS, 16.0f);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, floats(1.0f, 1.0f, 1.0f, 1.0f));
}),
unrepresentable("only AMBIENT_AND_DIFFUSE makes the lit colour scale with the vertex colour",
() -> GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_EMISSION)));
}
private static Arguments unrepresentable(String reason, Runnable state) {
return Arguments.of(reason, state);
}
@ParameterizedTest(name = "{0}")
@MethodSource("unrepresentableStates")
void additiveLightingIsNotRepresentable(String reason, Runnable state) {
state.run();
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), reason);
assertEquals(IDENTITY_FACTOR, factor, "a rejected solve must leave an identity factor");
}
@Test
void specularContributionIsNotRepresentable() {
GLStateManager.glMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_SPECULAR, floats(1.0f, 1.0f, 1.0f, 1.0f));
GLStateManager.glMaterialf(GL11.GL_FRONT_AND_BACK, GL11.GL_SHININESS, 16.0f);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPECULAR, floats(1.0f, 1.0f, 1.0f, 1.0f));
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), "specular is additive, so the lit colour is not vertexColor * factor");
}
@Test
void nonAmbientAndDiffuseColorMaterialIsNotRepresentable() {
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_EMISSION);
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), "only AMBIENT_AND_DIFFUSE makes the lit colour scale with the vertex colour");
}
@Test
void lightsBeyondTheFfpCountAreIgnored() {
assertTrue(FFPVertexLighting.modulatesVertexColor(factor), FFPVertexLightingGLTest::gateState);
@@ -11,10 +11,10 @@ import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.floats;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@@ -25,12 +25,6 @@ class FfpUniformBlockDedupTest {
return GL30.glGetInteger(GL31.GL_UNIFORM_BUFFER_START, FFPUniformBlock.BINDING_POINT);
}
private static FloatBuffer floats(float... values) {
final FloatBuffer buf = BufferUtils.createFloatBuffer(values.length);
buf.put(values).flip();
return buf;
}
private static DoubleBuffer doubles(double... values) {
final DoubleBuffer buf = BufferUtils.createDoubleBuffer(values.length);
buf.put(values).flip();
@@ -21,6 +21,7 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutFloat;
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.COLOR_BIT;
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.NORMAL_BIT;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.drainErrors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -100,7 +101,7 @@ class InstancedFFPDrawGLTest {
GLSMHooks.blendHandler = new LoggingBlendHandler();
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 0);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
drainErrors();
assertEquals(List.of("resolve", "flush"), log);
}
@@ -36,7 +36,6 @@ class LogicOpGLTest {
GLStateManager.glDisable(GL11.GL_BLEND);
LogicOpFixture.deleteResources();
FfpFixture.resetFfpState();
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
@Test
@@ -48,7 +47,6 @@ class LogicOpGLTest {
assertRgb(op, src);
}
}
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
@@ -15,7 +15,7 @@ class ProgramUniformTrackingTest {
private static Program buildProgram(boolean hasColor) {
final VertexKey vk = VertexKey.fromState(hasColor, false, false, false, 0);
final FragmentKey fk = FragmentKey.fromState();
return Program.create(vk, fk, VertexShaderGenerator.generate(vk), FragmentShaderGenerator.generate(fk), null);
return Program.create(vk, fk, VertexShaderGenerator.generate(vk), FragmentShaderGenerator.generate(fk));
}
@Test
@@ -5,15 +5,18 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.FloatBuffer;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@GLCoreTest
class WideLineClipGLTest {
@@ -23,7 +26,7 @@ class WideLineClipGLTest {
private static final float LINE_WIDTH = 3.0f;
private static final int BACKGROUND = 0xFF000000;
private static final float[] IN_FRONT = { 0f, -1f, -2f };
private static final float[] IN_FRONT = { 0f, -0.9875f, -2f };
private static final float[] BEHIND = { 0f, 0.8f, 2f };
private boolean savedEmulation;
@@ -32,7 +35,7 @@ class WideLineClipGLTest {
@BeforeEach
void setUp() {
assumeTrue(GLStateManager.supportsGeometryShaders());
FfpFixture.routeVertexFormats(true);
savedEmulation = GLStateManager.wideLineEmulationEnabled;
GLStateManager.wideLineEmulationEnabled = true;
@@ -51,6 +54,7 @@ class WideLineClipGLTest {
@AfterEach
void tearDown() {
FfpFixture.routeVertexFormats(false);
GLStateManager.wideLineEmulationEnabled = savedEmulation;
GLStateManager.glLineWidth(1.0f);
if (vbo != 0) { GLStateManager.glDeleteBuffers(vbo); vbo = 0; }
@@ -59,17 +63,18 @@ class WideLineClipGLTest {
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
FfpFixture.resetFfpState();
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
@Test
void lineEndingBehindTheCameraKeepsItsWidth() {
assertConstantWidth(IN_FRONT, BEHIND);
static Stream<Arguments> linesCrossingTheNearPlane() {
return Stream.of(
Arguments.of("line ending behind the camera", IN_FRONT, BEHIND),
Arguments.of("line starting behind the camera", BEHIND, IN_FRONT));
}
@Test
void lineStartingBehindTheCameraKeepsItsWidth() {
assertConstantWidth(BEHIND, IN_FRONT);
@ParameterizedTest(name = "{0}")
@MethodSource("linesCrossingTheNearPlane")
void lineCrossingTheNearPlaneKeepsItsWidth(String name, float[] start, float[] end) {
assertConstantWidth(start, end);
}
@Test
@@ -80,6 +85,60 @@ class WideLineClipGLTest {
}
}
private static void orthographic() {
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
private static float pixelCenter(int pixel) {
return (pixel + 0.5f) * 2.0f / SIZE - 1.0f;
}
private static int litInColumn(int[] pixels, int x) {
int lit = 0;
for (int row = 0; row < SIZE; row++) {
if (pixels[row * SIZE + x] != BACKGROUND) lit++;
}
return lit;
}
private int[] drawLineOnce(float[] start, float[] end) {
final int[] pixels = drawLine(start, end);
GLStateManager.glDeleteBuffers(vbo);
GLStateManager.glDeleteVertexArrays(vao);
vbo = 0;
vao = 0;
return pixels;
}
@Test
void wideLineCoversItsFirstPixelButNotItsLast() {
orthographic();
GLStateManager.glLineWidth(2.0f);
final float y = pixelCenter(16);
final int[] pixels = drawLineOnce(new float[] { pixelCenter(40), y, 0f }, new float[] { pixelCenter(20), y, 0f });
assertEquals(2, litInColumn(pixels, 40), "first pixel of a right-to-left line");
assertEquals(0, litInColumn(pixels, 20), "last pixel of a right-to-left line");
assertEquals(2, litInColumn(pixels, 21), "pixel next to the last");
}
@Test
void cullingKeepsWideLinesInBothDirections() {
orthographic();
GLStateManager.glLineWidth(2.0f);
GLStateManager.glEnable(GL11.GL_CULL_FACE);
try {
final int[] rightward = drawLineOnce(new float[] { -0.8f, 0f, 0f }, new float[] { 0.8f, 0f, 0f });
final int[] leftward = drawLineOnce(new float[] { 0.8f, 0f, 0f }, new float[] { -0.8f, 0f, 0f });
assertEquals(2, litInColumn(rightward, SIZE / 2), "left-to-right line with culling on");
assertEquals(2, litInColumn(leftward, SIZE / 2), "right-to-left line with culling on");
assertTrue(GLStateManager.getCullState().isEnabled(), "the game's cull state survives");
} finally {
GLStateManager.glDisable(GL11.GL_CULL_FACE);
}
}
private void assertConstantWidth(float[] start, float[] end) {
final int[] pixels = drawLine(start, end);
for (int row = 0; row < SIZE / 4; row++) {
@@ -90,7 +149,6 @@ class WideLineClipGLTest {
final int y = row, count = lit;
assertTrue(count > 0 && count <= LINE_WIDTH + 1, () -> "row " + y + " is " + count + " pixels wide");
}
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
private int[] drawLine(float[] start, float[] end) {
@@ -2,6 +2,9 @@ package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
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.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
@@ -12,6 +15,7 @@ import org.objectweb.asm.tree.MethodNode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
@@ -71,14 +75,16 @@ class ModRedirectorTest {
assertEquals(coreExclusions.length + 2, exclusions.size(), exclusions.toString());
}
@Test
void exclusionsCombineCoreLwjgl3AndExtra() {
assertStandardExclusions(redirector().getTransformerExclusions());
static Stream<Arguments> exclusionLists() {
return Stream.of(
Arguments.of("ModRedirector", redirector().getTransformerExclusions()),
Arguments.of("EarlyRedirectorCore", new EarlyRedirectorCore(EXTRA_EXCLUSION).getTransformerExclusions()));
}
@Test
void earlyRedirectorExclusionsCombineCoreLwjgl3AndExtra() {
assertStandardExclusions(new EarlyRedirectorCore(EXTRA_EXCLUSION).getTransformerExclusions());
@ParameterizedTest(name = "{0}")
@MethodSource("exclusionLists")
void exclusionsCombineCoreLwjgl3AndExtra(String name, String[] exclusions) {
assertStandardExclusions(exclusions);
}
@Test
@@ -86,22 +92,17 @@ class ModRedirectorTest {
assertNull(redirector().transform("com.example.TestClass", null, this));
}
@Test
void excludedPrefixReturnsOriginalArray() {
final byte[] original = bytesWithGlCall();
assertSame(original, redirector().transform(EXTRA_EXCLUSION + ".TestClass", original, this));
static Stream<Arguments> untouchedClasses() {
return Stream.of(
Arguments.of("extra exclusion prefix", EXTRA_EXCLUSION + ".TestClass", bytesWithGlCall()),
Arguments.of("core exclusion prefix", "org.lwjgl.opengl.TestClass", bytesWithGlCall()),
Arguments.of("no GL call", "com.example.TestClass", bytesWithoutGlCall()));
}
@Test
void coreExcludedPrefixReturnsOriginalArray() {
final byte[] original = bytesWithGlCall();
assertSame(original, redirector().transform("org.lwjgl.opengl.TestClass", original, this));
}
@Test
void unchangedClassReturnsOriginalArray() {
final byte[] original = bytesWithoutGlCall();
assertSame(original, redirector().transform("com.example.TestClass", original, this));
@ParameterizedTest(name = "{0}")
@MethodSource("untouchedClasses")
void untouchedClassReturnsOriginalArray(String name, String className, byte[] original) {
assertSame(original, redirector().transform(className, original, this));
}
@Test
@@ -1,6 +1,8 @@
package com.gtnewhorizons.angelica.glsm.profiling;
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;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -8,32 +10,26 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class TracyOptionsTest {
@Test
void resolveBooleanFallsBackToConfigWhenNoOverride() {
assertTrue(TracyOptions.resolveBoolean(null, true));
assertFalse(TracyOptions.resolveBoolean(null, false));
@ParameterizedTest(name = "{0}")
@CsvSource(value = {
"no override falls back to config true, null, true, true",
"no override falls back to config false, null, false, false",
"override true wins, true, false, true",
"override false wins, false, true, false"
}, nullValues = "null")
void resolveBoolean(String name, Boolean override, boolean config, boolean expected) {
assertEquals(expected, TracyOptions.resolveBoolean(override, config));
}
@Test
void resolveBooleanOverrideWinsEitherWay() {
assertTrue(TracyOptions.resolveBoolean(Boolean.TRUE, false));
assertFalse(TracyOptions.resolveBoolean(Boolean.FALSE, true));
}
@Test
void resolveMaxSrcLocsFallsBackToConfigWhenNoOverride() {
assertEquals(4096, TracyOptions.resolveMaxSrcLocs(null, 4096));
}
@Test
void resolveMaxSrcLocsOverrideWins() {
assertEquals(8192, TracyOptions.resolveMaxSrcLocs(8192, 4096));
}
@Test
void resolveMaxSrcLocsClampsBothConfigAndOverrideToTheMinimum() {
assertEquals(16, TracyOptions.resolveMaxSrcLocs(null, 4));
assertEquals(16, TracyOptions.resolveMaxSrcLocs(1, 4096));
@ParameterizedTest(name = "{0}")
@CsvSource(value = {
"no override falls back to config, null, 4096, 4096",
"override wins, 8192, 4096, 8192",
"config is clamped to the minimum, null, 4, 16",
"override is clamped to the minimum, 1, 4096, 16"
}, nullValues = "null")
void resolveMaxSrcLocs(String name, Integer override, int config, int expected) {
assertEquals(expected, TracyOptions.resolveMaxSrcLocs(override, config));
}
@Test
@@ -62,25 +62,6 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
MemoryUtilities.memFree(read);
}
@Test
void compileAndExecute_firesLiveAndBakes() {
final int vbo = defaultQuadVbo();
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
final int vao = setupPosColorVao(vbo);
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
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();
final BatchedIndexedDrawCmd cmd = firstBatchedCmd(list);
assertNotNull(cmd);
assertEquals(3, cmd.getIndexCount());
}
@Test
void quads_triangulateAtRecord() {
final int vbo = defaultQuadVbo();
@@ -457,11 +438,6 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
assertNotEquals(cmds.get(0).getSharedVAO(), cmds.get(1).getSharedVAO(), "VAOs from separate batches must be distinct");
}
@Test
void nullListProbeCompiles() {
assertNull(DisplayListManager.getDisplayList(0));
}
@Test
void multiLayoutDraws_emitInInsertionOrder() {
// Two distinct layouts. The builder uses a LinkedHashMap, so the first
@@ -1,24 +1,30 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
import com.gtnewhorizons.angelica.glsm.recording.support.DisplayListTestFixture;
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.opengl.GL15;
import org.lwjgl.opengl.GL20;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.drainErrors;
import static org.junit.jupiter.api.Assertions.assertEquals;
class IndexedDrawDeleteSafetyTest extends DisplayListTestFixture {
@Test
void vaoEboMapSweep_clearsStaleEntryOnForeignVao() {
@ParameterizedTest(name = "replayed before delete={0}")
@ValueSource(booleans = {false, true})
void listDelete_clearsSharedEboFromForeignVao(boolean replayFirst) {
final int vbo = defaultQuadVbo();
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
final int vao = setupPosColorVao(vbo);
final int list = recordSingleIndexedDraw(vao, ebo, GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT);
if (replayFirst) compiledOf(list).render();
final IndexedDrawBatch batch = firstBatch(list);
final int sharedEbo = batch.getSharedEBO();
@@ -31,32 +37,13 @@ class IndexedDrawDeleteSafetyTest extends DisplayListTestFixture {
untrackList(list);
GLStateManager.glBindVertexArray(foreignVao);
assertEquals(0, GLStateManager.getBoundEBO(), "vaoEboMap still references the freed sharedEBO — non-gen crash imminent");
GLStateManager.glBindVertexArray(0);
}
assertEquals(0, GLStateManager.getBoundEBO(), "vaoEboMap still references the freed sharedEBO - non-gen crash imminent");
@Test
void bindBufferCycle_afterListDelete_isGlNoError() {
final int vbo = defaultQuadVbo();
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
final int vao = setupPosColorVao(vbo);
final int list = recordSingleIndexedDraw(vao, ebo, GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT);
final int sharedEbo = firstBatch(list).getSharedEBO();
final int foreignVao = newVao();
GLStateManager.glBindVertexArray(foreignVao);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEbo);
GLStateManager.glBindVertexArray(0);
GLStateManager.glDeleteLists(list, 1);
untrackList(list);
GLStateManager.glBindVertexArray(foreignVao);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
drainErrors();
final int prevEbo = GLStateManager.getBoundEBO();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
final int err = GL11.glGetError();
assertEquals(GL11.GL_NO_ERROR, err, () -> "bindBuffer(prevEbo=" + prevEbo + ") hit GL 0x" + Integer.toHexString(err) + " — cache carried a freed id through a VAO rebind");
assertEquals(GL11.GL_NO_ERROR, err, () -> "bindBuffer(prevEbo=" + prevEbo + ") hit GL 0x" + Integer.toHexString(err) + " - cache carried a freed id through a VAO rebind");
GLStateManager.glBindVertexArray(0);
}
@@ -85,8 +72,8 @@ class IndexedDrawDeleteSafetyTest extends DisplayListTestFixture {
@Test
void glDeleteProgram_zero_isNoOpAndLeavesActiveProgram() {
final int prog = linkMinimalProgram();
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
final int prog = GlTestSupport.program(GlTestSupport.TRIVIAL_VS, GlTestSupport.TRIVIAL_FS);
drainErrors();
GLStateManager.glUseProgram(prog);
assertEquals(prog, GLStateManager.getActiveProgram());
@@ -97,13 +84,13 @@ class IndexedDrawDeleteSafetyTest extends DisplayListTestFixture {
GLStateManager.glUseProgram(0);
GLStateManager.glDeleteProgram(prog);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
drainErrors();
}
@Test
void activeProgram_retainedAfterDelete_perGlSpec() {
final int prog = linkMinimalProgram();
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
final int prog = GlTestSupport.program(GlTestSupport.TRIVIAL_VS, GlTestSupport.TRIVIAL_FS);
drainErrors();
GLStateManager.glUseProgram(prog);
assertEquals(prog, GLStateManager.getActiveProgram());
@@ -114,38 +101,6 @@ class IndexedDrawDeleteSafetyTest extends DisplayListTestFixture {
GLStateManager.glUseProgram(0);
assertEquals(0, GLStateManager.getActiveProgram(), "activeProgram clears once the deleted program is unbound");
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
drainErrors();
}
private static int linkMinimalProgram() {
final int vert = GLStateManager.glCreateShader(GL20.GL_VERTEX_SHADER);
GLStateManager.glShaderSource(vert, MIN_VERT_SRC);
GLStateManager.glCompileShader(vert);
if (GL20.glGetShaderi(vert, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
throw new AssertionError("vertex shader failed to compile: " + GL20.glGetShaderInfoLog(vert, 4096));
}
final int frag = GLStateManager.glCreateShader(GL20.GL_FRAGMENT_SHADER);
GLStateManager.glShaderSource(frag, MIN_FRAG_SRC);
GLStateManager.glCompileShader(frag);
if (GL20.glGetShaderi(frag, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
throw new AssertionError("fragment shader failed to compile: " + GL20.glGetShaderInfoLog(frag, 4096));
}
final int prog = GLStateManager.glCreateProgram();
GLStateManager.glAttachShader(prog, vert);
GLStateManager.glAttachShader(prog, frag);
GLStateManager.glLinkProgram(prog);
if (GL20.glGetProgrami(prog, GL20.GL_LINK_STATUS) == GL11.GL_FALSE) {
throw new AssertionError("program failed to link: " + GL20.glGetProgramInfoLog(prog, 4096));
}
GLStateManager.glDetachShader(prog, vert);
GLStateManager.glDetachShader(prog, frag);
GLStateManager.glDeleteShader(vert);
GLStateManager.glDeleteShader(frag);
return prog;
}
private static final String MIN_VERT_SRC = "#version 330 core\nvoid main(){gl_Position=vec4(0.0);}";
private static final String MIN_FRAG_SRC = "#version 330 core\nout vec4 oC;void main(){oC=vec4(1.0);}";
}
@@ -15,6 +15,7 @@ import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.List;
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.drainErrors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -36,7 +37,7 @@ class IndexedDrawReplayTest extends DisplayListTestFixture {
GL11.glViewport(0, 0, 16, 16);
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
drainErrors();
final int query = GL15.glGenQueries();
GL15.glBeginQuery(GL15.GL_SAMPLES_PASSED, query);
@@ -92,7 +93,7 @@ class IndexedDrawReplayTest extends DisplayListTestFixture {
GLStateManager.glBindVertexArray(distractorVao);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
drainErrors();
compiledOf(list).render();
@@ -103,35 +104,6 @@ class IndexedDrawReplayTest extends DisplayListTestFixture {
GLStateManager.glBindVertexArray(0);
}
@Test
void replayThenDelete_thenBindBufferCycle_isGlNoError() {
final int vbo = defaultQuadVbo();
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
final int vao = setupPosColorVao(vbo);
final int list = recordSingleIndexedDraw(vao, ebo, GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT);
compiledOf(list).render();
final int sharedEbo = firstBatch(list).getSharedEBO();
final int foreignVao = newVao();
GLStateManager.glBindVertexArray(foreignVao);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEbo);
GLStateManager.glBindVertexArray(0);
GLStateManager.glDeleteLists(list, 1);
untrackList(list);
GLStateManager.glBindVertexArray(foreignVao);
assertEquals(0, GLStateManager.getBoundEBO(), "batched EBO id survived list delete in vaoEboMap — non-gen crash imminent");
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
final int prevEbo = GLStateManager.getBoundEBO();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
final int err = GL11.glGetError();
assertEquals(GL11.GL_NO_ERROR, err, () -> "post-delete bindBuffer(" + prevEbo + ") hit GL 0x" + Integer.toHexString(err));
GLStateManager.glBindVertexArray(0);
}
@Test
void execute_doesNotRestoreVAO_backToBackDraws() {
final int vbo = defaultQuadVbo();
@@ -153,7 +125,7 @@ class IndexedDrawReplayTest extends DisplayListTestFixture {
final int distractor = newVao();
GLStateManager.glBindVertexArray(distractor);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
drainErrors();
compiledOf(list).render();
@@ -229,7 +201,7 @@ class IndexedDrawReplayTest extends DisplayListTestFixture {
GLStateManager.glDrawElements(GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glEndList();
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
drainErrors();
compiledOf(list).render();
compiledOf(list).render();
@@ -3,10 +3,8 @@ package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizons.angelica.glsm.GLCompatTest;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLCompatTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
@@ -33,37 +31,6 @@ class RecordingDataStructuresTest {
}
}
@Test
void testCompiledDisplayListCreation() {
// Create a display list with PushMatrix, Scale, PopMatrix
testList = GL11.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
GLStateManager.glPushMatrix();
GLStateManager.glScalef(2.0f, 2.0f, 2.0f);
GLStateManager.glPopMatrix();
GLStateManager.glEndList();
// Verify the compiled display list
CompiledDisplayList compiled = DisplayListManager.getDisplayList(testList);
assertNotNull(compiled, "Display list should be compiled");
assertNotNull(compiled.getCommandBuffer(), "Command buffer should not be null");
// Get command counts to verify structure
Int2IntMap counts = compiled.getCommandCounts();
// Should have PushMatrix and PopMatrix
assertEquals(1, counts.getOrDefault(GLCommand.PUSH_MATRIX, 0), "Should have 1 PushMatrix");
assertEquals(1, counts.getOrDefault(GLCommand.POP_MATRIX, 0), "Should have 1 PopMatrix");
// Scale should be collapsed into a MultMatrix (optimizer collapses transforms)
// Or if in debug mode, might have individual commands - just check we have some transforms
int transformCount = counts.getOrDefault(GLCommand.MULT_MATRIX, 0)
+ counts.getOrDefault(GLCommand.SCALE, 0);
assertTrue(transformCount >= 0, "Should have transform commands");
}
@Test
void testImmediateModeRecorder() {
DirectTessellator recorder = ImmediateModeRecorder.getInternalTessellator();
@@ -1,8 +1,13 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
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 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.assertNotEquals;
@@ -15,59 +20,50 @@ class AttribLayoutKeyTest {
return out;
}
@Test
void standardPosColorUvNormal_isAlignedAndPacked() {
final AttribLayoutKey k = new AttribLayoutKey(
new int[]{0, 1, 2, 4},
new int[]{3, 4, 2, 3},
new int[]{GL11.GL_FLOAT, GL11.GL_UNSIGNED_BYTE, GL11.GL_FLOAT, GL11.GL_BYTE},
new boolean[]{false, true, false, true});
assertArrayEquals(new int[]{0, 12, 16, 24}, offsetsOf(k));
assertEquals(28, k.stride());
static Stream<Arguments> layouts() {
return Stream.of(
Arguments.of("standard pos/color/uv/normal is aligned and packed",
new AttribLayoutKey(
new int[]{0, 1, 2, 4},
new int[]{3, 4, 2, 3},
new int[]{GL11.GL_FLOAT, GL11.GL_UNSIGNED_BYTE, GL11.GL_FLOAT, GL11.GL_BYTE},
new boolean[]{false, true, false, true}),
new int[]{0, 12, 16, 24}, 28),
Arguments.of("pos/uv/normal3 rounds the tail stride to 24",
new AttribLayoutKey(
new int[]{0, 2, 4},
new int[]{3, 2, 3},
new int[]{GL11.GL_FLOAT, GL11.GL_FLOAT, GL11.GL_BYTE},
new boolean[]{false, false, true}),
new int[]{0, 12, 20}, 24),
Arguments.of("byte attrib in the middle rounds the next offset",
new AttribLayoutKey(
new int[]{0, 4, 2},
new int[]{3, 3, 2},
new int[]{GL11.GL_FLOAT, GL11.GL_BYTE, GL11.GL_FLOAT},
new boolean[]{false, true, false}),
new int[]{0, 12, 16}, 24),
Arguments.of("all-byte layout packs tight",
new AttribLayoutKey(
new int[]{0, 1, 2},
new int[]{3, 4, 2},
new int[]{GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE, GL11.GL_BYTE},
new boolean[]{true, true, false}),
new int[]{0, 3, 7}, 9),
Arguments.of("shorts after a byte round offset and stride to even",
new AttribLayoutKey(
new int[]{0, 1},
new int[]{1, 2},
new int[]{GL11.GL_BYTE, GL11.GL_SHORT},
new boolean[]{true, false}),
new int[]{0, 2}, 6));
}
@Test
void posUvNormal3_roundsTailStrideTo24() {
final AttribLayoutKey k = new AttribLayoutKey(
new int[]{0, 2, 4},
new int[]{3, 2, 3},
new int[]{GL11.GL_FLOAT, GL11.GL_FLOAT, GL11.GL_BYTE},
new boolean[]{false, false, true});
assertArrayEquals(new int[]{0, 12, 20}, offsetsOf(k));
assertEquals(24, k.stride());
}
@Test
void interiorHazardOrder_roundsMidAttribOffset() {
final AttribLayoutKey k = new AttribLayoutKey(
new int[]{0, 4, 2},
new int[]{3, 3, 2},
new int[]{GL11.GL_FLOAT, GL11.GL_BYTE, GL11.GL_FLOAT},
new boolean[]{false, true, false});
assertArrayEquals(new int[]{0, 12, 16}, offsetsOf(k));
assertEquals(24, k.stride());
}
@Test
void allByteLayout_packsTight() {
final AttribLayoutKey k = new AttribLayoutKey(
new int[]{0, 1, 2},
new int[]{3, 4, 2},
new int[]{GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE, GL11.GL_BYTE},
new boolean[]{true, true, false});
assertArrayEquals(new int[]{0, 3, 7}, offsetsOf(k));
assertEquals(9, k.stride());
}
@Test
void shortsFollowingByte_roundToEvenAndStrideToEven() {
final AttribLayoutKey k = new AttribLayoutKey(
new int[]{0, 1},
new int[]{1, 2},
new int[]{GL11.GL_BYTE, GL11.GL_SHORT},
new boolean[]{true, false});
assertArrayEquals(new int[]{0, 2}, offsetsOf(k));
assertEquals(6, k.stride());
@ParameterizedTest(name = "{0}")
@MethodSource("layouts")
void offsetsAndStride(String name, AttribLayoutKey key, int[] offsets, int stride) {
assertArrayEquals(offsets, offsetsOf(key));
assertEquals(stride, key.stride());
}
@Test
@@ -5,6 +5,8 @@ import com.gtnewhorizons.angelica.config.SystemProperties.UnmappedGLMode;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
@@ -16,7 +18,6 @@ import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
@@ -32,19 +33,8 @@ class GLSMRedirectorTest {
private static final String GLSM = GLSMRedirector.getTargetClassName();
@SuppressWarnings("unchecked")
private static Set<String> seenSet(String field) {
try {
final Field f = GLSMRedirector.class.getDeclaredField(field);
f.setAccessible(true);
return (Set<String>) f.get(null);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
private static Set<String> unmappedSeen() { return seenSet("UNMAPPED_SEEN"); }
private static Set<String> debugSeen() { return seenSet("DEBUG_SEEN"); }
private static Set<String> unmappedSeen() { return Reflect.getStatic(GLSMRedirector.class, "UNMAPPED_SEEN"); }
private static Set<String> debugSeen() { return Reflect.getStatic(GLSMRedirector.class, "DEBUG_SEEN"); }
@BeforeEach
void reset() {
@@ -112,35 +102,25 @@ class GLSMRedirectorTest {
return (Handle) dyn.bsmArgs[1];
}
@Test
void invokeDynamicGL11HandleIsRedirected() {
final Handle impl = new Handle(Opcodes.H_INVOKESTATIC, "org/lwjgl/opengl/GL11", "glPopMatrix", "()V", false);
final ClassNode cn = makeClassWithInvokeDynamic(impl);
@ParameterizedTest(name = "{0}")
@CsvSource({
"GL11, org/lwjgl/opengl/GL11, glPopMatrix, ()V, glPopMatrix",
"extension class with a renamed target, org/lwjgl/opengl/EXTBlendFuncSeparate, glBlendFuncSeparateEXT, (IIII)V, tryBlendFuncSeparate",
"GLxxC variant, org/lwjgl/opengl/GL11C, glDrawArrays, (III)V, glDrawArrays"
})
void invokeDynamicHandleIsRedirected(String name, String owner, String method, String desc, String expectedName) {
final ClassNode cn = makeClassWithInvokeDynamic(new Handle(Opcodes.H_INVOKESTATIC, owner, method, desc, false));
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertTrue(changed);
final Handle result = getResultHandle(cn);
assertEquals(GLSM, result.getOwner());
assertEquals("glPopMatrix", result.getName());
assertEquals("()V", result.getDesc());
assertEquals(expectedName, result.getName());
assertEquals(desc, result.getDesc());
assertEquals(Opcodes.H_INVOKESTATIC, result.getTag());
}
@Test
void invokeDynamicNamedClassHandleIsRedirected() {
final Handle impl = new Handle(Opcodes.H_INVOKESTATIC, "org/lwjgl/opengl/EXTBlendFuncSeparate", "glBlendFuncSeparateEXT", "(IIII)V", false);
final ClassNode cn = makeClassWithInvokeDynamic(impl);
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertTrue(changed);
final Handle result = getResultHandle(cn);
assertEquals(GLSM, result.getOwner());
assertEquals("tryBlendFuncSeparate", result.getName());
assertEquals("(IIII)V", result.getDesc());
}
@Test
void invokeDynamicNonGLHandleIsNotTouched() {
final Handle impl = new Handle(Opcodes.H_INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
@@ -188,6 +168,7 @@ class GLSMRedirectorTest {
@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());
}
@@ -240,54 +221,16 @@ class GLSMRedirectorTest {
assertTrue(unmappedSeen().isEmpty());
}
@Test
void newlyMappedFramebufferAliasIsRedirected() {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/EXTFramebufferObject", "glBindFramebufferEXT", "(II)V");
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertTrue(changed);
assertEquals(GLSM, firstCall(cn).owner);
assertEquals("glBindFramebuffer", firstCall(cn).name);
assertTrue(unmappedSeen().isEmpty());
}
@Test
void arbGenerateMipmapAliasIsRedirected() {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/ARBFramebufferObject", "glGenerateMipmap", "(I)V");
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertTrue(changed);
assertEquals(GLSM, firstCall(cn).owner);
assertEquals("glGenerateMipmap", firstCall(cn).name);
assertEquals("(I)V", firstCall(cn).desc);
assertTrue(unmappedSeen().isEmpty());
}
@Test
void extGenerateMipmapAliasIsRedirected() {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/EXTFramebufferObject", "glGenerateMipmapEXT", "(I)V");
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertTrue(changed);
assertEquals(GLSM, firstCall(cn).owner);
assertEquals("glGenerateMipmap", firstCall(cn).name);
assertEquals("(I)V", firstCall(cn).desc);
assertTrue(unmappedSeen().isEmpty());
}
@Test
void newlyMappedReadPixelsIsRedirected() {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/GL11", "glReadPixels", "(IIIIIILjava/nio/IntBuffer;)V");
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertTrue(changed);
assertEquals(GLSM, firstCall(cn).owner);
assertEquals("glReadPixels", firstCall(cn).name);
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
@@ -299,47 +242,29 @@ class GLSMRedirectorTest {
});
}
@Test
void newlyMappedOcclusionQueryAliasIsRedirected() {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/ARBOcclusionQuery", "glGenQueriesARB", "(Ljava/nio/IntBuffer;)V");
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertTrue(changed);
assertEquals(GLSM, firstCall(cn).owner);
assertEquals("glGenQueries", firstCall(cn).name);
assertTrue(unmappedSeen().isEmpty());
}
@Test
void utilCheckGLErrorIsRedirected() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{{"org/lwjgl/opengl/Util", "checkGLError", "()V"}});
}
@Test
void indexedGettersHaveRealTargets() throws Exception {
final Class<?> glsm = Class.forName(GLSM.replace('/', '.'), false, getClass().getClassLoader());
assertDoesNotThrow(() -> glsm.getDeclaredMethod("glGetInteger", int.class, int.class), "LWJGL2 GL30.glGetInteger(pname, index) redirects here");
assertDoesNotThrow(() -> glsm.getDeclaredMethod("glGetIntegeri", int.class, int.class), "LWJGL3 GL31C.glGetIntegeri(pname, index) redirects here");
assertDoesNotThrow(() -> glsm.getDeclaredMethod("glGetIntegeri_v", int.class, int.class, java.nio.IntBuffer.class));
assertDoesNotThrow(() -> glsm.getDeclaredMethod("glGetBooleani_v", int.class, int.class, java.nio.ByteBuffer.class));
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"},
});
}
@Test
void indexedGettersAreRedirected() {
assertRedirected("org/lwjgl/opengl/GL30", "glGetInteger", "(II)I", "glGetInteger");
assertRedirected("org/lwjgl/opengl/GL31C", "glGetIntegeri", "(II)I", "glGetIntegeri");
assertRedirected("org/lwjgl/opengl/GL30", "glGetIntegeri_v", "(IILjava/nio/IntBuffer;)V", "glGetIntegeri_v");
assertRedirected("org/lwjgl/opengl/GL30", "glGetBooleani_v", "(IILjava/nio/ByteBuffer;)V", "glGetBooleani_v");
}
private void assertRedirected(String owner, String name, String desc, String expectedName) {
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);
assertTrue(changed, owner + "." + name + " should redirect");
assertEquals(GLSM, firstCall(cn).owner);
assertEquals(expectedName, firstCall(cn).name);
assertTrue(unmappedSeen().isEmpty(), owner + "." + name + " should not be recorded");
return firstCall(cn);
}
private static boolean glsmDeclares(String name, String desc) throws ClassNotFoundException {
@@ -465,20 +390,6 @@ class GLSMRedirectorTest {
assertRedirected("org/lwjgl/opengl/APPLEVertexArrayObject", "glGenVertexArraysAPPLE", "()I", "glGenVertexArrays");
}
@Test
void invokeDynamicGLCVariantIsRedirected() {
final Handle impl = new Handle(Opcodes.H_INVOKESTATIC, "org/lwjgl/opengl/GL11C", "glDrawArrays", "(III)V", false);
final ClassNode cn = makeClassWithInvokeDynamic(impl);
final boolean changed = new GLSMRedirector().transformClassNode("com.example.TestClass", cn);
assertTrue(changed);
final Handle result = getResultHandle(cn);
assertEquals(GLSM, result.getOwner());
assertEquals("glDrawArrays", result.getName());
assertEquals("(III)V", result.getDesc());
}
private static final String AWARE_SHIM = "com/gtnewhorizons/angelica/sdlgpu/compat/AwareGLDebugShim";
private static final String CALLBACK_I = "Lorg/lwjgl/opengl/GLDebugMessageCallbackI;";
@@ -502,15 +413,6 @@ class GLSMRedirectorTest {
assertEquals("glDebugMessageCallback", firstCall(cn).name);
}
@Test
void nonAwareDebugMessageCallbackTargetsGLStateManager() {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/KHRDebug", "glDebugMessageCallback", "(Lorg/lwjgl/opengl/KHRDebugCallback;)V");
assertTrue(new GLSMRedirector().transformClassNode("com.example.TestClass", cn, false));
assertEquals(GLSM, firstCall(cn).owner);
}
@Test
void awareGLUtilSetupDebugMessageCallbackIsRedirected() {
for (String desc : new String[]{"()Lorg/lwjgl/system/Callback;", "(Ljava/io/PrintStream;)Lorg/lwjgl/system/Callback;"}) {
@@ -8,6 +8,7 @@ import org.junit.jupiter.api.io.TempDir;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.shaderc.Shaderc;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -15,12 +16,35 @@ import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("lwjgl3")
class SpirvCompilerDiskCacheTest {
private static final String SRC = "#version 460 core\nlayout(location = 0) in vec4 p;\nvoid main() { gl_Position = p; }\n";
static final String REDEFINES_CLAMP = """
#version 330 core
in vec4 uv;
layout(location = 0) out vec4 color;
float clamp(float min, float f, float max) {
if (f > max) f = max;
if (f < min) f = min;
return f;
}
void main() {
color = vec4(clamp(0.0, uv.x, 1.0), clamp(0.0, uv.y, 1.0), 0.0, 1.0);
}
""";
private static final String BROKEN_WITH_HELPER = """
#version 330 core
layout(location = 0) out vec4 color;
float helper(float t) { return t; }
void main() { color = vec4(helper(undeclared_var)); }
""";
@TempDir
Path dir;
@@ -58,4 +82,24 @@ class SpirvCompilerDiskCacheTest {
MemoryUtil.memFree(r.spirv());
assertFalse(Files.exists(dir.resolve("spirv")));
}
@Test
void redefinedBuiltinCompilesAndIsStoredUnderTheOriginalSource() {
final SpirvCompiler.Options opts = SpirvCompiler.Options.vulkanForced460Core();
ShaderDiskCache.configure(dir, "t");
final SpirvCompiler.Result r = SpirvCompiler.compile(REDEFINES_CLAMP, Shaderc.shaderc_fragment_shader, "redefines-clamp", opts);
assertNotNull(r.spirv(), r.error());
MemoryUtil.memFree(r.spirv());
assertNull(r.dumpPath());
assertNotNull(ShaderDiskCache.get(SpirvCompiler.diskKey(REDEFINES_CLAMP, Shaderc.shaderc_fragment_shader, opts)));
}
@Test
void brokenShaderWithHelperReportsTheOriginalError() throws IOException {
final SpirvCompiler.Result r = SpirvCompiler.compile(BROKEN_WITH_HELPER, Shaderc.shaderc_fragment_shader, "broken-with-helper", SpirvCompiler.Options.vulkanForced460Core(), false);
assertNull(r.spirv());
assertTrue(r.error().contains("undeclared_var"), r.error());
assertNotNull(r.dumpPath());
assertFalse(Files.readString(r.dumpPath()).contains(GlslVulkanPreprocess.RESERVED_RENAMED_PREFIX));
}
}
@@ -131,6 +131,11 @@ class SpirvShaderTranslatorTest {
"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 = """
@@ -0,0 +1,82 @@
package com.gtnewhorizons.angelica.glsm.shader;
import org.junit.jupiter.api.Test;
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);
}
@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"));
}
}
@@ -0,0 +1,98 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static org.junit.jupiter.api.Assertions.assertEquals;
class LineQuadsTest {
private static final int RED = 0xFF0000FF;
private static final int GREEN = 0xFF00FF00;
private static final int BLUE = 0xFFFF0000;
private static ByteBuffer positionColor(float[][] positions, int[] colors) {
final ByteBuffer buf = ByteBuffer.allocateDirect(positions.length * 16).order(ByteOrder.nativeOrder());
for (int i = 0; i < positions.length; i++) {
buf.putFloat(positions[i][0]).putFloat(positions[i][1]).putFloat(positions[i][2]).putInt(colors[i]);
}
return buf.flip();
}
private static ByteBuffer allocateQuads(int drawMode, int vertexCount) {
final VertexFormat quadFormat = LineQuads.quadFormat(DefaultVertexFormat.POSITION_COLOR);
final int quadVertices = LineQuads.segmentCount(drawMode, vertexCount) * LineQuads.VERTICES_PER_SEGMENT;
return ByteBuffer.allocateDirect(quadVertices * quadFormat.getVertexSize()).order(ByteOrder.nativeOrder());
}
private static ByteBuffer expand(ByteBuffer src, int drawMode, int vertexCount) {
final ByteBuffer dst = allocateQuads(drawMode, vertexCount);
LineQuads.expandWithDirection(memAddress0(src), DefaultVertexFormat.POSITION_COLOR, drawMode, vertexCount, memAddress0(dst));
return dst;
}
@Test
void otherEndCornersCarryTheOppositeEndpoint() {
final ByteBuffer src = positionColor(new float[][] {{0, 0, 0}, {2, 0, 3}}, new int[] {RED, GREEN});
final ByteBuffer quads = allocateQuads(GL11.GL_LINES, 2);
LineQuads.expandWithOtherEnd(memAddress0(src), DefaultVertexFormat.POSITION_COLOR, GL11.GL_LINES, 2, memAddress0(quads));
final float[] end = {2, 0, 3};
final float[] start = {0, 0, 0};
final float[] ownX = {0, 0, 2, 0, 2, 2};
final int[] color = {RED, RED, GREEN, RED, GREEN, GREEN};
for (int i = 0; i < 6; i++) {
final boolean isStart = i == 0 || i == 1 || i == 3;
assertVertex(quads, i, ownX[i], isStart ? end : start, color[i]);
}
}
private static final int STRIDE = 28;
private static void assertVertex(ByteBuffer quads, int index, float x, float[] direction, int color) {
final int base = index * STRIDE;
assertEquals(x, quads.getFloat(base), "x of vertex " + index);
for (int axis = 0; axis < 3; axis++) {
assertEquals(direction[axis], quads.getFloat(base + 12 + axis * 4), "direction of vertex " + index);
}
assertEquals(color, quads.getInt(base + 24), "color of vertex " + index);
}
@Test
void stripSegmentsFollowTheGeometryShaderCornerOrder() {
assertEquals(STRIDE, LineQuads.quadFormat(DefaultVertexFormat.POSITION_COLOR).getVertexSize());
final ByteBuffer src = positionColor(new float[][] {{0, 0, 0}, {2, 0, 0}, {2, 0, 3}}, new int[] {RED, GREEN, BLUE});
final ByteBuffer quads = expand(src, GL11.GL_LINE_STRIP, 3);
final float[] alongX = {1, 0, 0};
final float[] alongZ = {0, 0, 1};
final float[] firstX = {0, 0, 2, 0, 2, 2};
final int[] firstColor = {RED, RED, GREEN, RED, GREEN, GREEN};
for (int i = 0; i < 6; i++) {
assertVertex(quads, i, firstX[i], alongX, firstColor[i]);
}
final int[] secondColor = {GREEN, GREEN, BLUE, GREEN, BLUE, BLUE};
for (int i = 0; i < 6; i++) {
assertVertex(quads, 6 + i, 2, alongZ, secondColor[i]);
}
}
@Test
void loopClosesBackToFirstVertex() {
final ByteBuffer src = positionColor(new float[][] {{0, 0, 0}, {2, 0, 0}, {2, 0, 3}}, new int[] {RED, GREEN, BLUE});
assertEquals(3, LineQuads.segmentCount(GL11.GL_LINE_LOOP, 3));
final ByteBuffer quads = expand(src, GL11.GL_LINE_LOOP, 3);
final int[] closingColor = {BLUE, BLUE, RED, BLUE, RED, RED};
for (int i = 0; i < 6; i++) {
assertEquals(closingColor[i], quads.getInt((12 + i) * STRIDE + 24), "color of closing vertex " + i);
}
}
}
@@ -5,6 +5,7 @@ import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
@@ -16,14 +17,15 @@ import java.util.stream.IntStream;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
public class GLSMUtil {
// Cached GL limits (queried once at class load time)
private static final int MAX_CLIP_PLANES = GL11.glGetInteger(GL11.GL_MAX_CLIP_PLANES);
private static final int MAX_LIGHTS = GL11.glGetInteger(GL11.GL_MAX_LIGHTS);
private static final int MAX_TEXTURE_UNITS = GL11.glGetInteger(GL13.GL_MAX_TEXTURE_UNITS);
// Compat-only pnames, so they load with resetGLState() and not with the class: a core driver answers GL_INVALID_ENUM
private static final class CompatLimits {
static final int MAX_CLIP_PLANES = GL11.glGetInteger(GL11.GL_MAX_CLIP_PLANES);
static final int MAX_LIGHTS = GL11.glGetInteger(GL11.GL_MAX_LIGHTS);
static final int MAX_TEXTURE_UNITS = GL11.glGetInteger(GL13.GL_MAX_TEXTURE_UNITS);
}
// MAP1 evaluator constants for DRY loops
private static final int[] MAP1_STATES = {
@@ -76,13 +78,25 @@ public class GLSMUtil {
GL11.GL_FOG_MODE, GL11.GL_FOG_INDEX,
// Also removed from core profile
GL11.GL_POINT_SMOOTH, GL11.GL_POLYGON_STIPPLE,
GL11.GL_INDEX_LOGIC_OP, GL11.GL_AUTO_NORMAL));
GL11.GL_INDEX_LOGIC_OP, GL11.GL_AUTO_NORMAL,
GL13.GL_CLIENT_ACTIVE_TEXTURE,
GL11.GL_VERTEX_ARRAY, GL11.GL_NORMAL_ARRAY, GL11.GL_COLOR_ARRAY, GL11.GL_TEXTURE_COORD_ARRAY,
GL11.GL_INDEX_ARRAY, GL11.GL_EDGE_FLAG_ARRAY, GL14.GL_SECONDARY_COLOR_ARRAY, GL14.GL_FOG_COORDINATE_ARRAY,
GL11.GL_VERTEX_ARRAY_SIZE, GL11.GL_VERTEX_ARRAY_TYPE, GL11.GL_VERTEX_ARRAY_STRIDE));
for (int cap : MAP1_STATES) caps.add(cap);
for (int cap : MAP2_STATES) caps.add(cap);
return caps;
}
// A core driver rejects these caps with GL_INVALID_ENUM. Only that error is dropped: one already pending is the test's.
private static boolean driverBoolean(int glCap, boolean isEnabled) {
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "GL error pending before the driver query");
final boolean value = isEnabled ? GL11.glIsEnabled(glCap) : GL11.glGetBoolean(glCap);
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
return value;
}
private static boolean isFFPOnly(int glCap) {
return FFP_ONLY_CAPS.contains(glCap);
}
@@ -94,7 +108,7 @@ public class GLSMUtil {
public static void verifyIsEnabled(int glCap, boolean expected, String message) {
if (isFFPOnly(glCap)) {
assertAll(message,
() -> assertFalse(GL11.glIsEnabled(glCap), "GL State Mismatch - cache-only cap reached the driver"),
() -> assertFalse(driverBoolean(glCap, true), "GL State Mismatch - cache-only cap reached the driver"),
() -> assertEquals(expected, GLStateManager.glIsEnabled(glCap), "GLSM State Mismatch")
);
} else {
@@ -112,7 +126,7 @@ public class GLSMUtil {
public static void verifyState(int glCap, boolean expected, String message) {
if (isFFPOnly(glCap)) {
assertAll(message,
() -> assertFalse(GL11.glGetBoolean(glCap), "GL State Mismatch - cache-only cap reached the driver"),
() -> assertFalse(driverBoolean(glCap, false), "GL State Mismatch - cache-only cap reached the driver"),
() -> assertEquals(expected, GLStateManager.glGetBoolean(glCap), "GLSM State Mismatch")
);
} else {
@@ -208,22 +222,6 @@ public class GLSMUtil {
}
}
public static void verifyNotDefaultState(int glCap, float[] expected, String message) {
if (isFFPOnly(glCap)) {
GLStateManager.glGetFloat(glCap, (FloatBuffer) glsmFloatBuffer.clear());
IntStream.range(0, expected.length).forEach(i ->
assertNotEquals(expected[i], glsmFloatBuffer.get(i), 0.0001f, message + " - GLSM State Mismatch: " + i)
);
} else {
GL11.glGetFloat(glCap, (FloatBuffer) glFloatBuffer.clear());
GLStateManager.glGetFloat(glCap, (FloatBuffer) glsmFloatBuffer.clear());
IntStream.range(0, expected.length).forEach(i -> assertAll(message,
() -> assertNotEquals(expected[i], glFloatBuffer.get(i), 0.0001f, "GL State Mismatch: " + i),
() -> assertNotEquals(expected[i], glsmFloatBuffer.get(i), 0.0001f, "GLSM State Mismatch: " + i)
));
}
}
public static void verifyState(int glCap, boolean[] expected) {
verifyState(glCap, expected, "Boolean State Mismatch");
}
@@ -275,7 +273,7 @@ public class GLSMUtil {
GLStateManager.disableBlend();
// Disable all clip planes (using cached MAX_CLIP_PLANES)
for(int i = 0; i < MAX_CLIP_PLANES; i++) {
for(int i = 0; i < CompatLimits.MAX_CLIP_PLANES; i++) {
GLStateManager.glDisable(GL11.GL_CLIP_PLANE0 + i);
}
@@ -290,7 +288,7 @@ public class GLSMUtil {
GLStateManager.disableLighting();
// Disable all lights (using cached MAX_LIGHTS)
for(int i = 0; i < MAX_LIGHTS; i++) {
for(int i = 0; i < CompatLimits.MAX_LIGHTS; i++) {
GLStateManager.disableLight(i);
}
@@ -330,7 +328,7 @@ public class GLSMUtil {
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
// Reset texture state for all units (using cached MAX_TEXTURE_UNITS)
for(int i = 0; i < MAX_TEXTURE_UNITS; i++) {
for(int i = 0; i < CompatLimits.MAX_TEXTURE_UNITS; i++) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + i);
GLStateManager.glDisable(GL11.GL_TEXTURE_1D);
GLStateManager.disableTexture(); // GL_TEXTURE_2D
@@ -383,9 +381,9 @@ public class GLSMUtil {
public static void setClientState(int glCap, boolean state) {
if (state) {
GL11.glEnableClientState(glCap);
GLStateManager.glEnableClientState(glCap);
} else {
GL11.glDisableClientState(glCap);
GLStateManager.glDisableClientState(glCap);
}
}
}
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
@@ -14,12 +15,13 @@ import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.PixelFormat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
/**
* 3.3 core forward-compatible context. FFP emulation stays enabled, so per-test isolation only deactivates it.
*/
public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback, ExtensionContext.Store.CloseableResource {
public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, ExtensionContext.Store.CloseableResource {
private static boolean started = false;
private static Throwable startFailure;
@@ -75,6 +77,13 @@ public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback,
}
}
@Override
public void afterEach(ExtensionContext context) {
final int error = GL11.glGetError();
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
assertEquals(GL11.GL_NO_ERROR, error, () -> "GL Error: 0x" + Integer.toHexString(error));
}
@Override
public void close() {
if (Display.isCreated()) Display.destroy();
@@ -0,0 +1,82 @@
package com.gtnewhorizons.angelica.glsm;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.fail;
public final class GlTestSupport {
public static final String TRIVIAL_VS = "#version 330 core\nvoid main() { gl_Position = vec4(0.0); }\n";
public static final String TRIVIAL_FS = "#version 330 core\nout vec4 color;\nvoid main() { color = vec4(1.0); }\n";
private GlTestSupport() {}
public static void drainErrors() {
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
public static int compile(int type, String source) {
final int shader = GLStateManager.glCreateShader(type);
GLStateManager.glShaderSource(shader, source);
GLStateManager.glCompileShader(shader);
if (GLStateManager.glGetShaderi(shader, GL20.GL_COMPILE_STATUS) == 0) {
fail("test shader failed to compile:\n" + GLStateManager.glGetShaderInfoLog(shader, 4096) + "\n\n" + source);
}
return shader;
}
/** Links and deletes the two shaders. */
public static int link(int vs, int fs) {
final int p = GLStateManager.glCreateProgram();
GLStateManager.glAttachShader(p, vs);
GLStateManager.glAttachShader(p, fs);
GLStateManager.glLinkProgram(p);
assertNotEquals(0, GLStateManager.glGetProgrami(p, GL20.GL_LINK_STATUS), () -> "test program must link: " + GLStateManager.glGetProgramInfoLog(p, 4096));
GLStateManager.glDeleteShader(vs);
GLStateManager.glDeleteShader(fs);
return p;
}
public static int program(String vertexSource, String fragmentSource) {
return link(compile(GL20.GL_VERTEX_SHADER, vertexSource), compile(GL20.GL_FRAGMENT_SHADER, fragmentSource));
}
/** Raw driver GL_TEXTURE_BINDING_2D of a unit, bypassing the GLSM cache. */
public static int driverTextureBinding(int unit) {
final int saved = GL11.glGetInteger(GL13.GL_ACTIVE_TEXTURE);
GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit);
final int binding = GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D);
GL13.glActiveTexture(saved);
return binding;
}
public static FloatBuffer floats(float... values) {
final FloatBuffer buf = BufferUtils.createFloatBuffer(values.length);
buf.put(values).flip();
return buf;
}
/** GLSM's answer for a 16-float matrix pname. */
public static FloatBuffer matrix(int pname) {
final FloatBuffer buf = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(pname, buf);
return buf;
}
public static FloatBuffer modelview() {
return matrix(GL11.GL_MODELVIEW_MATRIX);
}
public static void assertMatrixEquals(FloatBuffer expected, FloatBuffer actual, String message) {
for (int i = 0; i < 16; i++) {
assertEquals(expected.get(i), actual.get(i), 0.001f, message + " (element " + i + ")");
}
}
}
@@ -0,0 +1,84 @@
package com.gtnewhorizons.angelica.glsm;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL20;
import java.util.Arrays;
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 final class LegacyQueryFixture {
public static final int[] CLIENT_ARRAY_CAPS = {
GL11.GL_VERTEX_ARRAY, GL11.GL_NORMAL_ARRAY, GL11.GL_COLOR_ARRAY, GL11.GL_TEXTURE_COORD_ARRAY,
GL11.GL_INDEX_ARRAY, GL11.GL_EDGE_FLAG_ARRAY, GL14.GL_SECONDARY_COLOR_ARRAY, GL14.GL_FOG_COORDINATE_ARRAY
};
public static final int[] HINT_TARGETS = {
GL11.GL_PERSPECTIVE_CORRECTION_HINT, GL11.GL_POINT_SMOOTH_HINT, GL11.GL_LINE_SMOOTH_HINT, GL11.GL_POLYGON_SMOOTH_HINT,
GL11.GL_FOG_HINT, GL13.GL_TEXTURE_COMPRESSION_HINT, GL14.GL_GENERATE_MIPMAP_HINT, GL20.GL_FRAGMENT_SHADER_DERIVATIVE_HINT
};
private LegacyQueryFixture() {}
/** Every pname LegacyQueries answers from the cache. */
public static int[] legacyPnames() {
final int[] out = new int[0x10000];
int n = 0;
for (int pname = 0; pname <= 0xFFFF; pname++) {
if (LegacyQueries.desc(pname) != 0) {
out[n++] = pname;
}
}
return Arrays.copyOf(out, n);
}
public static String hex(int pname) {
return "0x" + Integer.toHexString(pname);
}
public static void setEnabled(int cap, boolean on) {
if (on) {
GLStateManager.glEnable(cap);
} else {
GLStateManager.glDisable(cap);
}
}
public static void popAttribTo(int base) {
while (GLStateManager.getAttribDepth() > base) {
GLStateManager.glPopAttrib();
}
}
public static void drainClientAttribStack() {
while (GLStateManager.glGetInteger(GL11.GL_CLIENT_ATTRIB_STACK_DEPTH) > 0) {
GLStateManager.glPopClientAttrib();
}
}
/** Enables then disables {@code cap}, checking glIsEnabled, glGetBoolean and glGetInteger at each step. */
public static void assertEnableRoundTrip(int cap, boolean clientState) {
final String name = hex(cap);
if (clientState) {
GLStateManager.glEnableClientState(cap);
} else {
GLStateManager.glEnable(cap);
}
assertTrue(GLStateManager.glIsEnabled(cap), "enabled isEnabled " + name);
assertTrue(GLStateManager.glGetBoolean(cap), "enabled getBoolean " + name);
assertEquals(1, GLStateManager.glGetInteger(cap), "enabled getInteger " + name);
if (clientState) {
GLStateManager.glDisableClientState(cap);
} else {
GLStateManager.glDisable(cap);
}
assertFalse(GLStateManager.glIsEnabled(cap), "disabled isEnabled " + name);
assertFalse(GLStateManager.glGetBoolean(cap), "disabled getBoolean " + name);
assertEquals(0, GLStateManager.glGetInteger(cap), "disabled getInteger " + name);
}
}
@@ -1,20 +1,21 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
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 java.nio.ByteBuffer;
import java.util.function.IntFunction;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
public final class FfpFixture {
@@ -125,26 +126,7 @@ public final class FfpFixture {
}
private static int buildProgram() {
final int vs = compile(GL20.GL_VERTEX_SHADER, VS);
final int fs = compile(GL20.GL_FRAGMENT_SHADER, FS);
final int p = GLStateManager.glCreateProgram();
GLStateManager.glAttachShader(p, vs);
GLStateManager.glAttachShader(p, fs);
GLStateManager.glLinkProgram(p);
assertNotEquals(0, GLStateManager.glGetProgrami(p, GL20.GL_LINK_STATUS), () -> "test program must link: " + GLStateManager.glGetProgramInfoLog(p, 4096));
GLStateManager.glDeleteShader(vs);
GLStateManager.glDeleteShader(fs);
return p;
}
private static int compile(int type, String source) {
final int shader = GLStateManager.glCreateShader(type);
GLStateManager.glShaderSource(shader, source);
GLStateManager.glCompileShader(shader);
if (GLStateManager.glGetShaderi(shader, GL20.GL_COMPILE_STATUS) == 0) {
fail("test shader failed to compile:\n" + GLStateManager.glGetShaderInfoLog(shader, 4096) + "\n\n" + source);
}
return shader;
return GlTestSupport.program(VS, FS);
}
}
@@ -153,6 +135,27 @@ public final class FfpFixture {
GLStateManager.glVertexAttribPointer(loc, size, type, normalized, stride, offset);
}
private static boolean formatRoutingRegistered;
private static boolean routeFormats;
public static void routeVertexFormats(boolean enabled) {
routeFormats = enabled;
if (formatRoutingRegistered) return;
formatRoutingRegistered = true;
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
if (!routeFormats) return false;
final int stride = format.getVertexSize();
int elementOffset = (int) offset;
for (VertexFormatElement element : format.elementsArray) {
final Usage usage = element.getUsage();
final boolean normalized = usage == Usage.COLOR || usage == Usage.NORMAL;
attrib(usage.getAttributeLocation(), element.getCount(), element.getType().getGlType(), normalized, stride, elementOffset);
elementOffset += element.getByteSize();
}
return true;
});
}
public static void clear() {
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
@@ -451,6 +451,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public void hint(int target, int mode) {
if (RenderSystem.isGLES() && target != GL20C.GL_FRAGMENT_SHADER_DERIVATIVE_HINT) return;
GL11C.glHint(target, mode);
}
@@ -1822,11 +1823,13 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public void pointParameterf(int pname, float param) {
if (RenderSystem.isGLES()) return;
GL14C.glPointParameterf(pname, param);
}
@Override
public void pointParameteri(int pname, int param) {
if (RenderSystem.isGLES()) return;
GL14C.glPointParameteri(pname, param);
}
@@ -871,6 +871,9 @@ public class SDLGPURenderBackend extends RenderBackend {
case GL11.GL_CULL_FACE -> {
if (cs.pipeline.cullEnabled != on) { cs.pipeline.cullEnabled = on; cs.pipeline.markOutputDirty(); }
}
case GL11.GL_POLYGON_OFFSET_FILL -> {
if (cs.pipeline.polygonOffsetFillEnabled != on) { cs.pipeline.polygonOffsetFillEnabled = on; cs.pipeline.markOutputDirty(); }
}
case GL11.GL_STENCIL_TEST -> {
if (cs.pipeline.stencilTestEnabled != on) { cs.pipeline.stencilTestEnabled = on; cs.pipeline.markOutputDirty(); }
}
@@ -1244,6 +1247,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (rp == 0) return;
final int firstIndex = (int) (indices / FormatMap.indexElementSize(type));
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "elementsInstanced", mode, count, type, firstIndex, 0);
pipelineApplier.bindInstancedConstants(st, rp, primcount);
drawDispatch.issueIndexedDraw(st, rp, st.currentVao.elementBuffer, type, count, primcount, firstIndex, 0);
captureVertexWrites(st, 0, count, primcount, type, indices, 0);
}
@@ -4122,7 +4126,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public void getFloat(int pname, FloatBuffer params) {
if (params == null || params.remaining() == 0) return;
if (pname == GL12.GL_ALIASED_LINE_WIDTH_RANGE || pname == GL11.GL_LINE_WIDTH_RANGE) {
if (pname == GL12.GL_ALIASED_LINE_WIDTH_RANGE || pname == GL11.GL_LINE_WIDTH_RANGE || pname == GL12.GL_ALIASED_POINT_SIZE_RANGE || pname == GL11.GL_POINT_SIZE_RANGE) {
if (params.remaining() >= 2) {
params.put(params.position(), 1.0f);
params.put(params.position() + 1, 1.0f);
@@ -4137,6 +4141,7 @@ public class SDLGPURenderBackend extends RenderBackend {
case GL11.GL_DEPTH_TEST -> cs.pipeline.depthTestEnabled;
case GL11.GL_BLEND -> cs.pipeline.blendEnabledPerDrawBuffer[0];
case GL11.GL_CULL_FACE -> cs.pipeline.cullEnabled;
case GL11.GL_POLYGON_OFFSET_FILL -> cs.pipeline.polygonOffsetFillEnabled;
case GL11.GL_SCISSOR_TEST -> cs.scissorEnabled;
case GL11.GL_STENCIL_TEST -> cs.pipeline.stencilTestEnabled;
case GL11.GL_DEPTH_WRITEMASK -> cs.pipeline.depthWriteEnabled;
@@ -4477,7 +4482,9 @@ public class SDLGPURenderBackend extends RenderBackend {
if (!frameManager.isRenderPassActive()) return;
if (!pipelineApplier.applyPipelineAndState(st)) return;
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysInstanced", mode, count, 0, first, 0);
SDL_DrawGPUPrimitives(frameManager.getRenderPass(), count, primcount, first, 0);
final long rp = frameManager.getRenderPass();
pipelineApplier.bindInstancedConstants(st, rp, primcount);
SDL_DrawGPUPrimitives(rp, count, primcount, first, 0);
captureVertexWrites(st, first, count, primcount, 0, 0, 0);
}
Loaded 100 of 349 files, more files were not shown because too many files have changed in this diff. Show more