Compare commits

...
20 Commits
Author SHA1 Message Date
KAMKEEL fa71eda3db Font Texture Crash Fix on Splash Screen (#2169) 2026-09-29 19:18:16 -07:00
KAMKEEL 9705722469 Fix Depth Mask Issue for Nametags, etc for Text (#2168) 2026-09-29 18:49:05 -07:00
Lumarin 977db3d604 Add option to change mouse movement from Cinematic to Standard while in Angelica Zoom (#2166) 2026-09-29 17:02:55 -07:00
Jason Mitchell c10d30b2da Parallelize atlas loading and fix SDL-GPU mip-level targets (#2167) 2026-09-29 16:57:53 -07:00
micvogandEclipse-Sol 2f4ac2a9b7 Apply the baked transform to vertices written directly into BakedTransformCapture (#2164)
While a direct capture is active, Tessellator.instance is the capture itself, so
RenderBlocks.renderBlockAsItem writes its vertices directly instead of going
through interceptDraw. Its own rotate/translate was therefore never baked into
the cached block mesh, which left held and dropped blocks off-center (e.g. the
snow golem's pumpkin).

Co-authored-by: Eclipse-Sol <[email protected]>
2026-09-29 18:26:31 -04:00
Death Fuel 9081f4c3e8 Various font improvements and fixes (#2165) 2026-09-29 14:58:53 -07:00
Eclipse-Sol 1f274a64ea TESR state and batching leak fixes (#2162) 2026-09-28 19:28:45 -07:00
micvog 9814a96b10 Fix attrib stack overflow from world-less TESR item renderers (#2159) 2026-09-28 07:05:46 -07:00
Eclipse-Sol af599f292e Fix Iris issues with instancing and batching (#2158) 2026-09-27 17:21:39 -07:00
Jason Mitchell 7bb5bfd45c Emulate color logic ops and fix pixel readback on SDL-GPU (#2157) 2026-09-27 15:33:04 -07:00
Jason Mitchell 74ea5c4709 Add in-game Tracy capture (#2156) 2026-09-27 14:16:01 -07:00
Eclipse-Sol 233c74e0d7 Better Biome Blending (#2152) 2026-09-27 13:19:48 -07:00
Jason Mitchell e17b45c842 Fix NEI items vanishing with ModularUI2 screens open on SDL-GPU (#2153) 2026-09-27 12:26:53 -07:00
Jason Mitchell c9d46e70e0 Update to Celeritas 2.5.12 with translucency sorting v3 (#2151) 2026-09-27 09:37:40 -07:00
Jason Mitchell a8c29fa3f9 Give each GL drawable its own GLSM context (#2142) 2026-09-26 22:31:07 -07:00
Eclipse-Sol 7c28a69bfe Optimize Enchant Glint Rendering (#2140)
* initial commit for enchant-glint-optimizations

* Fix spacing issues and remove GlintCompatibility.java
2026-09-26 19:21:21 -04:00
Eclipse-Sol 7f3fb35ebb Use actual camera when doing cloud related culling and fuctions (#2139) 2026-09-26 14:58:03 -07:00
Jason Mitchell 44c930f4ab Fix scissor clears wiping the whole screen on SDL-GPU (#2138) 2026-09-26 13:54:10 -07:00
Jason Mitchell b76284048a Fix shader packs in modded dimensions (#2137)
* Use world0 for unmapped dimensions
* Fix iris warnings for shaderpacks

Fixes https://github.com/GTNewHorizons/Angelica/issues/2128
2026-09-26 13:18:17 -04:00
Jason Mitchell 762c5661eb Stop blocking chunk workers on deferred ISBRH blocks (#2136) 2026-09-26 08:44:22 -07:00
397 changed files with 22438 additions and 3957 deletions

No files matched your search

+76 -17
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
# Builds the libTracyClient shared library for the tracy-client module.
# Builds the libTracyClient and AngelicaTracyCapture shared libraries for the tracy-client module.
set -euo pipefail
TRACY_VERSION="${TRACY_VERSION:-v0.14.0}"
TRACY_VERSION="${TRACY_VERSION:-v0.14.1}"
TRACY_VERSION_BARE="${TRACY_VERSION#v}"
OUT_DIR="${OUT_DIR:-dist/tracy-natives}"
TRACY_REPO="https://github.com/wolfpld/tracy"
@@ -31,36 +31,95 @@ else
git clone --depth 1 --branch "$TRACY_VERSION" "$TRACY_REPO" "$SRC_DIR"
fi
BUILD_DIR="$WORK_DIR/build"
export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-11.0}"
cmake -S "$REPO_ROOT/tracy-client/src/main/native" -B "$BUILD_DIR" \
CLIENT_BUILD_DIR="$WORK_DIR/build-client"
cmake -S "$REPO_ROOT/tracy-client/src/main/native" -B "$CLIENT_BUILD_DIR" \
-DTRACY_SRC_DIR="$SRC_DIR" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=ON \
-DTRACY_STATIC=OFF \
-DTRACY_ENABLE=ON \
-DTRACY_ON_DEMAND=ON \
-DTRACY_DELAYED_INIT=ON \
-DTRACY_MANUAL_LIFETIME=ON \
-DTRACY_NO_SAMPLING=ON \
-DTRACY_NO_SYSTEM_TRACING=ON \
-DTRACY_NO_CRASH_HANDLER=ON \
-DTRACY_LTO=OFF
cmake --build "$BUILD_DIR" --config Release --parallel
cmake --build "$CLIENT_BUILD_DIR" --config Release --parallel
mkdir -p "$OUT_DIR"
CPM_CACHE_DIR="$WORK_DIR/cpm"
CAPTURE_BUILD_DIR="$WORK_DIR/build-capture"
cmake -S "$REPO_ROOT/tracy-client/src/main/native-capture" -B "$CAPTURE_BUILD_DIR" \
-DTRACY_SRC_DIR="$SRC_DIR" \
-DCMAKE_BUILD_TYPE=Release \
-DCPM_SOURCE_CACHE="$CPM_CACHE_DIR"
cmake --build "$CAPTURE_BUILD_DIR" --config Release --parallel
find_one() {
find "$1" -name "$2" 2>/dev/null | head -1
}
CLIENT_LIB_NAME=""
CLIENT_LIB=""
for NAME in libTracyClient.so libTracyClient.dylib TracyClient.dll; do
LIB="$(find "$BUILD_DIR" -name "$NAME" | head -1)"
LIB="$(find_one "$CLIENT_BUILD_DIR" "$NAME")"
if [[ -n "$LIB" ]]; then
cp -L "$LIB" "$OUT_DIR/$NAME"
cp "$SRC_DIR/LICENSE" "$OUT_DIR/LICENSE"
mkdir -p "$RES_DIR"
cp -L "$LIB" "$RES_DIR/$NAME"
cp "$SRC_DIR/LICENSE" "$RES_DIR/LICENSE"
echo "built: $OUT_DIR/$NAME ($TRACY_VERSION), staged: $RES_DIR/$NAME"
exit 0
CLIENT_LIB="$LIB"
CLIENT_LIB_NAME="$NAME"
break
fi
done
echo "error: built library not found under $BUILD_DIR" >&2
exit 1
if [[ -z "$CLIENT_LIB" ]]; then
echo "error: libTracyClient not found under $CLIENT_BUILD_DIR" >&2
exit 1
fi
CAPTURE_LIB_NAME=""
CAPTURE_LIB=""
for NAME in libAngelicaTracyCapture.so libAngelicaTracyCapture.dylib AngelicaTracyCapture.dll; do
LIB="$(find_one "$CAPTURE_BUILD_DIR" "$NAME")"
if [[ -n "$LIB" ]]; then
CAPTURE_LIB="$LIB"
CAPTURE_LIB_NAME="$NAME"
break
fi
done
if [[ -z "$CAPTURE_LIB" ]]; then
echo "error: AngelicaTracyCapture not found under $CAPTURE_BUILD_DIR" >&2
exit 1
fi
STRIPPED_CAPTURE_LIB="$WORK_DIR/$CAPTURE_LIB_NAME"
cp -L "$CAPTURE_LIB" "$STRIPPED_CAPTURE_LIB"
case "$PLAT_OS" in
macos) strip -x "$STRIPPED_CAPTURE_LIB" ;;
linux) strip --strip-unneeded "$STRIPPED_CAPTURE_LIB" ;;
*) ;;
esac
LICENSE_ZSTD="$(find_one "$CPM_CACHE_DIR/zstd" LICENSE)"
LICENSE_CAPSTONE="$(find_one "$CPM_CACHE_DIR/capstone" LICENSE.TXT)"
LICENSE_PPQSORT="$(find_one "$CPM_CACHE_DIR/ppqsort" LICENSE)"
LICENSE_NAMES=(LICENSE LICENSE-zstd LICENSE-capstone LICENSE-ppqsort)
LICENSE_SRCS=("$SRC_DIR/LICENSE" "$LICENSE_ZSTD" "$LICENSE_CAPSTONE" "$LICENSE_PPQSORT")
for i in "${!LICENSE_NAMES[@]}"; do
if [[ -z "${LICENSE_SRCS[$i]}" || ! -f "${LICENSE_SRCS[$i]}" ]]; then
echo "error: ${LICENSE_NAMES[$i]} source not found under $CPM_CACHE_DIR" >&2
exit 1
fi
done
mkdir -p "$OUT_DIR" "$RES_DIR"
for DEST in "$OUT_DIR" "$RES_DIR"; do
cp -L "$CLIENT_LIB" "$DEST/$CLIENT_LIB_NAME"
cp -L "$STRIPPED_CAPTURE_LIB" "$DEST/$CAPTURE_LIB_NAME"
for i in "${!LICENSE_NAMES[@]}"; do
cp "${LICENSE_SRCS[$i]}" "$DEST/${LICENSE_NAMES[$i]}"
done
done
echo "built: $OUT_DIR/$CLIENT_LIB_NAME, $OUT_DIR/$CAPTURE_LIB_NAME ($TRACY_VERSION)"
echo "staged: $RES_DIR/$CLIENT_LIB_NAME, $RES_DIR/$CAPTURE_LIB_NAME"
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- name: Build libTracyClient
- name: Build Tracy natives
shell: bash
env:
OUT_DIR: dist/tracy-natives
+2
View File
@@ -162,6 +162,7 @@ DependencyHandlerScope.of(dependencies).apply {
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
compileOnly("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.49-GTNH:dev")
compileOnly("com.github.GTNewHorizons:OpenBlocks:1.12.19-GTNH:dev")
compileOnly("com.github.GTNewHorizons:Draconic-Evolution:1.5.30-GTNH:dev")
compileOnly("com.github.GTNewHorizons:Chisel:2.17.31-GTNH:dev")
compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
@@ -201,6 +202,7 @@ afterEvaluate {
testImplementation(libs.junit.platform.launcher)
testImplementation(libs.archunit.junit5)
testImplementation(libs.retrofuturabootstrap) { isTransitive = false }
glsmTestFixtures(testFixtures(project(":glsm")) as ModuleDependency) { isTransitive = false }
testCompileOnly(testFixtures(project(":glsm")))
@@ -40,10 +40,10 @@ public final class SystemProperties {
public static final boolean SDL_DISABLE_IN_PASS_CLEAR = Boolean.getBoolean("angelica.sdlgpu.disableInPassClear");
// Tracy
public static final boolean TRACY = Boolean.getBoolean("angelica.tracy");
public static final boolean TRACY_FINE_ZONES = Boolean.getBoolean("angelica.tracy.fineZones");
public static final Boolean TRACY_OVERRIDE = parseBooleanOverride("angelica.tracy");
public static final Boolean TRACY_FINE_ZONES_OVERRIDE = parseBooleanOverride("angelica.tracy.fineZones");
public static final String TRACY_DIR = System.getProperty("angelica.tracy.dir", "angelica" + File.separator + "natives" + File.separator + "tracy");
public static final int TRACY_MAX_SRC_LOCS = Math.max(16, Integer.getInteger("angelica.tracy.maxSrcLocs", 4096));
public static final Integer TRACY_MAX_SRC_LOCS_OVERRIDE = Integer.getInteger("angelica.tracy.maxSrcLocs");
// Profiling
public static final String PROFILE_OPTS = System.getProperty("angelica.profile.opts", "event=wall,interval=5ms,alloc=512k,lock=10ms");
@@ -152,6 +152,11 @@ public final class SystemProperties {
}
}
private static Boolean parseBooleanOverride(String key) {
final String raw = System.getProperty(key);
return raw == null ? null : Boolean.parseBoolean(raw);
}
private static double parseDouble(String key) {
final String raw = System.getProperty(key);
if (raw == null || raw.isEmpty()) return 0.0D;
@@ -117,6 +117,7 @@ public class CompatUniformManager {
private static final class ProgramUniforms {
final int[] locs;
GLContextState owner;
int lastMvGen = -1, lastMvLinearGen = -1, lastProjGen = -1, lastTexMatGen = -1;
int lastFragmentGen = -1, lastLightingGen = -1, lastClipPlaneGen = -1;
ProgramUniforms(int[] locs) { this.locs = locs; }
@@ -161,7 +162,13 @@ public class CompatUniformManager {
}
if (hasAny) {
programUniforms.put(program, new ProgramUniforms(locs));
final ProgramUniforms uniforms = new ProgramUniforms(locs);
final boolean locked = GLStateManager.acquireDrawLock();
try {
programUniforms.put(program, uniforms);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
GLStateManager.LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
}
}
@@ -171,16 +178,21 @@ public class CompatUniformManager {
*
* @return Whether the program needs emulation (FFP or Iris Program)
*/
public static boolean refreshCompatUniforms(int program) {
public static boolean refreshCompatUniforms(int program, GLContextState glCtx) {
final ProgramUniforms pu = programUniforms.get(program);
if (pu == null) return false;
final int[] locs = pu.locs;
if (pu.owner != glCtx) {
pu.owner = glCtx;
pu.lastMvGen = pu.lastMvLinearGen = pu.lastProjGen = pu.lastTexMatGen = -1;
pu.lastFragmentGen = pu.lastLightingGen = pu.lastClipPlaneGen = -1;
}
// Matrix uniforms — skip if this program's storage already holds the current generation
final int mvGen = GLStateManager.getMvGeneration();
final int mvLinearGen = GLStateManager.getMvLinearGeneration();
final int projGen = GLStateManager.getProjGeneration();
final int texMatGen = GLStateManager.getTexMatrixGeneration();
final int mvGen = glCtx.mvGeneration;
final int mvLinearGen = glCtx.mvLinearGeneration;
final int projGen = glCtx.projGeneration;
final int texMatGen = glCtx.texMatrixGeneration;
final boolean mvChanged = mvGen != pu.lastMvGen;
final boolean mvLinearChanged = mvLinearGen != pu.lastMvLinearGen;
final boolean projChanged = projGen != pu.lastProjGen;
@@ -194,14 +206,14 @@ public class CompatUniformManager {
}
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
final int fragGen = GLStateManager.getFragmentGeneration();
final int fragGen = glCtx.fragmentGeneration;
if (fragGen != pu.lastFragmentGen) {
pu.lastFragmentGen = fragGen;
uploadFragmentUniforms(locs);
}
// Lighting-derived uniforms (scene color, light sources, material)
final int litGen = GLStateManager.getLightingGeneration();
final int litGen = glCtx.lightingGeneration;
if (litGen != pu.lastLightingGen) {
pu.lastLightingGen = litGen;
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
@@ -211,7 +223,7 @@ public class CompatUniformManager {
// Clip plane equations + enabled bool — uploaded when enable state or equations change
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
final int cpGen = GLStateManager.getClipPlaneGeneration();
final int cpGen = glCtx.clipPlaneGeneration;
if (cpGen != pu.lastClipPlaneGen) {
pu.lastClipPlaneGen = cpGen;
uploadClipPlanes(locs);
@@ -438,7 +450,12 @@ public class CompatUniformManager {
}
public static void onDeleteProgram(int program) {
programUniforms.remove(program);
final boolean locked = GLStateManager.acquireDrawLock();
try {
programUniforms.remove(program);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
}
public static boolean hasProgram(int program) {
@@ -59,7 +59,7 @@ public class DisplayListIDAllocator {
numSetElements = 1;
}
public int allocRange(int count) {
public synchronized int allocRange(int count) {
if (count <= 0) throw new IllegalArgumentException("count must be > 0");
if (count == 1) return allocSingle();
return allocMultiple(count);
@@ -132,7 +132,7 @@ public class DisplayListIDAllocator {
return numElements;
}
public void free(int id) {
public synchronized void free(int id) {
if (id <= 0) return; // ID 0 is reserved
int idx = id / 32;
if (idx >= numElements) return;
@@ -147,7 +147,7 @@ public class DisplayListIDAllocator {
}
}
public void freeRange(int base, int count) {
public synchronized void freeRange(int base, int count) {
if (count <= 0 || base <= 0) return;
int startIdx = base / 32;
@@ -166,7 +166,7 @@ public class DisplayListIDAllocator {
}
}
public boolean isAllocated(int id) {
public synchronized boolean isAllocated(int id) {
if (id <= 0) return false;
int idx = id / 32;
if (idx >= numSetElements) return false;
@@ -86,7 +86,6 @@ public class DisplayListManager {
private static List<AccumulatedDraw> accumulatedDraws = null; // Accumulates quad draws for batching
private static AccumulatedDraw pendingDraw = null;
private static DisplayListCallback transformCallback = null;
private static int relativeTransformType;
private static final int TRANSFORM_SCALE = 0x1; // Can be extracted to a glScale easily
private static final int TRANSFORM_TRANSLATE = 0x2; // Can be extracted to a glTranslate easily
@@ -95,7 +94,7 @@ public class DisplayListManager {
private static StackTraceElement[] compilationStackTrace = null; // For logging: captured at glNewList()
// Debug logging: track sources of MULT_MATRIX commands and draw origins; only populated when LOG_DISPLAY_LIST_COMPILATION is true
private static List<String> pendingTransformOps = null; // Ops accumulated since last flush
private static List<List<String>> pendingTransformOps = null; // Ops per matrix slot since its last flush
private static List<List<String>> multMatrixSources = null; // Source ops for each MULT_MATRIX in raw buffer
private static List<String> drawRangeSources = null; // Source type for each DRAW_RANGE in final buffer
@@ -119,7 +118,7 @@ public class DisplayListManager {
int recordedStatePushes,
// Debug logging fields (only used when LOG_DISPLAY_LIST_COMPILATION)
List<String> pendingOps,
List<List<String>> pendingOps,
List<List<String>> matrixSources,
List<String> drawSources
) {}
@@ -144,7 +143,7 @@ public class DisplayListManager {
}
public static String describeCurrentCompilation() {
if (currentRecorder == null) {
if (!isRecording()) {
return "(not compiling)";
}
final String mode = glListMode == GL11.GL_COMPILE_AND_EXECUTE ? "GL_COMPILE_AND_EXECUTE" : "GL_COMPILE";
@@ -153,16 +152,20 @@ public class DisplayListManager {
}
public static boolean isCompileAndExecute() {
return glListMode == GL11.GL_COMPILE_AND_EXECUTE;
return isRecording() && glListMode == GL11.GL_COMPILE_AND_EXECUTE;
}
public static CommandRecorder pauseRecording() {
if (Thread.currentThread() != recordingThread) return null;
final CommandRecorder r = currentRecorder;
currentRecorder = null;
return r;
}
public static void resumeRecording(CommandRecorder r) {
if (Thread.currentThread() != recordingThread) {
throw new IllegalStateException("Cannot resume another thread's display list compilation");
}
currentRecorder = r;
}
@@ -171,7 +174,9 @@ public class DisplayListManager {
static void flushAll() {
drawBarrier();
flushMatrix();
for (int slot = 0; slot < DisplayListCallback.SLOT_COUNT; slot++) {
flushMatrix(slot);
}
}
@@ -186,13 +191,21 @@ public class DisplayListManager {
}
/**
* Emit accumulated transform as MultMatrix if non-identity, then reset.
* Emit the current matrix target's accumulated transform as MultMatrix if non-identity, then reset.
*/
static void flushMatrix() {
if (transformCallback.isIdentity()) {
flushMatrix(transformCallback.getCurrentSlot());
}
/**
* Emit one matrix target's accumulated transform as MultMatrix if non-identity, then reset.
*/
private static void flushMatrix(int slot) {
if (transformCallback.isIdentity(slot)) {
// Clear pending ops even if we don't emit - they were no-ops (identity)
transformCallback.reset(slot);
if (pendingTransformOps != null) {
pendingTransformOps.clear();
pendingTransformOps.get(slot).clear();
}
return;
}
@@ -200,44 +213,60 @@ public class DisplayListManager {
// Save pending transform ops for logging (before we clear them)
if (multMatrixSources != null && pendingTransformOps != null) {
if (pendingTransformOps.isEmpty()) {
final List<String> ops = pendingTransformOps.get(slot);
if (ops.isEmpty()) {
GLStateManager.LOGGER.warn("flushMatrix: non-identity transform with no tracked ops");
multMatrixSources.add(Collections.singletonList("(unknown source)"));
} else {
multMatrixSources.add(new ArrayList<>(pendingTransformOps));
multMatrixSources.add(new ArrayList<>(ops));
}
pendingTransformOps.clear();
ops.clear();
}
// Record the collapsed MultMatrix command (for playback)
// Always put a draw barrier BEFORE flushing the matrix (the transforms are already baked into the current draw)
drawBarrier();
if (relativeTransformType == TRANSFORM_SCALE) {
currentRecorder.writeScale(transformCallback.getScale(transformVector));
} else if (relativeTransformType == TRANSFORM_TRANSLATE) {
currentRecorder.writeTranslate(transformCallback.getTranslation(transformVector));
syncStreamMatrixMode(transformCallback.getSlotMatrixMode(slot));
final Matrix4f pending = transformCallback.getReadMatrix(slot);
final int type = transformCallback.getTransformType(slot);
if (type == TRANSFORM_SCALE) {
currentRecorder.writeScale(transformCallback.getScale(slot, transformVector));
} else if (type == TRANSFORM_TRANSLATE) {
currentRecorder.writeTranslate(transformCallback.getTranslation(slot, transformVector));
} else {
currentRecorder.writeMultMatrix(transformCallback.getReadMatrix());
currentRecorder.writeMultMatrix(pending);
}
if (glListMode == GL11.GL_COMPILE_AND_EXECUTE) {
GLStateManager.applyMultMatrix(transformCallback.getReadMatrix());
GLStateManager.applyMultMatrix(pending);
}
// Reset to identity - we're now synchronized with GL
resetRelativeTransform();
transformCallback.reset(slot);
}
private static void syncStreamMatrixMode(int mode) {
if (transformCallback.getStreamMatrixMode() == 0 && !transformCallback.explicitMatrixMode) {
return;
}
if (transformCallback.getStreamMatrixMode() == mode) {
return;
}
drawBarrier();
transformCallback.setStreamMatrixMode(mode);
currentRecorder.writeMatrixMode(mode);
}
public static int getRecordingListId() {
return glListId;
return Thread.currentThread() == recordingThread ? glListId : -1;
}
public static int getListMode() {
return glListMode;
return Thread.currentThread() == recordingThread ? glListMode : 0;
}
public static void trackDrawRangeSource(String source) {
if (drawRangeSources != null) {
if (Thread.currentThread() == recordingThread && drawRangeSources != null) {
drawRangeSources.add(source);
}
}
@@ -315,6 +344,11 @@ public class DisplayListManager {
currentRecorder.writeSecondaryColor(r, g, b);
}
public static void recordVertexAttrib(int index, float x, float y, float z, float w) {
drawBarrier();
currentRecorder.writeVertexAttrib(index, x, y, z, w);
}
public static void recordColorMask(boolean r, boolean g, boolean b, boolean a) {
drawBarrier();
currentRecorder.writeColorMask(r, g, b, a);
@@ -371,14 +405,37 @@ public class DisplayListManager {
}
public static void recordMatrixMode(int mode) {
flushMatrix(); // Matrix barrier: flush and reset
final int previous = transformCallback.getMatrixMode();
if (!transformCallback.explicitMatrixMode) {
// Pending transforms still inherit the caller's target until this explicit selection.
flushMatrix();
}
transformCallback.explicitMatrixMode = true;
if (previous == mode) {
// The initial target and the target after a nested list are unknown at playback.
syncStreamMatrixMode(mode);
return;
}
if (glListMode == GL11.GL_COMPILE_AND_EXECUTE) {
// Live GL follows the app's matrix mode, so a pending delta can only be replayed onto GL
// while its own target is still current.
flushAll();
} else {
// MODELVIEW and PROJECTION share the vertex delta, so a delta belonging to one of them has to
// be flushed before the other starts adding to it.
final int slot = DisplayListCallback.slotOf(mode);
if (transformCallback.getSlotMatrixMode(slot) != mode) {
flushMatrix(slot);
}
}
transformCallback.setMatrixMode(mode);
currentRecorder.writeMatrixMode(mode);
}
public static void recordPushMatrix() {
drawBarrier();
// Flush any pending delta, then record push.
flushMatrix();
syncStreamMatrixMode(transformCallback.getMatrixMode());
currentRecorder.writePushMatrix();
}
@@ -386,6 +443,7 @@ public class DisplayListManager {
// Discard any transformations & flush any pending draw operations
drawBarrier();
resetRelativeTransform();
syncStreamMatrixMode(transformCallback.getMatrixMode());
currentRecorder.writePopMatrix();
}
@@ -435,6 +493,8 @@ public class DisplayListManager {
}
public static void recordActiveTexture(int texture) {
drawBarrier();
flushMatrix(DisplayListCallback.SLOT_TEXTURE);
currentRecorder.writeActiveTexture(texture);
}
@@ -443,15 +503,31 @@ public class DisplayListManager {
currentRecorder.writeUseProgram(program);
}
// PushAttrib saves state but doesn't change it - not a draw barrier
private record AttribState(int matrixMode, boolean restoresTexture) {}
public static void recordPushAttrib(int mask) {
final boolean savesMode = (mask & GL11.GL_TRANSFORM_BIT) != 0;
if (savesMode) syncStreamMatrixMode(transformCallback.getMatrixMode());
transformCallback.attribStates.push(new AttribState(
savesMode ? transformCallback.getStreamMatrixMode() : -1, (mask & GL11.GL_TEXTURE_BIT) != 0));
currentRecorder.writePushAttrib(mask);
}
// PopAttrib restores potentially any state - draw barrier
public static void recordPopAttrib() {
drawBarrier();
final AttribState restored = transformCallback.attribStates.poll();
final int restoredMode = restored == null ? 0 : restored.matrixMode();
if (restoredMode != -1) flushAll();
else {
drawBarrier();
if (restored.restoresTexture()) flushMatrix(DisplayListCallback.SLOT_TEXTURE);
}
currentRecorder.writePopAttrib();
if (restoredMode != -1) {
transformCallback.setMatrixMode(restoredMode == 0 ? GL11.GL_MODELVIEW : restoredMode);
transformCallback.setStreamMatrixMode(restoredMode);
transformCallback.explicitMatrixMode = restoredMode != 0;
}
}
public static int virtualStateDepth(RecordMode mode) {
@@ -571,7 +647,10 @@ public class DisplayListManager {
public static void recordCallList(int listId) {
// Flush all pending draws & transformations
flushAll();
syncStreamMatrixMode(transformCallback.getMatrixMode());
currentRecorder.writeCallList(listId);
transformCallback.setStreamMatrixMode(0);
transformCallback.explicitMatrixMode = false;
}
public static void recordDrawBuffer(int mode) {
@@ -594,6 +673,19 @@ public class DisplayListManager {
currentRecorder.writeComplexCommand(cmd);
}
public static void recordStateCommand(DisplayListCommand cmd) {
drawBarrier();
currentRecorder.writeComplexCommand(cmd);
}
public static void recordStateCommandIfChanged(DisplayListCommand cmd) {
if (!currentRecorder.isLastCommand(cmd)) recordStateCommand(cmd);
}
public static void recordStateCommandOnce(DisplayListCommand cmd) {
if (currentRecorder.markRecordedOnce(cmd)) recordStateCommand(cmd);
}
public static void recordIndexedDrawCapture(IndexedDrawCapture capture) {
flushAll(); //TODO
currentRecorder.writeIndexedDrawCapture(capture);
@@ -602,12 +694,14 @@ public class DisplayListManager {
public static void recordLoadMatrix(Matrix4f matrix) {
drawBarrier();
resetRelativeTransform();
syncStreamMatrixMode(transformCallback.getMatrixMode());
currentRecorder.writeLoadMatrix(matrix);
}
public static void recordLoadIdentity() {
drawBarrier();
resetRelativeTransform();
syncStreamMatrixMode(transformCallback.getMatrixMode());
currentRecorder.writeLoadIdentity();
}
@@ -645,11 +739,10 @@ public class DisplayListManager {
}
public static void applyMatrixTranslation(float x, float y, float z) {
transformCallback.translate(x, y, z);
relativeTransformType |= TRANSFORM_TRANSLATE;
transformCallback.translate(x, y, z, TRANSFORM_TRANSLATE);
if (pendingTransformOps != null) {
pendingTransformOps.add(String.format("glTranslatef(%.4f, %.4f, %.4f)", x, y, z));
pendingTransformOps.get(transformCallback.getCurrentSlot()).add(String.format("glTranslatef(%.4f, %.4f, %.4f)", x, y, z));
}
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
@@ -658,11 +751,10 @@ public class DisplayListManager {
}
public static void applyMatrixScale(float x, float y, float z) {
transformCallback.scale(x, y, z);
relativeTransformType |= TRANSFORM_SCALE;
transformCallback.scale(x, y, z, TRANSFORM_SCALE);
if (pendingTransformOps != null) {
pendingTransformOps.add(String.format("glScalef(%.4f, %.4f, %.4f)", x, y, z));
pendingTransformOps.get(transformCallback.getCurrentSlot()).add(String.format("glScalef(%.4f, %.4f, %.4f)", x, y, z));
}
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
@@ -676,11 +768,10 @@ public class DisplayListManager {
* Requires the angle to be in radians & the coordinates to be normalized.
*/
public static void applyMatrixRotation(float rad, float x, float y, float z) {
transformCallback.rotate(rad, x, y, z);
relativeTransformType |= TRANSFORM_COMPLEX;
transformCallback.rotate(rad, x, y, z, TRANSFORM_COMPLEX);
if (pendingTransformOps != null) {
pendingTransformOps.add(String.format("glRotatef(%.4f, %.4f, %.4f, %.4f)", Math.toDegrees(rad), x, y, z));
pendingTransformOps.get(transformCallback.getCurrentSlot()).add(String.format("glRotatef(%.4f, %.4f, %.4f, %.4f)", Math.toDegrees(rad), x, y, z));
}
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
@@ -698,11 +789,10 @@ public class DisplayListManager {
* @param matrix The matrix to multiply
*/
public static void updateRelativeTransform(Matrix4f matrix) {
transformCallback.multMatrix(matrix);
relativeTransformType |= TRANSFORM_COMPLEX;
transformCallback.multMatrix(matrix, TRANSFORM_COMPLEX);
if (pendingTransformOps != null) {
pendingTransformOps.add("glMultMatrixf(...)");
pendingTransformOps.get(transformCallback.getCurrentSlot()).add("glMultMatrixf(...)");
}
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
@@ -716,11 +806,10 @@ public class DisplayListManager {
public static void updateRelativeTransformOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
orthoFrustumTemp.identity().ortho((float) left, (float) right, (float) bottom, (float) top, (float) zNear, (float) zFar);
transformCallback.multMatrix(orthoFrustumTemp);
relativeTransformType |= TRANSFORM_COMPLEX;
transformCallback.multMatrix(orthoFrustumTemp, TRANSFORM_COMPLEX);
if (pendingTransformOps != null) {
pendingTransformOps.add(String.format("glOrtho(%.4f, %.4f, %.4f, %.4f, %.4f, %.4f)", left, right, bottom, top, zNear, zFar));
pendingTransformOps.get(transformCallback.getCurrentSlot()).add(String.format("glOrtho(%.4f, %.4f, %.4f, %.4f, %.4f, %.4f)", left, right, bottom, top, zNear, zFar));
}
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
@@ -731,11 +820,10 @@ public class DisplayListManager {
public static void updateRelativeTransformFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
orthoFrustumTemp.identity().frustum((float) left, (float) right, (float) bottom, (float) top, (float) zNear, (float) zFar);
transformCallback.multMatrix(orthoFrustumTemp);
relativeTransformType |= TRANSFORM_COMPLEX;
transformCallback.multMatrix(orthoFrustumTemp, TRANSFORM_COMPLEX);
if (pendingTransformOps != null) {
pendingTransformOps.add(String.format("glFrustum(%.4f, %.4f, %.4f, %.4f, %.4f, %.4f)", left, right, bottom, top, zNear, zFar));
pendingTransformOps.get(transformCallback.getCurrentSlot()).add(String.format("glFrustum(%.4f, %.4f, %.4f, %.4f, %.4f, %.4f)", left, right, bottom, top, zNear, zFar));
}
if (SystemProperties.DEBUG_DISPLAY_LISTS) {
@@ -749,8 +837,8 @@ public class DisplayListManager {
* subsequent transforms are relative to that loaded matrix (i.e., start from identity).
*/
static void resetRelativeTransform() {
transformCallback.setIdentity();
relativeTransformType = 0;
transformCallback.reset(transformCallback.getCurrentSlot());
if (pendingTransformOps != null) pendingTransformOps.get(transformCallback.getCurrentSlot()).clear();
}
/**
@@ -761,8 +849,11 @@ public class DisplayListManager {
* @return true if the display list exists, false otherwise
*/
public static boolean displayListExists(int list) {
if (displayListCache.containsKey(list)) {
return true;
final boolean locked = GLStateManager.acquireDrawLock();
try {
if (displayListCache.containsKey(list)) return true;
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
// Check VBOManager for GTNHLib compatibility (negative IDs)
if (list < -1) {
@@ -778,7 +869,12 @@ public class DisplayListManager {
* @return The CompiledDisplayList, or null if not found
*/
public static CompiledDisplayList getDisplayList(int list) {
return displayListCache.get(list);
final boolean locked = GLStateManager.acquireDrawLock();
try {
return displayListCache.get(list);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
}
/**
@@ -787,73 +883,103 @@ public class DisplayListManager {
* GL_COMPILE: Commands are recorded only (not executed), GLSM cache unchanged
* GL_COMPILE_AND_EXECUTE: Commands are recorded AND executed (GLSM cache updated)
*/
public static void glNewList(int list, int mode) {
// Handle nested compilation - push current context onto stack
public static synchronized void glNewList(int list, int mode) {
final Thread recorder = recordingThread;
if (recorder != null && recorder != Thread.currentThread()) {
throw new IllegalStateException("glNewList(" + list + ") on " + Thread.currentThread().getName() + " while " + recorder.getName() + " is compiling a display list");
}
final boolean isNested = glListMode > 0;
final int captureDepth = TessellatorManager.getDirectCaptureDepth();
boolean pushed = false;
if (!isNested) recordingThread = Thread.currentThread();
try {
if (isNested) {
flushAll();
final CompilationContext parentContext = new CompilationContext(
glListId, glListMode, currentRecorder, accumulatedDraws, transformCallback,
compilationStackTrace, recordedStatePushes, pendingTransformOps, multMatrixSources, drawRangeSources
);
compilationStack.push(parentContext);
pushed = true;
}
if (isNested) {
flushAll();
// Nested display list compilation violates OpenGL spec, but some of our optimizations require it
// Save current compilation context and start fresh for nested list
final CompilationContext parentContext = new CompilationContext(
glListId, glListMode, currentRecorder, accumulatedDraws, transformCallback,
compilationStackTrace, recordedStatePushes, pendingTransformOps, multMatrixSources, drawRangeSources
);
compilationStack.push(parentContext);
glListId = list;
glListMode = mode;
currentRecorder = new CommandRecorder();
accumulatedDraws = new ArrayList<>(8);
transformCallback = new DisplayListCallback();
recordedStatePushes = 0;
compilationStackTrace = SystemProperties.LOG_DISPLAY_LIST_COMPILATION ? Thread.currentThread().getStackTrace() : null;
if (SystemProperties.LOG_DISPLAY_LIST_COMPILATION) {
pendingTransformOps = new ArrayList<>();
for (int slot = 0; slot < DisplayListCallback.SLOT_COUNT; slot++) {
pendingTransformOps.add(new ArrayList<>());
}
multMatrixSources = new ArrayList<>();
drawRangeSources = new ArrayList<>();
} else {
pendingTransformOps = null;
multMatrixSources = null;
drawRangeSources = null;
}
TessellatorManager.startCapturingDirect(transformCallback);
} catch (RuntimeException | Error failure) {
try {
if (TessellatorManager.getDirectCaptureDepth() > captureDepth) TessellatorManager.stopCapturingDirect();
} catch (RuntimeException | Error cleanup) {
failure.addSuppressed(cleanup);
}
try {
if (pushed) {
final CommandRecorder parent = compilationStack.peek().recorder();
try {
if (currentRecorder != null && currentRecorder != parent) currentRecorder.delete();
} finally {
popCompilationContext();
}
} else if (!isNested) {
try {
if (currentRecorder != null) currentRecorder.delete();
} finally {
resetCompilationState();
}
}
} catch (RuntimeException | Error cleanup) {
failure.addSuppressed(cleanup);
}
throw failure;
}
// Initialize fresh context for this (possibly nested) list
glListId = list;
glListMode = mode;
recordingThread = Thread.currentThread(); // Track which thread is recording
currentRecorder = new CommandRecorder(); // Create command recorder
accumulatedDraws = new ArrayList<>(8); // Fewer draws than commands typically
transformCallback = new DisplayListCallback();
recordedStatePushes = 0;
compilationStackTrace = SystemProperties.LOG_DISPLAY_LIST_COMPILATION ? Thread.currentThread().getStackTrace() : null;
// Initialize debug logging fields (only when logging enabled)
if (SystemProperties.LOG_DISPLAY_LIST_COMPILATION) {
pendingTransformOps = new ArrayList<>();
multMatrixSources = new ArrayList<>();
drawRangeSources = new ArrayList<>();
} else {
pendingTransformOps = null;
multMatrixSources = null;
drawRangeSources = null;
}
TessellatorManager.startCapturingDirect(transformCallback);
}
/**
* End display list compilation and build optimized/unoptimized versions.
*/
public static void glEndList() {
public static synchronized void glEndList() {
final Thread recorder = recordingThread;
if (recorder != null && recorder != Thread.currentThread()) {
throw new IllegalStateException("glEndList on " + Thread.currentThread().getName() + " while " + recorder.getName() + " is compiling a display list");
}
if (glListMode == 0) {
GLStateManager.warnOnce("endlist-outside", "glEndList called outside of a display list!");
return;
}
if (Tracy.ENABLED) Tracy.beginZone(Z_GL_LIST_COMPILE);
try {
TessellatorManager.stopCapturingDirect();
finishCurrentList();
} finally {
if (Tracy.ENABLED) Tracy.endZone();
// Non-null here means finishCurrentList threw before transferring or releasing
// ownership of the recorder.
if (currentRecorder != null) {
currentRecorder.delete();
try {
if (currentRecorder != null) currentRecorder.delete();
} finally {
popCompilationContext();
}
// Must always run, even when compilation fails - a leaked context leaves
// isRecording() true forever and every guarded GL call throws afterward.
popCompilationContext();
}
}
private static void finishCurrentList() {
// Stop compiling mode (works for both root and nested lists now)
TessellatorManager.stopCapturingDirect();
boolean unbalancedStatePush = false;
while (recordPopStateIfPending()) unbalancedStatePush = true;
if (unbalancedStatePush) {
@@ -862,10 +988,8 @@ public class DisplayListManager {
flushAll();
// Reset back to MODELVIEW (default state)
if (transformCallback.getMatrixMode() != GL11.GL_MODELVIEW) {
recordMatrixMode(GL11.GL_MODELVIEW);
}
transformCallback.setMatrixMode(GL11.GL_MODELVIEW);
syncStreamMatrixMode(GL11.GL_MODELVIEW);
final CompiledDisplayList compiled;
// Create CompiledDisplayList with both unoptimized and optimized versions
@@ -966,7 +1090,6 @@ public class DisplayListManager {
/** Clear all root-level compilation state back to "not recording". */
private static void resetCompilationState() {
currentRecorder = null;
recordingThread = null;
accumulatedDraws = null;
pendingDraw = null;
transformCallback = null;
@@ -977,9 +1100,13 @@ public class DisplayListManager {
glListId = -1;
glListMode = 0;
recordedStatePushes = 0;
recordingThread = null;
}
public static void abortCompilation() {
public static synchronized void abortCompilation() {
if (recordingThread != null && recordingThread != Thread.currentThread()) {
throw new IllegalStateException("Cannot abort another thread's display list compilation");
}
if (glListMode == 0 && compilationStack.isEmpty()) {
return;
}
@@ -1007,7 +1134,7 @@ public class DisplayListManager {
}
public static void abortIfLeaked() {
if (glListMode != 0 || !compilationStack.isEmpty()) {
if (Thread.currentThread() == recordingThread && (glListMode != 0 || !compilationStack.isEmpty())) {
GLStateManager.warnOnce("frame-leak", "Display list {} left open across a frame boundary - aborting to recover", glListId);
abortCompilation();
}
@@ -1043,7 +1170,7 @@ public class DisplayListManager {
* Execute a compiled display list.
*/
public static void glCallList(int list) {
if (currentRecorder != null) {
if (isRecording()) {
recordCallList(list);
if (getListMode() != GL11.GL_COMPILE) {
@@ -1357,12 +1484,32 @@ public class DisplayListManager {
private static final class DisplayListCallback extends VertexTransformCallback {
static final int SLOT_VERTEX = 0; // GL_MODELVIEW + GL_PROJECTION, feeds into gl_Position
static final int SLOT_TEXTURE = 1;
static final int SLOT_COLOR = 2;
static final int SLOT_OTHER = 3;
static final int SLOT_COUNT = 4;
static int slotOf(int mode) {
return switch (mode) {
case GL11.GL_MODELVIEW, GL11.GL_PROJECTION -> SLOT_VERTEX;
case GL11.GL_TEXTURE -> SLOT_TEXTURE;
case GL11.GL_COLOR -> SLOT_COLOR;
default -> SLOT_OTHER;
};
}
private int matrixMode = GL11.GL_MODELVIEW;
private int streamMatrixMode;
private boolean explicitMatrixMode;
private final Deque<AttribState> attribStates = new ArrayDeque<>();
private final Matrix3f normalMatrix = new Matrix3f();
private final Matrix4f transformMatrix = new Matrix4f();
private final Matrix4f[] transformMatrices = { new Matrix4f(), new Matrix4f(), new Matrix4f(), new Matrix4f() };
private final int[] transformTypes = new int[SLOT_COUNT];
private final int[] slotModes = { GL11.GL_MODELVIEW, GL11.GL_TEXTURE, GL11.GL_COLOR, 0 };
private DirectTessellator bakedOwner;
private int bakedVertices;
@@ -1374,49 +1521,69 @@ public class DisplayListManager {
}
}
public void scale(float x, float y, float z) {
transformMatrix.scale(x, y, z);
private Matrix4f begin(int typeBits) {
final int slot = getCurrentSlot();
transformTypes[slot] |= typeBits;
slotModes[slot] = matrixMode;
return transformMatrices[slot];
}
public void scale(float x, float y, float z, int typeBits) {
begin(typeBits).scale(x, y, z);
markDirty();
}
public void translate(float x, float y, float z) {
transformMatrix.translate(x, y, z);
public void translate(float x, float y, float z, int typeBits) {
begin(typeBits).translate(x, y, z);
markDirty();
}
public void rotate(float rad, float x, float y, float z) {
transformMatrix.rotate(rad, x, y, z);
public void rotate(float rad, float x, float y, float z, int typeBits) {
begin(typeBits).rotate(rad, x, y, z);
markDirty();
}
public void multMatrix(Matrix4f matrix4f) {
transformMatrix.mul(matrix4f);
public void multMatrix(Matrix4f matrix4f, int typeBits) {
begin(typeBits).mul(matrix4f);
markDirty();
}
public void setIdentity() {
transformMatrix.identity();
markDirty();
public void reset(int slot) {
transformMatrices[slot].identity();
transformTypes[slot] = 0;
if (slot == SLOT_VERTEX) {
markDirty();
}
}
public Vector3f getScale(Vector3f dest) {
return transformMatrix.getScale(dest);
public Vector3f getScale(int slot, Vector3f dest) {
final Matrix4f matrix = transformMatrices[slot];
return dest.set(matrix.m00(), matrix.m11(), matrix.m22());
}
public Vector3f getTranslation(Vector3f dest) {
return transformMatrix.getTranslation(dest);
public Vector3f getTranslation(int slot, Vector3f dest) {
return transformMatrices[slot].getTranslation(dest);
}
/**
* WARNING: If you apply transformations to this matrix, make sure to call markDirty() afterward!
*/
public Matrix4f getReadMatrix() {
return transformMatrix;
public Matrix4f getReadMatrix(int slot) {
return transformMatrices[slot];
}
public int getTransformType(int slot) {
return transformTypes[slot];
}
/** The GL matrix target a slot's pending delta belongs to. */
public int getSlotMatrixMode(int slot) {
return slotModes[slot];
}
public void markDirty() {
if (matrixMode == GL11.GL_MODELVIEW) {
NormalHelper.getNormalMatrix(transformMatrix, normalMatrix);
NormalHelper.getNormalMatrix(transformMatrices[SLOT_VERTEX], normalMatrix);
}
}
@@ -1425,15 +1592,26 @@ public class DisplayListManager {
*/
public void setMatrixMode(int mode) {
this.matrixMode = mode;
markDirty();
}
public int getMatrixMode() {
return this.matrixMode;
}
public boolean isIdentity() {
return MatrixHelper.isIdentity(transformMatrix);
public int getCurrentSlot() {
return slotOf(matrixMode);
}
public int getStreamMatrixMode() {
return this.streamMatrixMode;
}
public void setStreamMatrixMode(int mode) {
this.streamMatrixMode = mode;
}
public boolean isIdentity(int slot) {
return MatrixHelper.isIdentity(transformMatrices[slot]);
}
@Override
@@ -1446,10 +1624,12 @@ public class DisplayListManager {
@Override
public boolean onDraw(CallbackTessellator tessellator) {
if (!tessellator.isEmpty()) {
if (!isIdentity()) {
if (!isIdentity(SLOT_VERTEX) || !isIdentity(SLOT_TEXTURE) || !isIdentity(SLOT_COLOR)) {
final int baked = bakedOwner == tessellator ? bakedVertices : 0;
if (bakedVertices == 0) {
flushMatrix();
flushMatrix(SLOT_VERTEX);
flushMatrix(SLOT_TEXTURE);
flushMatrix(SLOT_COLOR);
} else if (baked != tessellator.vertexCount) {
GLStateManager.warnOnce("dl-mixed-baked-raw", "Display list {}: draw of {} vertices carries {} transformed ones - the rest lose the pending transform", glListId, tessellator.vertexCount, baked);
}
@@ -1473,22 +1653,22 @@ public class DisplayListManager {
@Override
protected Matrix4f getMVPMatrix() {
return matrixMode <= GL11.GL_PROJECTION ? transformMatrix : null;
return transformTypes[SLOT_VERTEX] != 0 ? transformMatrices[SLOT_VERTEX] : null;
}
@Override
protected Matrix4f getTextureMatrix() {
return matrixMode == GL11.GL_TEXTURE ? transformMatrix : null;
return transformTypes[SLOT_TEXTURE] != 0 ? transformMatrices[SLOT_TEXTURE] : null;
}
@Override
protected Matrix4f getColorMatrix() {
return matrixMode == GL11.GL_COLOR ? transformMatrix : null;
return transformTypes[SLOT_COLOR] != 0 ? transformMatrices[SLOT_COLOR] : null;
}
@Override
protected Matrix3f getNormalMatrix() {
return matrixMode == GL11.GL_MODELVIEW ? normalMatrix : null; // GL_PROJECTION doesn't transform normals
return transformTypes[SLOT_VERTEX] != 0 && slotModes[SLOT_VERTEX] == GL11.GL_MODELVIEW ? normalMatrix : null;
}
}
}
@@ -302,7 +302,7 @@ public class Feature {
));
attribToFeatures.put(GL11.GL_SCISSOR_BIT, ImmutableSet.of(
GLStateManager.getScissorTest()
// Scissor box
, GLStateManager.getScissorState()
));
attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of(
GLStateManager.getStencilTest()
@@ -1,6 +1,5 @@
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;
@@ -48,6 +47,10 @@ public class FeedbackManager {
return renderMode == GL11.GL_FEEDBACK;
}
public static int getRenderMode() {
return renderMode;
}
public static void glFeedbackBuffer(int type, FloatBuffer buffer) {
if (renderMode == GL11.GL_FEEDBACK) {
GLStateManager.LOGGER.error("glFeedbackBuffer called while already in feedback mode");
@@ -126,30 +129,32 @@ public class FeedbackManager {
feedbackVertex(winX, winY, winZ, winW);
}
private static int positionStride() {
final int s = VAOManager.getAttribEffectiveStride(0);
private static int positionStride(GLContextState glCtx) {
final int s = glCtx.vaos.attribEffectiveStride(0);
return (s != 0) ? s : 12;
}
private static long positionOffset() {
return VAOManager.getAttribOffset(0);
private static long positionOffset(GLContextState glCtx) {
return glCtx.vaos.attribOffset(0);
}
public static void processDrawArrays(int mode, int first, int count) {
computeMVP();
final int stride = positionStride();
final long posOffset = positionOffset();
final GLContextState glCtx = GLStateManager.ctx();
final int stride = positionStride(glCtx);
final long posOffset = positionOffset(glCtx);
final int byteCount = count * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
readPositionBuffer((long) first * stride + posOffset, readbackBuf);
readPositionBuffer((long) first * stride + posOffset, readbackBuf, glCtx);
emitAllPrimitives(mode, count, i -> i, stride);
}
public static void processDrawElements(int mode, int indexCount, int type, long eboOffset) {
computeMVP();
final int stride = positionStride();
final GLContextState glCtx = GLStateManager.ctx();
final int stride = positionStride(glCtx);
final int elementSize = GLTypes.sizeBytes(type);
final int indexByteCount = indexCount * elementSize;
@@ -165,12 +170,12 @@ public class FeedbackManager {
if (idx > maxIdx) maxIdx = idx;
}
final long posOffset = positionOffset();
final long posOffset = positionOffset(glCtx);
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf, glCtx);
final int base = minIdx;
emitAllPrimitives(mode, indexCount, i -> ImmediateModeRecorder.readIndex(indexBuf, type, 0, i) - base, stride);
@@ -204,7 +209,8 @@ public class FeedbackManager {
private static void processDrawElementsImpl(int mode, int count, IndexReader reader) {
computeMVP();
final int stride = positionStride();
final GLContextState glCtx = GLStateManager.ctx();
final int stride = positionStride(glCtx);
int minIdx = Integer.MAX_VALUE, maxIdx = Integer.MIN_VALUE;
for (int i = 0; i < count; i++) {
@@ -213,7 +219,7 @@ public class FeedbackManager {
if (idx > maxIdx) maxIdx = idx;
}
readVerticesFromVBO(minIdx, maxIdx, stride);
readVerticesFromVBO(minIdx, maxIdx, stride, glCtx);
final int base = minIdx;
emitAllPrimitives(mode, count, i -> reader.read(i) - base, stride);
}
@@ -245,18 +251,18 @@ public class FeedbackManager {
}
}
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride) {
final long posOffset = positionOffset();
private static void readVerticesFromVBO(int minIdx, int maxIdx, int stride, GLContextState glCtx) {
final long posOffset = positionOffset(glCtx);
final int vertexCount = maxIdx - minIdx + 1;
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf, glCtx);
}
private static void readPositionBuffer(long offset, ByteBuffer destination) {
final int positionVbo = VAOManager.getAttribVBO(0);
final int previousVbo = GLStateManager.getBoundVBO();
private static void readPositionBuffer(long offset, ByteBuffer destination, GLContextState glCtx) {
final int positionVbo = glCtx.vaos.attribVBO(0);
final int previousVbo = glCtx.boundVBO;
if (positionVbo != 0 && positionVbo != previousVbo) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, positionVbo);
@@ -3,8 +3,6 @@ package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import com.gtnewhorizons.angelica.glsm.stacks.AlphaStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
@@ -23,6 +21,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.MaterialStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.MatrixModeStack;
import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.ScissorStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
@@ -34,16 +33,24 @@ import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.ImageUnitArray;
import com.gtnewhorizons.angelica.glsm.states.SamplerUnitArray;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingVaos;
import org.joml.Matrix4d;
import org.joml.Matrix4f;
import org.joml.Matrix4fStack;
import org.joml.Vector4f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -100,6 +107,8 @@ public class GLContextState {
public int maxBoundImageUnit = 0;
public final StateSet[] attribSets = new StateSet[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public int attribDepth = 0;
public int batchStateGeneration;
public final int[] savedBatchStateGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public CowStateStack<?>[] stackById;
public CowDepths[] depthsById;
public int[] restoreBitById;
@@ -125,6 +134,7 @@ public class GLContextState {
public final BlendStateStack blendState = member(new BlendStateStack(StackIdAllocator.nextId()));
public final BooleanStateStack blendMode = track(new BooleanStateStack(GL11.GL_BLEND));
public final BooleanStateStack scissorTest = track(new BooleanStateStack(GL11.GL_SCISSOR_TEST));
public final BooleanStateStack rasterizerDiscard = track(new BooleanStateStack(GL30.GL_RASTERIZER_DISCARD));
public final DepthStateStack depthState = member(new DepthStateStack(StackIdAllocator.nextId()));
public final BooleanStateStack depthTest = track(new BooleanStateStack(GL11.GL_DEPTH_TEST));
public final FogStateStack fogState = member(new FogStateStack(StackIdAllocator.nextId()));
@@ -200,6 +210,7 @@ public class GLContextState {
public final MaterialStateStack frontMaterial = member(new MaterialStateStack(GL11.GL_FRONT, StackIdAllocator.nextId()));
public final MaterialStateStack backMaterial = member(new MaterialStateStack(GL11.GL_BACK, StackIdAllocator.nextId()));
public final ViewPortStateStack viewportState = member(new ViewPortStateStack(StackIdAllocator.nextId()));
public final ScissorStateStack scissorState = member(new ScissorStateStack(StackIdAllocator.nextId()));
public int activeProgram = 0;
public final IntegerStateStack programStack = member(new IntegerStateStack(0, StackIdAllocator.nextId()));
public int listBase = 0;
@@ -210,7 +221,6 @@ public class GLContextState {
public int boundCopyReadBuffer;
public int boundCopyWriteBuffer;
public final Int2IntMap boundOtherBuffers = new Int2IntOpenHashMap();
public final IntSet writeMappedBuffers = new IntOpenHashSet();
public PixelUnpackState pixelUnpackState = PixelUnpackState.DEFAULT;
public final int[] clientAttribSavedTextureUnit = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedVertexFlags = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
@@ -223,6 +233,34 @@ public class GLContextState {
public int drawFramebuffer = 0;
public int readFramebuffer = 0;
public int texGenGeneration;
public int defaultVAO;
public final VAOManager vaos = new VAOManager();
public final StreamingVaos streamingVaos = new StreamingVaos();
public int fontVao;
public boolean lockBindCallback;
public int foreignDrawDepth;
public int internalGlDepth;
public int internalDrawDepth;
public int savedFilterTextureId;
public int savedFilterMin = -1;
public int savedFilterMag = -1;
public float savedFilterAniso = -1.0f;
public boolean wideLineEmulationActive = false;
public boolean lineStippleActive = false;
public Instancing ffpInstancing = Instancing.NONE;
public float lastBrightnessX;
public float lastBrightnessY;
public final Matrix4f multMatrix = new Matrix4f();
public final Matrix4d conversionMatrix4d = new Matrix4d();
public final Matrix4f conversionMatrix4f = new Matrix4f();
public final Matrix4f gluMatrix = new Matrix4f();
public final Matrix4f gluMatrix2 = new Matrix4f();
public final Vector4f gluVec4 = new Vector4f();
public final FloatBuffer gluBuffer = BufferUtils.createFloatBuffer(16);
public final float[] texGenTempPlane = new float[4];
public final IntBuffer shaderBuf = BufferUtils.createIntBuffer(8);
public final FloatBuffer projectionMatrixBuffer = BufferUtils.createFloatBuffer(16);
public final ShaderManager ffp = new ShaderManager();
GLContextState() {
allStacksBuilder.addAll(textures.idStacks());
@@ -238,11 +276,11 @@ public class GLContextState {
for (int i = 0; i < lightDataStates.length; i++) {
lightDataStates[i] = member(new LightStateStack(GL11.GL_LIGHT0 + i, StackIdAllocator.nextId()));
}
allStacksBuilder.add(ShaderManager.getNormalStack());
allStacksBuilder.add(ShaderManager.getTexCoordStack());
allStacksBuilder.add(ffp.normalStack);
allStacksBuilder.add(ffp.texCoordStack);
allStacks = allStacksBuilder.toArray(new IStateStack<?>[0]);
final int capacity = StackIdAllocator.capacity();
final int capacity = StackIdAllocator.allocated();
stackById = new CowStateStack<?>[capacity];
depthsById = new CowDepths[capacity];
restoreBitById = new int[capacity];
@@ -253,6 +291,7 @@ public class GLContextState {
final int id = s.stackId();
stackById[id] = s;
depthsById[id] = s.cowDepths();
depthsById[id].owner = this;
restoreBitById[id] = s.restoreBit();
restoreUnitById[id] = s.restoreUnit();
kindById[id] = CowDispatch.kindOf(s);
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,26 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.VertexKey;
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import org.lwjgl.opengl.GL11;
public final class LightingSnapshot {
final boolean[] lightEnabled = new boolean[VertexKey.FFP_LIGHT_COUNT];
final LightState[] lights = new LightState[VertexKey.FFP_LIGHT_COUNT];
final LightModelState lightModel = new LightModelState();
final MaterialState frontMaterial = new MaterialState(GL11.GL_FRONT);
final MaterialState backMaterial = new MaterialState(GL11.GL_BACK);
boolean colorMaterial;
int colorMaterialFace;
int colorMaterialParameter;
boolean normalize;
boolean rescaleNormal;
public LightingSnapshot() {
for (int i = 0; i < lights.length; i++) {
lights[i] = new LightState(GL11.GL_LIGHT0 + i);
}
}
}
@@ -82,17 +82,17 @@ public final class QuadConverter {
* @param first first vertex index (must be aligned to quad boundary, i.e. multiple of 4)
* @param vertexCount number of vertices (must be multiple of 4)
*/
public static void drawQuadsAsTriangles(int first, int vertexCount) {
drawSharedQuadEbo(first, vertexCount, 1, false);
public static void drawQuadsAsTriangles(GLContextState glCtx, int first, int vertexCount) {
drawSharedQuadEbo(glCtx, first, vertexCount, 1, false);
}
public static void drawQuadsAsTrianglesInstanced(int first, int vertexCount, int primcount) {
drawSharedQuadEbo(first, vertexCount, primcount, true);
public static void drawQuadsAsTrianglesInstanced(GLContextState glCtx, int first, int vertexCount, int primcount) {
drawSharedQuadEbo(glCtx, first, vertexCount, primcount, true);
}
private static int misalignedDraws;
private static void drawSharedQuadEbo(int first, int vertexCount, int primcount, boolean instanced) {
private static void drawSharedQuadEbo(GLContextState glCtx, int first, int vertexCount, int primcount, boolean instanced) {
final int quadCount = vertexCount / 4;
if ((first & 3) != 0) {
misalignedDraws++;
@@ -111,26 +111,21 @@ public final class QuadConverter {
memPutInt(ptr + 20, base + 3);
ptr += 24;
}
uploadAndDraw(dst, quadCount * 6, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
uploadAndDraw(glCtx, dst, quadCount * 6, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
return;
}
final boolean locked = GLStateManager.acquireDrawLock();
try {
GLStateManager.preDraw();
final int prevEbo = GLStateManager.getBoundEBO();
ensureCapacity(first / 4 + quadCount);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
final long indexOffset = (long) (first / 4) * 6 * 4;
if (instanced) {
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset, primcount);
} else {
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
} finally {
if (locked) GLStateManager.releaseDrawLock();
GLStateManager.preDraw(glCtx, GL11.GL_TRIANGLES);
final int prevEbo = glCtx.vaos.boundEBO;
ensureCapacity(first / 4 + quadCount);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
final long indexOffset = (long) (first / 4) * 6 * 4;
if (instanced) {
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset, primcount);
} else {
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
}
/**
@@ -143,42 +138,37 @@ public final class QuadConverter {
* @param indexType GL_UNSIGNED_INT or GL_UNSIGNED_SHORT
* @param bytesPerIndex bytes per index element
*/
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
uploadAndDraw(dst, triIndexCount, indexType, bytesPerIndex, 1, false);
private static void uploadAndDraw(GLContextState glCtx, ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
uploadAndDraw(glCtx, dst, triIndexCount, indexType, bytesPerIndex, 1, false);
}
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex, int primcount, boolean instanced) {
final boolean locked = GLStateManager.acquireDrawLock();
try {
GLStateManager.preDraw();
final int needed = triIndexCount * bytesPerIndex;
final int prevEbo = GLStateManager.getBoundEBO();
private static void uploadAndDraw(GLContextState glCtx, ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex, int primcount, boolean instanced) {
GLStateManager.preDraw(glCtx, GL11.GL_TRIANGLES);
final int needed = triIndexCount * bytesPerIndex;
final int prevEbo = glCtx.vaos.boundEBO;
if (scratchEboId == 0) {
scratchEboId = RENDER_BACKEND.genBuffers();
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
if (needed > scratchEboCapacity) {
// Power-of-2 growth -- allocate full capacity, upload actual data
int newCap = Math.max(4096, scratchEboCapacity);
while (newCap < needed) newCap *= 2;
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
scratchEboCapacity = newCap;
}
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
if (instanced) {
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L, primcount);
} else {
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
} finally {
if (locked) GLStateManager.releaseDrawLock();
if (scratchEboId == 0) {
scratchEboId = RENDER_BACKEND.genBuffers();
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
if (needed > scratchEboCapacity) {
// Power-of-2 growth -- allocate full capacity, upload actual data
int newCap = Math.max(4096, scratchEboCapacity);
while (newCap < needed) newCap *= 2;
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
scratchEboCapacity = newCap;
}
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
if (instanced) {
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L, primcount);
} else {
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
memFree(dst);
}
@@ -190,15 +180,15 @@ public final class QuadConverter {
* @param type GL_UNSIGNED_INT, GL_UNSIGNED_SHORT, or GL_UNSIGNED_BYTE
* @param offset byte offset into the currently bound EBO
*/
public static void drawQuadElementsAsTriangles(int indexCount, int type, long offset) {
drawQuadElementsFromEbo(indexCount, type, offset, 1, false);
public static void drawQuadElementsAsTriangles(GLContextState glCtx, int indexCount, int type, long offset) {
drawQuadElementsFromEbo(glCtx, indexCount, type, offset, 1, false);
}
public static void drawQuadElementsAsTrianglesInstanced(int indexCount, int type, long offset, int primcount) {
drawQuadElementsFromEbo(indexCount, type, offset, primcount, true);
public static void drawQuadElementsAsTrianglesInstanced(GLContextState glCtx, int indexCount, int type, long offset, int primcount) {
drawQuadElementsFromEbo(glCtx, indexCount, type, offset, primcount, true);
}
private static void drawQuadElementsFromEbo(int indexCount, int type, long offset, int primcount, boolean instanced) {
private static void drawQuadElementsFromEbo(GLContextState glCtx, int indexCount, int type, long offset, int primcount, boolean instanced) {
if (indexCount == 0) return;
assert indexCount % 4 == 0 : "QuadConverter: indexCount must be multiple of 4";
final int quadCount = indexCount / 4;
@@ -213,13 +203,13 @@ public final class QuadConverter {
triangulateQuads(memAddress0(src), type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
memFree(src);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
}
/**
* Convert a client-side IntBuffer of quad indices to triangles.
*/
public static void drawQuadElementsAsTriangles(IntBuffer indices) {
public static void drawQuadElementsAsTriangles(GLContextState glCtx, IntBuffer indices) {
final int indexCount = indices.remaining();
if (indexCount == 0) return;
assert indexCount % 4 == 0;
@@ -230,13 +220,13 @@ public final class QuadConverter {
final long srcAddr = memAddress0(indices) + (long) indices.position() * 4;
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_INT, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
* Convert a client-side ShortBuffer of quad indices to triangles.
*/
public static void drawQuadElementsAsTriangles(ShortBuffer indices) {
public static void drawQuadElementsAsTriangles(GLContextState glCtx, ShortBuffer indices) {
final int indexCount = indices.remaining();
if (indexCount == 0) return;
assert indexCount % 4 == 0;
@@ -247,13 +237,13 @@ public final class QuadConverter {
final long srcAddr = memAddress0(indices) + (long) indices.position() * 2;
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_SHORT, memAddress0(dst), GL11.GL_UNSIGNED_SHORT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
}
/**
* Convert a client-side ByteBuffer of quad indices (type-agnostic) to triangles.
*/
public static void drawQuadElementsAsTriangles(int count, int type, ByteBuffer indices) {
public static void drawQuadElementsAsTriangles(GLContextState glCtx, int count, int type, ByteBuffer indices) {
if (count == 0) return;
assert count % 4 == 0;
final int quadCount = count / 4;
@@ -263,23 +253,7 @@ public final class QuadConverter {
final ByteBuffer dst = memAlloc(triIndexCount * 4);
triangulateQuads(srcAddr, type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
* Clean up the shared EBO.
*/
public static void destroy() {
if (eboId != 0) {
GLStateManager.glDeleteBuffers(eboId);
eboId = 0;
maxQuads = 0;
}
if (scratchEboId != 0) {
GLStateManager.glDeleteBuffers(scratchEboId);
scratchEboId = 0;
scratchEboCapacity = 0;
}
uploadAndDraw(glCtx, dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
/**
@@ -169,13 +169,13 @@ public class RenderSystem {
if (isGLES) {
GLStateManager.LOGGER.info("GL ES context detected, enabling shader transformer.");
ShaderManager.getInstance().enable();
ShaderManager.enable();
} else if (GLStateManager.capabilities.OpenGL32) {
final int profileMask = RENDER_BACKEND.getInteger(GL32.GL_CONTEXT_PROFILE_MASK);
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
coreProfile = true;
GLStateManager.LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
ShaderManager.getInstance().enable();
ShaderManager.enable();
} else {
throw new IllegalStateException("Non-core GL context (profile mask 0x" + Integer.toHexString(profileMask)
+ "); FFP emulation requires a core profile and nothing would render. Context creation should have rejected this.");
@@ -496,20 +496,21 @@ public class RenderSystem {
}
public static void bindTextureToUnit(int unit, int texture) {
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
dsaState.bindTextureToUnit(unit, texture);
}
public static void bindTextureToUnit(int target, int unit, int texture) {
if (unit != 0 && GLStateManager.getBoundTextureForServerState(unit) != texture) GLStateManager.beforeUncapturedStateChange();
dsaState.bindTextureToUnit(target, unit, texture);
}
public static final FloatBuffer PROJECTION_MATRIX_BUFFER = BufferUtils.createFloatBuffer(16);
public static void setupProjectionMatrix(Matrix4f matrix) {
final FloatBuffer projectionMatrixBuffer = GLStateManager.ctx().projectionMatrixBuffer;
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPushMatrix();
matrix.get(0, PROJECTION_MATRIX_BUFFER);
GLStateManager.glLoadMatrix(PROJECTION_MATRIX_BUFFER);
matrix.get(0, projectionMatrixBuffer);
GLStateManager.glLoadMatrix(projectionMatrixBuffer);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
@@ -23,6 +23,7 @@ public final class StateSet {
public static final int R_TEXTURE = 1 << 12;
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 B_FRAG_COLORBUF = 1;
public static final int B_FRAG_TEXTURE = 1 << 1;
@@ -87,10 +88,15 @@ public final class StateSet {
GLStateManager.getActiveTextureUnitStack(),
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
GLStateManager.getLightingState(), GLStateManager.getCullState(), GLStateManager.getPolygonOffsetFillState(),
GLStateManager.getTextures().getTextureUnitStates(0)
GLStateManager.getTextures().getTextureUnitStates(0), GLStateManager.getTextures().getTextureUnitStates(1),
GLStateManager.getLightStates()[0], GLStateManager.getLightStates()[1],
GLStateManager.getLightDataStates()[0], GLStateManager.getLightDataStates()[1],
GLStateManager.getColorMaterial(), GLStateManager.getColorMaterialFace(), GLStateManager.getColorMaterialParameter(),
GLStateManager.getLightModel(), GLStateManager.getFrontMaterial(), GLStateManager.getBackMaterial(),
GLStateManager.getNormalizeState(), GLStateManager.getRescaleNormalState()
),
R_BLEND | R_DEPTH | R_COLOR_MASK | R_POLYGON | R_TEXTURE | R_ACTIVE_UNIT,
B_FRAG_COLORBUF,
B_FRAG_COLORBUF | B_LIGHTING,
true);
}
@@ -205,6 +211,9 @@ public final class StateSet {
if ((mask & GL11.GL_VIEWPORT_BIT) != 0) {
restore |= R_VIEWPORT;
}
if ((mask & GL11.GL_SCISSOR_BIT) != 0) {
restore |= R_SCISSOR;
}
if ((mask & GL11.GL_LINE_BIT) != 0) {
restore |= R_LINE;
}
@@ -652,6 +652,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL11.glReadPixels(x, y, width, height, format, type, pixels);
}
@Override
public void readPixels(int x, int y, int width, int height, int format, int type, long pixelBufferOffset) {
GL11.glReadPixels(x, y, width, height, format, type, pixelBufferOffset);
}
@Override
public void getTexImage(int target, int level, int format, int type, ByteBuffer pixels) {
GL11.glGetTexImage(target, level, format, type, pixels);
@@ -1,10 +1,14 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLESFormatRemap;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.texture.TextureStaging;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL20;
import java.nio.ByteBuffer;
@@ -66,6 +70,7 @@ public abstract class RenderBackend {
public abstract void flush();
public abstract void finish();
private final ByteBuffer[] clientStagingCache = new ByteBuffer[16];
private VSyncMode effectiveVSyncMode = VSyncMode.ON;
private VSyncMode preferredTearFreeMode;
@@ -351,6 +356,71 @@ public abstract class RenderBackend {
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, IntBuffer pixels) {}
public void texSubImage1D(int target, int level, int xoffset, int width, int format, int type, ByteBuffer pixels) {}
public void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pboOffset) {}
public TextureStaging beginTextureStaging(int level, int x, int y, int width, int height) {
final int size = Math.multiplyExact(Math.multiplyExact(width, height), 4);
ByteBuffer buffer = null;
for (int i = 0; i < clientStagingCache.length; i++) {
final ByteBuffer cached = clientStagingCache[i];
if (cached != null && cached.capacity() == size) {
clientStagingCache[i] = null;
buffer = cached;
break;
}
}
if (buffer == null) buffer = MemoryUtilities.memAlloc(size);
buffer.clear();
return new ClientTextureStaging(buffer, level, x, y, width, height);
}
public boolean commitTextureStaging(TextureStaging staging) {
final int type = RenderSystem.isGLES() ? GLESFormatRemap.remapPixelType(GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV) : GL12.GL_UNSIGNED_INT_8_8_8_8_REV;
try {
final ClientTextureStaging s = (ClientTextureStaging) staging;
texSubImage2D(GL11.GL_TEXTURE_2D, s.level(), s.x(), s.y(), s.width(), s.height(), GL12.GL_BGRA, type, s.buffer());
} finally {
recycleClientStaging(staging.buffer());
}
return true;
}
public void abandonTextureStaging(TextureStaging staging) {
recycleClientStaging(staging.buffer());
}
public void trimTextureStaging() {
for (int i = 0; i < clientStagingCache.length; i++) {
MemoryUtilities.memFree(clientStagingCache[i]);
clientStagingCache[i] = null;
}
}
private record ClientTextureStaging(ByteBuffer buffer, int level, int x, int y, int width, int height) implements TextureStaging {
@Override
public boolean bgra() {
return true;
}
}
private void recycleClientStaging(ByteBuffer buffer) {
int victim = -1;
for (int i = 0; i < clientStagingCache.length; i++) {
final ByteBuffer cached = clientStagingCache[i];
if (cached == null) {
clientStagingCache[i] = buffer;
return;
}
if (cached.capacity() < buffer.capacity() && (victim < 0 || cached.capacity() < clientStagingCache[victim].capacity())) {
victim = i;
}
}
if (victim < 0) {
MemoryUtilities.memFree(buffer);
return;
}
MemoryUtilities.memFree(clientStagingCache[victim]);
clientStagingCache[victim] = buffer;
}
public void texSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, ByteBuffer pixels) {}
public void copyTexImage1D(int target, int level, int internalFormat, int x, int y, int width, int border) {}
public void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {}
@@ -393,6 +463,7 @@ public abstract class RenderBackend {
public abstract void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, long pixelBufferOffset);
public abstract void getTexImage(int target, int level, int format, int type, ByteBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, IntBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, long pixelBufferOffset);
@@ -0,0 +1,55 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL14;
public final class CombinedGlint {
private static final Matrix4f savedMatrix = new Matrix4f();
private static final Matrix4f inverse = new Matrix4f();
private static final Matrix4f relative = new Matrix4f();
private static boolean active;
private static boolean replaceAlpha;
private CombinedGlint() {}
public static boolean isActive() {
return active;
}
public static boolean replacesAlpha() {
return active && replaceAlpha;
}
public static boolean begin(Matrix4fc secondTextureMatrix) {
if (active || GLStateManager.getActiveProgram() != 0 || GLStateManager.getLightingState().isEnabled() || GLStateManager.getColorSumState().isEnabled() || !GLStateManager.glIsEnabled(GL11.GL_BLEND)) return false;
final var blend = GLStateManager.getBlendState();
final boolean lastAlpha = blend.getSrcAlpha() == GL11.GL_ONE && blend.getDstAlpha() == GL11.GL_ZERO && blend.getEquationAlpha() == GL14.GL_FUNC_ADD;
final boolean preserveAlpha = blend.getSrcAlpha() == GL11.GL_ZERO && blend.getDstAlpha() == GL11.GL_ONE && (blend.getEquationAlpha() == GL14.GL_FUNC_ADD || blend.getEquationAlpha() == GL14.GL_FUNC_REVERSE_SUBTRACT);
if (blend.getSrcRgb() != GL11.GL_SRC_COLOR || blend.getDstRgb() != GL11.GL_ONE
|| (!lastAlpha && !preserveAlpha && (blend.getSrcAlpha() != GL11.GL_SRC_COLOR || blend.getDstAlpha() != GL11.GL_ONE || blend.getEquationAlpha() != blend.getEquationRgb()))
|| (blend.getEquationRgb() != GL14.GL_FUNC_ADD && blend.getEquationRgb() != GL14.GL_FUNC_REVERSE_SUBTRACT)) return false;
final var textures = GLStateManager.getTextures();
if (!textures.getTextureUnitStates(0).isEnabled() || textures.getTextureUnitBindings(0).getBinding() == 0 || textures.getTexEnvState(0).mode != GL11.GL_MODULATE) return false;
if (textures.getTextureUnitStates(1).isEnabled() && textures.getTextureUnitBindings(1).getBinding() != 0 && textures.getTexEnvState(1).mode != GL11.GL_MODULATE) return false;
for (int unit = 2; unit < 4; unit++) {
if (textures.getTextureUnitStates(unit).isEnabled() && textures.getTextureUnitBindings(unit).getBinding() != 0) return false;
}
inverse.set(textures.getTextureUnitMatrix(0)).invert();
relative.set(secondTextureMatrix).mul(inverse);
if (!relative.isFinite()) return false;
savedMatrix.set(textures.getTextureUnitMatrix(3));
GLStateManager.setTextureMatrix(3, relative);
replaceAlpha = lastAlpha;
active = true;
return true;
}
public static void end() {
if (!active) return;
active = false;
GLStateManager.setTextureMatrix(3, savedMatrix);
}
}
@@ -2,7 +2,9 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.cel.api.util.NormI8;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -59,7 +61,6 @@ public final class FfpExtendedAttribs {
private static final HashMap<Key, ExtBuffer> cache = new HashMap<>();
private static final Key lookup = new Key();
private static int internalGlDepth = 0;
private static final HashMap<EboKey, Boolean> eboPatternCache = new HashMap<>();
private static final EboKey eboLookup = new EboKey();
@@ -79,15 +80,19 @@ public final class FfpExtendedAttribs {
return cache.isEmpty() && eboPatternCache.isEmpty();
}
private static int internalDrawDepth = 0;
public static void beginInternalDraw() { GLStateManager.ctx().internalDrawDepth++; }
public static void beginInternalDraw() { internalDrawDepth++; }
public static void endInternalDraw() {
final GLContextState glCtx = GLStateManager.ctx();
if (glCtx.internalDrawDepth > 0) glCtx.internalDrawDepth--;
}
public static void endInternalDraw() { if (internalDrawDepth > 0) internalDrawDepth--; }
private static void beginInternalGl() { GLStateManager.ctx().internalGlDepth++; }
private static void beginInternalGl() { internalGlDepth++; }
private static void endInternalGl() { if (internalGlDepth > 0) internalGlDepth--; }
private static void endInternalGl() {
final GLContextState glCtx = GLStateManager.ctx();
if (glCtx.internalGlDepth > 0) glCtx.internalGlDepth--;
}
private static boolean isNotConventionalTexturedArray(VAOManager.Attrib a) {
return a == null || !a.enabled || a.genericPointer || a.vboId == 0 || a.size < 2;
@@ -104,20 +109,20 @@ public final class FfpExtendedAttribs {
return true;
}
public static boolean maybeBind(int mode, int first, int count) {
public static boolean maybeBind(GLContextState glCtx, int mode, int first, int count) {
final ImmediateExtendedAttribHandler h = GLSMHooks.immediateExtendedHandler;
if (h == null) return false;
if (internalDrawDepth > 0) return false;
if (glCtx.internalDrawDepth > 0) return false;
final int extPrim = ImmediateExtendedAttribHandler.extPrimVerts(mode, count);
if (extPrim == 0 || first < 0) return false;
if (!h.wantsExtended()) return false;
final VAOManager.Attrib pos = VAOManager.get(POSITION_LOC);
final VAOManager.Attrib uv = VAOManager.get(UV_LOC);
final VAOManager.Attrib pos = glCtx.vaos.attrib(POSITION_LOC);
final VAOManager.Attrib uv = glCtx.vaos.attrib(UV_LOC);
if (isNotConventionalTexturedArray(pos) || isNotConventionalTexturedArray(uv)) return false;
if (isStreaming(pos.vboId) || isStreaming(uv.vboId)) return false;
VAOManager.Attrib normal = extPrim == 3 ? VAOManager.get(NORMAL_LOC) : null;
VAOManager.Attrib normal = extPrim == 3 ? glCtx.vaos.attrib(NORMAL_LOC) : null;
if (normal != null && (!normal.enabled || normal.genericPointer || normal.vboId == 0 || normal.size < 3
|| isStreaming(normal.vboId))) {
normal = null;
@@ -126,17 +131,17 @@ public final class FfpExtendedAttribs {
return bindExt(h, mode, first, count, extPrim, pos, uv, normal);
}
public static boolean maybeBindIndexed(int mode, int indexCount, int indexType, long indicesOffset) {
public static boolean maybeBindIndexed(GLContextState glCtx, int mode, int indexCount, int indexType, long indicesOffset) {
final ImmediateExtendedAttribHandler h = GLSMHooks.immediateExtendedHandler;
if (h == null) return false;
if (internalDrawDepth > 0) return false;
if (glCtx.internalDrawDepth > 0) return false;
if (mode != GL11.GL_TRIANGLES || indexCount <= 0 || indexCount % 6 != 0) return false;
if (!h.wantsExtended()) return false;
final VAOManager.Attrib pos = VAOManager.get(POSITION_LOC);
final VAOManager.Attrib uv = VAOManager.get(UV_LOC);
final VAOManager.Attrib pos = glCtx.vaos.attrib(POSITION_LOC);
final VAOManager.Attrib uv = glCtx.vaos.attrib(UV_LOC);
if (isNotConventionalTexturedArray(pos) || isNotConventionalTexturedArray(uv)) return false;
if (isStreaming(pos.vboId) || isStreaming(uv.vboId) || isStreaming(VAOManager.boundEBO)) return false;
if (isStreaming(pos.vboId) || isStreaming(uv.vboId) || isStreaming(glCtx.vaos.boundEBO)) return false;
final int indexSize = indexTypeBytes(indexType);
if (indexSize == 0) return false;
@@ -144,7 +149,7 @@ public final class FfpExtendedAttribs {
final int first = (int) (indicesOffset / indexSize) / 6 * 4;
final int count = indexCount / 6 * 4;
if (!isQuadPatternEbo(VAOManager.boundEBO, indicesOffset, indexCount, indexType, indexSize, first)) return false;
if (!isQuadPatternEbo(glCtx.vaos.boundEBO, indicesOffset, indexCount, indexType, indexSize, first)) return false;
// Build/bind as GL_QUADS (the real primitive): face-normal tangents, 4-vertex tiles, no per-vertex normal.
return bindExt(h, GL11.GL_QUADS, first, count, 4, pos, uv, null);
@@ -222,12 +227,17 @@ public final class FfpExtendedAttribs {
}
public static void setNeutralCurrentValues() {
GLStateManager.glVertexAttrib2f(ImmediateExtendedAttribHandler.LOC_MID_TEX, 0.5f, 0.5f);
GLStateManager.glVertexAttrib4f(ImmediateExtendedAttribHandler.LOC_TANGENT, 1.0f, 0.0f, 0.0f, 1.0f);
BatchStateGuard.suspend();
try {
GLStateManager.glVertexAttrib2f(ImmediateExtendedAttribHandler.LOC_MID_TEX, 0.5f, 0.5f);
GLStateManager.glVertexAttrib4f(ImmediateExtendedAttribHandler.LOC_TANGENT, 1.0f, 0.0f, 0.0f, 1.0f);
} finally {
BatchStateGuard.resume();
}
}
public static void onDeleteBuffer(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
public static void onDeleteBuffer(GLContextState glCtx, int vboId) {
if (glCtx.internalGlDepth > 0 || vboId == 0) return;
dropDerivedFrom(vboId);
respecCounts.remove(vboId);
respecFrames.remove(vboId);
@@ -236,8 +246,8 @@ public final class FfpExtendedAttribs {
persistentSources.remove(vboId);
}
public static void onBufferRespecified(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
public static void onBufferRespecified(GLContextState glCtx, int vboId) {
if (glCtx.internalGlDepth > 0 || vboId == 0) return;
if (streamingSources.contains(vboId)) {
respecFrames.put(vboId, frame);
return;
@@ -268,16 +278,15 @@ public final class FfpExtendedAttribs {
respecFrames.put(vboId, frame);
}
public static void markStreaming(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
private static void markStreaming(int vboId) {
respecCounts.remove(vboId);
respecRunStart.remove(vboId);
respecFrames.put(vboId, frame);
if (streamingSources.add(vboId)) dropDerivedFrom(vboId);
}
public static void markPersistent(int vboId) {
if (internalGlDepth > 0 || vboId == 0) return;
public static void markPersistent(GLContextState glCtx, int vboId) {
if (glCtx.internalGlDepth > 0 || vboId == 0) return;
markStreaming(vboId);
persistentSources.add(vboId);
}
@@ -66,6 +66,8 @@ public final class FragmentKey {
public static final int FOG_EXP2 = 3;
private static final int GLOBAL_BITS = 14;
private static final int BIT_COMBINED_GLINT = 61;
private static final int BIT_GLINT_REPLACE_ALPHA = 62;
private static final int BIT_FOG_MODE = 0; // 2 bits
private static final int BIT_ALPHA_TEST = 2; // 1 bit
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
@@ -111,6 +113,8 @@ public final class FragmentKey {
public static int packFromState(long[] out, GLContextState glCtx) {
long global = 0;
if (CombinedGlint.isActive()) global |= 1L << BIT_COMBINED_GLINT;
if (CombinedGlint.replacesAlpha()) global |= 1L << BIT_GLINT_REPLACE_ALPHA;
// Fog
if (glCtx.fogMode.isEnabled()) {
@@ -130,13 +134,13 @@ public final class FragmentKey {
}
// Damage overlay
if (GLStateManager.ffpInstancing.hasInstanceHead()) {
if (glCtx.ffpInstancing.hasInstanceHead()) {
global |= (1L << BIT_OVERLAY_INSTANCED);
} else if (glCtx.overlayA != 0.0f) {
global |= (1L << BIT_OVERLAY_ENABLED);
}
if (GLStateManager.lineStippleActive) {
if (glCtx.lineStippleActive) {
global |= (1L << BIT_LINE_STIPPLE);
}
@@ -248,6 +252,8 @@ public final class FragmentKey {
}
public int fogMode() { return (int) (packed[0] & 0x3); }
public boolean combinedGlint() { return (packed[0] & (1L << BIT_COMBINED_GLINT)) != 0; }
public boolean glintReplaceAlpha() { return (packed[0] & (1L << BIT_GLINT_REPLACE_ALPHA)) != 0; }
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
@@ -389,7 +395,9 @@ public final class FragmentKey {
default -> "?";
};
final StringBuilder sb = new StringBuilder();
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d", fogName, alphaTestEnabled(), alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-", separateSpecular(), colorSum(), overlayEnabled() || overlayInstanced(), nrEnabledUnits()));
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b colorSum=%b overlay=%b units=%d", fogName, alphaTestEnabled(),
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-", separateSpecular(), colorSum(),
overlayEnabled() || overlayInstanced(), nrEnabledUnits()));
for (int i = 0; i < nrEnabledUnits(); i++) {
if (!unitEnabled(i)) {
sb.append(String.format(" u%d=OFF", i));
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import org.lwjgl.opengl.GL11;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
/**
* Generates GLSL 330 core fragment shaders for FFP emulation.
@@ -17,6 +18,15 @@ public final class FragmentShaderGenerator {
emitInputs(sb, key);
emitUniforms(sb, key);
if (key.combinedGlint()) {
sb.append("#define GLINT_REPLACE_ALPHA ").append(key.glintReplaceAlpha() ? 1 : 0).append('\n');
sb.append("#define GLINT_FOG_MODE ").append(key.fogMode()).append('\n');
sb.append("#define GLINT_ALPHA_FUNC ").append(key.alphaTestEnabled() ? key.alphaTestFunc() : 7).append('\n');
sb.append("#define GLINT_OVERLAY ").append(key.overlayInstanced() ? 2 : key.overlayEnabled() ? 1 : 0).append('\n');
sb.append("#define GLINT_LIGHTMAP ").append(key.nrEnabledUnits() > 1 && key.unitEnabled(1) ? 1 : 0).append('\n');
sb.append(ShaderLoader.getShaderSource("angelica:combined_glint.frag.glsl"));
return sb.toString();
}
sb.append("out vec4 fragColor;\n\n");
sb.append("void main() {\n");
@@ -55,13 +55,18 @@ public final class InstancedAttribs {
return (entity & 0xFFFFL) | ((blockEntity & 0xFFFFL) << 16) | ((item & 0xFFFFL) << 32);
}
public static int unpackId(long info, int shift) {
final int id = (int) (info >>> shift) & 0xFFFF;
return id == 0xFFFF ? -1 : id;
}
public static void pointHead(long base, int stride) {
for (int r = 0; r < 3; r++) {
GLStateManager.glVertexAttribPointer(LOC_ROW0 + r, 4, GL11.GL_FLOAT, false, stride, base + OFFSET_ROWS + r * 16L);
}
GLStateManager.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_COLOR);
GLStateManager.glVertexAttribPointer(LOC_OVERLAY, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_OVERLAY);
GLStateManager.glVertexAttribIPointer(LOC_ENTITY, 4, GL11.GL_SHORT, stride, base + OFFSET_ENTITY);
GLStateManager.glVertexAttribIPointer(LOC_ENTITY, 4, GL11.GL_UNSIGNED_SHORT, stride, base + OFFSET_ENTITY);
}
public static void pointTemplate(long base) {
@@ -102,7 +102,11 @@ public class Program {
for (int i = 0; i < 4; i++) {
if (ffpProgram.locSampler[i] != -1) backend.uniform1i(ffpProgram.locSampler[i], i);
}
backend.useProgram(previousProgram);
if (previousProgram != 0) {
backend.useProgram(previousProgram);
} else {
ShaderManager.getInstance().invalidateProgram();
}
return ffpProgram;
} catch (RuntimeException e) {
@@ -9,7 +9,6 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.IdentityHashMap;
@@ -271,25 +270,6 @@ public class ShaderCache {
}
}
/** Destroy all cached programs and clear caches. */
public void destroy() {
for (int i = 0; i < capacity; i++) {
if (programs[i] != null) {
programs[i].destroy();
}
}
Arrays.fill(keys, 0);
Arrays.fill(programs, null);
Arrays.fill(fragSources, null);
count = 0;
uniqueFragCount = 0;
vertexSourceCache.clear();
geometrySourceCache.clear();
lastProgram = null;
lastVK = Long.MIN_VALUE;
GLStateManager.LOGGER.debug("FFP shader cache cleared");
}
public int getProgramCount() {
return count;
}
@@ -8,7 +8,6 @@ import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
@@ -28,45 +27,42 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
*/
public final class ShaderManager {
private static final class Holder {
static final ShaderManager INSTANCE = new ShaderManager();
}
private final ShaderCache cache = new ShaderCache();
private static final ShaderCache cache = new ShaderCache();
private final Uniforms uniforms = new Uniforms();
@Getter
private boolean active = false;
private boolean active;
private Program currentProgram = null;
private int lastBoundProgramId = -1;
private int lastBoundProgramId;
private long currentVertexKeyPacked = Long.MIN_VALUE;
private final long[] currentFKScratch = new long[FragmentKey.MAX_UNITS];
private final long[] currentFKPacked = new long[FragmentKey.MAX_UNITS];
private int currentFKLen = 0;
@Getter private static final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
private static final Vector4f[] currentTexCoords = {
public final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
public final Vector4f[] currentTexCoords = {
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
};
@Getter private static final Vec3fStack normalStack = new Vec3fStack(currentNormal, StackIdAllocator.nextStaticId());
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoords[0], StackIdAllocator.nextStaticId());
@Getter private static int normalGeneration;
@Getter private static int texCoordGeneration;
public final Vec3fStack normalStack = new Vec3fStack(currentNormal, StackIdAllocator.nextId());
public final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoords[0], StackIdAllocator.nextId());
public int normalGeneration;
public int texCoordGeneration;
private int preDrawCalls;
private int lastFramePreDrawCalls;
public static long variantSwitches;
public static Vector4f getCurrentTexCoord() { return currentTexCoords[0]; }
public static Vector4f getCurrentTexCoord(int unit) { return currentTexCoords[unit]; }
@Getter private boolean enabled = false;
public static Vector3f getCurrentNormal() { return GLStateManager.ctx().ffp.currentNormal; }
public static Vector4f getCurrentTexCoord() { return GLStateManager.ctx().ffp.currentTexCoords[0]; }
public static Vec3fStack getNormalStack() { return GLStateManager.ctx().ffp.normalStack; }
public static Vec4fStack getTexCoordStack() { return GLStateManager.ctx().ffp.texCoordStack; }
@Getter private static boolean enabled = false;
private ShaderManager() {
static {
cache.setDumpDir(SystemProperties.shaderDumpDir("ffp"));
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
VAOManager.setCurrentVertexFlags(format.getVertexFlags());
@@ -74,18 +70,20 @@ public final class ShaderManager {
});
}
public static ShaderManager getInstance() {
return Holder.INSTANCE;
public ShaderManager() {
active = enabled;
lastBoundProgramId = enabled ? 0 : -1;
}
public void enable() {
public static ShaderManager getInstance() {
return GLStateManager.ctx().ffp;
}
public static void enable() {
warmUp();
enabled = true;
if (GLStateManager.getActiveProgram() == 0) {
activate();
}
final GLContextState glCtx = GLStateManager.ctx();
if (glCtx.activeProgram == 0) glCtx.ffp.activate();
GLStateManager.LOGGER.info("FFP shader emulation enabled");
}
@@ -112,19 +110,21 @@ public final class ShaderManager {
}
}
public void disable() {
public static void disable() {
enabled = false;
}
public void activate() {
active = true;
lastBoundProgramId = GLStateManager.getActiveProgram();
invalidateProgram();
}
public static void invalidateBoundProgram() {
final ShaderManager sm = Holder.INSTANCE;
sm.lastBoundProgramId = -1;
sm.currentProgram = null;
/** Invalidate both binding and state key so the next draw selects and binds an FFP variant. */
public void invalidateProgram() {
lastBoundProgramId = -1;
currentProgram = null;
currentVertexKeyPacked = Long.MIN_VALUE;
uniforms.invalidateBinding();
}
public void deactivate() {
@@ -136,30 +136,29 @@ public final class ShaderManager {
GLStateManager.forceAttribDefaultsDirty();
}
public void preDraw() {
public void preDraw(GLContextState glCtx) {
GLSMHooks.resolvePendingProgram();
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null) bh.flushDeferredBlend();
// Handle FFP & Iris uniforms
final int currentProgramId = GLStateManager.getActiveProgram();
final int currentProgramId = glCtx.activeProgram;
if (currentProgramId != 0) {
if (!CompatUniformManager.refreshCompatUniforms(currentProgramId)) {
if (!CompatUniformManager.refreshCompatUniforms(currentProgramId, glCtx)) {
return; // Don't emulate FFP on non-iris core shaders
}
}
final int vertexFlags = VAOManager.getCurrentVertexFlags();
final int vertexFlags = glCtx.vaos.getVertexFlags();
final boolean hasColor = (vertexFlags & VertexFlags.COLOR_BIT) != 0;
final boolean hasNormal = (vertexFlags & VertexFlags.NORMAL_BIT) != 0;
final boolean hasTexCoord = (vertexFlags & VertexFlags.TEXTURE_BIT) != 0;
final boolean hasLightmap = (vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0;
GLStateManager.flushDeferredVertexAttribs(hasColor, hasNormal, hasTexCoord, hasLightmap);
GLStateManager.flushDeferredVertexAttribs(glCtx, hasColor, hasNormal, hasTexCoord, hasLightmap);
if (!active) return;
preDrawCalls++;
final GLContextState glCtx = GLStateManager.ctx();
final int fkLen = FragmentKey.packFromState(currentFKScratch, glCtx);
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask, glCtx);
@@ -172,10 +171,11 @@ public final class ShaderManager {
}
private void commitVariant(long vkPacked, int fkLen) {
currentVertexKeyPacked = vkPacked;
System.arraycopy(currentFKScratch, 0, currentFKPacked, 0, fkLen);
currentFKLen = fkLen;
currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen);
// Building a variant can invalidate the previous binding and key.
currentVertexKeyPacked = vkPacked;
final int programId = currentProgram.getProgramId();
if (programId != lastBoundProgramId) {
if (Tracy.ENABLED) variantSwitches++;
@@ -191,44 +191,31 @@ public final class ShaderManager {
}
public static void endFrame() {
final ShaderManager sm = Holder.INSTANCE;
final ShaderManager sm = GLStateManager.ctx().ffp;
sm.lastFramePreDrawCalls = sm.preDrawCalls;
sm.preDrawCalls = 0;
sm.uniforms.endFrame();
FfpExtendedAttribs.endFrame();
}
public static void setCurrentNormal(float x, float y, float z) {
public void setNormal(float x, float y, float z) {
normalStack.beforeModify();
currentNormal.set(x, y, z);
normalGeneration++;
}
public static void setCurrentTexCoord(float s, float t, float r, float q) {
public void setTexCoord(float s, float t, float r, float q) {
texCoordStack.beforeModify();
currentTexCoords[0].set(s, t, r, q);
texCoordGeneration++;
}
public static void setCurrentTexCoord(int unit, float s, float t, float r, float q) {
public void setTexCoord(int unit, float s, float t, float r, float q) {
if (unit == 0) texCoordStack.beforeModify();
currentTexCoords[unit].set(s, t, r, q);
texCoordGeneration++;
}
public static void bumpNormalGeneration() { normalGeneration++; }
public static void bumpTexCoordGeneration() { texCoordGeneration++; }
public void destroy() {
cache.destroy();
uniforms.destroy();
final GLSMInitConfig config = GLStateManager.getInitConfig();
if (config != null && config.getStreamingDrawerDestroy() != null) config.getStreamingDrawerDestroy().run();
QuadConverter.destroy();
active = false;
currentProgram = null;
}
public String getDebugInfo() {
return String.format("FFP: %d programs (%d vert, %d frag variants) | UBO: %d/%d draws wrote (mat %d, light %d, frag %d, misc %d)",
cache.getProgramCount(), cache.getVertexVariantCount(), cache.getFragmentVariantCount(),
@@ -11,7 +11,6 @@ import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import com.gtnewhorizons.angelica.glsm.streaming.UniformRingBuffer;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.joml.Math;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
@@ -125,25 +124,26 @@ public class Uniforms {
stagedColor++;
dirty = true;
}
final int normG = ShaderManager.getNormalGeneration();
final ShaderManager ffp = glCtx.ffp;
final int normG = ffp.normalGeneration;
if (normG != normalGen) {
final Vector3f normal = ShaderManager.getCurrentNormal();
final Vector3f normal = ffp.currentNormal;
putVec3(FFPUniformBlock.CURRENT_NORMAL, normal.x, normal.y, normal.z);
normalGen = normG;
stagedNormal++;
dirty = true;
}
final int tcG = ShaderManager.getTexCoordGeneration();
final int tcG = ffp.texCoordGeneration;
if (tcG != texCoordGen) {
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_0, ShaderManager.getCurrentTexCoord(0));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_2, ShaderManager.getCurrentTexCoord(2));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_3, ShaderManager.getCurrentTexCoord(3));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_0, ffp.currentTexCoords[0]);
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_2, ffp.currentTexCoords[2]);
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_3, ffp.currentTexCoords[3]);
texCoordGen = tcG;
stagedTexCoord++;
dirty = true;
}
final float brightX = GLSMConfig.lastBrightnessX;
final float brightY = GLSMConfig.lastBrightnessY;
final float brightX = glCtx.lastBrightnessX;
final float brightY = glCtx.lastBrightnessY;
if (brightX != lightmapX || brightY != lightmapY) {
putFloat(FFPUniformBlock.CURRENT_LIGHTMAP_COORD, brightX);
putFloat(FFPUniformBlock.CURRENT_LIGHTMAP_COORD + 4, brightY);
@@ -446,6 +446,10 @@ public class Uniforms {
stagedMisc = 0;
}
void invalidateBinding() {
bound = false;
}
UniformRingBuffer getRing() {
return ring;
}
@@ -14,30 +14,34 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
public final class VAOManager {
public static final int MAX_ATTRIBS = 16;
public static final Int2ObjectOpenHashMap<VAOData> vaoMap = new Int2ObjectOpenHashMap<>();
public final Int2ObjectOpenHashMap<VAOData> vaoMap = new Int2ObjectOpenHashMap<>();
static final Int2ObjectOpenHashMap<VAOData> eboOwners = new Int2ObjectOpenHashMap<>();
final Int2ObjectOpenHashMap<VAOData> eboOwners = new Int2ObjectOpenHashMap<>();
private static VAOData current = null;
private static int currentVertexFlags = 0;
private static Attrib[] currentAttribs = null;
public static int boundEBO = 0;
private VAOData current = null;
private int currentVertexFlags = 0;
private Attrib[] currentAttribs = null;
public int boundEBO = 0;
private static int clientSideEnabledCount = 0;
private int clientSideEnabledCount = 0;
public static void init(int defaultVAO) {
public void init(int defaultVAO) {
final VAOData data = new VAOData();
current = data;
currentAttribs = data.attribs;
vaoMap.put(defaultVAO, data);
}
public static void onBindEBO(int ebo) {
public boolean isInitialized() {
return current != null;
}
public void onBindEBO(int ebo) {
boundEBO = ebo;
}
public static void onBindVertexArrayPre(int vaoId) {
public void onBindVertexArrayPre(int vaoId) {
// Save old VAO data
if (current != null) {
current.vertexFlags = currentVertexFlags;
@@ -62,14 +66,14 @@ public final class VAOManager {
clientSideEnabledCount = data.clientSideEnabledCount;
}
public static void onDeleteVertexArray(int vaoId) {
public void onDeleteVertexArray(int vaoId) {
final VAOData data = vaoMap.remove(vaoId);
if (data == null) return;
removeEboOwner(data.ebo, data);
if (data == current) current = null;
}
public static void onDeleteBuffer(int buffer) {
public void onDeleteBuffer(int buffer) {
if (buffer == 0) return;
VAOData owner = eboOwners.remove(buffer);
while (owner != null) {
@@ -80,12 +84,12 @@ public final class VAOManager {
}
}
private static void addEboOwner(int ebo, VAOData data) {
private void addEboOwner(int ebo, VAOData data) {
if (ebo == 0) return;
data.nextEboOwner = eboOwners.put(ebo, data);
}
private static void removeEboOwner(int ebo, VAOData data) {
private void removeEboOwner(int ebo, VAOData data) {
if (ebo == 0) return;
VAOData node = eboOwners.get(ebo);
if (node == data) {
@@ -105,10 +109,11 @@ public final class VAOManager {
}
}
public static void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
public static void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
public static int getCurrentVertexFlags() { return currentVertexFlags; }
public static void setCurrentVertexFlags(int flags) { currentVertexFlags = flags; }
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 static void setCurrentVertexFlags(int flags) { GLStateManager.ctx().vaos.currentVertexFlags = flags; }
public static final class VAOData {
public Attrib[] attribs;
@@ -122,7 +127,7 @@ public final class VAOManager {
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
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;
@@ -139,7 +144,7 @@ public final class VAOManager {
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
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;
@@ -156,19 +161,19 @@ public final class VAOManager {
}
}
public static boolean isGenericPointerEnabled(int index) {
public boolean isGenericPointerEnabled(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return false;
final Attrib a = currentAttribs[index];
return a != null && a.genericPointer && a.enabled;
}
public static void markConventional(int index) {
public void markConventional(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = currentAttribs[index];
if (a != null) a.genericPointer = false;
}
public static void setEnabled(int index, boolean enabled) {
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) {
@@ -177,7 +182,7 @@ public final class VAOManager {
a.enabled = enabled;
}
public static void enableAttribute(int index) {
public void enableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && !a.enabled) {
@@ -186,7 +191,7 @@ public final class VAOManager {
a.enabled = true;
}
public static void disableAttribute(int index) {
public void disableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled) {
@@ -196,7 +201,7 @@ public final class VAOManager {
}
// Lazily allocate each attrib. Most VAOs don't even have 4 attribs, let alone 16.
private static Attrib getAttrib(int index) {
private Attrib getAttrib(int index) {
Attrib a = currentAttribs[index];
if (a == null) {
a = new Attrib();
@@ -205,28 +210,32 @@ public final class VAOManager {
return a;
}
public static boolean isClientSideAttrib(int index) {
public boolean isClientSideAttrib(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null && attrib.isClientSide();
}
public static int getAttribEffectiveStride(int index) {
public Attrib attrib(int index) {
return currentAttribs[index];
}
public int attribEffectiveStride(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.effectiveStride() : 0;
}
public static long getAttribOffset(int index) {
public long attribOffset(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.offset : 0;
}
public static int getAttribVBO(int index) {
public int attribVBO(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.vboId : 0;
}
public static @Nullable Attrib get(int index) {
return currentAttribs[index];
return GLStateManager.ctx().vaos.attrib(index);
}
@@ -236,7 +245,7 @@ public final class VAOManager {
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
* offsets into a non-existent buffer, causing a native crash at draw time.
*/
public static boolean hasAnyClientSideEnabledAttrib() {
public boolean hasAnyClientSideEnabledAttrib() {
return clientSideEnabledCount > 0;
}
@@ -244,15 +253,15 @@ public final class VAOManager {
* If any enabled vertex attribute uses a client-side pointer (no VBO), upload all such
* attribs into a shared stream VBO so the draw succeeds under core profile.
*/
private static int clientArraysVBO = 0;
private static int clientArraysVBOCapacity = 0;
private static final int[] clientArraysVBOOffsets = new int[VAOManager.MAX_ATTRIBS];
private int clientArraysVBO = 0;
private int clientArraysVBOCapacity = 0;
private final int[] clientArraysVBOOffsets = new int[VAOManager.MAX_ATTRIBS];
public static void uploadClientArraysToVBO() {
public void uploadClientArraysToVBO() {
int totalBytes = 0;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
clientArraysVBOOffsets[i] = -1;
final VAOManager.Attrib a = VAOManager.get(i);
final VAOManager.Attrib a = currentAttribs[i];
if (a == null || !a.enabled || a.clientPointer == null) continue;
clientArraysVBOOffsets[i] = totalBytes;
totalBytes += a.clientPointer.remaining();
@@ -275,7 +284,7 @@ public final class VAOManager {
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
if (clientArraysVBOOffsets[i] < 0) continue;
final VAOManager.Attrib a = VAOManager.get(i);
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]);
}
@@ -238,15 +238,15 @@ public final class VertexKey {
bits |= (1L << BIT_CLIP_PLANES);
}
if (GLStateManager.wideLineEmulationActive) {
if (glCtx.wideLineEmulationActive) {
bits |= (1L << BIT_WIDE_LINE);
}
if (GLStateManager.lineStippleActive) {
if (glCtx.lineStippleActive) {
bits |= (1L << BIT_LINE_STIPPLE);
}
bits |= (long) GLStateManager.ffpInstancing.ordinal() << BIT_INSTANCING;
bits |= (long) glCtx.ffpInstancing.ordinal() << BIT_INSTANCING;
return bits;
}
@@ -0,0 +1,30 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
/** Drains deferred geometry before changing state that is not stored with each batch. */
public final class BatchStateGuard {
public static Runnable flush;
private static int suspended;
private BatchStateGuard() {}
public static void suspend() { suspended++; }
public static void resume() { suspended--; }
public static boolean isSuspended() { return suspended != 0; }
public static void beforeChange() {
if (flush == null || suspended != 0 || !GLStateManager.isMainThread()
|| DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.COMPILE) return;
final CommandRecorder recorder = DisplayListManager.isRecording() ? DisplayListManager.pauseRecording() : null;
suspend();
try {
flush.run();
} finally {
resume();
if (recorder != null) DisplayListManager.resumeRecording(recorder);
}
}
}
@@ -41,10 +41,6 @@ public final class FrameHooks {
BackendManager.shutdown();
}
public static void bindSplashVao() {
GLStateManager.glBindVertexArray(GLStateManager.glGenVertexArrays());
}
public static void splashFinished() {
GLStateManager.markSplashComplete("SplashProgress.finish");
}
@@ -1,23 +1,24 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public final class GLSMConfig {
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
// Lightmap (replaces OpenGlHelper.lastBrightnessX/Y)
public static float lastBrightnessX;
public static float lastBrightnessY;
public final class GLSMConfig {
public static int packBrightness(float x, float y) {
return ((int) y << 16) | ((int) x & 0xFFFF);
}
public static int packedLastBrightness() {
return packBrightness(lastBrightnessX, lastBrightnessY);
final GLContextState glCtx = GLStateManager.ctx();
return packBrightness(glCtx.lastBrightnessX, glCtx.lastBrightnessY);
}
public static boolean hudCacheOverride;
public static boolean extendedAttribsExpected;
public static volatile boolean expandVertexFormats;
public static int workerThreadCount;
private GLSMConfig() {}
}
@@ -11,7 +11,6 @@ public final class GLSMHooks {
public static DeferredDepthColorHandler depthColorHandler;
public static GlintColorHandler glintColorHandler;
public static ImmediateExtendedAttribHandler immediateExtendedHandler;
public static ShaderWorkSubmitter shaderWorkSubmitter;
public static ShaderTransformPostProcessor postTransformProcessor;
public static PerFrameUniformBlock perFrameUniformBlock;
public static PerFrameUniformBlock perPassUniformBlock;
@@ -12,7 +12,6 @@ public final class GLSMInitConfig {
@Getter private final boolean lwjglDebug;
@Getter private final StreamingUploader.UploadStrategy streamingUploadStrategy;
@Getter private final Consumer<DirectTessellator> directDrawer;
@Getter private final Runnable streamingDrawerDestroy;
@Getter private final int displayWidth;
@Getter private final int displayHeight;
@Getter private final Runnable postInitCallback;
@@ -24,7 +23,6 @@ public final class GLSMInitConfig {
this.streamingUploadStrategy = builder.streamingUploadStrategy;
this.noErrorChecks = builder.noErrorChecks;
this.directDrawer = builder.directDrawer;
this.streamingDrawerDestroy = builder.streamingDrawerDestroy;
this.displayWidth = builder.displayWidth;
this.displayHeight = builder.displayHeight;
this.postInitCallback = builder.postInitCallback;
@@ -43,7 +41,6 @@ public final class GLSMInitConfig {
private boolean lwjglDebug = SystemProperties.LWJGL_DEBUG;
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
private Consumer<DirectTessellator> directDrawer = TessellatorStreamingDrawer::drawDirect;
private Runnable streamingDrawerDestroy = TessellatorStreamingDrawer::destroy;
private int displayWidth = 0;
private int displayHeight = 0;
private Runnable postInitCallback = null;
@@ -72,11 +69,6 @@ public final class GLSMInitConfig {
return this;
}
public Builder streamingDrawerDestroy(Runnable streamingDrawerDestroy) {
this.streamingDrawerDestroy = streamingDrawerDestroy;
return this;
}
public Builder displaySize(int width, int height) {
this.displayWidth = width;
this.displayHeight = height;
@@ -1,8 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
public interface ShaderWorkSubmitter {
<T> CompletableFuture<T> submit(Supplier<T> work);
}
@@ -1,32 +1,54 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
public final class TextureUtilHooks {
private TextureUtilHooks() {}
private static int pausedTexture;
private static int pausedMin;
private static int pausedMag;
private static float pausedAniso;
private static int savedFilterTextureId;
private static int nearestTexture;
private static int nearestMin;
private static int nearestMag;
private static float nearestAniso;
private TextureUtilHooks() {}
public static void setMaxLevel(int mipmapLevels) {
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, mipmapLevels);
}
public static void captureBoundFilterTexture() {
savedFilterTextureId = GLStateManager.getBoundTextureForServerState();
final GLContextState ctx = GLStateManager.ctx();
final int id = GLStateManager.getBoundTextureForServerState();
ctx.savedFilterTextureId = id;
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(id);
if (info != null) {
ctx.savedFilterMin = info.getMinFilter();
ctx.savedFilterMag = info.getMagFilter();
ctx.savedFilterAniso = info.getMaxAnisotropy();
} else {
ctx.savedFilterMin = -1;
}
}
public static void clearSavedFilterTexture() {
savedFilterTextureId = 0;
GLStateManager.ctx().savedFilterTextureId = 0;
}
public static void restoreFilter(int min, int mag) {
final int id = savedFilterTextureId;
final int id = GLStateManager.ctx().savedFilterTextureId;
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
@@ -37,11 +59,68 @@ public final class TextureUtilHooks {
}
public static void restoreAniso(float aniso) {
final int id = savedFilterTextureId;
final int id = GLStateManager.ctx().savedFilterTextureId;
if (id != 0 && DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
RenderSystem.texParameterf(id, GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
} else {
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
}
}
public static boolean pauseTemporaryFilter() {
final GLContextState ctx = GLStateManager.ctx();
final int id = ctx.savedFilterTextureId;
if (id == 0 || pausedTexture != 0 || ctx.savedFilterMin < 0
|| DisplayListManager.getRecordMode() != DisplayListManager.RecordMode.NONE) return false;
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(id);
if (info == null) return false;
pausedTexture = id;
pausedMin = info.getMinFilter();
pausedMag = info.getMagFilter();
pausedAniso = info.getMaxAnisotropy();
setFilter(id, ctx.savedFilterMin, ctx.savedFilterMag, ctx.savedFilterAniso);
return true;
}
public static void resumeTemporaryFilter() {
if (pausedTexture == 0) return;
final int id = pausedTexture;
pausedTexture = 0;
setFilter(id, pausedMin, pausedMag, pausedAniso);
}
/** TextureUtil.func_152777_a(false, false, 1) without using vanilla's single save slot, which the caller may hold. */
public static void forceNearestFilter() {
final int id = GLStateManager.getBoundTextureForServerState();
final TextureInfo info = id != 0 ? TextureInfoCache.INSTANCE.getInfo(id) : null;
if (info == null) {
nearestTexture = 0;
return;
}
nearestTexture = id;
nearestMin = info.getMinFilter();
nearestMag = info.getMagFilter();
nearestAniso = info.getMaxAnisotropy();
setFilter(id, GL11.GL_NEAREST, GL11.GL_NEAREST, 1.0f);
}
public static void restoreFilterAfterNearest() {
if (nearestTexture == 0) return;
final int id = nearestTexture;
nearestTexture = 0;
setFilter(id, nearestMin, nearestMag, nearestAniso);
}
private static void setFilter(int id, int min, int mag, float aniso) {
final boolean anisotropic = BackendManager.RENDER_BACKEND.isAnisotropicSupported();
if (DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
if (anisotropic) RenderSystem.texParameterf(id, GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
} else {
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
if (anisotropic) GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
}
}
}
@@ -38,6 +38,10 @@ public class ModRedirector {
return allExclusions;
}
public String[] getConstantPoolSearchStrings() {
return core.getConstantPoolSearchStrings();
}
public boolean shouldTransform(byte[] basicClass) {
return core.shouldTransform(basicClass);
}
@@ -1,5 +1,7 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.retrofuturabootstrap.api.BytePatternMatcher;
import com.gtnewhorizons.retrofuturabootstrap.api.ClassHeaderMetadata;
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
import com.gtnewhorizons.retrofuturabootstrap.api.ExtensibleClassLoader;
import com.gtnewhorizons.retrofuturabootstrap.api.RfbClassTransformer;
@@ -16,10 +18,12 @@ public abstract class RfbModRedirector implements RfbClassTransformer {
private final String id;
private final ModRedirector inner;
private final BytePatternMatcher cstPoolMatcher;
protected RfbModRedirector(@Pattern("[a-z0-9-]+") String id, ModRedirector inner) {
this.id = id;
this.inner = inner;
this.cstPoolMatcher = new BytePatternMatcher(inner.getConstantPoolSearchStrings(), BytePatternMatcher.Mode.StartsWith);
}
@Override
@@ -53,7 +57,11 @@ public abstract class RfbModRedirector implements RfbClassTransformer {
if (!classNode.isPresent()) {
return false;
}
return inner.shouldTransform(classNode.getOriginalBytes());
final ClassHeaderMetadata metadata = classNode.getOriginalMetadata();
if (metadata == null) {
return inner.shouldTransform(classNode.getOriginalBytes());
}
return metadata.matchesBytes(classNode.getOriginalBytes(), cstPoolMatcher);
}
@Override
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.glsm.profiling;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CaptureGate;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.sun.management.ThreadMXBean;
@@ -66,12 +65,12 @@ public final class Tracy {
static {
TracyBackend backend = null;
if (SystemProperties.TRACY) {
if (TracyOptions.enabled()) {
backend = loadAndInit();
}
BACKEND = backend;
ENABLED = backend != null;
FINE_ZONES = ENABLED && SystemProperties.TRACY_FINE_ZONES;
FINE_ZONES = ENABLED && TracyOptions.fineZones();
if (ENABLED) {
Runtime.getRuntime().addShutdownHook(new Thread(Tracy::shutdown, "Tracy-Shutdown"));
SECTIONS.registerCategory(SECTION_WORLD, "World");
@@ -86,6 +85,18 @@ public final class Tracy {
private static void shutdown() {
SHUTTING_DOWN = true;
if (isCaptureActive(BACKEND.captureState())) {
BACKEND.captureStop();
final long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);
while (isCaptureActive(BACKEND.captureState()) && System.nanoTime() < deadline) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
final Thread worker = new Thread(BACKEND::shutdown, "Tracy-Shutdown-Worker");
worker.setDaemon(true);
worker.start();
@@ -96,6 +107,10 @@ public final class Tracy {
}
}
private static boolean isCaptureActive(int state) {
return state == TracyBackend.CAPTURE_CONNECTING || state == TracyBackend.CAPTURE_RECORDING || state == TracyBackend.CAPTURE_SAVING;
}
private static TracyBackend loadAndInit() {
TracyBackend found = null;
try {
@@ -104,11 +119,11 @@ public final class Tracy {
found = it.next();
}
} catch (ServiceConfigurationError | LinkageError e) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but backend unavailable: {}", e.getMessage());
LOGGER.warn("Tracy: backend unavailable: {}", e.getMessage());
return null;
}
if (found == null) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but no TracyBackend service present");
LOGGER.warn("Tracy: enabled but the angelica-tracy jar is not installed");
return null;
}
if (!implementsCurrentAbi(found)) return null;
@@ -120,7 +135,7 @@ public final class Tracy {
init.start();
try {
if (!done.await(15, TimeUnit.SECONDS) && result.compareAndSet(null, Boolean.FALSE)) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but init timed out; profiling disabled");
LOGGER.warn("Tracy: init timed out; profiling disabled");
return null;
}
} catch (InterruptedException e) {
@@ -131,7 +146,7 @@ public final class Tracy {
return null;
}
if (!Boolean.TRUE.equals(result.get())) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but native init failed; profiling disabled");
LOGGER.warn("Tracy: native init failed; profiling disabled");
return null;
}
return backend;
@@ -388,6 +403,31 @@ public final class Tracy {
return ENABLED && BACKEND.isConnected();
}
public static String captureStart(String path, int seconds) {
if (!ENABLED) return "Tracy not enabled";
return BACKEND.captureStart(path, seconds);
}
public static void captureStop() {
if (!ENABLED) return;
BACKEND.captureStop();
}
public static int captureState() {
if (!ENABLED) return 0;
return BACKEND.captureState();
}
public static long captureElapsedMs() {
if (!ENABLED) return 0;
return BACKEND.captureElapsedMs();
}
public static String captureError() {
if (!ENABLED) return "";
return BACKEND.captureError();
}
private static boolean gpuInitAttempted;
private static boolean gpuReady;
@Setter private static boolean gpuZonesEnabled = true;
@@ -12,6 +12,13 @@ public interface TracyBackend {
int SEVERITY_ERROR = 4;
int SEVERITY_FATAL = 5;
int CAPTURE_IDLE = 0;
int CAPTURE_CONNECTING = 1;
int CAPTURE_RECORDING = 2;
int CAPTURE_SAVING = 3;
int CAPTURE_DONE = 4;
int CAPTURE_FAILED = 5;
boolean init();
long internSrcLoc(String name, int color);
@@ -47,4 +54,12 @@ public interface TracyBackend {
boolean gpuBeginZone(long srcLoc);
void gpuEndZone();
void gpuCollect();
default String captureStart(String path, int seconds) {
return "Tracy capture unsupported by this angelica-tracy build";
}
default void captureStop() {}
default int captureState() { return CAPTURE_IDLE; }
default long captureElapsedMs() { return 0; }
default String captureError() { return ""; }
}
@@ -0,0 +1,64 @@
package com.gtnewhorizons.angelica.glsm.profiling;
import com.gtnewhorizons.angelica.config.SystemProperties;
public final class TracyOptions {
private static boolean configEnabled;
private static boolean configRemote;
private static boolean configFineZones;
private static int configMaxSrcLocs = 4096;
private TracyOptions() {}
public static void latch(boolean enabled, boolean allowRemote, boolean fineZones, int maxSrcLocs) {
configEnabled = enabled;
configRemote = allowRemote;
configFineZones = fineZones;
configMaxSrcLocs = maxSrcLocs;
}
public static boolean enabled() {
return resolveBoolean(SystemProperties.TRACY_OVERRIDE, configEnabled);
}
public static boolean allowRemote() {
return configRemote;
}
public static boolean fineZones() {
return resolveBoolean(SystemProperties.TRACY_FINE_ZONES_OVERRIDE, configFineZones);
}
public static int maxSrcLocs() {
return resolveMaxSrcLocs(SystemProperties.TRACY_MAX_SRC_LOCS_OVERRIDE, configMaxSrcLocs);
}
public static boolean configEnabled() {
return configEnabled;
}
public static boolean configRemote() {
return configRemote;
}
public static boolean configFineZones() {
return configFineZones;
}
public static int configMaxSrcLocs() {
return configMaxSrcLocs;
}
public static boolean backendPresent() {
return TracyOptions.class.getResource("/com/gtnewhorizons/angelica/tracy/TracyClientBackend.class") != null;
}
static boolean resolveBoolean(Boolean override, boolean config) {
return override != null ? override : config;
}
static int resolveMaxSrcLocs(Integer override, int config) {
return Math.max(16, override != null ? override : config);
}
}
@@ -68,6 +68,7 @@ public final class AttribSnapshot {
final AttribDesc[] out = new AttribDesc[VAOManager.MAX_ATTRIBS];
final List<ByteBuffer> allocated = new ArrayList<>();
final int prevVBO = GLStateManager.getBoundVBO();
final VAOManager vaos = GLStateManager.ctx().vaos;
boolean success = false;
// Pass 1 — unioned [start,end] per vboId. Hot path: one source VBO, zero allocations.
@@ -78,7 +79,7 @@ public final class AttribSnapshot {
final long lastVtx = (long) firstVertex + vertexCount - 1;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
final VAOManager.Attrib a = vaos.attrib(i);
if (a == null || !a.enabled || a.vboId == 0) continue;
final int stride = a.effectiveStride();
final long typeBytes = (long) a.size * GLTypes.sizeBytes(a.type);
@@ -114,7 +115,7 @@ public final class AttribSnapshot {
try {
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
final VAOManager.Attrib a = vaos.attrib(i);
if (a == null || !a.enabled) continue;
if (a.vboId != 0) {
@@ -97,7 +97,8 @@ public final class CommandBufferExecutor {
ptr += 4;
}
case GLCommand.USE_PROGRAM -> {
GLStateManager.glUseProgram(memGetInt(ptr));
final int recordedProgram = memGetInt(ptr);
GLStateManager.glUseProgram(compiledDisplayList.resolveProgram(recordedProgram));
ptr += 4;
}
case GLCommand.PUSH_ATTRIB -> {
@@ -252,6 +253,11 @@ public final class CommandBufferExecutor {
}
// === Float commands ===
case GLCommand.VERTEX_ATTRIB -> {
GLStateManager.glVertexAttrib4f(memGetInt(ptr), memGetFloat(ptr + 4), memGetFloat(ptr + 8),
memGetFloat(ptr + 12), memGetFloat(ptr + 16));
ptr += 20;
}
case GLCommand.POINT_SIZE -> {
GLStateManager.glPointSize(memGetFloat(ptr));
ptr += 4;
@@ -10,6 +10,8 @@ import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
@@ -21,6 +23,13 @@ public final class CommandRecorder {
private long writePointer;
private final ArrayList<DisplayListCommand> complexObjects = new ArrayList<>();
private final IndexedDrawBatchBuilder indexedDraws = new IndexedDrawBatchBuilder();
private Set<DisplayListCommand> onceCommands;
private DisplayListCommand lastComplexCommand;
private int lastComplexCommandEnd = -1;
public boolean isLastCommand(DisplayListCommand command) {
return size() == lastComplexCommandEnd && command.equals(lastComplexCommand);
}
public CommandRecorder() {
this(DEFAULT_CAPACITY);
@@ -32,6 +41,11 @@ public final class CommandRecorder {
this.writePointer = basePointer;
}
public boolean markRecordedOnce(DisplayListCommand command) {
if (onceCommands == null) onceCommands = new HashSet<>();
return onceCommands.add(command);
}
// === Low-level write operations ===
private void writeInt(int value) {
@@ -364,6 +378,16 @@ public final class CommandRecorder {
writeFloat(b);
}
public void writeVertexAttrib(int index, float x, float y, float z, float w) {
ensureCapacity(24);
writeInt(GLCommand.VERTEX_ATTRIB);
writeInt(index);
writeFloat(x);
writeFloat(y);
writeFloat(z);
writeFloat(w);
}
public void writeClearColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.CLEAR_COLOR);
@@ -649,6 +673,8 @@ public final class CommandRecorder {
complexObjects.add(cmd);
writeInt(GLCommand.COMPLEX_REF);
writeInt(index);
lastComplexCommand = cmd;
lastComplexCommandEnd = size();
}
// === Lifecycle ===
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -35,6 +36,24 @@ public final class CompiledDisplayList {
private final DisplayListCommand[] complexObjects; // Complex commands (TexImage2D, etc.)
private final DisplayListVBO ownedVbos; // GPU resources referenced by index
private final List<IndexedDrawBatch> indexedBatches; // Shared VAO/VBO/EBO triples per layout group
private Int2IntMap replayPrograms;
private long programLifetimeGeneration = -1;
int resolveProgram(int program) {
if (program == 0) return 0;
final long generation = GLStateManager.getProgramLifetimeGeneration();
if (replayPrograms == null) replayPrograms = new Int2IntOpenHashMap();
if (programLifetimeGeneration != generation) {
replayPrograms.clear();
programLifetimeGeneration = generation;
}
if (replayPrograms.containsKey(program)) return replayPrograms.get(program);
final boolean valid = GLStateManager.glIsProgram(program);
if (!valid || !GLStateManager.isProgramPendingDeletion(program)) {
replayPrograms.put(program, valid ? program : 0);
}
return valid ? program : 0;
}
public CompiledDisplayList(ByteBuffer commandBuffer, DisplayListCommand[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches) {
this.commandBuffer = commandBuffer;
@@ -24,9 +24,9 @@ public final class GLCommand {
public static final int USE_PROGRAM = 11; // [cmd:4][program:4]
public static final int PUSH_ATTRIB = 12; // [cmd:4][mask:4]
public static final int POP_ATTRIB = 13; // [cmd:4][unused:4]
public static final int LOAD_IDENTITY = 14; // [cmd:4][matrixMode:4]
public static final int PUSH_MATRIX = 15; // [cmd:4][matrixMode:4]
public static final int POP_MATRIX = 16; // [cmd:4][matrixMode:4]
public static final int LOAD_IDENTITY = 14; // [cmd:4]
public static final int PUSH_MATRIX = 15; // [cmd:4]
public static final int POP_MATRIX = 16; // [cmd:4]
public static final int STENCIL_MASK = 17; // [cmd:4][mask:4]
public static final int DEPTH_MASK = 18; // [cmd:4][flag:4] (0 or 1)
public static final int FRONT_FACE = 19; // [cmd:4][mode:4]
@@ -62,6 +62,7 @@ public final class GLCommand {
public static final int CLEAR_COLOR = 55; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int BLEND_COLOR = 56; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int SECONDARY_COLOR = 57; // [cmd:4][r:4f][g:4f][b:4f]
public static final int VERTEX_ATTRIB = 58; // [cmd:4][index:4][x:4f][y:4f][z:4f][w:4f]
// === Mixed int+float commands ===
public static final int ALPHA_FUNC = 60; // [cmd:4][func:4][ref:4f]
@@ -73,14 +74,13 @@ public final class GLCommand {
public static final int MATERIALF = 66; // [cmd:4][face:4][pname:4][param:4f]
public static final int TEX_PARAMETERF = 67; // [cmd:4][target:4][pname:4][param:4f]
// === Double commands ===
public static final int TRANSLATE = 70; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int SCALE = 72; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int TRANSLATE = 70; // [cmd:4][x:4f][y:4f][z:4f] = 16 bytes
public static final int SCALE = 72; // [cmd:4][x:4f][y:4f][z:4f] = 16 bytes
public static final int CLEAR_DEPTH = 75; // [cmd:4][depth:8d] = 12 bytes
// === Matrix commands (72 bytes) ===
public static final int MULT_MATRIX = 80; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
public static final int LOAD_MATRIX = 81; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
// === Matrix commands (68 bytes) ===
public static final int MULT_MATRIX = 80; // [cmd:4][m00-m33:64f] = 68 bytes
public static final int LOAD_MATRIX = 81; // [cmd:4][m00-m33:64f] = 68 bytes
// === Buffer commands (inline 4 floats max) ===
public static final int FOG = 90; // [cmd:4][pname:4][count:4][params:16f] = 28 bytes
@@ -157,6 +157,7 @@ public final class GLCommand {
case CLEAR_COLOR -> "CLEAR_COLOR";
case BLEND_COLOR -> "BLEND_COLOR";
case SECONDARY_COLOR -> "SECONDARY_COLOR";
case VERTEX_ATTRIB -> "VERTEX_ATTRIB";
case ALPHA_FUNC -> "ALPHA_FUNC";
case FOGF -> "FOGF";
case LIGHTF -> "LIGHTF";
@@ -221,6 +222,8 @@ public final class GLCommand {
GLCommand.SCISSOR,
GLCommand.COLOR, GLCommand.CLEAR_COLOR, GLCommand.BLEND_COLOR -> 20;
case GLCommand.VERTEX_ATTRIB -> 24;
// DRAW_RANGE_RESTORE: 28 bytes
case GLCommand.DRAW_RANGE_RESTORE -> 28;
@@ -176,6 +176,7 @@ public class GLSMRedirector {
private static final Map<Integer, String> glCapRedirects = new HashMap<>();
private static final Map<String, String> typeRedirects = new HashMap<>();
private static final ClassConstantPoolParser cstPoolParser;
private static final String[] CONSTANT_POOL_SEARCH_STRINGS;
private static final String[] CORE_EXCLUSIONS = {
"org.lwjgl",
@@ -826,13 +827,18 @@ public class GLSMRedirector {
stringsToSearch.add(Display);
stringsToSearch.addAll(typeRedirects.keySet());
stringsToSearch.addAll(methodRedirects.keySet());
cstPoolParser = new ClassConstantPoolParser(stringsToSearch.toArray(new String[0]));
CONSTANT_POOL_SEARCH_STRINGS = stringsToSearch.toArray(new String[0]);
cstPoolParser = new ClassConstantPoolParser(CONSTANT_POOL_SEARCH_STRINGS);
}
public String[] getCoreExclusions() {
return CORE_EXCLUSIONS.clone();
}
public String[] getConstantPoolSearchStrings() {
return CONSTANT_POOL_SEARCH_STRINGS.clone();
}
public static String getGLPrefix() { return GL_PREFIX; }
public static String getTargetClassName() { return GLStateManager; }
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.VanillaBooleanLayer;
import com.gtnewhorizons.angelica.glsm.states.BooleanState;
@@ -89,7 +90,8 @@ public class BooleanStateStack extends BooleanState implements CowStateStack<Boo
@Override
public void setEnabled(boolean enabled) {
if (GLStateManager.getAttribDepth() > 0
final GLContextState o = cow.owner;
if (o != null && o.attribDepth > 0
&& (stateUnknown || !GLStateManager.isCachingEnabled() || enabled != vanillaValue())) {
beforeModify();
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
/**
@@ -14,6 +15,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
public final class CowDepths {
public int id = -1;
public GLContextState owner;
public final int restoreBit;
public final int restoreUnit;
private final int[] slotDepth = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
@@ -40,6 +40,7 @@ public final class CowDispatch {
public static final byte LINE = 20;
public static final byte TEXTURE_BINDING = 21;
public static final byte TEX_ENV = 22;
public static final byte SCISSOR = 23;
public static byte kindOf(CowStateStack<?> s) {
final Class<?> c = s.getClass();
@@ -66,6 +67,7 @@ public final class CowDispatch {
if (c == LineStateStack.class) return LINE;
if (c == TextureBindingStack.class) return TEXTURE_BINDING;
if (c == TexEnvState.class) return TEX_ENV;
if (c == ScissorStateStack.class) return SCISSOR;
throw new IllegalStateException("Unknown CowStateStack class: " + c.getName());
}
@@ -94,6 +96,7 @@ public final class CowDispatch {
case LINE -> ((LineStateStack) s).topSlotChanged();
case TEXTURE_BINDING -> ((TextureBindingStack) s).topSlotChanged();
case TEX_ENV -> ((TexEnvState) s).topSlotChanged();
case SCISSOR -> ((ScissorStateStack) s).topSlotChanged();
default -> throw new IllegalStateException("Unknown kind: " + kind);
};
}
@@ -123,6 +126,7 @@ public final class CowDispatch {
case LINE -> ((LineStateStack) s).restoreSlot(slot);
case TEXTURE_BINDING -> ((TextureBindingStack) s).restoreSlot(slot);
case TEX_ENV -> ((TexEnvState) s).restoreSlot(slot);
case SCISSOR -> ((ScissorStateStack) s).restoreSlot(slot);
default -> throw new IllegalStateException("Unknown kind: " + kind);
}
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
/**
@@ -41,15 +42,19 @@ public interface CowStateStack<T> extends IStateStack<T> {
@Override
default int pushDepth() {
return GLStateManager.getAttribDepth();
final GLContextState o = cowDepths().owner;
return o == null ? 0 : o.attribDepth;
}
@Override
default void beforeModify() {
final int s = cowDepths().claim(GLStateManager.getAttribDepth());
final CowDepths d = cowDepths();
final GLContextState o = d.owner;
if (o == null) return;
final int s = d.claim(o.attribDepth);
if (s >= 0) {
captureSlot(s);
GLStateManager.registerModifiedState(stackId());
GLStateManager.registerModifiedState(o, d.id);
}
}
@@ -0,0 +1,44 @@
package com.gtnewhorizons.angelica.glsm.stacks;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.glsm.states.ScissorState;
public final class ScissorStateStack extends ScissorState implements CowStateStack<ScissorStateStack> {
protected final ScissorState[] stack;
private final CowDepths cow = new CowDepths(StateSet.R_SCISSOR);
public ScissorStateStack(int id) {
cow.id = id;
stack = new ScissorState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
stack[i] = new ScissorState();
}
}
@Override
public CowDepths cowDepths() {
return cow;
}
@Override
public void captureSlot(int s) {
stack[s].set(this);
}
@Override
public void restoreSlot(int s) {
set(stack[s]);
}
@Override
public boolean topSlotChanged() {
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
}
@Override
public int stackId() {
return cow.id;
}
}
@@ -4,12 +4,8 @@ public final class StackIdAllocator {
private StackIdAllocator() {}
private static final int STATIC_BASE = 4096;
private static final int STATIC_CAPACITY = 16;
private static int next = -1;
private static int perContext = -1;
private static int staticNext;
public static void beginContext() {
next = 0;
@@ -28,14 +24,7 @@ public final class StackIdAllocator {
return next >= 0 ? next++ : -1;
}
public static int nextStaticId() {
if (staticNext >= STATIC_CAPACITY) {
throw new IllegalStateException("static stack id capacity exceeded");
}
return STATIC_BASE + staticNext++;
}
public static int capacity() {
return STATIC_BASE + STATIC_CAPACITY;
public static int allocated() {
return next;
}
}
@@ -24,6 +24,9 @@ public final class TextureUnitBooleanStateStack extends BooleanStateStack {
@Override
public void setEnabled(boolean enabled) {
if (!GLStateManager.isCachingEnabled() || enabled != this.enabled) {
GLStateManager.beforeUncapturedTextureUnitChange(glCap, unitIndex);
}
beforeModify();
setEnabledWithUnitSwitch(enabled);
}
@@ -46,6 +46,7 @@ public class BooleanState implements ISettableState<BooleanState> {
public void setEnabled(boolean enabled) {
final boolean bypass = !GLStateManager.isCachingEnabled();
if (stateUnknown || bypass || enabled != this.enabled || (this.glCap == GL11.GL_BLEND && GLStateManager.vendorIsAMD() && GLStateManager.isPoppingAttributes())) {
GLStateManager.beforeUncapturedCapabilityChange(glCap);
stateUnknown = false;
this.enabled = enabled;
onEnabledChanged();
@@ -0,0 +1,54 @@
package com.gtnewhorizons.angelica.glsm.states;
import java.nio.IntBuffer;
public class ScissorState implements ISettableState<ScissorState> {
public int x;
public int y;
public int width;
public int height;
public void setScissor(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public void get(IntBuffer params) {
params.put(0, x);
params.put(1, y);
params.put(2, width);
params.put(3, height);
}
public void get(int[] params) {
params[0] = x;
params[1] = y;
params[2] = width;
params[3] = height;
}
@Override
public ScissorState set(ScissorState state) {
this.x = state.x;
this.y = state.y;
this.width = state.width;
this.height = state.height;
return this;
}
@Override
public boolean sameAs(Object state) {
if (this == state) return true;
if (!(state instanceof ScissorState scissorState)) return false;
return x == scissorState.x && y == scissorState.y && width == scissorState.width && height == scissorState.height;
}
@Override
public ScissorState copy() {
return new ScissorState().set(this);
}
}
@@ -0,0 +1,13 @@
package com.gtnewhorizons.angelica.glsm.streaming;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
public final class StreamingVaos {
private static final int FORMAT_COUNT = VertexFlags.BITSET_SIZE;
final int[] persistentVAOs = new int[FORMAT_COUNT];
final int[] orphanVAOs = new int[FORMAT_COUNT];
final int[] extendedPersistentVAOs = new int[FORMAT_COUNT];
final int[] extendedOrphanVAOs = new int[FORMAT_COUNT];
}
@@ -6,6 +6,7 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
@@ -47,11 +48,6 @@ public class TessellatorStreamingDrawer {
private static PersistentStreamingBuffer persistentBuffer;
private static final OrphanStreamingBuffer[] orphanBuffers = new OrphanStreamingBuffer[FORMAT_COUNT];
private static final int[] persistentVAOs = new int[FORMAT_COUNT];
private static final int[] orphanVAOs = new int[FORMAT_COUNT];
private static final int[] extendedPersistentVAOs = new int[FORMAT_COUNT];
private static final int[] extendedOrphanVAOs = new int[FORMAT_COUNT];
private static ByteBuffer extScratch;
private static long extScratchAddress;
private static int extScratchCapacity;
@@ -243,13 +239,18 @@ public class TessellatorStreamingDrawer {
final int normalOffset = ImmediateExtendedAttribHandler.normalOffset(format);
final int combinedStride = vertexSize + ImmediateExtendedAttribHandler.EXT_STRIDE;
final int combinedBytes = vertexCount * combinedStride;
ensureExtScratch(combinedBytes);
interleaveBase(srcBase, extScratchAddress, vertexCount, vertexSize, combinedStride);
extHandler.buildPacked(srcBase, vertexSize, 0, texOffset, normalOffset, vertexCount, extPrim, extScratchAddress + vertexSize, combinedStride);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, flags, format, combinedStride, drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
final boolean locked = GLStateManager.acquireDrawLock();
try {
ensureExtScratch(combinedBytes);
interleaveBase(srcBase, extScratchAddress, vertexCount, vertexSize, combinedStride);
extHandler.buildPacked(srcBase, vertexSize, 0, texOffset, normalOffset, vertexCount, extPrim, extScratchAddress + vertexSize, combinedStride);
extScratch.position(0);
extScratch.limit(combinedBytes);
uploadAndDrawExtended(extScratch, flags, format, combinedStride, drawMode, vertexCount);
shrinkExtScratchIfOversized(combinedBytes);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
return true;
}
@@ -311,7 +312,9 @@ public class TessellatorStreamingDrawer {
Tracy.beginZone(Z_SDL_STREAM_DRAW);
final boolean locked = GLStateManager.acquireDrawLock();
try {
ensureVAO(flags, format);
final GLContextState glCtx = GLStateManager.ctx();
final StreamingVaos vaos = glCtx.streamingVaos;
ensureVAO(vaos, flags, format);
if (Tracy.ENABLED) {
streamedBytes += packed.remaining();
@@ -326,10 +329,10 @@ public class TessellatorStreamingDrawer {
final boolean fromRing = firstVertex >= 0;
if (fromRing) {
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.persistentVAOs[flags]);
} else {
if (Tracy.ENABLED && persistentBuffer != null) orphanFallbacks++;
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.orphanVAOs[flags]);
orphanBuffers[flags].upload(packed);
firstVertex = 0;
}
@@ -340,7 +343,7 @@ public class TessellatorStreamingDrawer {
lastStreamDrawMode = drawMode;
}
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
drawWithQuadConversion(glCtx, drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
} finally {
if (locked) GLStateManager.releaseDrawLock();
@@ -352,11 +355,11 @@ public class TessellatorStreamingDrawer {
return persistentBuffer == null ? 0L : persistentBuffer.getWraps();
}
private static void drawWithQuadConversion(int drawMode, int firstVertex, int vertexCount) {
private static void drawWithQuadConversion(GLContextState glCtx, int drawMode, int firstVertex, int vertexCount) {
FfpExtendedAttribs.beginInternalDraw();
try {
if (drawMode == GL11.GL_QUADS) {
QuadConverter.drawQuadsAsTriangles(firstVertex, vertexCount);
QuadConverter.drawQuadsAsTriangles(glCtx, firstVertex, vertexCount);
} else {
GLStateManager.glDrawArrays(drawMode, firstVertex, vertexCount);
}
@@ -366,8 +369,10 @@ public class TessellatorStreamingDrawer {
}
private static void uploadAndDrawExtended(ByteBuffer combined, int flags, VertexFormat format, int combinedStride, int drawMode, int vertexCount) {
ensureVAO(flags, format);
ensureExtendedVAOs(flags, format);
final GLContextState glCtx = GLStateManager.ctx();
final StreamingVaos vaos = glCtx.streamingVaos;
ensureVAO(vaos, flags, format);
ensureExtendedVAOs(vaos, flags, format);
int firstVertex = -1;
@@ -376,18 +381,20 @@ public class TessellatorStreamingDrawer {
}
if (firstVertex >= 0) {
GLStateManager.glBindVertexArray(extendedPersistentVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.extendedPersistentVAOs[flags]);
} else {
GLStateManager.glBindVertexArray(extendedOrphanVAOs[flags]);
GLStateManager.glBindVertexArray(vaos.extendedOrphanVAOs[flags]);
orphanBuffers[flags].upload(combined);
firstVertex = 0;
}
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
drawWithQuadConversion(glCtx, drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
}
private static void ensureExtendedVAOs(int flags, VertexFormat format) {
private static void ensureExtendedVAOs(StreamingVaos vaos, int flags, VertexFormat format) {
final int[] extendedOrphanVAOs = vaos.extendedOrphanVAOs;
final int[] extendedPersistentVAOs = vaos.extendedPersistentVAOs;
if (extendedOrphanVAOs[flags] == 0) {
extendedOrphanVAOs[flags] = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(extendedOrphanVAOs[flags]);
@@ -467,12 +474,13 @@ public class TessellatorStreamingDrawer {
return repackBuffer;
}
private static void ensureVAO(int flags, VertexFormat format) {
private static void ensureVAO(StreamingVaos vaos, 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) {
orphanBuffers[flags] = new OrphanStreamingBuffer();
orphanVAOs[flags] = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, orphanBuffers[flags].getBufferId());
@@ -489,33 +497,4 @@ public class TessellatorStreamingDrawer {
}
}
/**
* Clean up all VAOs, streaming buffers, and the repack buffer.
*/
public static void destroy() {
for (int i = 0; i < FORMAT_COUNT; i++) {
if (persistentVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(persistentVAOs[i]); persistentVAOs[i] = 0; }
if (orphanVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(orphanVAOs[i]); orphanVAOs[i] = 0; }
if (extendedPersistentVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(extendedPersistentVAOs[i]); extendedPersistentVAOs[i] = 0; }
if (extendedOrphanVAOs[i] != 0) { GLStateManager.glDeleteVertexArrays(extendedOrphanVAOs[i]); extendedOrphanVAOs[i] = 0; }
if (orphanBuffers[i] != null) { orphanBuffers[i].destroy(); orphanBuffers[i] = null; }
}
if (extScratch != null) {
memFree(extScratch);
extScratch = null;
extScratchAddress = 0;
extScratchCapacity = 0;
}
if (persistentBuffer != null) {
persistentBuffer.destroy();
persistentBuffer = null;
}
if (repackBuffer != null) {
memFree(repackBuffer);
repackBuffer = null;
repackAddress = 0;
repackCapacity = 0;
}
initialized = false;
}
}
@@ -0,0 +1,9 @@
package com.gtnewhorizons.angelica.glsm.texture;
import java.nio.ByteBuffer;
public interface TextureStaging {
ByteBuffer buffer();
boolean bgra();
int level();
}
@@ -0,0 +1,186 @@
package com.gtnewhorizons.angelica.glsm.threading;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public final class AngelicaWorkers {
private static final Logger LOGGER = LogManager.getLogger("Angelica");
private static final long IDLE_TIMEOUT_SECONDS = 120;
private static final Object lock = new Object();
private static final List<Runnable> idleShutdownHooks = new ArrayList<>();
private static ExecutorService executor;
private static ScheduledExecutorService scheduler;
private static volatile long lastActivityTime;
private static final AtomicInteger inFlight = new AtomicInteger(0);
private static boolean idleCheckScheduled;
private static int resolvedThreads;
private AngelicaWorkers() {}
private static final class Worker extends Thread {
Worker(Runnable r, String name) {
super(r, name);
setDaemon(true);
}
}
public static boolean isWorkerThread() {
return Thread.currentThread() instanceof Worker;
}
public static void onIdleShutdown(Runnable hook) {
Objects.requireNonNull(hook);
synchronized (lock) {
idleShutdownHooks.add(hook);
}
}
public static void prestart() {
executor();
}
public static int threads() {
synchronized (lock) {
resolveThreads();
return resolvedThreads;
}
}
private static void resolveThreads() {
if (resolvedThreads == 0) {
final int configured = GLSMConfig.workerThreadCount;
resolvedThreads = configured > 0 ? configured : Math.max(2, Math.min(12, Runtime.getRuntime().availableProcessors() / 2));
}
}
private static void noteActivity() {
lastActivityTime = System.nanoTime();
}
private static ExecutorService executor() {
synchronized (lock) {
noteActivity();
if (executor != null && !executor.isShutdown()) {
return executor;
}
resolveThreads();
final AtomicInteger threadCounter = new AtomicInteger();
final ThreadFactory factory = r -> new Worker(r, "Angelica-Worker-" + threadCounter.getAndIncrement());
final ThreadPoolExecutor tpe = new ThreadPoolExecutor(resolvedThreads, resolvedThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), factory);
tpe.prestartAllCoreThreads();
executor = tpe;
LOGGER.debug("Created Angelica worker pool with {} prestarted threads", resolvedThreads);
if (scheduler == null || scheduler.isShutdown()) {
scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
final Thread t = new Thread(r, "Angelica-Worker-Scheduler");
t.setDaemon(true);
return t;
});
}
if (!idleCheckScheduled) {
idleCheckScheduled = true;
scheduleIdleCheck(IDLE_TIMEOUT_SECONDS);
}
return executor;
}
}
private static void scheduleIdleCheck(long delaySeconds) {
try {
scheduler.schedule(AngelicaWorkers::checkIdleShutdown, delaySeconds, TimeUnit.SECONDS);
} catch (Exception e) {
idleCheckScheduled = false;
LOGGER.warn("Failed to schedule idle check", e);
}
}
public static <T> CompletableFuture<T> submit(Supplier<T> work) {
Objects.requireNonNull(work);
noteActivity();
inFlight.incrementAndGet();
try {
return CompletableFuture.supplyAsync(() -> {
try {
return work.get();
} finally {
noteActivity();
inFlight.decrementAndGet();
}
}, executor());
} catch (Exception e) {
inFlight.decrementAndGet();
throw e;
}
}
public static CompletableFuture<Void> run(Runnable work) {
Objects.requireNonNull(work);
noteActivity();
inFlight.incrementAndGet();
try {
return CompletableFuture.runAsync(() -> {
try {
work.run();
} finally {
noteActivity();
inFlight.decrementAndGet();
}
}, executor());
} catch (Exception e) {
inFlight.decrementAndGet();
throw e;
}
}
private static void checkIdleShutdown() {
synchronized (lock) {
final ExecutorService current = executor;
if (current == null || current.isShutdown()) {
if (scheduler != null && !scheduler.isShutdown()) {
scheduler.shutdown();
scheduler = null;
}
idleCheckScheduled = false;
return;
}
final long idleSeconds = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - lastActivityTime);
if (idleSeconds >= IDLE_TIMEOUT_SECONDS && inFlight.get() == 0) {
LOGGER.debug("Shutting down idle Angelica worker pool after {} seconds", idleSeconds);
current.shutdown();
executor = null;
scheduler.shutdown();
scheduler = null;
idleCheckScheduled = false;
for (int i = 0; i < idleShutdownHooks.size(); i++) {
idleShutdownHooks.get(i).run();
}
} else {
scheduleIdleCheck(Math.max(1, IDLE_TIMEOUT_SECONDS - idleSeconds + 1));
}
}
}
}
@@ -0,0 +1,56 @@
out vec4 fragColor;
vec4 glintLayer(vec4 coord, out bool visible) {
vec4 color = textureProj(u_Sampler0, coord) * v_Color;
#if GLINT_OVERLAY == 2
color.rgb = mix(color.rgb, v_Overlay.rgb, v_Overlay.a);
#elif GLINT_OVERLAY == 1
color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);
#endif
#if GLINT_LIGHTMAP
color *= textureProj(u_Sampler1, v_TexCoord1);
#endif
float qa = round(color.a * 255.0);
float ref = u_AlphaRef * 255.0;
#if GLINT_ALPHA_FUNC == 0
visible = false;
#elif GLINT_ALPHA_FUNC == 1
visible = qa < ref;
#elif GLINT_ALPHA_FUNC == 2
visible = qa == round(ref);
#elif GLINT_ALPHA_FUNC == 3
visible = qa <= ref;
#elif GLINT_ALPHA_FUNC == 4
visible = qa > ref;
#elif GLINT_ALPHA_FUNC == 5
visible = qa != round(ref);
#elif GLINT_ALPHA_FUNC == 6
visible = qa >= ref;
#else
visible = true;
#endif
if (!visible) return vec4(0.0);
#if GLINT_FOG_MODE == 1
float fog = v_FogCoord * u_FogParams.x + u_FogParams.y;
#elif GLINT_FOG_MODE == 2
float fog = exp2(-(v_FogCoord * u_FogParams.z));
#elif GLINT_FOG_MODE == 3
float fogDistance = v_FogCoord * u_FogParams.w;
float fog = exp2(-(fogDistance * fogDistance));
#endif
#if GLINT_FOG_MODE != 0
color.rgb = mix(u_FogColor.rgb, color.rgb, clamp(fog, 0.0, 1.0));
#endif
return color;
}
void main() {
bool visible0, visible1;
vec4 a = glintLayer(v_TexCoord0, visible0);
vec4 b = glintLayer(u_TextureMatrix3 * v_TexCoord0, visible1);
if (!visible0 && !visible1) discard;
fragColor = sqrt(a * a + b * b);
#if GLINT_REPLACE_ALPHA
fragColor.a = visible1 ? b.a : a.a;
#endif
}
@@ -68,7 +68,7 @@ class CompatUniformManagerTest {
// Use program and upload — should not error
GL20.glUseProgram(program);
CompatUniformManager.refreshCompatUniforms(program);
CompatUniformManager.refreshCompatUniforms(program, GLStateManager.ctx());
// Verify no GL errors
assertEquals(0, GL11.glGetError(), "GL error after uniform upload");
@@ -146,7 +146,7 @@ class CompatUniformManagerTest {
// Use program and upload — should not error
GL20.glUseProgram(program);
CompatUniformManager.refreshCompatUniforms(program);
CompatUniformManager.refreshCompatUniforms(program, GLStateManager.ctx());
assertEquals(0, GL11.glGetError(), "GL error after fog uniform upload");
}
@@ -1,7 +1,11 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
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;
@@ -12,6 +16,28 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class DisplayListIdAllocatorTest {
@Test
void concurrentAllocationsRemainUnique() throws Exception {
final DisplayListIDAllocator alloc = new DisplayListIDAllocator();
final int[][] ids = new int[2][500];
final CountDownLatch start = new CountDownLatch(1);
final TestThreads.Worker[] workers = new TestThreads.Worker[2];
for (int t = 0; t < workers.length; t++) {
final int[] lane = ids[t];
workers[t] = TestThreads.start("DisplayListIdAllocator-" + t, () -> {
if (!start.await(5, TimeUnit.SECONDS)) throw new AssertionError("start timed out");
for (int i = 0; i < lane.length; i++) lane[i] = alloc.allocRange(1);
});
}
start.countDown();
for (TestThreads.Worker worker : workers) worker.join();
final Set<Integer> unique = new HashSet<>();
for (int[] lane : ids) {
for (int id : lane) assertTrue(unique.add(id));
}
assertEquals(1000, unique.size());
}
@Test
void zeroNeverReturned() {
DisplayListIDAllocator alloc = new DisplayListIDAllocator(1);
@@ -0,0 +1,246 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.support.DisplayListTestFixture;
import org.junit.jupiter.api.BeforeEach;
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.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.nio.FloatBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
class DisplayListMatrixRestorationTest extends DisplayListTestFixture {
@Test
void flushedScalesPreserveNegativeComponentsInEveryMatrixTarget() {
for (int compileMode : new int[] { GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE }) {
for (int target : new int[] { GL11.GL_MODELVIEW, GL11.GL_PROJECTION, GL11.GL_TEXTURE }) {
resetMatrices();
int list = newList();
GLStateManager.glNewList(list, compileMode);
GLStateManager.glMatrixMode(target);
GLStateManager.glScalef(-2, 3, -4);
GLStateManager.glEndList();
resetMatrices();
GLStateManager.glCallList(list);
FloatBuffer matrix = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(switch (target) {
case GL11.GL_PROJECTION -> GL11.GL_PROJECTION_MATRIX;
case GL11.GL_TEXTURE -> GL11.GL_TEXTURE_MATRIX;
default -> GL11.GL_MODELVIEW_MATRIX;
}, matrix);
assertEquals(-2f, matrix.get(0));
assertEquals(3f, matrix.get(5));
assertEquals(-4f, matrix.get(10));
}
}
}
@BeforeEach
@AfterEach
void resetMatrices() {
for (int unit = 0; unit < 2; unit++) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
}
private static float translation(int target) {
FloatBuffer b = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(target, b);
return b.get(12);
}
@Test
void transformAttributesSaveAndRestoreLazyMode() {
for (int mask : new int[] { GL11.GL_TRANSFORM_BIT, GL11.GL_ALL_ATTRIB_BITS }) {
for (int compileMode : new int[] { GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE }) {
int list = newList();
GLStateManager.glNewList(list, compileMode);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glPushAttrib(mask);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(3, 0, 0);
GLStateManager.glPopAttrib();
GLStateManager.glLoadIdentity();
GLStateManager.glEndList();
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(9, 0, 0);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(5, 0, 0);
GLStateManager.glCallList(list);
assertEquals(3f, translation(GL11.GL_MODELVIEW_MATRIX));
assertEquals(0f, translation(GL11.GL_TEXTURE_MATRIX));
}
}
}
@Test
void nestedAttributesRestoreInheritedCallerMode() {
int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glPushAttrib(GL11.GL_TRANSFORM_BIT);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glPopAttrib();
GLStateManager.glLoadIdentity();
GLStateManager.glPopAttrib();
GLStateManager.glLoadIdentity();
GLStateManager.glEndList();
GLStateManager.glTranslatef(5, 0, 0);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glTranslatef(7, 0, 0);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glTranslatef(9, 0, 0);
GLStateManager.glCallList(list);
assertEquals(5f, translation(GL11.GL_MODELVIEW_MATRIX));
assertEquals(0f, translation(GL11.GL_PROJECTION_MATRIX));
assertEquals(0f, translation(GL11.GL_TEXTURE_MATRIX));
assertEquals(GL11.GL_TEXTURE, GLStateManager.glGetInteger(GL11.GL_MATRIX_MODE));
}
@Test
void implicitMatrixCommandsAndEmptyListsInheritCallerTarget() {
int empty = newList();
GLStateManager.glNewList(empty, GL11.GL_COMPILE);
GLStateManager.glEndList();
int load = newList();
GLStateManager.glNewList(load, GL11.GL_COMPILE);
GLStateManager.glLoadIdentity();
GLStateManager.glEndList();
GLStateManager.glTranslatef(5, 0, 0);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glTranslatef(9, 0, 0);
GLStateManager.glCallList(empty);
assertEquals(GL11.GL_TEXTURE, GLStateManager.glGetInteger(GL11.GL_MATRIX_MODE));
assertEquals(9f, translation(GL11.GL_TEXTURE_MATRIX));
GLStateManager.glCallList(load);
assertEquals(GL11.GL_TEXTURE, GLStateManager.glGetInteger(GL11.GL_MATRIX_MODE));
assertEquals(0f, translation(GL11.GL_TEXTURE_MATRIX));
assertEquals(5f, translation(GL11.GL_MODELVIEW_MATRIX));
}
@Test
void explicitInitialModelviewSurvivesReplayFromTexture() {
int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glEndList();
GLStateManager.glTranslatef(5, 0, 0);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glTranslatef(9, 0, 0);
GLStateManager.glCallList(list);
assertEquals(0f, translation(GL11.GL_MODELVIEW_MATRIX));
assertEquals(9f, translation(GL11.GL_TEXTURE_MATRIX));
}
@ParameterizedTest
@ValueSource(ints = { GL11.GL_MODELVIEW, GL11.GL_PROJECTION, GL11.GL_TEXTURE })
void transformsBeforeExplicitModeInheritCallerTarget(int explicitTarget) {
for (int compileMode : new int[] { GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE }) {
resetMatrices();
int list = newList();
GLStateManager.glNewList(list, compileMode);
GLStateManager.glTranslatef(5, 0, 0);
GLStateManager.glMatrixMode(explicitTarget);
GLStateManager.glTranslatef(7, 0, 0);
GLStateManager.glEndList();
for (int callerTarget : new int[] { GL11.GL_MODELVIEW, GL11.GL_PROJECTION, GL11.GL_TEXTURE }) {
resetMatrices();
GLStateManager.glMatrixMode(callerTarget);
GLStateManager.glCallList(list);
String context = "compileMode=" + compileMode + ", callerTarget=" + callerTarget;
assertEquals((callerTarget == GL11.GL_MODELVIEW ? 5f : 0f) + (explicitTarget == GL11.GL_MODELVIEW ? 7f : 0f),
translation(GL11.GL_MODELVIEW_MATRIX), context);
assertEquals((callerTarget == GL11.GL_PROJECTION ? 5f : 0f) + (explicitTarget == GL11.GL_PROJECTION ? 7f : 0f),
translation(GL11.GL_PROJECTION_MATRIX), context);
assertEquals((callerTarget == GL11.GL_TEXTURE ? 5f : 0f) + (explicitTarget == GL11.GL_TEXTURE ? 7f : 0f),
translation(GL11.GL_TEXTURE_MATRIX), context);
}
}
}
@ParameterizedTest
@ValueSource(ints = { GL11.GL_TEXTURE_BIT, GL11.GL_TEXTURE_BIT | GL11.GL_TRANSFORM_BIT, GL11.GL_ALL_ATTRIB_BITS })
void textureAttributesFlushTransformsBeforeRestoringUnit(int mask) {
for (int compileMode : new int[] { GL11.GL_COMPILE, GL11.GL_COMPILE_AND_EXECUTE }) {
resetMatrices();
int list = newList();
GLStateManager.glNewList(list, compileMode);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glPushAttrib(mask);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glTranslatef(7, 0, 0);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glTranslatef(3, 0, 0);
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glPopAttrib();
GLStateManager.glPopAttrib();
GLStateManager.glEndList();
resetMatrices();
GLStateManager.glCallList(list);
String context = "compileMode=" + compileMode;
assertEquals(GL13.GL_TEXTURE0, GLStateManager.glGetInteger(GL13.GL_ACTIVE_TEXTURE), context);
assertEquals(3f, translation(GL11.GL_MODELVIEW_MATRIX), context);
assertEquals(0f, translation(GL11.GL_TEXTURE_MATRIX), context);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
assertEquals(7f, translation(GL11.GL_TEXTURE_MATRIX), context);
}
}
@Test
void pendingTextureTransformStaysOnOriginalUnit() {
int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glTranslatef(7, 0, 0);
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
GLStateManager.glEndList();
GLStateManager.glCallList(list);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
assertEquals(7f, translation(GL11.GL_TEXTURE_MATRIX));
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
assertEquals(0f, translation(GL11.GL_TEXTURE_MATRIX));
}
@Test
void explicitModeAfterNestedListIsNotDeduplicated() {
int child = newList();
GLStateManager.glNewList(child, GL11.GL_COMPILE);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glTranslatef(5, 0, 0);
GLStateManager.glEndList();
int parent = newList();
GLStateManager.glNewList(parent, GL11.GL_COMPILE);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
GLStateManager.glCallList(child);
GLStateManager.glMatrixMode(GL11.GL_TEXTURE);
GLStateManager.glLoadIdentity();
GLStateManager.glEndList();
GLStateManager.glCallList(parent);
assertEquals(5f, translation(GL11.GL_MODELVIEW_MATRIX));
}
}
@@ -0,0 +1,38 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import org.lwjgl.opengl.SharedDrawable;
import java.util.Arrays;
import java.util.Map;
final class DrawableContexts {
private DrawableContexts() {}
static TestThreads.Worker start(SharedDrawable drawable, String name, TestThreads.Body body) {
return TestThreads.start(name, () -> {
GLStateManager.makeCurrent(drawable);
try {
body.run();
} finally {
GLStateManager.releaseContext(drawable);
}
});
}
static void run(SharedDrawable drawable, String name, TestThreads.Body body) throws InterruptedException {
start(drawable, name, body).join();
}
static void destroy(SharedDrawable drawable) {
final Map<Object, GLContextState> contexts = Reflect.getStatic(GLStateManager.class, "drawableContexts");
final GLContextState ctx = contexts.remove(drawable);
if (ctx != null) {
final GLContextState[] states = Reflect.getStatic(GLStateManager.class, "drawableContextStates");
Reflect.setStatic(GLStateManager.class, "drawableContextStates", Arrays.stream(states).filter(s -> s != ctx).toArray(GLContextState[]::new));
}
drawable.destroy();
}
}
@@ -3,8 +3,10 @@ package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyIsEnabled;
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyState;
import static org.junit.jupiter.api.Assertions.assertEquals;
@GLCoreTest
@@ -170,6 +172,66 @@ public class GLSM_PopAttribMaskFidelity_GLTest {
}
}
@Test
void scissorBoxFollowsScissorBitOnly() {
final int[] boxA = { 10, 20, 100, 200 };
final int[] boxB = { 30, 40, 50, 60 };
try {
GLStateManager.glScissor(boxA[0], boxA[1], boxA[2], boxA[3]);
GLStateManager.disableScissorTest();
verifyState(GL11.GL_SCISSOR_BOX, boxA, "Scissor box - Initial");
verifyIsEnabled(GL11.GL_SCISSOR_TEST, false, "Scissor - Initial");
GLStateManager.glPushAttrib(GL11.GL_SCISSOR_BIT);
GLStateManager.enableScissorTest();
GLStateManager.glScissor(boxB[0], boxB[1], boxB[2], boxB[3]);
GLStateManager.glPopAttrib();
verifyState(GL11.GL_SCISSOR_BOX, boxA, "Scissor box - GL_SCISSOR_BIT member, must restore");
verifyIsEnabled(GL11.GL_SCISSOR_TEST, false, "Scissor - GL_SCISSOR_BIT member, must restore");
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.enableScissorTest();
GLStateManager.glScissor(boxB[0], boxB[1], boxB[2], boxB[3]);
GLStateManager.glPopAttrib();
verifyState(GL11.GL_SCISSOR_BOX, boxB, "Scissor box - Not a GL_ENABLE_BIT member, must stay changed");
verifyIsEnabled(GL11.GL_SCISSOR_TEST, false, "Scissor - GL_ENABLE_BIT member, must restore");
} finally {
GLStateManager.disableScissorTest();
GLStateManager.glScissor(0, 0, 800, 600);
}
}
@Test
void rasterizerDiscardIsNotAnEnableBitMember() {
try {
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, false, "RasterizerDiscard - Initial");
GLStateManager.glEnable(GL30.GL_RASTERIZER_DISCARD);
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, true, "RasterizerDiscard - After enable");
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
GLStateManager.glDisable(GL30.GL_RASTERIZER_DISCARD);
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, false, "RasterizerDiscard - After disable inside GL_ENABLE_BIT");
GLStateManager.glPopAttrib();
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, false, "RasterizerDiscard - Not a GL_ENABLE_BIT member, must stay disabled");
} finally {
GLStateManager.glDisable(GL30.GL_RASTERIZER_DISCARD);
}
}
@Test
void clearDepthIsClamped() {
try {
GLStateManager.glClearDepth(2.0);
verifyState(GL11.GL_DEPTH_CLEAR_VALUE, 1.0f, "Depth clear value - glClearDepth(2.0) must clamp to 1.0");
} finally {
GLStateManager.glClearDepth(1.0);
}
}
@Test
void colorBufferBitPopDoesNotRestoreClipPlaneButMaskStaysFaithful() {
try {
@@ -15,6 +15,7 @@ import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@@ -37,7 +38,10 @@ public class GLSM_ServerSideState_UnitTest {
private Runnable pendingCommand;
private Throwable backgroundError;
private volatile boolean shutdownRequested;
private volatile GLContextState workerContext;
private volatile boolean workerCaching;
private boolean originalSplashComplete;
private Thread originalDrawableGLHolder;
private static void setSplashComplete(boolean value) {
SplashWindow.setSplashComplete(value);
@@ -50,6 +54,7 @@ public class GLSM_ServerSideState_UnitTest {
@BeforeEach
void setupSharedDrawable() throws Exception {
originalSplashComplete = getSplashComplete();
originalDrawableGLHolder = GLStateManager.getDrawableGLHolder();
// Reset splash state to simulate splash screen scenario
setSplashComplete(false);
@@ -67,6 +72,8 @@ public class GLSM_ServerSideState_UnitTest {
shutdownRequested = false;
backgroundError = null;
workerContext = null;
workerCaching = false;
initLatch = new CountDownLatch(1);
commandLatch = new CountDownLatch(1);
completeLatch = new CountDownLatch(1);
@@ -75,10 +82,8 @@ public class GLSM_ServerSideState_UnitTest {
backgroundThread = new Thread(() -> {
try {
GLStateManager.makeCurrent(sharedDrawable);
if (GLStateManager.isCachingEnabled()) {
throw new AssertionError("SharedDrawable thread should have caching DISABLED");
}
workerContext = GLStateManager.ctx();
workerCaching = GLStateManager.isCachingEnabled();
// Signal that initialization is complete
initLatch.countDown();
@@ -102,11 +107,12 @@ public class GLSM_ServerSideState_UnitTest {
}
} catch (InterruptedException e) {
// Normal shutdown
} catch (LWJGLException e) {
backgroundError = e;
} catch (Throwable t) {
backgroundError = t;
initLatch.countDown();
} finally {
try {
sharedDrawable.releaseContext();
GLStateManager.releaseContext(sharedDrawable);
} catch (LWJGLException e) {
// Ignore on cleanup
}
@@ -116,6 +122,11 @@ public class GLSM_ServerSideState_UnitTest {
// Wait for background thread to complete initialization
assertTrue(initLatch.await(5, TimeUnit.SECONDS), "Background thread failed to initialize");
if (backgroundError != null) {
fail("Background thread failed to make SharedDrawable current", backgroundError);
}
assertTrue(workerCaching, "SharedDrawable thread should cache into its own context");
assertNotSame(GLStateManager.ctx(), workerContext, "SharedDrawable thread must not share the main thread's context");
}
@AfterEach
@@ -132,11 +143,12 @@ public class GLSM_ServerSideState_UnitTest {
}
if (sharedDrawable != null) {
sharedDrawable.destroy();
DrawableContexts.destroy(sharedDrawable);
}
// Restore original state
GLStateManager.setDrawableGL(null);
GLStateManager.setDrawableGLHolder(originalDrawableGLHolder);
setSplashComplete(originalSplashComplete);
if (originalSplashComplete) {
GLStateManager.markSplashComplete("test");
@@ -328,7 +340,11 @@ public class GLSM_ServerSideState_UnitTest {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId1);
assertEquals(texId1, GLStateManager.getBoundTextureForServerState(), "Main thread cache should show texId1 bound");
executeOnSharedDrawable(() -> GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId2));
executeOnSharedDrawable(() -> {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId2);
assertEquals(texId2, GLStateManager.getBoundTextureForServerState(), "SharedDrawable cache should show texId2 bound");
assertEquals(texId2, GL11.glGetInteger(GL11.GL_TEXTURE_BINDING_2D), "SharedDrawable actual GL binding should be texId2");
});
assertEquals(texId1, GLStateManager.getBoundTextureForServerState(), "Main thread cache should still show texId1 (client-side state is per-context)");
@@ -436,38 +452,40 @@ public class GLSM_ServerSideState_UnitTest {
}
}
/**
* Test glDeleteTextures: SharedDrawable deletes texture, verify cache cleared.
*/
@Test
void testDeleteTexturesFromSharedDrawable() throws InterruptedException {
int texId = GL11.glGenTextures();
final int texId = GLStateManager.glGenTextures();
int flushGen = 0;
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, 32, 32, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
int cachedFilter = GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER);
assertEquals(GL11.GL_LINEAR, cachedFilter, "Cache should have LINEAR before delete");
assertEquals(GL11.GL_LINEAR, GLStateManager.glGetTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER), "Cache should have LINEAR before delete");
assertTrue(GL11.glIsTexture(texId), "Texture should exist before delete");
// SharedDrawable: delete the texture (deferred — name stays valid)
executeOnSharedDrawable(() -> {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
GLStateManager.glDeleteTextures(texId);
assertEquals(0, GLStateManager.getBoundTextureForServerState(), "Delete should unbind from the deleting context");
assertTrue(GL11.glIsTexture(texId), "Texture name should still be valid (deferred delete)");
});
assertTrue(GL11.glIsTexture(texId), "Texture name should still be valid on main thread (deferred delete)");
assertEquals(texId, GLStateManager.getBoundTextureForServerState(), "Main thread binding should survive until the flush");
// glGenTextures flushes deferred deletes
int newTexId = GLStateManager.glGenTextures();
assertFalse(GL11.glIsTexture(texId), "Texture should be gone after flush via glGenTextures");
GL11.glDeleteTextures(newTexId);
final int deferredGen = GLStateManager.glGenTextures();
GL11.glDeleteTextures(deferredGen);
assertTrue(GL11.glIsTexture(texId), "Delete should stay deferred while the splash is not complete");
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");
} finally {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
if (flushGen != 0) GL11.glDeleteTextures(flushGen);
if (GL11.glIsTexture(texId)) {
GL11.glDeleteTextures(texId);
}
@@ -0,0 +1,302 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.FfpFixture;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.SharedDrawable;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
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.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@GLCoreTest
public class GLSM_SharedDrawableContext_GLTest {
private SharedDrawable sharedDrawable;
@AfterEach
void cleanup() {
if (sharedDrawable != null) {
DrawableContexts.destroy(sharedDrawable);
sharedDrawable = null;
}
}
@Test
void sharedDrawableBindsItsOwnContext() throws Exception {
final GLContextState primary = GLStateManager.ctx();
sharedDrawable = new SharedDrawable(Display.getDrawable());
TestThreads.run("SharedDrawableContext-Bind-Thread", () -> {
GLStateManager.makeCurrent(sharedDrawable);
try {
final GLContextState workerCtx = GLStateManager.ctx();
assertNotSame(primary, workerCtx);
assertNotEquals(0, workerCtx.defaultVAO);
assertEquals(workerCtx.defaultVAO, GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING));
final IntBuffer viewport = BufferUtils.createIntBuffer(16);
GL11.glGetInteger(GL11.GL_VIEWPORT, viewport);
assertEquals(0, viewport.get(0));
assertEquals(0, viewport.get(1));
assertEquals(Display.getWidth(), viewport.get(2));
assertEquals(Display.getHeight(), viewport.get(3));
} finally {
GLStateManager.releaseContext(sharedDrawable);
}
assertSame(primary, GLStateManager.ctx());
});
}
@Test
void failedContextSwitchRestoresPreviousBinding() throws Exception {
final GLContextState primary = GLStateManager.ctx();
sharedDrawable = new SharedDrawable(Display.getDrawable());
final CountDownLatch acquired = new CountDownLatch(1);
final CountDownLatch release = new CountDownLatch(1);
final TestThreads.Worker worker = DrawableContexts.start(sharedDrawable, "SharedDrawableContext-Owner-Thread", () -> {
acquired.countDown();
if (!release.await(5, TimeUnit.SECONDS)) throw new AssertionError("release timed out");
});
try {
assertTrue(acquired.await(5, TimeUnit.SECONDS));
assertThrows(IllegalStateException.class, () -> GLStateManager.makeCurrent(sharedDrawable));
assertSame(primary, GLStateManager.ctx());
assertEquals(primary.defaultVAO, GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING));
} finally {
release.countDown();
worker.join();
}
GLStateManager.makeCurrent(sharedDrawable);
assertNotSame(primary, GLStateManager.ctx());
GLStateManager.releaseContext(sharedDrawable);
GLStateManager.makeCurrent(Display.getDrawable());
assertSame(primary, GLStateManager.ctx());
}
@Test
void foreignThreadCannotFinishOrRecordIntoActiveList() throws Exception {
sharedDrawable = new SharedDrawable(Display.getDrawable());
final int existing = GLStateManager.glGenLists(1);
final int active = GLStateManager.glGenLists(1);
final int rejected = GLStateManager.glGenLists(1);
GLStateManager.glNewList(existing, GL11.GL_COMPILE);
GLStateManager.enableBlend();
GLStateManager.glEndList();
GLStateManager.disableBlend();
final CountDownLatch started = new CountDownLatch(1);
final CountDownLatch finish = new CountDownLatch(1);
final TestThreads.Worker worker = DrawableContexts.start(sharedDrawable, "SharedDrawableContext-List-Thread", () -> {
GLStateManager.glNewList(active, GL11.GL_COMPILE);
started.countDown();
if (!finish.await(5, TimeUnit.SECONDS)) throw new AssertionError("finish timed out");
GLStateManager.glEndList();
});
try {
try {
assertTrue(started.await(5, TimeUnit.SECONDS));
assertThrows(IllegalStateException.class, () -> GLStateManager.glNewList(rejected, GL11.GL_COMPILE));
assertThrows(IllegalStateException.class, GLStateManager::glEndList);
assertEquals(0, GLStateManager.getListMode());
DisplayListManager.abortIfLeaked();
assertThrows(IllegalStateException.class, DisplayListManager::abortCompilation);
GLStateManager.glCallList(existing);
assertTrue(GLStateManager.glIsEnabled(GL11.GL_BLEND));
assertEquals(0, GLStateManager.getListMode());
} finally {
finish.countDown();
worker.join();
}
assertTrue(DisplayListManager.displayListExists(active));
GLStateManager.glCallList(active);
} finally {
GLStateManager.glDeleteLists(existing, 1);
GLStateManager.glDeleteLists(active, 1);
GLStateManager.glDeleteLists(rejected, 1);
GLStateManager.disableBlend();
}
}
@Test
void simultaneousListStartsClaimOneOwner() throws Exception {
sharedDrawable = new SharedDrawable(Display.getDrawable());
final int first = GLStateManager.glGenLists(1);
final int second = GLStateManager.glGenLists(1);
final CyclicBarrier start = new CyclicBarrier(2);
final CountDownLatch attempted = new CountDownLatch(2);
final AtomicInteger completed = new AtomicInteger();
final AtomicInteger rejected = new AtomicInteger();
final TestThreads.Worker worker = DrawableContexts.start(sharedDrawable, "SharedDrawableContext-List-Race-Thread", () -> {
start.await();
try {
GLStateManager.glNewList(second, GL11.GL_COMPILE);
} catch (IllegalStateException expected) {
rejected.incrementAndGet();
attempted.countDown();
return;
}
attempted.countDown();
if (!attempted.await(5, TimeUnit.SECONDS)) throw new AssertionError("attempts timed out");
GLStateManager.glEndList();
completed.incrementAndGet();
});
try {
start.await();
try {
GLStateManager.glNewList(first, GL11.GL_COMPILE);
} catch (IllegalStateException expected) {
rejected.incrementAndGet();
attempted.countDown();
}
if (GLStateManager.getListMode() != 0) {
attempted.countDown();
assertTrue(attempted.await(5, TimeUnit.SECONDS));
GLStateManager.glEndList();
completed.incrementAndGet();
}
worker.join();
} finally {
GLStateManager.glDeleteLists(first, 1);
GLStateManager.glDeleteLists(second, 1);
}
assertEquals(1, completed.get());
assertEquals(1, rejected.get());
}
@Test
void reenteredContextRebindsFfpProgram() throws Exception {
sharedDrawable = new SharedDrawable(Display.getDrawable());
DrawableContexts.run(sharedDrawable, "SharedDrawableContext-Reentry-Thread", () -> {
final ShaderManager sm = ShaderManager.getInstance();
final boolean wasEnabled = ShaderManager.isEnabled();
ShaderManager.enable();
sm.activate();
try {
final FloatBuffer vertices = BufferUtils.createFloatBuffer(9);
vertices.put(new float[] { -0.5f, -0.5f, 0f, 0.5f, -0.5f, 0f, 0f, 0.5f, 0f });
vertices.flip();
final int vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, vertices, GL15.GL_STATIC_DRAW);
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, 12, 0);
VAOManager.setCurrentVertexFlags(0);
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
GLStateManager.releaseContext(sharedDrawable);
GLStateManager.makeCurrent(sharedDrawable);
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
assertNotEquals(0, GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM), "invalidateProgram must force a rebind after replay clears the program to 0");
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glDeleteBuffers(vbo);
} finally {
sm.deactivate();
if (!wasEnabled) ShaderManager.disable();
}
});
}
@Test
void deferredTextureDeletesWaitForSplashAndScrubDrawableContexts() throws Exception {
sharedDrawable = new SharedDrawable(Display.getDrawable());
final int t = GLStateManager.glGenTextures();
final AtomicReference<GLContextState> workerCtxRef = new AtomicReference<>();
DrawableContexts.run(sharedDrawable, "SharedDrawableContext-DeferredDelete-Thread", () -> {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, t);
workerCtxRef.set(GLStateManager.ctx());
});
final GLContextState workerCtx = workerCtxRef.get();
int deferredGen = 0;
int flushGen = 0;
try {
SplashWindow.setSplashComplete(false);
GLStateManager.glDeleteTextures(t);
deferredGen = GLStateManager.glGenTextures();
assertTrue(GL11.glIsTexture(t), "delete must stay deferred while the splash is not complete");
assertEquals(t, workerCtx.textures.getTextureUnitBindings(0).getBinding(), "worker ctx binding must survive until the flush");
SplashWindow.setSplashComplete(true);
flushGen = GLStateManager.glGenTextures();
assertFalse(GL11.glIsTexture(t), "the flush must delete the texture once the splash is complete");
assertEquals(0, workerCtx.textures.getTextureUnitBindings(0).getBinding(), "the flush must scrub the drawable context's binding");
} finally {
SplashWindow.setSplashComplete(true);
if (deferredGen != 0) GL11.glDeleteTextures(deferredGen);
if (flushGen != 0) GL11.glDeleteTextures(flushGen);
}
}
@Test
void concurrentContextsDoNotShareAttribOrCurrentState() throws Exception {
final GLContextState primary = GLStateManager.ctx();
sharedDrawable = new SharedDrawable(Display.getDrawable());
final CyclicBarrier barrier = new CyclicBarrier(2);
final AtomicReference<GLContextState> workerCtxRef = new AtomicReference<>();
final TestThreads.Worker worker = DrawableContexts.start(sharedDrawable, "SharedDrawableContext-Concurrent-Thread", () -> {
workerCtxRef.set(GLStateManager.ctx());
barrier.await();
for (int i = 0; i < 20000; i++) {
final int t = GLStateManager.pushState(StateSet.FONT);
GLStateManager.glNormal3f(0.1f, 0.2f, 0.3f);
GLStateManager.popStateTo(t);
}
GLStateManager.glNormal3f(0.1f, 0.2f, 0.3f);
});
try {
barrier.await();
for (int i = 0; i < 20000; i++) {
final int t = GLStateManager.pushState(StateSet.FONT);
GLStateManager.glNormal3f(0.25f, 0.5f, 0.75f);
GLStateManager.popStateTo(t);
}
GLStateManager.glNormal3f(0.25f, 0.5f, 0.75f);
worker.join();
final GLContextState workerCtx = workerCtxRef.get();
assertNotSame(primary, workerCtx);
assertEquals(0, primary.attribDepth);
assertEquals(0, workerCtx.attribDepth);
assertEquals(0.25f, primary.ffp.currentNormal.x, 0.0f);
assertEquals(0.5f, primary.ffp.currentNormal.y, 0.0f);
assertEquals(0.75f, primary.ffp.currentNormal.z, 0.0f);
assertEquals(0.1f, workerCtx.ffp.currentNormal.x, 0.0f);
assertEquals(0.2f, workerCtx.ffp.currentNormal.y, 0.0f);
assertEquals(0.3f, workerCtx.ffp.currentNormal.z, 0.0f);
} finally {
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
}
}
}
@@ -1,9 +1,14 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.Display;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -63,4 +68,37 @@ public class GLSM_SplashCompleteLatch_UnitTest {
t.join();
assertTrue(cachingOffThread.get(), "after the latch caching is global - this is what the latch buys");
}
@Test
void pausedSplashThreadDoesNotCompleteUntilItExits() throws Exception {
final CountDownLatch released = new CountDownLatch(1);
final CountDownLatch resume = new CountDownLatch(1);
GLStateManager.releaseContext(Display.getDrawable());
try {
final TestThreads.Worker worker = TestThreads.start("SplashPause-Worker-Thread", () -> {
GLStateManager.makeCurrent(Display.getDrawable());
GLStateManager.releaseContext(Display.getDrawable());
released.countDown();
resume.await();
});
assertTrue(released.await(10, TimeUnit.SECONDS), "worker did not release the Display drawable in time");
GLStateManager.makeCurrent(Display.getDrawable());
assertFalse(GLStateManager.isSplashComplete());
resume.countDown();
worker.join();
GLStateManager.releaseContext(Display.getDrawable());
GLStateManager.makeCurrent(Display.getDrawable());
assertTrue(GLStateManager.isSplashComplete());
} finally {
resume.countDown();
Reflect.setStatic(GLStateManager.class, "stateSeedPending", false);
Reflect.setStatic(GLStateManager.class, "splashDisplayReleaser", null);
GLStateManager.makeCurrent(Display.getDrawable());
}
}
}
@@ -706,4 +706,48 @@ public class GLSM_StateSet_GLTest {
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
}
}
@Test
void batchAppliesPassLightingAndRestoresLiveLighting() {
final boolean lightmapWas = GLStateManager.getTextures().getTextureUnitStates(1).isEnabled();
final LightingSnapshot pass = new LightingSnapshot();
try {
GLStateManager.glEnable(GL11.GL_LIGHT0);
GLStateManager.glEnable(GL11.GL_LIGHT1);
GLStateManager.enableColorMaterial();
GLStateManager.glLightf(GL11.GL_LIGHT0, GL11.GL_CONSTANT_ATTENUATION, 2.0f);
GLStateManager.captureLighting(pass);
GLStateManager.glDisable(GL11.GL_LIGHT0);
GLStateManager.glDisable(GL11.GL_LIGHT1);
GLStateManager.disableColorMaterial();
GLStateManager.glLightf(GL11.GL_LIGHT0, GL11.GL_CONSTANT_ATTENUATION, 1.0f);
GLStateManager.getTextures().getTextureUnitStates(1).setEnabled(false);
final int liveGeneration = GLStateManager.getLightingGeneration();
final int d = GLStateManager.pushState(StateSet.BATCH);
GLStateManager.applyLighting(pass);
GLStateManager.getTextures().getTextureUnitStates(1).setEnabled(true);
assertTrue(GLStateManager.getLightStates()[0].isEnabled(), "light 0 from the pass");
assertTrue(GLStateManager.getLightStates()[1].isEnabled(), "light 1 from the pass");
assertTrue(GLStateManager.getColorMaterial().isEnabled(), "color material from the pass");
assertEquals(2.0f, GLStateManager.getLightDataStates()[0].constantAttenuation, "light 0 data from the pass");
assertTrue(GLStateManager.getLightingGeneration() != liveGeneration, "lighting uniforms marked dirty");
final int passGeneration = GLStateManager.getLightingGeneration();
GLStateManager.popStateTo(d);
assertFalse(GLStateManager.getLightStates()[0].isEnabled(), "light 0 restored by BATCH pop");
assertFalse(GLStateManager.getLightStates()[1].isEnabled(), "light 1 restored by BATCH pop");
assertFalse(GLStateManager.getColorMaterial().isEnabled(), "color material restored by BATCH pop");
assertEquals(1.0f, GLStateManager.getLightDataStates()[0].constantAttenuation, "light 0 data restored by BATCH pop");
assertFalse(GLStateManager.getTextures().getTextureUnitStates(1).isEnabled(), "lightmap enable restored by BATCH pop");
assertTrue(GLStateManager.getLightingGeneration() != passGeneration, "lighting uniforms marked dirty after pop");
} finally {
GLStateManager.glDisable(GL11.GL_LIGHT0);
GLStateManager.glDisable(GL11.GL_LIGHT1);
GLStateManager.disableColorMaterial();
GLStateManager.glLightf(GL11.GL_LIGHT0, GL11.GL_CONSTANT_ATTENUATION, 1.0f);
GLStateManager.getTextures().getTextureUnitStates(1).setEnabled(lightmapWas);
}
}
}
@@ -17,7 +17,7 @@ public class GLSM_VAO_UnitTest {
@Test
void testDefaultStates() {
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, GLStateManager.getDefaultVAO(), "GL_VERTEX_ARRAY_BINDING Initial State");
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, GLStateManager.ctx().defaultVAO, "GL_VERTEX_ARRAY_BINDING Initial State");
verifyState(GL13.GL_CLIENT_ACTIVE_TEXTURE, GL13.GL_TEXTURE0, "GL_CLIENT_ACTIVE_TEXTURE Initial State");
testClientStates();
}
@@ -84,7 +84,7 @@ public class GLSM_VAO_UnitTest {
// Check if everything got reset to default
glBindVertexArray(0);
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, GLStateManager.getDefaultVAO(), "GL_VERTEX_ARRAY_BINDING - Unbound");
verifyState(GL30.GL_VERTEX_ARRAY_BINDING, GLStateManager.ctx().defaultVAO, "GL_VERTEX_ARRAY_BINDING - Unbound");
verifyState(GL15.GL_ARRAY_BUFFER_BINDING, 0, "GL_ARRAY_BUFFER_BINDING - Unbound");
verifyState(GL11.GL_VERTEX_ARRAY, defaultVertexEnabled, "GL_VERTEX_ARRAY - Reset");
@@ -192,9 +192,10 @@ public class GLSM_VAO_UnitTest {
}
private static void glBindVertexArray(int array) {
if (array == 0) array = GLStateManager.getDefaultVAO();
GLStateManager.ctx().boundVAO = array;
VAOManager.onBindVertexArrayPre(array);
final GLContextState glCtx = GLStateManager.ctx();
if (array == 0) array = glCtx.defaultVAO;
glCtx.boundVAO = array;
glCtx.vaos.onBindVertexArrayPre(array);
UniversalVAO.bindVertexArray(array);
}
}
@@ -45,7 +45,7 @@ public class GLSM_ValueStackCow_GLTest {
"glAlphaFunc", "glBlendFunc", "tryBlendFuncSeparate", "glBlendEquation", "glBlendEquationSeparate",
"glBlendColor", "glColorMask", "glClearColor", "glDrawBuffer", "glLogicOp",
"glDepthFunc", "glDepthMask", "glClearDepth", "glStencilFunc", "glStencilOp",
"glStencilMask", "glClearStencil", "glViewport", "glDepthRange", "glLineWidth",
"glStencilMask", "glClearStencil", "glViewport", "glScissor", "glDepthRange", "glLineWidth",
"glLineStipple", "glPointSize", "glPolygonMode", "glCullFace", "glFrontFace",
"glPolygonOffset", "fogColor", "glFogf", "glFogi", "glLightf",
"glLighti", "glLight", "glLightModelf", "glLightModeli", "glLightModel",
@@ -74,6 +74,7 @@ public class GLSM_ValueStackCow_GLTest {
() -> GLStateManager.glStencilMask(0x0F),
() -> GLStateManager.glClearStencil(9),
() -> GLStateManager.glViewport(1, 2, 3, 4),
() -> GLStateManager.glScissor(1, 2, 3, 4),
() -> GLStateManager.glDepthRange(0.2, 0.8),
() -> GLStateManager.glLineWidth(3.0f),
() -> GLStateManager.glLineStipple(3, (short) 0x00FF),
@@ -0,0 +1,72 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import static org.junit.jupiter.api.Assertions.assertEquals;
@GLCoreTest
class ReadbackGLTest {
@AfterEach
void noErrors() {
ReadbackFixture.resetPack();
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void rgba8() {
ReadbackFixture.rgba8();
}
@Test
void packState() {
ReadbackFixture.packState();
}
@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();
}
}
@@ -28,7 +28,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlColor4fSetsVertexAttrib() {
GLStateManager.glColor4f(0.5f, 0.25f, 0.75f, 0.8f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.5f, color[0], EPSILON, "color.r");
@@ -40,7 +40,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlNormal3fSetsVertexAttrib() {
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] normal = getVertexAttrib4f(NORMAL_ATTRIB);
assertEquals(0.0f, normal[0], EPSILON, "normal.x");
@@ -51,7 +51,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testSetLightmapTextureCoordsOnUnit1SetsVertexAttrib() {
GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, 240.0f, 128.0f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] lm = getVertexAttrib4f(SECONDARY_UV_ATTRIB);
assertEquals(240.0f, lm[0], EPSILON, "lightmap.s");
@@ -64,7 +64,7 @@ class VertexAttribDefaultBridgeTest {
void testGlTexCoord2fSetsVertexAttrib() {
// glTexCoord2f outside recording → sets default vertex attrib 2 (PRIMARY_UV)
GLStateManager.glTexCoord2f(0.5f, 0.75f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] uv = getVertexAttrib4f(PRIMARY_UV_ATTRIB);
assertEquals(0.5f, uv[0], EPSILON, "texcoord.s");
@@ -77,7 +77,7 @@ class VertexAttribDefaultBridgeTest {
// Second call is a cache hit — changeColor not called.
// Vertex attrib should still hold the correct value from the first call.
GLStateManager.glColor4f(0.3f, 0.6f, 0.9f, 1.0f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.3f, color[0], EPSILON, "color.r after dedup");
@@ -89,7 +89,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testCompileMode_DoesNotSetVertexAttrib() {
GLStateManager.glColor4f(0.1f, 0.2f, 0.3f, 1.0f);
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
GLStateManager.flushDeferredVertexAttribs(GLStateManager.ctx(), false, false, false, false);
int listId = GLStateManager.glGenLists(1);
GLStateManager.glNewList(listId, GL11.GL_COMPILE);
@@ -245,7 +245,7 @@ class FFPCombineShaderTest {
@Test
void testMultiTexCoordRoutesToShaderManager() {
// Not in immediate mode or display list recording, so should go to ShaderManager.currentTexCoord
ShaderManager.setCurrentTexCoord(0.0f, 0.0f, 0.0f, 1.0f); // reset
GLStateManager.ctx().ffp.setTexCoord(0.0f, 0.0f, 0.0f, 1.0f); // reset
GLStateManager.glMultiTexCoord2f(GL13.GL_TEXTURE0, 0.5f, 0.75f);
var texCoord = ShaderManager.getCurrentTexCoord();
@@ -255,7 +255,7 @@ class FFPCombineShaderTest {
@Test
void testMultiTexCoord2dDelegatesToFloat() {
ShaderManager.setCurrentTexCoord(0.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.ctx().ffp.setTexCoord(0.0f, 0.0f, 0.0f, 1.0f);
GLStateManager.glMultiTexCoord2d(GL13.GL_TEXTURE0, 0.5, 0.75);
var texCoord = ShaderManager.getCurrentTexCoord();
@@ -69,7 +69,7 @@ class FFPVertexLightingGLTest {
GLStateManager.glDisable(GL11.GL_COLOR_MATERIAL);
final ShaderManager sm = ShaderManager.getInstance();
if (sm.isActive()) sm.deactivate();
sm.disable();
ShaderManager.disable();
GLStateManager.glBindVertexArray(0);
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
@@ -148,7 +148,7 @@ class FFPVertexLightingGLTest {
private float[] drawAndReadCenter() {
buildTriangle();
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestFactory;
@@ -50,8 +49,8 @@ class FfpUniformBlockDedupTest {
GLStateManager.glAlphaFunc(GL11.GL_ALWAYS, 0.0f);
GLStateManager.glNormal3f(0.0f, 0.0f, 1.0f);
GLStateManager.glTexCoord2f(0.0f, 0.0f);
GLSMConfig.lastBrightnessX = 0.0f;
GLSMConfig.lastBrightnessY = 0.0f;
GLStateManager.ctx().lastBrightnessX = 0.0f;
GLStateManager.ctx().lastBrightnessY = 0.0f;
GLStateManager.glFog(GL11.GL_FOG_COLOR, floats(0.0f, 0.0f, 0.0f, 1.0f));
GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 1.0f);
GLStateManager.glTexEnv(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_COLOR, floats(0.0f, 0.0f, 0.0f, 0.0f));
@@ -128,7 +127,7 @@ class FfpUniformBlockDedupTest {
addCategory(tests, "currentColor", () -> GLStateManager.glColor4f(0.25f, 0.5f, 0.75f, 1.0f));
addCategory(tests, "currentNormal", () -> GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f));
addCategory(tests, "currentTexCoord", () -> GLStateManager.glTexCoord2f(0.125f, 0.375f));
addCategory(tests, "lightmapCoord", () -> GLSMConfig.lastBrightnessX = 96.0f);
addCategory(tests, "lightmapCoord", () -> GLStateManager.ctx().lastBrightnessX = 96.0f);
addCategory(tests, "texGenPlane", () -> GLStateManager.glTexGen(GL11.GL_S, GL11.GL_OBJECT_PLANE, floats(0.5f, 0.0f, 0.0f, 0.25f)));
addCategory(tests, "clipPlane", () -> GLStateManager.glClipPlane(GL11.GL_CLIP_PLANE0, doubles(0.0, 1.0, 0.0, 7.0)));
addCategory(tests, "alphaRef", () -> GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.35f));
@@ -147,7 +147,7 @@ class InstancedFFPDrawGLTest {
CubeParityFixture.setupScene(lightmap, lit);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
final CubeParityFixture.Spec[] specs = CubeParityFixture.specs();
@@ -172,7 +172,7 @@ class InstancedFFPDrawGLTest {
ParticleParityFixture.setupScene(true);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
final ParticleParityFixture.Particle[] particles = ParticleParityFixture.particles();
@@ -206,7 +206,7 @@ class InstancedFFPDrawGLTest {
assertPixel(51, 50, 255, 0, 0, "entity -1 must render red");
assertPixel(150, 50, 0, 255, 0, "entity 7 must render green");
assertPixel(249, 50, 0, 0, 255, "entity 42 must render blue");
assertPixel(249, 50, 0, 0, 255, "entity 50020 must render blue");
}
private static void assertPixel(int x, int y, int r, int g, int b, String label) {
@@ -222,9 +222,9 @@ class InstancedFFPDrawGLTest {
@Test
void instancedSourceUsesInstanceAttribs() {
GLStateManager.ffpInstancing = Instancing.TEMPLATE;
GLStateManager.setFfpInstancing(Instancing.TEMPLATE);
final VertexKey key = VertexKey.fromState(true, true, false, false, 0b10);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
final String source = VertexShaderGenerator.generate(key);
assertTrue(source.contains("a_InstRow0"), "instance matrix attribs declared");
@@ -248,7 +248,7 @@ class InstancedFFPDrawGLTest {
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
@@ -278,7 +278,7 @@ class InstancedFFPDrawGLTest {
uploadInstances(0xFF0000FF, 0, 0x0D0000FF, 0);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
GLStateManager.enableAlphaTest();
GLStateManager.glAlphaFunc(GL11.GL_GREATER, 0.1f);
@@ -311,7 +311,7 @@ class InstancedFFPDrawGLTest {
uploadInstances(0xFF0000FF, 0, 0xFF00FF00, 0);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
@@ -351,7 +351,7 @@ class InstancedFFPDrawGLTest {
uploadInstances(0xFFFFFFFF, 0, 0xFFFFFFFF, 0xFF0000FF);
final ShaderManager sm = ShaderManager.getInstance();
sm.enable();
ShaderManager.enable();
sm.activate();
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
@@ -430,8 +430,8 @@ class InstancedFFPDrawGLTest {
}
private static void drawInstances() {
GLStateManager.ffpInstancing = Instancing.TEMPLATE;
GLStateManager.setFfpInstancing(Instancing.TEMPLATE);
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 2);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
}
}
@@ -0,0 +1,71 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
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.lwjgl.opengl.GL11;
import static org.junit.jupiter.api.Assertions.assertEquals;
@GLCoreTest
class LogicOpGLTest {
private static final int SIZE = 8;
@BeforeEach
void setUp() {
FfpFixture.resetFfpState();
ShaderManager.enable();
ShaderManager.getInstance().activate();
GLStateManager.glViewport(0, 0, SIZE, SIZE);
GLStateManager.glMatrixMode(GL11.GL_PROJECTION);
GLStateManager.glLoadIdentity();
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glLoadIdentity();
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
GLStateManager.glDisable(GL11.GL_BLEND);
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
}
@AfterEach
void tearDown() {
GLStateManager.glDisable(GL11.GL_COLOR_LOGIC_OP);
GLStateManager.glLogicOp(GL11.GL_COPY);
GLStateManager.glDisable(GL11.GL_BLEND);
LogicOpFixture.deleteResources();
FfpFixture.resetFfpState();
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
}
@Test
void everyOpMatchesTheBitwiseReference() {
for (int op : LogicOpFixture.OPS) {
for (int[] src : LogicOpFixture.SOURCES) {
LogicOpFixture.clearToDst();
LogicOpFixture.drawQuad(op, src);
assertRgb(op, src);
}
}
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError());
}
@Test
void blendingIsIgnoredWhileLogicOpIsEnabled() {
GLStateManager.glEnable(GL11.GL_BLEND);
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
final int[] src = LogicOpFixture.SOURCES[1];
LogicOpFixture.clearToDst();
LogicOpFixture.drawQuad(GL11.GL_XOR, src);
assertRgb(GL11.GL_XOR, src);
}
private static void assertRgb(int op, int[] src) {
final int[] px = FfpFixture.readPixel(SIZE / 2, SIZE / 2);
final String label = LogicOpFixture.label(op, src);
assertEquals(LogicOpFixture.expected(op, src[0], LogicOpFixture.DST_R), px[0], () -> label + " red");
assertEquals(LogicOpFixture.expected(op, src[1], LogicOpFixture.DST_G), px[1], () -> label + " green");
assertEquals(LogicOpFixture.expected(op, src[2], LogicOpFixture.DST_B), px[2], () -> label + " blue");
}
}
@@ -17,6 +17,7 @@ public class VAOManagerEboIndexTest {
private static final int DEFAULT_VAO = 0;
private static final int NO_VAO = -1;
private final VAOManager vaos = new VAOManager();
private final Int2IntOpenHashMap expectedEbo = new Int2IntOpenHashMap();
private int currentVao;
@@ -24,7 +25,7 @@ public class VAOManagerEboIndexTest {
void reset() {
drainManagerState();
expectedEbo.clear();
VAOManager.init(DEFAULT_VAO);
vaos.init(DEFAULT_VAO);
expectedEbo.put(DEFAULT_VAO, 0);
currentVao = DEFAULT_VAO;
}
@@ -34,23 +35,23 @@ public class VAOManagerEboIndexTest {
drainManagerState();
}
private static void drainManagerState() {
for (int vao : VAOManager.vaoMap.keySet().toIntArray()) {
VAOManager.onDeleteVertexArray(vao);
private void drainManagerState() {
for (int vao : vaos.vaoMap.keySet().toIntArray()) {
vaos.onDeleteVertexArray(vao);
}
VAOManager.boundEBO = 0;
assertEquals(0, VAOManager.eboOwners.size(), "draining every VAO left entries in the index");
vaos.boundEBO = 0;
assertEquals(0, vaos.eboOwners.size(), "draining every VAO left entries in the index");
}
private void bindVao(int vao) {
if (currentVao != NO_VAO) expectedEbo.put(currentVao, VAOManager.boundEBO);
VAOManager.onBindVertexArrayPre(vao);
if (currentVao != NO_VAO) expectedEbo.put(currentVao, vaos.boundEBO);
vaos.onBindVertexArrayPre(vao);
expectedEbo.putIfAbsent(vao, 0);
currentVao = vao;
}
private void bindEbo(int ebo) {
VAOManager.onBindEBO(ebo);
vaos.onBindEBO(ebo);
}
private int countOwners(int buffer) {
@@ -62,20 +63,20 @@ public class VAOManagerEboIndexTest {
}
private void deleteBuffer(int buffer) {
VAOManager.onDeleteBuffer(buffer);
vaos.onDeleteBuffer(buffer);
for (int vao : expectedEbo.keySet().toIntArray()) {
if (expectedEbo.get(vao) == buffer) expectedEbo.put(vao, 0);
}
}
private void deleteVao(int vao) {
VAOManager.onDeleteVertexArray(vao);
vaos.onDeleteVertexArray(vao);
expectedEbo.remove(vao);
if (vao == currentVao) currentVao = NO_VAO;
}
private void assertExpectedEbos(int op) {
for (var entry : VAOManager.vaoMap.int2ObjectEntrySet()) {
for (var entry : vaos.vaoMap.int2ObjectEntrySet()) {
final int vao = entry.getIntKey();
if (vao == currentVao) continue;
assertEquals(expectedEbo.get(vao), entry.getValue().ebo, () -> "op " + op + ": vao " + vao + " ebo mismatch");
@@ -140,10 +141,10 @@ public class VAOManagerEboIndexTest {
deleteBuffer(ebo);
assertEquals(0, VAOManager.vaoMap.get(1).ebo, "vao 1 kept a freed shared EBO");
assertEquals(0, VAOManager.vaoMap.get(2).ebo, "vao 2 kept a freed shared EBO");
assertEquals(0, VAOManager.vaoMap.get(3).ebo, "vao 3 kept a freed shared EBO");
assertFalse(VAOManager.eboOwners.containsKey(ebo), "index kept owners for a freed EBO");
assertEquals(0, vaos.vaoMap.get(1).ebo, "vao 1 kept a freed shared EBO");
assertEquals(0, vaos.vaoMap.get(2).ebo, "vao 2 kept a freed shared EBO");
assertEquals(0, vaos.vaoMap.get(3).ebo, "vao 3 kept a freed shared EBO");
assertFalse(vaos.eboOwners.containsKey(ebo), "index kept owners for a freed EBO");
}
@Test
@@ -156,9 +157,9 @@ public class VAOManagerEboIndexTest {
bindVao(DEFAULT_VAO);
deleteVao(1);
assertTrue(VAOManager.eboOwners.containsKey(ebo), "index dropped a still-live owner");
assertTrue(vaos.eboOwners.containsKey(ebo), "index dropped a still-live owner");
deleteVao(2);
assertFalse(VAOManager.eboOwners.containsKey(ebo), "index kept an entry after its last owner went away");
assertFalse(vaos.eboOwners.containsKey(ebo), "index kept an entry after its last owner went away");
}
@Test
@@ -170,8 +171,8 @@ public class VAOManagerEboIndexTest {
deleteVao(1);
bindVao(DEFAULT_VAO);
assertFalse(VAOManager.eboOwners.containsKey(ebo), "deleting the bound VAO left an unreachable owner in the index");
assertEquals(0, VAOManager.eboOwners.size(), "index leaked an entry for a deleted VAO");
assertFalse(vaos.eboOwners.containsKey(ebo), "deleting the bound VAO left an unreachable owner in the index");
assertEquals(0, vaos.eboOwners.size(), "index leaked an entry for a deleted VAO");
}
@Test
@@ -184,6 +185,6 @@ public class VAOManagerEboIndexTest {
for (int vao = 1; vao <= 64; vao++) {
deleteVao(vao);
}
assertEquals(0, VAOManager.eboOwners.size(), "index leaked entries after every VAO was deleted");
assertEquals(0, vaos.eboOwners.size(), "index leaked entries after every VAO was deleted");
}
}
@@ -0,0 +1,62 @@
package com.gtnewhorizons.angelica.glsm.profiling;
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.assertTrue;
class TracyOptionsTest {
@Test
void resolveBooleanFallsBackToConfigWhenNoOverride() {
assertTrue(TracyOptions.resolveBoolean(null, true));
assertFalse(TracyOptions.resolveBoolean(null, false));
}
@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));
}
@Test
void latchExposesConfigValuesThroughTheAccessors() {
TracyOptions.latch(true, true, true, 8192);
assertTrue(TracyOptions.configEnabled());
assertTrue(TracyOptions.configRemote());
assertTrue(TracyOptions.configFineZones());
assertEquals(8192, TracyOptions.configMaxSrcLocs());
TracyOptions.latch(false, false, false, 4096);
assertFalse(TracyOptions.configEnabled());
assertFalse(TracyOptions.configRemote());
assertFalse(TracyOptions.configFineZones());
assertEquals(4096, TracyOptions.configMaxSrcLocs());
}
@Test
void allowRemoteHasNoJvmOverrideAndTracksConfigDirectly() {
TracyOptions.latch(false, true, false, 4096);
assertTrue(TracyOptions.allowRemote());
TracyOptions.latch(false, false, false, 4096);
assertFalse(TracyOptions.allowRemote());
}
}
@@ -314,10 +314,11 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
final ByteBuffer colorSlice = clientData.duplicate().order(ByteOrder.nativeOrder());
colorSlice.position(8);
VAOManager.setAttribute(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, clientData);
VAOManager.enableAttribute(0);
VAOManager.setAttribute(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, colorSlice);
VAOManager.enableAttribute(1);
final VAOManager vaos = GLStateManager.ctx().vaos;
vaos.setAttribute(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, clientData);
vaos.enableAttribute(0);
vaos.setAttribute(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, colorSlice);
vaos.enableAttribute(1);
GLStateManager.glBindVertexArray(0);
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
@@ -593,8 +594,9 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
VAOManager.setAttribute(0, 3, GL11.GL_FLOAT, false, 0, clientVerts);
VAOManager.enableAttribute(0);
final VAOManager vaos = GLStateManager.ctx().vaos;
vaos.setAttribute(0, 3, GL11.GL_FLOAT, false, 0, clientVerts);
vaos.enableAttribute(0);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, indices);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
@@ -264,6 +264,15 @@ class GLSMRedirectorTest {
assertTrue(unmappedSeen().isEmpty());
}
@Test
void pixelPackBufferReadbackOverloadsHaveDeclaredTargets() throws Exception {
assertRedirectedToDeclaredTarget(new String[][]{
{"org/lwjgl/opengl/GL11", "glReadPixels", "(IIIIIIJ)V"},
{"org/lwjgl/opengl/GL11C", "glReadPixels", "(IIIIIIJ)V"},
{"org/lwjgl/opengl/GL11", "glGetTexImage", "(IIIIJ)V"},
});
}
@Test
void newlyMappedOcclusionQueryAliasIsRedirected() {
final ClassNode cn = makeClassWithMethodCall("org/lwjgl/opengl/ARBOcclusionQuery", "glGenQueriesARB", "(Ljava/nio/IntBuffer;)V");
@@ -42,7 +42,6 @@ public class GLSMCoreExtension implements BeforeAllCallback, BeforeEachCallback,
GLStateManager.initialize(GLSMInitConfig.builder()
.displaySize(800, 600)
.directDrawer(t -> {})
.streamingDrawerDestroy(() -> {})
.enableDSA(false)
.build());
GLStateManager.setRunningSplash(false);
@@ -70,7 +70,6 @@ public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, Aft
final GLSMInitConfig config = GLSMInitConfig.builder()
.displaySize(displayMode.getWidth(), displayMode.getHeight())
.directDrawer(t -> {})
.streamingDrawerDestroy(() -> {})
.enableDSA(false)
.build();
setMainThread(Thread.currentThread());
@@ -144,7 +143,7 @@ public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, Aft
if (ffp.isActive()) {
ffp.deactivate();
}
ffp.disable();
ShaderManager.disable();
// Force-sync VAO binding. Tests may unbind via raw GL, desynchronizing GLSM's cache.
// Invalidate the cache so glBindVertexArray(0) actually issues the GL call.
@@ -0,0 +1,275 @@
package com.gtnewhorizons.angelica.glsm;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public final class ReadbackFixture {
public static final int SIZE = 4;
private ReadbackFixture() {}
public static int colorFbo(int internalFormat, int format, int type) {
return colorFbo(internalFormat, format, type, null);
}
public static int colorFbo(int internalFormat, int format, int type, ByteBuffer data) {
final int tex = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, internalFormat, SIZE, SIZE, 0, format, type, data);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
final int fbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, tex, 0);
GLStateManager.glViewport(0, 0, SIZE, SIZE);
return fbo;
}
public static int depthStencilFbo() {
final int fbo = colorFbo(GL11.GL_RGBA8, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
final int rb = GLStateManager.glGenRenderbuffers();
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, rb);
GLStateManager.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_DEPTH24_STENCIL8, SIZE, SIZE);
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, rb);
return fbo;
}
public static void release(int fbo) {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glDeleteFramebuffers(fbo);
resetPack();
}
public static void clearColor(float r, float g, float b, float a) {
GLStateManager.glClearColor(r, g, b, a);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
}
public static void clearTopHalf(float r, float g, float b, float a) {
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(0, SIZE / 2, SIZE, SIZE / 2);
clearColor(r, g, b, a);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
}
public static void resetPack() {
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 0);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_PIXELS, 0);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 0);
GLStateManager.glPixelStorei(GL11.GL_PACK_SWAP_BYTES, GL11.GL_FALSE);
}
public static ByteBuffer read(int format, int type, int bytes) {
final ByteBuffer out = BufferUtils.createByteBuffer(bytes);
for (int i = 0; i < bytes; i++) out.put(i, (byte) 0x7E);
GLStateManager.glReadPixels(0, 0, SIZE, SIZE, format, type, out);
return out.order(ByteOrder.nativeOrder());
}
public static void assertBytes(ByteBuffer buf, int offset, int[] expected, String label) {
for (int i = 0; i < expected.length; i++) {
final int idx = i;
assertEquals(expected[i], buf.get(offset + i) & 0xFF, () -> label + " byte " + idx);
}
}
public static void assertFloats(ByteBuffer buf, int offset, float[] expected, String label) {
for (int i = 0; i < expected.length; i++) {
final int idx = i;
assertEquals(expected[i], buf.getFloat(offset + i * 4), () -> label + " float " + idx);
}
}
public static void assertFloatsNear(ByteBuffer buf, int offset, float[] expected, String label) {
for (int i = 0; i < expected.length; i++) {
final int idx = i;
assertEquals(expected[i], buf.getFloat(offset + i * 4), 1f / 4096f, () -> label + " float " + idx);
}
}
public static void assertShorts(ByteBuffer buf, int offset, int[] expected, String label) {
for (int i = 0; i < expected.length; i++) {
final int idx = i;
assertEquals(expected[i], buf.getShort(offset + i * 2) & 0xFFFF, () -> label + " short " + idx);
}
}
public static void rgba8() {
final int fbo = colorFbo(GL11.GL_RGBA8, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
clearColor(51 / 255f, 102 / 255f, 153 / 255f, 204 / 255f);
clearTopHalf(1f, 0f, 0f, 1f);
final ByteBuffer rgba = read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4);
assertBytes(rgba, 0, new int[] { 51, 102, 153, 204 }, "RGBA8 as RGBA/UBYTE bottom row");
assertBytes(rgba, (SIZE - 1) * SIZE * 4, new int[] { 255, 0, 0, 255 }, "RGBA8 as RGBA/UBYTE top row");
assertBytes(read(GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 153, 102, 51, 204 }, "RGBA8 as BGRA/UBYTE");
final ByteBuffer rev = read(GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, SIZE * SIZE * 4);
assertEquals(0xCC996633, rev.getInt(0), "RGBA8 as RGBA/8_8_8_8_REV");
final ByteBuffer f = read(GL11.GL_RGBA, GL11.GL_FLOAT, SIZE * SIZE * 16);
assertFloatsNear(f, 0, new float[] { 51 / 255f, 102 / 255f, 153 / 255f, 204 / 255f }, "RGBA8 as RGBA/FLOAT");
final ByteBuffer rgb = read(GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, SIZE * 12);
assertBytes(rgb, 0, new int[] { 51, 102, 153, 51, 102, 153 }, "RGBA8 as RGB/UBYTE");
assertBytes(rgb, 12 * (SIZE - 1), new int[] { 255, 0, 0 }, "RGBA8 as RGB/UBYTE top row at 4-aligned stride");
assertBytes(read(GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE), 0, new int[] { 51, 51, 51, 51 }, "RGBA8 as RED/UBYTE");
release(fbo);
}
public static void packState() {
final int fbo = colorFbo(GL11.GL_RGBA8, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
clearColor(51 / 255f, 102 / 255f, 153 / 255f, 204 / 255f);
clearTopHalf(1f, 0f, 0f, 1f);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
final ByteBuffer rgb = read(GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 3);
assertBytes(rgb, (SIZE - 1) * SIZE * 3, new int[] { 255, 0, 0 }, "PACK_ALIGNMENT 1 tightly packs RGB rows");
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 6);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_PIXELS, 1);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 2);
final int rowBytes = 6 * 4;
final ByteBuffer skipped = read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, rowBytes * (SIZE + 2));
assertBytes(skipped, 0, new int[] { 0x7E, 0x7E, 0x7E, 0x7E }, "skipped rows untouched");
assertBytes(skipped, 2 * rowBytes, new int[] { 0x7E, 0x7E, 0x7E, 0x7E }, "skipped pixel untouched");
assertBytes(skipped, 2 * rowBytes + 4, new int[] { 51, 102, 153, 204 }, "first pixel after skips");
assertBytes(skipped, (2 + SIZE - 1) * rowBytes + 4, new int[] { 255, 0, 0, 255 }, "top row at ROW_LENGTH stride");
assertBytes(skipped, 2 * rowBytes + 4 + SIZE * 4, new int[] { 0x7E, 0x7E, 0x7E, 0x7E }, "row padding untouched");
resetPack();
GLStateManager.glPixelStorei(GL11.GL_PACK_SWAP_BYTES, GL11.GL_TRUE);
final ByteBuffer swapped = read(GL11.GL_RGBA, GL30.GL_HALF_FLOAT, SIZE * SIZE * 8);
assertBytes(swapped, 0, new int[] { 0x32, 0x66 }, "SWAP_BYTES on HALF_FLOAT");
resetPack();
release(fbo);
}
public static void pixelPackBuffer() {
final int fbo = colorFbo(GL11.GL_RGBA8, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
clearColor(51 / 255f, 102 / 255f, 153 / 255f, 204 / 255f);
final int pbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, pbo);
GLStateManager.glBufferData(GL21.GL_PIXEL_PACK_BUFFER, 16 + SIZE * SIZE * 4, GL15.GL_STREAM_READ);
GLStateManager.glReadPixels(0, 0, SIZE, SIZE, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, 16L);
final ByteBuffer mapped = GLStateManager.glMapBuffer(GL21.GL_PIXEL_PACK_BUFFER, GL15.GL_READ_ONLY);
assertTrue(mapped != null, "PBO map");
assertBytes(mapped, 16, new int[] { 51, 102, 153, 204 }, "PBO readPixels at offset 16");
GLStateManager.glUnmapBuffer(GL21.GL_PIXEL_PACK_BUFFER);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
GLStateManager.glDeleteBuffers(pbo);
release(fbo);
}
public static void rgb8() {
final int fbo = colorFbo(GL11.GL_RGB8, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE);
clearColor(51 / 255f, 102 / 255f, 153 / 255f, 0.5f);
assertBytes(read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 51, 102, 153, 255 }, "RGB8 as RGBA reads alpha 1");
release(fbo);
}
public static void rgba16f() {
final int fbo = colorFbo(GL30.GL_RGBA16F, GL11.GL_RGBA, GL11.GL_FLOAT);
clearColor(0.25f, 0.625f, 0.75f, 2.0f);
assertFloats(read(GL11.GL_RGBA, GL11.GL_FLOAT, SIZE * SIZE * 16), 0, new float[] { 0.25f, 0.625f, 0.75f, 2.0f }, "RGBA16F as RGBA/FLOAT keeps >1");
assertShorts(read(GL11.GL_RGBA, GL30.GL_HALF_FLOAT, SIZE * SIZE * 8), 0, new int[] { 0x3400, 0x3900, 0x3A00, 0x4000 }, "RGBA16F as RGBA/HALF_FLOAT");
assertBytes(read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 64, 159, 191, 255 }, "RGBA16F as RGBA/UBYTE clamps");
release(fbo);
}
public static void rgba32f() {
final int fbo = colorFbo(GL30.GL_RGBA32F, GL11.GL_RGBA, GL11.GL_FLOAT);
clearColor(0.2f, -0.5f, 3.0f, 1.0f);
assertFloats(read(GL11.GL_RGBA, GL11.GL_FLOAT, SIZE * SIZE * 16), 0, new float[] { 0.2f, -0.5f, 3.0f, 1.0f }, "RGBA32F as RGBA/FLOAT");
assertBytes(read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 51, 0, 255, 255 }, "RGBA32F as RGBA/UBYTE clamps both ends");
release(fbo);
}
public static void r8() {
final int fbo = colorFbo(GL30.GL_R8, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE);
clearColor(0.2f, 0.9f, 0.9f, 0.9f);
assertBytes(read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 51, 0, 0, 255 }, "R8 as RGBA rebases G/B=0 A=1");
release(fbo);
}
public static void rg16f() {
final int fbo = colorFbo(GL30.GL_RG16F, GL30.GL_RG, GL11.GL_FLOAT);
clearColor(0.25f, 0.625f, 0.9f, 0.9f);
assertFloats(read(GL11.GL_RGBA, GL11.GL_FLOAT, SIZE * SIZE * 16), 0, new float[] { 0.25f, 0.625f, 0f, 1f }, "RG16F as RGBA/FLOAT");
release(fbo);
}
public static void rgb10a2() {
final int texel = (1 << 30) | (614 << 20) | (409 << 10) | 205;
final ByteBuffer data = BufferUtils.createByteBuffer(SIZE * SIZE * 4).order(ByteOrder.nativeOrder());
for (int i = 0; i < SIZE * SIZE; i++) data.putInt(i * 4, texel);
final int fbo = colorFbo(GL11.GL_RGB10_A2, GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_2_10_10_10_REV, data);
final ByteBuffer raw = read(GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_2_10_10_10_REV, SIZE * SIZE * 4);
assertEquals(texel, raw.getInt(0), "RGB10A2 as RGBA/2_10_10_10_REV");
assertBytes(read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 51, 102, 153, 85 }, "RGB10A2 as RGBA/UBYTE");
release(fbo);
}
public static void r11g11b10f() {
final int fbo = colorFbo(GL30.GL_R11F_G11F_B10F, GL11.GL_RGB, GL11.GL_FLOAT);
clearColor(0.25f, 0.625f, 1.5f, 1f);
assertFloats(read(GL11.GL_RGBA, GL11.GL_FLOAT, SIZE * SIZE * 16), 0, new float[] { 0.25f, 0.625f, 1.5f, 1f }, "R11G11B10F as RGBA/FLOAT");
release(fbo);
}
public static void depthStencil() {
final int fbo = depthStencilFbo();
GLStateManager.glDepthMask(true);
GLStateManager.glStencilMask(0xFF);
GLStateManager.glClearDepth(0.25);
GLStateManager.glClearStencil(0xC3);
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(0, SIZE / 2, SIZE, SIZE / 2);
GLStateManager.glClearDepth(0.75);
GLStateManager.glClearStencil(0x5A);
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
GLStateManager.glClearDepth(1.0);
GLStateManager.glClearStencil(0);
final ByteBuffer depth = read(GL11.GL_DEPTH_COMPONENT, GL11.GL_FLOAT, SIZE * SIZE * 4);
assertEquals(0.25f, depth.getFloat(0), 1e-6f, "depth bottom row as FLOAT");
assertEquals(0.75f, depth.getFloat((SIZE - 1) * SIZE * 4), 1e-6f, "depth top row as FLOAT");
final ByteBuffer depthUs = read(GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_SHORT, SIZE * SIZE * 2);
assertEquals(16384, depthUs.getShort(0) & 0xFFFF, 1, "depth as UNSIGNED_SHORT");
final ByteBuffer depthUi = read(GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_INT, SIZE * SIZE * 4);
assertEquals(0x40000000L >>> 8, (depthUi.getInt(0) & 0xFFFFFFFFL) >>> 8, 1, "depth as UNSIGNED_INT (top 24 bits)");
final ByteBuffer stencil = read(GL11.GL_STENCIL_INDEX, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE);
assertBytes(stencil, 0, new int[] { 0xC3, 0xC3, 0xC3, 0xC3 }, "stencil bottom row");
assertBytes(stencil, (SIZE - 1) * SIZE, new int[] { 0x5A, 0x5A, 0x5A, 0x5A }, "stencil top row");
final ByteBuffer ds = read(GL30.GL_DEPTH_STENCIL, GL30.GL_UNSIGNED_INT_24_8, SIZE * SIZE * 4);
final int packed = ds.getInt(0);
assertEquals(0xC3, packed & 0xFF, "DEPTH_STENCIL stencil byte");
assertEquals(0x400000, (packed >>> 8) & 0xFFFFFF, 1, "DEPTH_STENCIL depth 24 bits");
release(fbo);
}
}
@@ -272,9 +272,9 @@ public final class CubeParityFixture {
CubeInstancedAttribs.pointInstanceAttribs(0L);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.ffpInstancing = Instancing.CUBE;
GLStateManager.setFfpInstancing(Instancing.CUBE);
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, UnitCubeMesh.VERTEX_COUNT, specs.length);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glBindVertexArray(0);
}
@@ -30,16 +30,17 @@ public final class FfpFixture {
layout(location = 5) in vec4 a_InstRow0;
layout(location = 6) in vec4 a_InstRow1;
layout(location = 7) in vec4 a_InstRow2;
layout(location = 11) in ivec4 iris_Entity;
layout(location = 11) in uvec4 iris_Entity;
flat out vec3 v_Color;
void main() {
""";
private static final String VS_TAIL = """
gl_Position = instMV * vec4(a_Position, 1.0);
if (iris_Entity.x == -1) { v_Color = vec3(1.0, 0.0, 0.0); }
else if (iris_Entity.x == 7) { v_Color = vec3(0.0, 1.0, 0.0); }
else { v_Color = vec3(0.0, 0.0, 1.0); }
if (iris_Entity.x == 65535u) { v_Color = vec3(1.0, 0.0, 0.0); }
else if (iris_Entity.x == 7u) { v_Color = vec3(0.0, 1.0, 0.0); }
else if (iris_Entity.x == 50020u) { v_Color = vec3(0.0, 0.0, 1.0); }
else { v_Color = vec3(1.0, 0.0, 1.0); }
}
""";
@@ -64,7 +65,7 @@ public final class FfpFixture {
GLStateManager.glUseProgram(program);
buildTemplate();
buildInstances(new float[] { -spacing, 0.0f, spacing }, new int[] { -1, 7, 42 });
buildInstances(new float[] { -spacing, 0.0f, spacing }, new int[] { -1, 7, 50020 });
}
public static void draw() {
@@ -244,10 +245,10 @@ public final class FfpFixture {
}
public static void resetFfpState() {
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
final ShaderManager sm = ShaderManager.getInstance();
if (sm.isActive()) sm.deactivate();
sm.disable();
ShaderManager.disable();
GLStateManager.glBindVertexArray(0);
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
@@ -0,0 +1,99 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.FloatBuffer;
public final class LogicOpFixture {
public static final int[] OPS = {
GL11.GL_CLEAR, GL11.GL_AND, GL11.GL_AND_REVERSE, GL11.GL_COPY,
GL11.GL_AND_INVERTED, GL11.GL_NOOP, GL11.GL_XOR, GL11.GL_OR,
GL11.GL_NOR, GL11.GL_EQUIV, GL11.GL_INVERT, GL11.GL_OR_REVERSE,
GL11.GL_COPY_INVERTED, GL11.GL_OR_INVERTED, GL11.GL_NAND, GL11.GL_SET
};
public static final int DST_R = 0x3C;
public static final int DST_G = 0xA5;
public static final int DST_B = 0xF0;
public static final int DST_A = 0x5A;
public static final int[][] SOURCES = {
{ 0x00, 0x00, 0xFF, 0xFF },
{ 0x33, 0x99, 0xE6, 0x80 },
{ 0xFF, 0xFF, 0xFF, 0xFF },
};
private static int vao;
private static int vbo;
private LogicOpFixture() {}
public static int expected(int op, int s, int d) {
final int r = switch (op) {
case GL11.GL_CLEAR -> 0;
case GL11.GL_AND -> s & d;
case GL11.GL_AND_REVERSE -> s & ~d;
case GL11.GL_COPY -> s;
case GL11.GL_AND_INVERTED -> ~s & d;
case GL11.GL_NOOP -> d;
case GL11.GL_XOR -> s ^ d;
case GL11.GL_OR -> s | d;
case GL11.GL_NOR -> ~(s | d);
case GL11.GL_EQUIV -> ~(s ^ d);
case GL11.GL_INVERT -> ~d;
case GL11.GL_OR_REVERSE -> s | ~d;
case GL11.GL_COPY_INVERTED -> ~s;
case GL11.GL_OR_INVERTED -> ~s | d;
case GL11.GL_NAND -> ~(s & d);
case GL11.GL_SET -> 0xFF;
default -> throw new IllegalArgumentException("op 0x" + Integer.toHexString(op));
};
return r & 0xFF;
}
public static void clearToDst() {
GLStateManager.glClearColor(DST_R / 255f, DST_G / 255f, DST_B / 255f, DST_A / 255f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
}
public static void drawQuad(int op, int[] src) {
GLStateManager.glEnable(GL11.GL_COLOR_LOGIC_OP);
GLStateManager.glLogicOp(op);
quad(src);
GLStateManager.glDisable(GL11.GL_COLOR_LOGIC_OP);
GLStateManager.glLogicOp(GL11.GL_COPY);
}
public static void quad(int[] src) {
if (vao == 0) {
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
final FloatBuffer positions = BufferUtils.createFloatBuffer(12);
positions.put(new float[] { -1f, -1f, 0f, 1f, -1f, 0f, 1f, 1f, 0f, -1f, 1f, 0f }).flip();
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, positions, GL15.GL_STATIC_DRAW);
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, 12, 0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
GLStateManager.glBindVertexArray(vao);
VAOManager.setCurrentVertexFlags(0);
GLStateManager.glColor4f(src[0] / 255f, src[1] / 255f, src[2] / 255f, src[3] / 255f);
GLStateManager.glDrawArrays(GL11.GL_QUADS, 0, 4);
GLStateManager.glBindVertexArray(0);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}
public static void deleteResources() {
if (vbo != 0) { GLStateManager.glDeleteBuffers(vbo); vbo = 0; }
if (vao != 0) { GLStateManager.glDeleteVertexArrays(vao); vao = 0; }
}
public static String label(int op, int[] src) {
return "op=0x" + Integer.toHexString(op) + " src=" + src[0] + "," + src[1] + "," + src[2] + "," + src[3];
}
}
@@ -184,9 +184,9 @@ public final class ParticleParityFixture {
ParticleInstancedAttribs.pointInstanceAttribs(0L);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.ffpInstancing = Instancing.PARTICLE;
GLStateManager.setFfpInstancing(Instancing.PARTICLE);
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, ParticleQuadMesh.VERTEX_COUNT, particles.length);
GLStateManager.ffpInstancing = Instancing.NONE;
GLStateManager.setFfpInstancing(Instancing.NONE);
GLStateManager.glBindVertexArray(0);
}
@@ -0,0 +1,53 @@
package com.gtnewhorizons.angelica.glsm.testutil;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.fail;
public final class TestThreads {
private static final long JOIN_TIMEOUT_MS = 10_000;
private TestThreads() {}
@FunctionalInterface
public interface Body {
void run() throws Throwable;
}
public static final class Worker {
private final Thread thread;
private final AtomicReference<Throwable> error = new AtomicReference<>();
private Worker(String name, Body body) {
thread = new Thread(() -> {
try {
body.run();
} catch (Throwable t) {
error.set(t);
}
}, name);
thread.setDaemon(true);
thread.start();
}
public Throwable error() {
return error.get();
}
public void join() throws InterruptedException {
thread.join(JOIN_TIMEOUT_MS);
assertFalse(thread.isAlive(), thread.getName() + " did not finish");
if (error.get() != null) fail(error.get());
}
}
public static Worker start(String name, Body body) {
return new Worker(name, body);
}
public static void run(String name, Body body) throws InterruptedException {
start(name, body).join();
}
}
+2 -2
View File
@@ -9,13 +9,13 @@ asmCompile = "5.0.3"
lwjgl3 = "3.4.2-SNAPSHOT"
lwjgl3ify = "3.0.31"
celeritas = "2.5.11-GTNH"
celeritas = "2.5.12-GTNH"
gtnhlib = "0.11.36"
eventbus = "7.0-beta.15"
glslTransformLib = "0.2.0-33.g3ec381e"
antlr4Runtime = "4.13.2"
jcpp = "1.4.14"
tracy = "0.14.0"
tracy = "0.14.1"
asyncProfiler = "4.4"
retroFuturaBootstrap = "1.1.0"
@@ -10,6 +10,7 @@ import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import me.eigenraven.lwjgl3ify.client.MainThreadExec;
import org.lwjgl.PointerBuffer;
import org.lwjglx.Lwjgl3ifyEventLoop;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -59,6 +60,7 @@ import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
@@ -73,6 +75,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
private GLCapabilities caps;
private GLDebugMessageCallback debugCallback;
private boolean debugOutputActive;
private boolean amdShaderSourceWorkaround;
private SDL_EventFilter dropEventFilter;
private final ConcurrentLinkedQueue<String> droppedFiles = new ConcurrentLinkedQueue<>();
@@ -82,6 +85,15 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public void init() {
caps = GL.getCapabilities();
if (GLStateManager.isWindows()) {
final String vendor = GL11C.glGetString(GL11C.GL_VENDOR);
if (vendor != null) {
final String normalizedVendor = vendor.toLowerCase(Locale.ROOT);
amdShaderSourceWorkaround = normalizedVendor.startsWith("amd")
|| normalizedVendor.startsWith("ati technologies")
|| normalizedVendor.startsWith("advanced micro devices");
}
}
if (RenderSystem.isGLES()) {
pfnClearDepthf = GL.getFunctionProvider().getFunctionAddress("glClearDepthf");
pfnDepthRangef = GL.getFunctionProvider().getFunctionAddress("glDepthRangef");
@@ -778,6 +790,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL11C.glReadPixels(x, y, width, height, format, type, pixels);
}
@Override
public void readPixels(int x, int y, int width, int height, int format, int type, long pixelBufferOffset) {
GL11C.glReadPixels(x, y, width, height, format, type, pixelBufferOffset);
}
@Override
public void getTexImage(int target, int level, int format, int type, ByteBuffer pixels) {
GL11C.glGetTexImage(target, level, format, type, pixels);
@@ -810,7 +827,20 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public void shaderSource(int shader, CharSequence source) {
GL20C.glShaderSource(shader, source);
if (!amdShaderSourceWorkaround) {
GL20C.glShaderSource(shader, source);
return;
}
try (MemoryStack stack = MemoryStack.stackPush()) {
final ByteBuffer encodedSource = MemoryUtil.memUTF8(source, true);
try {
final PointerBuffer pointers = stack.mallocPointer(1);
pointers.put(encodedSource);
GL20C.nglShaderSource(shader, 1, pointers.address0(), 0L);
} finally {
MemoryUtil.memFree(encodedSource);
}
}
}
@Override
+3
View File
@@ -104,6 +104,9 @@ if [ -n "$JFR_OUT" ]; then
PROFILE_ARGS+=(-Dangelica.profile.opts="$ASPROF_OPTS")
fi
fi
if [ "$MODE" = "tracy" ]; then
PROFILE_ARGS+=(-Dangelica.tracy=true)
fi
if [ "$MODE" = "tracy" ]; then
if ! command -v "$TRACY_CAPTURE" >/dev/null 2>&1 && [ ! -x "$TRACY_CAPTURE" ]; then
File diff suppressed because it is too large. Load diff
@@ -1,7 +1,10 @@
package com.gtnewhorizons.angelica.sdlgpu.frame;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
import com.gtnewhorizons.angelica.sdlgpu.resource.PackState;
import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpVariant;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import com.gtnewhorizons.angelica.sdlgpu.shader.UniformStaging;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
@@ -12,6 +15,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import org.lwjgl.PointerBuffer;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
@@ -48,12 +52,17 @@ public final class ContextState {
public int unpackSkipPixels = 0;
public int unpackSkipRows = 0;
public int packAlignment = 4;
public int packRowLength = 0;
public int packSkipPixels = 0;
public int packSkipRows = 0;
public boolean packSwapBytes;
public boolean isDefault() {
return unpackAlignment == 4 && unpackRowLength == 0 && unpackSkipPixels == 0 && unpackSkipRows == 0 && packAlignment == 4;
}
}
public final PixelStoreState pixelStore = new PixelStoreState();
public final PackState packState = new PackState();
public static final class ProxyTextureState {
public int target;
@@ -113,6 +122,7 @@ public final class ContextState {
public float viewportDepthFar = 1.0f;
public int scissorX, scissorY, scissorW, scissorH;
public boolean scissorEnabled;
public boolean rasterizerDiscard;
public int activeTextureUnit;
public int boundProgram;
public int autoPushedProgram;
@@ -124,6 +134,21 @@ public final class ContextState {
public int ssboBinds;
public float blendColorR, blendColorG, blendColorB, blendColorA;
public boolean logicOpEnabled;
public int logicOpMode = GL11.GL_COPY;
public long appliedLogicOpKey;
public LogicOpVariant activeLogicOpVariant;
public final int[] blitRect = new int[8];
public final long[] logicOpScratch = new long[MAX_COLOR_ATTACHMENTS];
public final int[] logicOpScratchFormat = new int[MAX_COLOR_ATTACHMENTS];
public final int[] logicOpScratchWidth = new int[MAX_COLOR_ATTACHMENTS];
public final int[] logicOpScratchHeight = new int[MAX_COLOR_ATTACHMENTS];
public long readbackDepthScratch;
public int readbackDepthScratchWidth;
public int readbackDepthScratchHeight;
public long readbackStencilScratch;
public int readbackStencilScratchWidth;
public int readbackStencilScratchHeight;
public int stencilRef;
public float depthClearValue = 1.0f;
public int stencilClearValue;
@@ -303,6 +328,7 @@ public final class ContextState {
public final long[] lastVertSamplerTex = new long[MAX_SAMPLERS];
public final long[] lastVertSamplerSmp = new long[MAX_SAMPLERS];
public int lastFragSamplerProgram;
public LogicOpVariant lastFragSamplerVariant;
public int lastVertSamplerProgram;
public final PointerBuffer fragStorageTexBindings = PointerBuffer.allocateDirect(MAX_IMAGE_UNITS);
@@ -327,9 +353,8 @@ public final class ContextState {
public int boundFboId;
public int boundReadFboId;
public final LongOpenHashSet clearedTexturesThisFrame = new LongOpenHashSet();
public final LongOpenHashSet clearedStencilTexturesThisFrame = new LongOpenHashSet();
public int defaultFboId;
public GLContextState mirroredContext;
public final LongOpenHashSet pendingColorTextures = new LongOpenHashSet();
public final Long2ObjectOpenHashMap<float[]> pendingColorValues = new Long2ObjectOpenHashMap<>();
@@ -415,6 +440,7 @@ public final class ContextState {
lastAppliedSamplerProgram = 0;
lastAppliedSamplerCb = 0;
lastFragSamplerProgram = 0;
lastFragSamplerVariant = null;
lastVertSamplerProgram = 0;
lastAppliedStorageTexBindGen = -1;
lastAppliedStorageTexProgram = 0;
@@ -55,7 +55,9 @@ public final class FrameManager {
public static final int PASS_END_TARGET = 3;
public static final int PASS_END_UNIFORM_BLOCK = 4;
public static final int PASS_END_FRAME_END = 5;
public static final int PASS_END_CAUSE_COUNT = 6;
public static final int PASS_END_LOGIC_OP = 6;
public static final int PASS_END_READBACK = 7;
public static final int PASS_END_CAUSE_COUNT = 8;
public static final class FrameState {
public final Thread owner = Thread.currentThread();
@@ -647,7 +649,11 @@ public final class FrameManager {
public void submitMidFrame() {
final FrameState f = frame();
if (f.commandBuffer == 0) return;
if (f.commandBuffer == 0) {
if (f.wantFenceOnNextSubmit) getOrCreatePendingUploadCommandBuffer(f);
flushPendingUploadCommandBuffer(f);
return;
}
endCopyPassIfActive(f);
endRenderPassIfActive(f, PASS_END_FRAME_END);
Tracy.beginZone(Z_SDL_SUBMIT);
@@ -747,7 +753,14 @@ public final class FrameManager {
Tracy.beginZone(Z_SDL_SUBMIT);
final boolean submitted;
try {
submitted = SDL_SubmitGPUCommandBuffer(f.pendingUploadCommandBuffer);
if (f.wantFenceOnNextSubmit) {
f.wantFenceOnNextSubmit = false;
if (f.lastAcquiredFence != 0) SDL_ReleaseGPUFence(device.getDevice(), f.lastAcquiredFence);
f.lastAcquiredFence = SDL_SubmitGPUCommandBufferAndAcquireFence(f.pendingUploadCommandBuffer);
submitted = f.lastAcquiredFence != 0;
} else {
submitted = SDL_SubmitGPUCommandBuffer(f.pendingUploadCommandBuffer);
}
} finally {
Tracy.endZone();
}
@@ -53,7 +53,7 @@ public final class OffscreenTarget {
public void markContent() { hasContent = true; }
public boolean isFor(ContextState st) {
return fboGlId != 0 && st.boundFboId == fboGlId;
return fboGlId != 0 && st.defaultFboId == fboGlId;
}
public void destroy(ResourceManager rm) {
@@ -67,6 +67,8 @@ final class SdlFramePlots {
private static final long PLOT_PASS_END_TARGET = Tracy.plotHandle("sdl.passEnd.target");
private static final long PLOT_PASS_END_UNIFORM_BLOCK = Tracy.plotHandle("sdl.passEnd.uniformBlock");
private static final long PLOT_PASS_END_FRAME_END = Tracy.plotHandle("sdl.passEnd.frameEnd");
private static final long PLOT_PASS_END_LOGIC_OP = Tracy.plotHandle("sdl.passEnd.logicOp");
private static final long PLOT_PASS_END_READBACK = Tracy.plotHandle("sdl.passEnd.readback");
private static final int[] contextCounters = new int[SDLGPURenderBackend.CTR_COUNT];
@@ -129,5 +131,7 @@ final class SdlFramePlots {
Tracy.plotInt(PLOT_PASS_END_TARGET, f.passEndCauseCountsThisFrame[FrameManager.PASS_END_TARGET]);
Tracy.plotInt(PLOT_PASS_END_UNIFORM_BLOCK, f.passEndCauseCountsThisFrame[FrameManager.PASS_END_UNIFORM_BLOCK]);
Tracy.plotInt(PLOT_PASS_END_FRAME_END, f.passEndCauseCountsThisFrame[FrameManager.PASS_END_FRAME_END]);
Tracy.plotInt(PLOT_PASS_END_LOGIC_OP, f.passEndCauseCountsThisFrame[FrameManager.PASS_END_LOGIC_OP]);
Tracy.plotInt(PLOT_PASS_END_READBACK, f.passEndCauseCountsThisFrame[FrameManager.PASS_END_READBACK]);
}
}
@@ -1,29 +1,19 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.lwjgl.opengl.GL20;
import org.lwjgl.sdl.SDLError;
import org.lwjgl.sdl.SDL_GPUColorTargetDescription;
import org.lwjgl.sdl.SDL_GPUDepthStencilState;
import org.lwjgl.sdl.SDL_GPUGraphicsPipelineCreateInfo;
import org.lwjgl.sdl.SDL_GPUGraphicsPipelineTargetInfo;
import org.lwjgl.sdl.SDL_GPUMultisampleState;
import org.lwjgl.sdl.SDL_GPURasterizerState;
import org.lwjgl.sdl.SDL_GPUVertexInputState;
import org.lwjgl.sdl.SDL_GPUViewport;
import org.lwjgl.sdl.SDL_Rect;
import org.lwjgl.system.MemoryStack;
import org.taumc.glsl.Transformer;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.function.LongSupplier;
@@ -32,21 +22,34 @@ import static org.lwjgl.system.MemoryStack.stackPush;
public final class AttachmentClear {
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
private static final String VERTEX_SOURCE = "angelica:sdlgpu/clear_attachment.vsh";
private static final String FRAGMENT_SOURCE = "angelica:sdlgpu/clear_attachment.fsh";
private static final long KEY_SEED = 0x5B1D2A7C4E9F3061L;
private static final int CACHE_SLOTS = 4;
private static final int MAX_COLOR_TARGETS = ContextState.MAX_COLOR_ATTACHMENTS;
public static final int EMPTY = 0;
public static final int PARTIAL = 1;
public static final int FULL = 2;
public static final class Target {
public int[] colorFormats;
public int[] drawBuffers;
public int depthFormat;
public long depthTexture;
public int width;
public int height;
public boolean flipY;
}
private final FrameManager frameManager;
private final PipelineStore pipelineStore;
private final ShaderManager shaderManager;
private int program;
private long sdlVertexShader;
private long sdlFragmentShader;
private boolean failed;
private final Target fboTarget = new Target();
private final Target fbo0Target = new Target();
private final FullscreenPass fullscreen;
private final long[] sdlFragmentShaders = new long[MAX_COLOR_TARGETS + 1];
private final boolean[] fragmentFailed = new boolean[MAX_COLOR_TARGETS + 1];
private final long[] cacheKeys = new long[CACHE_SLOTS];
private final long[] cachePipelines = new long[CACHE_SLOTS];
@@ -55,118 +58,143 @@ public final class AttachmentClear {
private final int[] scissorRect = new int[4];
private final Builder builder = new Builder();
private int[] buildColorFormats;
private int buildColorCount;
private int buildDepthFormat;
private Target buildTarget;
private int buildColorTargets;
private boolean buildDepthFlag;
private boolean buildStencilFlag;
private int buildWriteMask;
private int buildStencilMask;
private int buildColorMask;
public AttachmentClear(FrameManager frameManager, PipelineStore pipelineStore, ShaderManager shaderManager) {
this.frameManager = frameManager;
this.pipelineStore = pipelineStore;
this.shaderManager = shaderManager;
this.fullscreen = new FullscreenPass(shaderManager);
}
private final class Builder implements LongSupplier {
@Override public long getAsLong() { return createPipeline(); }
}
public boolean eligible(ContextState st, FrameState f, FboState fbo, boolean wantDepth, boolean wantStencil) {
if (SystemProperties.SDL_DISABLE_IN_PASS_CLEAR) return false;
if (st.boundFboId == 0) return false;
if (f.renderPass == 0) return false;
if (fbo.depthTexture == 0) return false;
if (f.currentDepthTarget != fbo.depthTexture) return false;
if (f.activeLayoutHash != fbo.structuralLayoutHash || f.currentColorTarget != fbo.primaryTarget) return false;
if (FBOClearTracker.fboHasPendingClear(st, fbo)) return false;
if (wantStencil && !PixelOps.isDepthStencilFormat(fbo.depthFormat)) return false;
if (fbo.width <= 0 || fbo.height <= 0) return false;
return computeScissor(st, fbo);
public Target forFbo(FboState fbo) {
final Target t = fboTarget;
t.colorFormats = fbo.cachedColorFormats;
t.drawBuffers = fbo.drawBuffers;
t.depthFormat = fbo.depthTexture != 0 ? fbo.depthFormat : 0;
t.depthTexture = fbo.depthTexture;
t.width = fbo.width;
t.height = fbo.height;
t.flipY = false;
return t;
}
private boolean computeScissor(ContextState st, FboState fbo) {
public Target forFbo0(int[] swapchainFormats, int w, int h) {
final Target t = fbo0Target;
t.colorFormats = swapchainFormats;
t.drawBuffers = null;
t.depthFormat = 0;
t.depthTexture = 0;
t.width = w;
t.height = h;
t.flipY = true;
return t;
}
static boolean enabled(Target t, int i) {
return t.drawBuffers == null || t.drawBuffers[i] >= 0;
}
public boolean hasIntegerColorTarget(Target t) {
final int[] formats = t.colorFormats;
if (formats == null) return false;
for (int i = 0; i < formats.length; i++) {
if (enabled(t, i) && PixelOps.isSdlFormatInteger(formats[i])) return true;
}
return false;
}
public int coverage(ContextState st, Target t) {
final int sx, sy, sw, sh;
if (st.scissorEnabled) {
sx = st.scissorX;
sy = st.scissorY;
sy = t.flipY ? t.height - st.scissorY - st.scissorH : st.scissorY;
sw = st.scissorW;
sh = st.scissorH;
} else {
sx = 0;
sy = 0;
sw = fbo.width;
sh = fbo.height;
sw = t.width;
sh = t.height;
}
return ScissorClamp.clamp(sx, sy, sw, sh, fbo.width, fbo.height, scissorRect);
if (!ScissorClamp.clamp(sx, sy, sw, sh, t.width, t.height, scissorRect)) return EMPTY;
if (scissorRect[ScissorClamp.X] == 0 && scissorRect[ScissorClamp.Y] == 0
&& scissorRect[ScissorClamp.W] == t.width && scissorRect[ScissorClamp.H] == t.height) return FULL;
return PARTIAL;
}
public boolean clearInPass(ContextState st, FrameState f, FboState fbo, boolean depth, boolean stencil) {
final int writeMask = stencil ? (st.pipeline.stencilWriteMask & 0xFF) : 0;
final long pipeline = getOrCreatePipeline(fbo, depth, stencil, writeMask);
public boolean clearInPass(ContextState st, FrameState f, Target t, boolean color, boolean depth, boolean stencil) {
final int colorTargets = color ? t.colorFormats.length : 0;
final int stencilMask = stencil ? (st.pipeline.stencilWriteMask & 0xFF) : 0;
final int colorMask = color ? st.pipeline.colorWriteMask : 0;
final long pipeline = getOrCreatePipeline(t, colorTargets, depth, stencil, stencilMask, colorMask);
if (pipeline == 0) return false;
if (!computeScissor(st, fbo)) return false;
if (coverage(st, t) == EMPTY) return false;
final long rp = f.renderPass;
SDL_BindGPUGraphicsPipeline(rp, pipeline);
final float clearDepth = Math.min(1.0f, Math.max(0.0f, st.depthClearValue));
final long vpAddr = st.cachedViewport.address();
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.X, 0f);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.Y, 0f);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.W, fbo.width);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.H, fbo.height);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.MIN_DEPTH, clearDepth);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.MAX_DEPTH, clearDepth);
SDL_SetGPUViewport(rp, st.cachedViewport);
final long scAddr = st.cachedScissor.address();
MemoryAccess.putInt(scAddr + SDL_Rect.X, scissorRect[ScissorClamp.X]);
MemoryAccess.putInt(scAddr + SDL_Rect.Y, scissorRect[ScissorClamp.Y]);
MemoryAccess.putInt(scAddr + SDL_Rect.W, scissorRect[ScissorClamp.W]);
MemoryAccess.putInt(scAddr + SDL_Rect.H, scissorRect[ScissorClamp.H]);
SDL_SetGPUScissor(rp, st.cachedScissor);
FullscreenPass.setViewport(rp, st, t.width, t.height, clearDepth);
FullscreenPass.setScissor(rp, st, scissorRect[ScissorClamp.X], scissorRect[ScissorClamp.Y], scissorRect[ScissorClamp.W], scissorRect[ScissorClamp.H]);
if (stencil) {
SDL_SetGPUStencilReference(rp, (byte) st.stencilClearValue);
}
if (colorTargets > 0) {
try (MemoryStack stack = stackPush()) {
final ByteBuffer clearColor = stack.malloc(16);
clearColor.putFloat(0, st.clearR);
clearColor.putFloat(4, st.clearG);
clearColor.putFloat(8, st.clearB);
clearColor.putFloat(12, st.clearA);
SDL_PushGPUFragmentUniformData(frameManager.getCommandBuffer(f), 0, clearColor);
}
st.lastPushedProgramFs = -1;
}
SDL_DrawGPUPrimitives(rp, 3, 1, 0, 0);
st.lastBoundPipeline = 0;
st.viewportDirty = true;
st.scissorDirty = true;
st.lastAppliedStencilRef = Integer.MIN_VALUE;
st.lastAppliedVboBindCb = 0;
if (depth) st.clearedTexturesThisFrame.add(fbo.depthTexture);
if (stencil) st.clearedStencilTexturesThisFrame.add(fbo.depthTexture);
FullscreenPass.markStateClobbered(st);
frameManager.noteInPassClear();
return true;
}
long getOrCreatePipeline(FboState fbo, boolean depthFlag, boolean stencilFlag, int writeMask) {
if (!ensureShaders()) return 0;
long getOrCreatePipeline(Target t, int colorTargets, boolean depth, boolean stencil, int stencilMask, int colorMask) {
if (!ensureShaders(colorTargets)) return 0;
final int[] formats = fbo.cachedColorFormats;
final int[] formats = t.colorFormats;
final int count = formats != null ? formats.length : 0;
long key = KEY_SEED;
key = Hashing.fmix64(key, count);
for (int i = 0; i < count; i++) key = Hashing.fmix64(key, formats[i]);
key = Hashing.fmix64(key, fbo.depthFormat);
key = Hashing.fmix64(key, (depthFlag ? 1 : 0) | ((stencilFlag ? 1 : 0) << 1) | (writeMask << 8));
for (int i = 0; i < count; i++) {
key = Hashing.fmix64(key, formats[i]);
key = Hashing.fmix64(key, enabled(t, i) ? 1 : 0);
}
key = Hashing.fmix64(key, t.depthFormat);
key = Hashing.fmix64(key, colorTargets);
key = Hashing.fmix64(key, (depth ? 1 : 0) | ((stencil ? 1 : 0) << 1) | (stencilMask << 8) | (colorMask << 16));
for (int i = 0; i < CACHE_SLOTS; i++) {
if (cacheKeys[i] == key && cachePipelines[i] != 0) return cachePipelines[i];
}
buildColorFormats = formats;
buildColorCount = count;
buildDepthFormat = fbo.depthFormat;
buildDepthFlag = depthFlag;
buildStencilFlag = stencilFlag;
buildWriteMask = writeMask;
buildTarget = t;
buildColorTargets = colorTargets;
buildDepthFlag = depth;
buildStencilFlag = stencil;
buildStencilMask = stencilMask;
buildColorMask = colorMask;
final long pipeline = pipelineStore.getOrBuild(key, builder);
if (pipeline != 0) {
cacheKeys[cacheNext] = key;
@@ -178,14 +206,16 @@ public final class AttachmentClear {
private long createPipeline() {
try (var stack = stackPush()) {
final int numColorTargets = buildColorCount;
final Target t = buildTarget;
final int numColorTargets = t.colorFormats != null ? t.colorFormats.length : 0;
final SDL_GPUColorTargetDescription.Buffer colorDesc = SDL_GPUColorTargetDescription.calloc(numColorTargets, stack);
for (int i = 0; i < numColorTargets; i++) {
final var desc = colorDesc.get(i);
desc.format(buildColorFormats[i]);
desc.format(t.colorFormats[i]);
final int mask = (buildColorTargets > 0 && enabled(t, i)) ? buildColorMask : 0;
desc.blend_state()
.enable_blend(false)
.color_write_mask((byte) 0)
.color_write_mask((byte) mask)
.enable_color_write_mask(true);
}
@@ -195,7 +225,7 @@ public final class AttachmentClear {
.compare_op(SDL_GPU_COMPAREOP_ALWAYS)
.enable_stencil_test(buildStencilFlag)
.compare_mask((byte) 0xFF)
.write_mask((byte) buildWriteMask);
.write_mask((byte) buildStencilMask);
if (buildStencilFlag) {
depthStencil.front_stencil_state()
.fail_op(SDL_GPU_STENCILOP_REPLACE)
@@ -209,83 +239,44 @@ public final class AttachmentClear {
.compare_op(SDL_GPU_COMPAREOP_ALWAYS);
}
final SDL_GPURasterizerState rasterizer = SDL_GPURasterizerState.calloc(stack)
.cull_mode(SDL_GPU_CULLMODE_NONE)
.front_face(SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE)
.fill_mode(SDL_GPU_FILLMODE_FILL);
final SDL_GPUMultisampleState multisample = SDL_GPUMultisampleState.calloc(stack)
.sample_count(SDL_GPU_SAMPLECOUNT_1);
final SDL_GPUGraphicsPipelineTargetInfo targetInfo = SDL_GPUGraphicsPipelineTargetInfo.calloc(stack)
.num_color_targets(numColorTargets)
.color_target_descriptions(colorDesc)
.depth_stencil_format(buildDepthFormat)
.has_depth_stencil_target(true);
final SDL_GPUVertexInputState vertexInput = SDL_GPUVertexInputState.calloc(stack);
final SDL_GPUGraphicsPipelineCreateInfo ci = SDL_GPUGraphicsPipelineCreateInfo.calloc(stack)
.vertex_shader(sdlVertexShader)
.fragment_shader(sdlFragmentShader)
.primitive_type(SDL_GPU_PRIMITIVETYPE_TRIANGLELIST)
.rasterizer_state(rasterizer)
.multisample_state(multisample)
.depth_stencil_state(depthStencil)
.target_info(targetInfo)
.vertex_input_state(vertexInput);
final long pipeline = SDL_CreateGPUGraphicsPipeline(pipelineStore.device().getDevice(), ci);
if (pipeline == 0) {
LOG.error("AttachmentClear: pipeline creation failed: {}", SDLError.SDL_GetError());
return PipelineStore.BAD_PIPELINE_SENTINEL;
}
return pipeline;
return fullscreen.createPipeline(pipelineStore, stack, sdlFragmentShaders[buildColorTargets], colorDesc, numColorTargets, depthStencil, t.depthFormat, "AttachmentClear");
}
}
private boolean ensureShaders() {
if (program != 0) return true;
if (failed) return false;
failed = true;
private boolean ensureShaders(int colorTargets) {
if (colorTargets < 0 || colorTargets > MAX_COLOR_TARGETS) return false;
if (sdlFragmentShaders[colorTargets] != 0) return true;
if (fragmentFailed[colorTargets]) return false;
fragmentFailed[colorTargets] = true;
final int vs = shaderManager.createShader(GL20.GL_VERTEX_SHADER);
shaderManager.shaderSource(vs, ShaderLoader.getShaderSource(VERTEX_SOURCE));
shaderManager.compileShader(vs);
final int fs = shaderManager.createShader(GL20.GL_FRAGMENT_SHADER);
shaderManager.shaderSource(fs, ShaderLoader.getShaderSource(FRAGMENT_SOURCE));
shaderManager.compileShader(fs);
final String baseSource = ShaderLoader.getShaderSource(FRAGMENT_SOURCE);
final String fragmentSource = colorTargets == 0 ? baseSource : withColorOutputs(baseSource, colorTargets);
final long fragmentShader = fullscreen.compileFragment(fragmentSource, colorTargets > 0 ? 16 : -1, "AttachmentClear: clear_attachment COLOR_TARGETS=" + colorTargets);
if (fragmentShader == 0) return false;
final int prog = shaderManager.createProgram();
shaderManager.attachShader(prog, vs);
shaderManager.attachShader(prog, fs);
shaderManager.linkProgram(prog);
final ShaderManager.ProgramObject po = shaderManager.getProgram(prog);
if (po == null || !po.linked) {
LOG.error("AttachmentClear: clear_attachment program failed to link: {}", po == null ? "no program object" : po.infoLog);
return false;
}
po.sdlVertexShader = shaderManager.createSDLShader(po.vertexSpirv, SDL_GPU_SHADERSTAGE_VERTEX, po.vertexResources.numSamplers(), po.vertexResources.numUBOs(), po.vertexResources.numStorageBuffers(), po.vertexResources.numStorageTextures());
po.sdlFragmentShader = shaderManager.createSDLShader(po.fragmentSpirv, SDL_GPU_SHADERSTAGE_FRAGMENT, po.fragmentResources.numSamplers(), po.fragmentResources.numUBOs(), po.fragmentResources.numStorageBuffers(), po.fragmentResources.numStorageTextures());
if (po.sdlVertexShader == 0 || po.sdlFragmentShader == 0) {
LOG.error("AttachmentClear: clear_attachment SDL shader creation failed");
return false;
}
program = prog;
sdlVertexShader = po.sdlVertexShader;
sdlFragmentShader = po.sdlFragmentShader;
failed = false;
sdlFragmentShaders[colorTargets] = fragmentShader;
fragmentFailed[colorTargets] = false;
return true;
}
private static String withColorOutputs(String source, int count) {
final GlslTransformUtils.QuietParse parsed = GlslTransformUtils.parseBothQuiet(source);
final Transformer transformer = new Transformer(parsed.full());
transformer.injectVariable("uniform vec4 clearColor;");
for (int i = 0; i < count; i++) {
transformer.injectVariable("layout(location = " + i + ") out vec4 angelica_ClearColor" + i + ";");
transformer.appendMain("angelica_ClearColor" + i + " = clearColor;");
}
final String header = GlslTransformUtils.getFormattedShader(parsed.pre(), "").trim();
final StringBuilder out = new StringBuilder();
transformer.mutateTree(tree -> out.append(GlslTransformUtils.getFormattedShader(tree, header)));
return out.toString();
}
public void shutdown() {
program = 0;
sdlVertexShader = 0;
sdlFragmentShader = 0;
failed = false;
fullscreen.shutdown();
Arrays.fill(sdlFragmentShaders, 0L);
Arrays.fill(fragmentFailed, false);
Arrays.fill(cacheKeys, 0L);
Arrays.fill(cachePipelines, 0L);
cacheNext = 0;
@@ -0,0 +1,314 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.lwjgl.sdl.SDLError;
import org.lwjgl.sdl.SDL_FColor;
import org.lwjgl.sdl.SDL_GPUColorTargetDescription;
import org.lwjgl.sdl.SDL_GPUColorTargetInfo;
import org.lwjgl.sdl.SDL_GPUDepthStencilState;
import org.lwjgl.sdl.SDL_GPUDepthStencilTargetInfo;
import org.lwjgl.sdl.SDL_GPUSamplerCreateInfo;
import org.lwjgl.sdl.SDL_GPUTextureSamplerBinding;
import org.lwjgl.system.MemoryStack;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.function.LongSupplier;
import static org.lwjgl.sdl.SDLGPU.*;
import static org.lwjgl.system.MemoryStack.stackPush;
public final class DepthStencilReadback {
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
private static final String DEPTH_FRAGMENT_SOURCE = "angelica:sdlgpu/readback_depth.fsh";
private static final String STENCIL_FRAGMENT_SOURCE = "angelica:sdlgpu/readback_stencil.fsh";
private static final long KEY_SEED = 0x3C6EF372A54FF53AL;
private static final int STENCIL_BITS = 8;
private static final int DEPTH_TARGET_FORMAT = SDL_GPU_TEXTUREFORMAT_R32_FLOAT;
private static final int STENCIL_TARGET_FORMAT = SDL_GPU_TEXTUREFORMAT_R8_UNORM;
private final FrameManager frameManager;
private final ResourceManager resourceManager;
private final PipelineStore pipelineStore;
private final FBOClearTracker fboClearTracker;
private final FullscreenPass fullscreen;
private long depthFragmentShader;
private boolean depthFragmentFailed;
private long stencilFragmentShader;
private boolean stencilFragmentFailed;
private long nearestSampler;
private long depthPipeline;
private final long[] stencilPipelines = new long[STENCIL_BITS];
private int stencilPipelinesFormat;
private final Builder builder = new Builder();
private int buildBit;
private int buildDepthFormat;
public DepthStencilReadback(FrameManager frameManager, ResourceManager resourceManager, PipelineStore pipelineStore, ShaderManager shaderManager, FBOClearTracker fboClearTracker) {
this.frameManager = frameManager;
this.resourceManager = resourceManager;
this.pipelineStore = pipelineStore;
this.fboClearTracker = fboClearTracker;
this.fullscreen = new FullscreenPass(shaderManager);
}
private final class Builder implements LongSupplier {
@Override public long getAsLong() { return buildBit < 0 ? createDepthPipeline() : createStencilPipeline(buildBit, buildDepthFormat); }
}
public long renderDepth(ContextState st, long depthTexture, int depthSdlFormat, int width, int height) {
final FrameState f = frameManager.frame();
if (!canRender(f, depthTexture, width, height)) return 0L;
if (depthTexture == resourceManager.getSwapchainDepthStencil() && !resourceManager.isSwapchainDepthStencilSampleable()) return 0L;
final long pipeline = getDepthPipeline();
final long sampler = getNearestSampler();
if (pipeline == 0L || sampler == 0L) return 0L;
applyPendingClears(st, f, depthTexture);
final long scratch = ensureDepthScratch(st, width, height);
frameManager.endRenderPassIfActive(f, FrameManager.PASS_END_READBACK);
try (MemoryStack stack = stackPush()) {
final SDL_GPUColorTargetInfo.Buffer color = colorTarget(stack, scratch, SDL_GPU_LOADOP_DONT_CARE);
final long rp = frameManager.beginRenderPass(color, null);
SDL_BindGPUGraphicsPipeline(rp, pipeline);
FullscreenPass.setViewport(rp, st, width, height, 0f);
FullscreenPass.setScissor(rp, st, 0, 0, width, height);
final SDL_GPUTextureSamplerBinding.Buffer binding = SDL_GPUTextureSamplerBinding.calloc(1, stack);
binding.get(0).texture(depthTexture).sampler(sampler);
SDL_BindGPUFragmentSamplers(rp, 0, binding);
SDL_DrawGPUPrimitives(rp, 3, 1, 0, 0);
frameManager.endRenderPassIfActive(f, FrameManager.PASS_END_READBACK);
}
FullscreenPass.markStateClobbered(st);
return scratch;
}
public long renderStencil(ContextState st, long depthStencilTexture, int depthSdlFormat, int width, int height) {
final FrameState f = frameManager.frame();
if (!canRender(f, depthStencilTexture, width, height)) return 0L;
if (!ensureStencilPipelines(depthSdlFormat)) return 0L;
applyPendingClears(st, f, depthStencilTexture);
final long scratch = ensureStencilScratch(st, width, height);
frameManager.endRenderPassIfActive(f, FrameManager.PASS_END_READBACK);
try (MemoryStack stack = stackPush()) {
final SDL_GPUColorTargetInfo.Buffer color = colorTarget(stack, scratch, SDL_GPU_LOADOP_CLEAR);
final SDL_GPUDepthStencilTargetInfo ds = SDL_GPUDepthStencilTargetInfo.calloc(stack);
final long dsAddr = ds.address();
MemoryAccess.putAddress(dsAddr + SDL_GPUDepthStencilTargetInfo.TEXTURE, depthStencilTexture);
MemoryAccess.putInt(dsAddr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, SDL_GPU_LOADOP_LOAD);
MemoryAccess.putInt(dsAddr + SDL_GPUDepthStencilTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
MemoryAccess.putInt(dsAddr + SDL_GPUDepthStencilTargetInfo.STENCIL_LOAD_OP, SDL_GPU_LOADOP_LOAD);
MemoryAccess.putInt(dsAddr + SDL_GPUDepthStencilTargetInfo.STENCIL_STORE_OP, SDL_GPU_STOREOP_STORE);
final long rp = frameManager.beginRenderPass(color, ds);
final long cb = frameManager.getCommandBuffer(f);
FullscreenPass.setViewport(rp, st, width, height, 0f);
FullscreenPass.setScissor(rp, st, 0, 0, width, height);
final ByteBuffer bitValue = stack.calloc(16);
for (int b = 0; b < STENCIL_BITS; b++) {
SDL_BindGPUGraphicsPipeline(rp, stencilPipelines[b]);
SDL_SetGPUStencilReference(rp, (byte) (1 << b));
bitValue.putFloat(0, (1 << b) / 255.0f);
SDL_PushGPUFragmentUniformData(cb, 0, bitValue);
SDL_DrawGPUPrimitives(rp, 3, 1, 0, 0);
}
frameManager.endRenderPassIfActive(f, FrameManager.PASS_END_READBACK);
}
FullscreenPass.markStateClobbered(st);
st.lastPushedProgramFs = -1;
return scratch;
}
private static boolean canRender(FrameState f, long texture, int width, int height) {
return f.frameActive && f.commandBuffer != 0L && texture != 0L && width > 0 && height > 0;
}
private void applyPendingClears(ContextState st, FrameState f, long texture) {
fboClearTracker.materializePendingClearForRead(st, f, texture);
}
private static SDL_GPUColorTargetInfo.Buffer colorTarget(MemoryStack stack, long texture, int loadOp) {
final SDL_GPUColorTargetInfo.Buffer color = SDL_GPUColorTargetInfo.calloc(1, stack);
final long addr = color.get(0).address();
MemoryAccess.putAddress(addr + SDL_GPUColorTargetInfo.TEXTURE, texture);
MemoryAccess.putInt(addr + SDL_GPUColorTargetInfo.LOAD_OP, loadOp);
MemoryAccess.putInt(addr + SDL_GPUColorTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
final long ccAddr = addr + SDL_GPUColorTargetInfo.CLEAR_COLOR;
MemoryAccess.putFloat(ccAddr + SDL_FColor.R, 0f);
MemoryAccess.putFloat(ccAddr + SDL_FColor.G, 0f);
MemoryAccess.putFloat(ccAddr + SDL_FColor.B, 0f);
MemoryAccess.putFloat(ccAddr + SDL_FColor.A, 0f);
return color;
}
private long ensureDepthScratch(ContextState st, int width, int height) {
if (st.readbackDepthScratch == 0L || st.readbackDepthScratchWidth != width || st.readbackDepthScratchHeight != height) {
if (st.readbackDepthScratch != 0L) resourceManager.releaseTextureDeferred(st.readbackDepthScratch);
st.readbackDepthScratch = resourceManager.createScratchTexture(DEPTH_TARGET_FORMAT, width, height, SDL_GPU_TEXTUREUSAGE_COLOR_TARGET);
st.readbackDepthScratchWidth = width;
st.readbackDepthScratchHeight = height;
}
return st.readbackDepthScratch;
}
private long ensureStencilScratch(ContextState st, int width, int height) {
if (st.readbackStencilScratch == 0L || st.readbackStencilScratchWidth != width || st.readbackStencilScratchHeight != height) {
if (st.readbackStencilScratch != 0L) resourceManager.releaseTextureDeferred(st.readbackStencilScratch);
st.readbackStencilScratch = resourceManager.createScratchTexture(STENCIL_TARGET_FORMAT, width, height, SDL_GPU_TEXTUREUSAGE_COLOR_TARGET);
st.readbackStencilScratchWidth = width;
st.readbackStencilScratchHeight = height;
}
return st.readbackStencilScratch;
}
private long getDepthPipeline() {
if (depthPipeline != 0L) return depthPipeline;
if (!ensureDepthShader()) return 0L;
buildBit = -1;
buildDepthFormat = 0;
depthPipeline = pipelineStore.getOrBuild(Hashing.fmix64(KEY_SEED, -1), builder);
return depthPipeline;
}
private boolean ensureStencilPipelines(int depthFormat) {
if (depthFormat == 0) return false;
if (stencilPipelinesFormat == depthFormat) return true;
if (!ensureStencilShader()) return false;
buildDepthFormat = depthFormat;
final long formatKey = Hashing.fmix64(KEY_SEED, depthFormat);
for (int b = 0; b < STENCIL_BITS; b++) {
buildBit = b;
final long pipeline = pipelineStore.getOrBuild(Hashing.fmix64(formatKey, b), builder);
if (pipeline == 0L) {
stencilPipelinesFormat = 0;
return false;
}
stencilPipelines[b] = pipeline;
}
stencilPipelinesFormat = depthFormat;
return true;
}
private long createDepthPipeline() {
try (MemoryStack stack = stackPush()) {
final SDL_GPUColorTargetDescription.Buffer colorDesc = SDL_GPUColorTargetDescription.calloc(1, stack);
colorDesc.get(0).format(DEPTH_TARGET_FORMAT).blend_state().enable_blend(false);
final SDL_GPUDepthStencilState depthStencil = SDL_GPUDepthStencilState.calloc(stack)
.compare_op(SDL_GPU_COMPAREOP_ALWAYS);
return fullscreen.createPipeline(pipelineStore, stack, depthFragmentShader, colorDesc, 1, depthStencil, 0, "DepthStencilReadback depth");
}
}
private long createStencilPipeline(int bit, int depthFormat) {
try (MemoryStack stack = stackPush()) {
final SDL_GPUColorTargetDescription.Buffer colorDesc = SDL_GPUColorTargetDescription.calloc(1, stack);
colorDesc.get(0).format(STENCIL_TARGET_FORMAT).blend_state()
.enable_blend(true)
.src_color_blendfactor(SDL_GPU_BLENDFACTOR_ONE)
.dst_color_blendfactor(SDL_GPU_BLENDFACTOR_ONE)
.color_blend_op(SDL_GPU_BLENDOP_ADD)
.src_alpha_blendfactor(SDL_GPU_BLENDFACTOR_ONE)
.dst_alpha_blendfactor(SDL_GPU_BLENDFACTOR_ONE)
.alpha_blend_op(SDL_GPU_BLENDOP_ADD);
final SDL_GPUDepthStencilState depthStencil = SDL_GPUDepthStencilState.calloc(stack)
.enable_depth_test(false)
.enable_depth_write(false)
.compare_op(SDL_GPU_COMPAREOP_ALWAYS)
.enable_stencil_test(true)
.compare_mask((byte) (1 << bit))
.write_mask((byte) 0);
depthStencil.front_stencil_state()
.fail_op(SDL_GPU_STENCILOP_KEEP)
.pass_op(SDL_GPU_STENCILOP_KEEP)
.depth_fail_op(SDL_GPU_STENCILOP_KEEP)
.compare_op(SDL_GPU_COMPAREOP_EQUAL);
depthStencil.back_stencil_state()
.fail_op(SDL_GPU_STENCILOP_KEEP)
.pass_op(SDL_GPU_STENCILOP_KEEP)
.depth_fail_op(SDL_GPU_STENCILOP_KEEP)
.compare_op(SDL_GPU_COMPAREOP_EQUAL);
return fullscreen.createPipeline(pipelineStore, stack, stencilFragmentShader, colorDesc, 1, depthStencil, depthFormat, "DepthStencilReadback stencil");
}
}
private boolean ensureDepthShader() {
if (depthFragmentShader != 0L) return true;
if (depthFragmentFailed) return false;
depthFragmentFailed = true;
depthFragmentShader = fullscreen.compileFragment(ShaderLoader.getShaderSource(DEPTH_FRAGMENT_SOURCE), -1, "DepthStencilReadback: readback_depth");
if (depthFragmentShader == 0L) return false;
depthFragmentFailed = false;
return true;
}
private boolean ensureStencilShader() {
if (stencilFragmentShader != 0L) return true;
if (stencilFragmentFailed) return false;
stencilFragmentFailed = true;
stencilFragmentShader = fullscreen.compileFragment(ShaderLoader.getShaderSource(STENCIL_FRAGMENT_SOURCE), 16, "DepthStencilReadback: readback_stencil");
if (stencilFragmentShader == 0L) return false;
stencilFragmentFailed = false;
return true;
}
private long getNearestSampler() {
if (nearestSampler != 0L) return nearestSampler;
try (MemoryStack stack = stackPush()) {
final SDL_GPUSamplerCreateInfo ci = SDL_GPUSamplerCreateInfo.calloc(stack)
.min_filter(SDL_GPU_FILTER_NEAREST)
.mag_filter(SDL_GPU_FILTER_NEAREST)
.mipmap_mode(SDL_GPU_SAMPLERMIPMAPMODE_NEAREST)
.address_mode_u(SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE)
.address_mode_v(SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE)
.address_mode_w(SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE);
nearestSampler = SDL_CreateGPUSampler(pipelineStore.device().getDevice(), ci);
if (nearestSampler == 0L) LOG.error("DepthStencilReadback: sampler creation failed: {}", SDLError.SDL_GetError());
}
return nearestSampler;
}
public void release(ContextState st) {
if (st.readbackDepthScratch != 0L) resourceManager.releaseTextureHandle(st.readbackDepthScratch);
st.readbackDepthScratch = 0L;
st.readbackDepthScratchWidth = 0;
st.readbackDepthScratchHeight = 0;
if (st.readbackStencilScratch != 0L) resourceManager.releaseTextureHandle(st.readbackStencilScratch);
st.readbackStencilScratch = 0L;
st.readbackStencilScratchWidth = 0;
st.readbackStencilScratchHeight = 0;
}
public void shutdown() {
final long dev = pipelineStore.device().getDevice();
if (dev != 0L) {
if (depthFragmentShader != 0L) SDL_ReleaseGPUShader(dev, depthFragmentShader);
if (stencilFragmentShader != 0L) SDL_ReleaseGPUShader(dev, stencilFragmentShader);
if (fullscreen.vertexShader() != 0L) SDL_ReleaseGPUShader(dev, fullscreen.vertexShader());
if (nearestSampler != 0L) SDL_ReleaseGPUSampler(dev, nearestSampler);
}
fullscreen.shutdown();
depthFragmentShader = 0L;
depthFragmentFailed = false;
stencilFragmentShader = 0L;
stencilFragmentFailed = false;
nearestSampler = 0L;
depthPipeline = 0L;
Arrays.fill(stencilPipelines, 0L);
stencilPipelinesFormat = 0;
}
}
Loaded 100 of 397 files, more files were not shown because too many files have changed in this diff. Show more