Compare commits

..
16 Commits
Author SHA1 Message Date
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
Jason Mitchell 1babfd968c Properly reset/flush state for crash interceptors (#2133) 2026-09-25 20:43:05 -07:00
Eclipse-Sol 888a4de0c9 Add Galaxy Space compat and fix culling issues (#2131) 2026-09-25 19:18:45 -07:00
DarkShadow44 a23cefba92 Only enable shader DH compat if DH is enabled (#2130) 2026-09-23 17:42:45 -07:00
Eclipse-Sol f2c07dea28 Weather instancing (#2127)
* Initial commit for weather-instancing

* attempt to make the builder less ugly
2026-09-22 19:27:05 -04:00
372 changed files with 19802 additions and 4097 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
+1
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"))
@@ -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");
@@ -62,6 +62,7 @@ public final class SystemProperties {
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
public static final boolean FLYBY_JFR = Boolean.getBoolean("angelica.flyby.jfr");
// Debug
@@ -77,6 +78,7 @@ public final class SystemProperties {
public static final boolean FFP_TRACE = Boolean.getBoolean("angelica.debug.ffpTrace");
public static final boolean LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.debug.displayLists.compilation");
public static final boolean FORCE_ORPHAN_STREAMING = Boolean.getBoolean("angelica.debug.forceOrphanStreaming");
public static final boolean WEATHER_REBUILD_ALWAYS = Boolean.getBoolean("angelica.debug.weatherRebuildAlways");
public static final String SHADER_DUMP_ROOT = "angelica_dumps";
// Set by us, read by celeritas
@@ -125,6 +127,20 @@ public final class SystemProperties {
CONFIGURED
}
public enum FlybyWeather {
CLEAR,
RAIN,
THUNDER;
public boolean isRaining() {
return this != CLEAR;
}
public boolean isThundering() {
return this == THUNDER;
}
}
private static <E extends Enum<E>> E parseEnum(String key, E whenAbsent, E whenInvalid, Class<E> type) {
final String raw = System.getProperty(key);
if (raw == null || raw.isEmpty()) return whenAbsent;
@@ -136,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,31 @@ 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 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 +273,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 +288,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);
@@ -19,6 +19,7 @@ import org.lwjgl.opengl.GL43;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Arrays;
public final class GLDebug {
@@ -586,6 +587,13 @@ public final class GLDebug {
}
}
public static void resetGroupStack() {
if (debugState instanceof KHRDebugState khr) {
khr.depth = 0;
Arrays.fill(khr.groupStack, null);
}
}
public static void debugMessage(String message) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.debugMessage(message);
File diff suppressed because it is too large. Load diff
@@ -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;
@@ -205,6 +206,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);
@@ -242,6 +242,8 @@ public abstract class RenderBackend {
}
}
public void invalidateStateMirror() {}
public void onFrameEnd() {}
/** Fired by lwjgl3ify Display before a mutation that recreates the swapchain. */
@@ -391,6 +393,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);
}
}
@@ -107,6 +107,12 @@ public final class FFPUniformBlock {
public static final int FOG_PARAMS = vec4("u_FogParams");
public static final int FOG_COLOR = vec4("u_FogColor");
// Rain and snow, for Instancing.WEATHER. 0 = (translate.xyz, 1/radius),
// 1 = (cameraFrac.xy, partialTicks, age), 2 = (rainScroll, snowScroll, rainStrength, unused).
public static final int WEATHER_PARAMS_0 = vec4("u_WeatherParams0");
public static final int WEATHER_PARAMS_1 = vec4("u_WeatherParams1");
public static final int WEATHER_PARAMS_2 = vec4("u_WeatherParams2");
public static final int SIZE;
public static final String GLSL_DECL;
@@ -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) {
@@ -34,6 +34,62 @@ public final class InstancedGlslHelpers {
+ "0.0, 1.0)";
}
// wHalfWidth is vanilla's rainXCoords table.
private static final String WEATHER_PROLOGUE = """
vec2 {p}wColumn = {span}.xy;
float {p}wBottom = {span}.z;
float {p}wTop = {span}.w;
vec2 {p}wLightmap = {params}.xy;
float {p}wHash = {params}.z;
bool {p}wSnow = {params}.w > 0.5;
vec3 {p}wTranslate = {p}WeatherParams0.xyz;
float {p}wInvRadius = {p}WeatherParams0.w;
vec2 {p}wCameraFrac = {p}WeatherParams1.xy;
float {p}wPartialTicks = {p}WeatherParams1.z;
float {p}wAge = {p}WeatherParams1.w;
float {p}wRainScroll = {p}WeatherParams2.x;
float {p}wSnowScroll = {p}WeatherParams2.y;
float {p}wStrength = {p}WeatherParams2.z;
vec2 {p}wHalfWidth = vec2(-{p}wColumn.y, {p}wColumn.x) * (0.5 / sqrt(dot({p}wColumn, {p}wColumn)));
vec2 {p}wCornerXZ = {p}wColumn + vec2(0.5) + ({corner}.x * 2.0 - 1.0) * {p}wHalfWidth;
float {p}wCornerY = mix({p}wBottom, {p}wTop, {corner}.y);
{vertex} = vec4(vec3({p}wCornerXZ.x, {p}wCornerY, {p}wCornerXZ.y) + {p}wTranslate, 1.0);
float {p}wDistance = length({p}wColumn + vec2(0.5) - {p}wCameraFrac) * {p}wInvRadius;
float {p}wFade = 1.0 - {p}wDistance * {p}wDistance;
float {p}wScroll;
vec2 {p}wDrift;
float {p}wAlpha;
if ({p}wSnow) {
{p}wScroll = ({p}wSnowScroll + {p}wPartialTicks) / 512.0;
{p}wDrift = vec2({jitter}.x + {p}wAge * 0.01 * {jitter}.y, {jitter}.z + {p}wAge * {jitter}.w * 0.001);
{p}wAlpha = {p}wFade * 0.3 + 0.5;
} else {
{p}wScroll = (mod({p}wRainScroll + {p}wHash, 32.0) + {p}wPartialTicks) / 32.0 * (3.0 + {jitter}.x);
{p}wDrift = vec2(0.0);
{p}wAlpha = {p}wFade * 0.5 + 0.5;
}
{tex0} = vec4({corner}.x + {p}wDrift.x, {p}wCornerY * 0.25 + {p}wScroll + {p}wDrift.y, 0.0, 1.0);
{tex1} = vec4({p}wLightmap, 0.0, 1.0);
{color} = vec4(1.0, 1.0, 1.0, {p}wAlpha * {p}wStrength);""";
public static String weatherPrologue(String uniformPrefix, String attribPrefix, String corner,
String vertex, String color, String tex0, String tex1, String sep) {
return WEATHER_PROLOGUE
.replace("{p}", uniformPrefix)
.replace("{span}", attribPrefix + "InstColumnSpan")
.replace("{jitter}", attribPrefix + "InstJitter")
.replace("{params}", attribPrefix + "InstWeatherParams")
.replace("{corner}", corner)
.replace("{vertex}", vertex)
.replace("{color}", color)
.replace("{tex0}", tex0)
.replace("{tex1}", tex1)
.replace("\n", sep);
}
public static String cubePrelude(String prefix, String mid, String delta, String normal, String tex, String sep) {
return "float " + prefix + "cubeMirror = " + tex + ".z < 0.0 ? 1.0 : 0.0;" + sep
+ "vec2 " + prefix + "cubeMidU = " + mid + ".xy - " + prefix + "cubeMirror * " + normal + ".x;" + sep
@@ -65,6 +121,11 @@ public final class InstancedGlslHelpers {
decls.add(decl(ParticleInstancedAttribs.LOC_COLOR, "vec4", prefix + "InstColor"));
decls.add(decl(ParticleInstancedAttribs.LOC_LIGHTMAP, "vec2", prefix + "InstLightmap"));
}
case WEATHER -> {
decls.add(decl(WeatherInstancedAttribs.LOC_COLUMN_SPAN, "vec4", prefix + "InstColumnSpan"));
decls.add(decl(WeatherInstancedAttribs.LOC_JITTER, "vec4", prefix + "InstJitter"));
decls.add(decl(WeatherInstancedAttribs.LOC_PARAMS, "vec4", prefix + "InstWeatherParams"));
}
}
return decls.toArray(String[]::new);
}
@@ -4,7 +4,8 @@ public enum Instancing {
NONE(""),
TEMPLATE("_instanced"),
CUBE("_cubeinstanced"),
PARTICLE("_particleinstanced");
PARTICLE("_particleinstanced"),
WEATHER("_weatherinstanced");
public static final Instancing[] VALUES = values();
@@ -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,13 +110,21 @@ public final class ShaderManager {
}
}
public void disable() {
public static void disable() {
enabled = false;
}
public void activate() {
active = true;
lastBoundProgramId = GLStateManager.getActiveProgram();
invalidateProgram();
}
/** 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() {
@@ -130,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);
@@ -166,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++;
@@ -185,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;
@@ -49,6 +48,7 @@ public class Uniforms {
private float lightmapX = Float.NaN, lightmapY = Float.NaN, lineWidth = Float.NaN;
private int viewportX = Integer.MIN_VALUE, viewportY = Integer.MIN_VALUE, viewportWidth = -1, viewportHeight = -1;
private int lineStipple = -1;
private int weatherGen = -1;
int blockWrites;
int blockSkips;
@@ -124,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);
@@ -192,6 +193,12 @@ public class Uniforms {
stagedMisc++;
dirty = true;
}
if (WeatherParams.generation != weatherGen) {
stageWeatherParams();
weatherGen = WeatherParams.generation;
stagedMisc++;
dirty = true;
}
final int stippleFactor = Math.max(line.getStippleFactor(), 1);
final int stipplePacked = (line.getStipplePattern() & 0xFFFF) | (stippleFactor << 16);
if (stipplePacked != lineStipple) {
@@ -379,6 +386,15 @@ public class Uniforms {
putVec3(offset + 32, m.m20(), m.m21(), m.m22());
}
private void stageWeatherParams() {
putVec4(FFPUniformBlock.WEATHER_PARAMS_0, WeatherParams.translateX, WeatherParams.translateY,
WeatherParams.translateZ, WeatherParams.invRadius);
putVec4(FFPUniformBlock.WEATHER_PARAMS_1, WeatherParams.cameraFracX, WeatherParams.cameraFracZ,
WeatherParams.partialTicks, WeatherParams.age);
putVec4(FFPUniformBlock.WEATHER_PARAMS_2, WeatherParams.rainScroll, WeatherParams.snowScroll,
WeatherParams.rainStrength, 0.0f);
}
private void putVec4(int offset, Vector4f v) {
putVec4(offset, v.x, v.y, v.z, v.w);
}
@@ -430,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]);
}
@@ -51,6 +51,13 @@ public final class VertexKey {
private static final int BIT_UNIT23_UV_FROM_UNIT0 = BIT_UNIT_TEXMAT_BASE + MAX_UNITS;
private static final int BIT_LINE_STIPPLE = BIT_UNIT23_UV_FROM_UNIT0 + 1;
private static final int BIT_INSTANCING = BIT_LINE_STIPPLE + 1;
private static final long INSTANCING_MASK = 0x7L;
static {
if (Instancing.VALUES.length > INSTANCING_MASK + 1) {
throw new IllegalStateException("Instancing has outgrown the VertexKey field; widen INSTANCING_MASK");
}
}
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
@@ -98,7 +105,7 @@ public final class VertexKey {
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & 0x3)]; }
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & INSTANCING_MASK)]; }
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
@@ -231,21 +238,21 @@ 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;
}
static long withInstancing(long packed, Instancing kind) {
return (packed & ~(0x3L << BIT_INSTANCING)) | ((long) kind.ordinal() << BIT_INSTANCING);
return (packed & ~(INSTANCING_MASK << BIT_INSTANCING)) | ((long) kind.ordinal() << BIT_INSTANCING);
}
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
@@ -21,6 +21,10 @@ public final class VertexShaderGenerator {
emitOutputs(sb, key);
sb.append("void main() {\n");
if (key.instancing() == Instancing.WEATHER) {
sb.append(" ").append(InstancedGlslHelpers.weatherPrologue("u_", "a_", "a_TexCoord0",
"vec4 weatherVertex", "vec4 weatherColor", "vec4 weatherTex0", "vec4 weatherTex1", "\n ")).append('\n');
}
emitPositionTransform(sb, key);
if (key.lightingEnabled()) {
emitNormalTransform(sb, key);
@@ -58,7 +62,7 @@ public final class VertexShaderGenerator {
final Instancing kind = key.instancing();
sb.append("// Vertex attributes\n");
sb.append("layout(location = ").append(VertexFormatElement.Usage.POSITION.getAttributeLocation()).append(") in vec3 a_Position;\n");
if (kind == Instancing.PARTICLE) {
if (kind == Instancing.PARTICLE || kind == Instancing.WEATHER) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation()).append(") in vec2 a_TexCoord0;\n");
} else {
if (key.hasVertexColor()) {
@@ -88,6 +92,7 @@ public final class VertexShaderGenerator {
return switch (key.instancing()) {
case CUBE -> "cubeUv";
case PARTICLE -> PARTICLE_UV;
case WEATHER -> "weatherTex0";
case NONE, TEMPLATE -> key.hasVertexTexCoord() ? "vec4(a_TexCoord0, 0.0, 1.0)" : "u_CurrentTexCoord0";
};
}
@@ -134,6 +139,8 @@ public final class VertexShaderGenerator {
sb.append(" // Position transform\n");
if (key.instancing() == Instancing.PARTICLE) {
sb.append(" vec4 pos4 = ").append(InstancedGlslHelpers.particlePos("a_InstCenterHalf", "a_Position")).append(";\n");
} else if (key.instancing() == Instancing.WEATHER) {
sb.append(" vec4 pos4 = weatherVertex;\n");
} else {
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
}
@@ -282,6 +289,7 @@ public final class VertexShaderGenerator {
final String src = key.hasVertexColor() ? "a_Color" : "u_CurrentColor";
switch (key.instancing()) {
case PARTICLE -> sb.append(" v_Color = a_InstColor;\n");
case WEATHER -> sb.append(" v_Color = weatherColor;\n");
case TEMPLATE, CUBE -> sb.append(" v_Color = ").append(src).append(" * a_InstColor;\n");
case NONE -> sb.append(" v_Color = ").append(src).append(";\n");
}
@@ -312,6 +320,7 @@ public final class VertexShaderGenerator {
final String lightmap = switch (key.instancing()) {
case PARTICLE, TEMPLATE -> "a_InstLightmap";
case CUBE -> "a_InstLightmapScale.xy";
case WEATHER -> "weatherTex1.xy";
case NONE -> key.hasVertexLightmap() ? "a_TexCoord1" : "u_CurrentLightmapCoord";
};
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(").append(lightmap).append(", 0.0, 1.0);\n");
@@ -0,0 +1,34 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
/**
* One rain or snow column per instance.
*/
public final class WeatherInstancedAttribs {
public static final int LOC_COLUMN_SPAN = ParticleInstancedAttribs.LOC_CENTER_HALF;
public static final int LOC_JITTER = ParticleInstancedAttribs.LOC_UV;
public static final int LOC_PARAMS = InstancedAttribs.LOC_COLOR;
public static final int OFFSET_COLUMN_SPAN = 0;
public static final int OFFSET_JITTER = 16;
public static final int OFFSET_PARAMS = 32;
public static final int STRIDE = 48;
public static void pointInstanceAttribs(long base) {
GLStateManager.glVertexAttribPointer(LOC_COLUMN_SPAN, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_COLUMN_SPAN);
GLStateManager.glVertexAttribPointer(LOC_JITTER, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_JITTER);
GLStateManager.glVertexAttribPointer(LOC_PARAMS, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_PARAMS);
}
static void enableInstanceArrays() {
InstancedAttribs.enableInstanced(LOC_COLUMN_SPAN);
InstancedAttribs.enableInstanced(LOC_JITTER);
InstancedAttribs.enableInstanced(LOC_PARAMS);
}
private WeatherInstancedAttribs() {}
}
@@ -0,0 +1,42 @@
package com.gtnewhorizons.angelica.glsm.ffp;
/**
* The per-frame half of the rain and snow instancing, set once per weather draw.
*/
public final class WeatherParams {
public static float translateX;
public static float translateY;
public static float translateZ;
public static float invRadius;
public static float cameraFracX;
public static float cameraFracZ;
public static float partialTicks;
public static float age;
public static float rainScroll;
public static float snowScroll;
public static float rainStrength;
public static int generation;
private WeatherParams() {}
public static void set(float translateX, float translateY, float translateZ, float invRadius,
float cameraFracX, float cameraFracZ, float partialTicks, float age,
float rainScroll, float snowScroll, float rainStrength) {
WeatherParams.translateX = translateX;
WeatherParams.translateY = translateY;
WeatherParams.translateZ = translateZ;
WeatherParams.invRadius = invRadius;
WeatherParams.cameraFracX = cameraFracX;
WeatherParams.cameraFracZ = cameraFracZ;
WeatherParams.partialTicks = partialTicks;
WeatherParams.age = age;
WeatherParams.rainScroll = rainScroll;
WeatherParams.snowScroll = snowScroll;
WeatherParams.rainStrength = rainStrength;
generation++;
}
}
@@ -0,0 +1,69 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* The four corners every rain and snow column is expanded from. x picks the end of the billboard, y picks
* the bottom or top edge.
*/
public final class WeatherQuadMesh {
public static final int VERTEX_FLAGS = VertexFlags.TEXTURE_BIT;
public static final int VERTEX_COUNT = 4;
private static final float[] CORNERS = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f };
private static int vao;
private static int vbo;
private WeatherQuadMesh() {}
public static int vao() {
if (vao != 0) return vao;
final ByteBuffer data = ByteBuffer.allocateDirect(CORNERS.length * Float.BYTES).order(ByteOrder.nativeOrder());
for (float corner : CORNERS) data.putFloat(corner);
data.flip();
vao = GLStateManager.glGenVertexArrays();
GLStateManager.glBindVertexArray(vao);
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
final int corner = VertexFormatElement.Usage.PRIMARY_UV.getAttributeLocation();
GLStateManager.glEnableVertexAttribArray(corner);
GLStateManager.glVertexAttribPointer(corner, 2, GL11.GL_FLOAT, false, 2 * Float.BYTES, 0L);
WeatherInstancedAttribs.enableInstanceArrays();
GLStateManager.glBindVertexArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
return vao;
}
public static void bindInstanceBuffer(int bufferId) {
GLStateManager.glBindVertexArray(vao());
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
WeatherInstancedAttribs.pointInstanceAttribs(0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
}
public static void delete() {
if (vbo != 0) {
GLStateManager.glDeleteBuffers(vbo);
vbo = 0;
}
if (vao != 0) {
GLStateManager.glDeleteVertexArrays(vao);
vao = 0;
}
}
}
@@ -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,17 +1,17 @@
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;
@@ -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;
@@ -11,22 +11,20 @@ public final class TextureUtilHooks {
private TextureUtilHooks() {}
private static int savedFilterTextureId;
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();
GLStateManager.ctx().savedFilterTextureId = GLStateManager.getBoundTextureForServerState();
}
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,7 +35,7 @@ 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 {
@@ -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;
@@ -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;
}
}
@@ -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,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());
}
}
}
@@ -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), 1e-6f, () -> 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
+2 -1
View File
@@ -14,8 +14,9 @@ PLOTS = [
"items.bail.ineligible", "items.bail.material", "items.bail.isbrh", "items.bail.blockState", "items.bail.notAllowed", "items.bail.template", "items.bail.queue",
"particles.direct", "particles.captured", "particles.spilled", "particles.undecodable", "particles.draws", "sdl.renderPasses", "sdl.submits", "sdl.gpuWaitUs",
"sdl.acquireWaitUs",
"weather.strength", "weather.quads", "weather.drawCalls", "weather.micros",
]
ZONES = ["entityModelParts", "tesrBatch", "tesrInstanced", "entityLayerLoop", "tesrOpaque", "particlePass"]
ZONES = ["entityModelParts", "tesrBatch", "tesrInstanced", "entityLayerLoop", "tesrOpaque", "particlePass", "cl:weather"]
CLOCK = "gl.drawCalls"
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.sdlgpu.compute.VoxelizationDispatcher;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CaptureGate;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
@@ -24,6 +25,7 @@ import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
import com.gtnewhorizons.angelica.sdlgpu.frame.Presenter;
import com.gtnewhorizons.retrofuturabootstrap.MainStartOnFirstThread;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.AttachmentClear;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.DepthStencilReadback;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.DrawDispatch;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.FFPTrace;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineApplier;
@@ -38,8 +40,10 @@ import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.FormatMap;
import com.gtnewhorizons.angelica.sdlgpu.resource.Image3DClear;
import com.gtnewhorizons.angelica.sdlgpu.resource.PersistentBufferSync;
import com.gtnewhorizons.angelica.sdlgpu.resource.PackState;
import com.gtnewhorizons.angelica.sdlgpu.resource.PersistentMapping;
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelPack;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.ReadbackShadows;
import com.gtnewhorizons.angelica.sdlgpu.resource.TextureOps;
@@ -153,6 +157,7 @@ public class SDLGPURenderBackend extends RenderBackend {
});
private final PipelineApplier pipelineApplier = new PipelineApplier(frameManager, resourceManager, shaderManager, pipelineStore, fboClearTracker, persistentSync, samplerBinder, storageTextureBinder, storageBufferBinder);
private final AttachmentClear attachmentClear = new AttachmentClear(frameManager, pipelineStore, shaderManager);
private final DepthStencilReadback depthStencilReadback = new DepthStencilReadback(frameManager, resourceManager, pipelineStore, shaderManager, fboClearTracker);
private final DrawDispatch drawDispatch = new DrawDispatch(device, frameManager, resourceManager, pipelineApplier, this::enqueuePreCopied);
private final FFPTrace ffpTrace = SystemProperties.FFP_TRACE ? new FFPTrace(resourceManager) : null;
@@ -162,6 +167,7 @@ public class SDLGPURenderBackend extends RenderBackend {
private boolean droppedSsboWriteWarned;
private boolean depthClearSuppressedWarned;
private boolean stencilClearSuppressedWarned;
private boolean partialClearFallbackWarned;
private final DeferredCopyOp deferredCopyOp = new DeferredCopyOp();
private TransferThread transferThread;
@@ -196,6 +202,10 @@ public class SDLGPURenderBackend extends RenderBackend {
for (ContextState st : registeredStates.snapshot()) st.pipeline.markInputDirty();
}
@Override public void invalidateStateMirror() {
s().invalidateMirror();
}
public static final int CTR_SLOT_WRITES = 0;
public static final int CTR_SLOT_WRITES_ELIDED = 1;
public static final int CTR_SSBO_BINDS = 2;
@@ -488,6 +498,14 @@ public class SDLGPURenderBackend extends RenderBackend {
if (st.cachedViewport != null) { st.cachedViewport.free(); st.cachedViewport = null; }
if (st.cachedScissor != null) { st.cachedScissor.free(); st.cachedScissor = null; }
if (st.cachedBlendColor != null) { st.cachedBlendColor.free(); st.cachedBlendColor = null; }
for (int i = 0; i < st.logicOpScratch.length; i++) {
if (st.logicOpScratch[i] != 0L) resourceManager.releaseTextureHandle(st.logicOpScratch[i]);
st.logicOpScratch[i] = 0L;
}
depthStencilReadback.release(st);
st.activeLogicOpVariant = null;
st.lastFragSamplerVariant = null;
st.appliedLogicOpKey = 0;
st.fragSamplerBindings.free();
st.vertSamplerBindings.free();
if (st.mappedStagingBuffer != null) {
@@ -499,6 +517,8 @@ public class SDLGPURenderBackend extends RenderBackend {
drawDispatch.clearWarnedState();
attachmentClear.shutdown();
depthStencilReadback.shutdown();
textureOps.shutdown();
pipelineStore.shutdown();
shaderManager.shutdown();
resourceManager.shutdown();
@@ -536,11 +556,12 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void onRenderThreadReleased(Thread t) {
if (shutdown || t == GLStateManager.getMainThread()) return;
final ContextState st = tlState.get();
if (frameManager.isFrameActive()) {
if (st != null) fboClearTracker.materializeAllPendingClears(st);
frameManager.endFrame();
}
frameManager.releaseThreadState();
final ContextState st = tlState.get();
if (st != null) {
tlState.remove();
registeredStates.remove(st);
@@ -550,14 +571,34 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public boolean handleMakeCurrent(Object drawable) {
if (!isSDLManagedDrawable(drawable)) return false;
Lwjgl3GLCapabilitiesShim.installOnCurrentThread(advertisedCapabilities());
final OffscreenTarget splash = splashTarget;
if (splash != null && Thread.currentThread() != GLStateManager.getMainThread()) {
s().boundFboId = splash.fboId();
frameManager.frame().swapchainUnavailable = true;
}
enterContext(drawable instanceof SDLDrawable);
return true;
}
private void enterContext(boolean displayDrawable) {
final ContextState st = s();
final GLContextState ctx = GLStateManager.ctx();
if (st.mirroredContext != ctx) {
st.invalidateMirror();
st.mirroredContext = ctx;
}
final OffscreenTarget splash = splashTarget;
final boolean splashContext = displayDrawable && splash != null && Thread.currentThread() != GLStateManager.getMainThread();
st.defaultFboId = splashContext ? splash.fboId() : 0;
if (splashContext && !frameManager.isFrameActive()) {
beginSplashFrame();
} else if (st.needsSeed) {
st.needsSeed = false;
GLStateManager.replayStateToBackend();
}
}
private void beginSplashFrame() {
frameManager.beginFrame();
beginFrameInit();
frameManager.frame().swapchainUnavailable = true;
}
@Override public boolean handleReleaseContext(Object drawable) {
if (!isSDLManagedDrawable(drawable)) return false;
if (Thread.currentThread() == GLStateManager.getMainThread()) return true;
@@ -575,12 +616,11 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public boolean handleSwapBuffers() {
final OffscreenTarget splash = splashTarget;
if (splash != null && splash.isFor(s()) && Thread.currentThread() != GLStateManager.getMainThread()) {
if (frameManager.isFrameActive()) fboClearTracker.materializeAllPendingClears(s());
endFrameUploadFlush();
frameManager.endFrame();
SplashDispatcher.signalFrameReady((int) s().viewportW, (int) s().viewportH);
frameManager.beginFrame();
beginFrameInit();
frameManager.frame().swapchainUnavailable = true;
beginSplashFrame();
return true;
}
frameManager.presentFrame();
@@ -623,8 +663,6 @@ public class SDLGPURenderBackend extends RenderBackend {
GLStateManager.replayStateToBackend();
}
ResourceManager.setSingleThreadedReads(GLStateManager.isSplashComplete());
st.clearedTexturesThisFrame.clear();
st.clearedStencilTexturesThisFrame.clear();
st.pendingColorTextures.clear();
st.pendingDepthTextures.clear();
st.pendingStencilTextures.clear();
@@ -704,6 +742,7 @@ public class SDLGPURenderBackend extends RenderBackend {
frameManager.markFrameEmpty(f);
}
if (f.frameActive) fboClearTracker.materializeAllPendingClears(s());
awaitUploadFlush();
if (SystemProperties.FFP_TRACE) ffpTrace.frameEnd(f);
frameManager.endFrame();
@@ -716,6 +755,7 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void onPreSwapchainInvalidatingChange(Object change) {
super.onPreSwapchainInvalidatingChange(change);
if (presenter != null) presenter.drain();
if (frameManager.isFrameActive()) fboClearTracker.materializeAllPendingClears(s());
endFrameUploadFlush();
if (frameManager.isFrameActive()) {
frameManager.endFrame();
@@ -809,7 +849,15 @@ public class SDLGPURenderBackend extends RenderBackend {
st.scissorEnabled = on;
st.scissorDirty = true;
}
case GL11.GL_COLOR_LOGIC_OP -> {
if (cs.logicOpEnabled != on) {
cs.logicOpEnabled = on;
cs.pipeline.logicOpEnabled = on;
cs.pipeline.markOutputDirty();
}
}
case GL31.GL_PRIMITIVE_RESTART -> cs.primitiveRestartEnabled = on;
case GL30.GL_RASTERIZER_DISCARD -> cs.rasterizerDiscard = on;
}
}
@@ -950,28 +998,78 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void clearStencil(int s) { s().stencilClearValue = s & 0xFF; }
@Override public void clear(int mask) {
final ContextState cs = s();
final ContextState st = s();
if (!frameManager.isFrameActive()) return;
final boolean wantColor = (mask & GL11.GL_COLOR_BUFFER_BIT) != 0;
boolean wantColor = (mask & GL11.GL_COLOR_BUFFER_BIT) != 0;
boolean wantDepth = (mask & GL11.GL_DEPTH_BUFFER_BIT) != 0;
boolean wantStencil = (mask & GL11.GL_STENCIL_BUFFER_BIT) != 0;
if (wantDepth && !cs.pipeline.depthWriteEnabled) {
if (wantDepth && !st.pipeline.depthWriteEnabled) {
wantDepth = false;
if (!depthClearSuppressedWarned) {
depthClearSuppressedWarned = true;
LOG.warn("clear: GL_DEPTH_BUFFER_BIT requested with glDepthMask(false); clear suppressed per GL semantics");
}
}
if (wantStencil && cs.pipeline.stencilWriteMask == 0) {
if (wantStencil && st.pipeline.stencilWriteMask == 0) {
wantStencil = false;
if (!stencilClearSuppressedWarned) {
stencilClearSuppressedWarned = true;
LOG.warn("clear: GL_STENCIL_BUFFER_BIT requested with a zero glStencilMask; clear suppressed per GL semantics");
}
}
if (wantColor && (st.pipeline.colorWriteMask & 0xF) == 0) {
wantColor = false;
}
if (!wantColor && !wantDepth && !wantStencil) return;
final ContextState st = cs;
final FboState fbo;
if (st.boundFboId == 0) {
fbo = null;
} else {
fbo = resourceManager.getFbo(st.boundFboId);
if (fbo == null) return;
if (fbo.targetsDirty) fbo.recomputeTargets();
if (fbo.cachedFormatsDirty && fbo.hasAnyColor) fboClearTracker.updatePipelineCacheColorFormats(st, fbo);
}
final AttachmentClear.Target target = fbo != null ? attachmentClear.forFbo(fbo) : attachmentClear.forFbo0(cachedSwapchainFormatArray, frameManager.getFbo0Width(), frameManager.getFbo0Height());
int skipSlots = 0;
boolean colorPartial = false;
if (wantColor) {
final int colorWriteMask = st.pipeline.colorWriteMask;
final int[] formats = target.colorFormats;
final int slotCount = formats != null ? formats.length : 0;
boolean anyLive = false;
for (int i = 0; i < slotCount; i++) {
final boolean present;
if (fbo != null) {
final int db = target.drawBuffers[i];
present = db >= 0 && db < ContextState.MAX_COLOR_ATTACHMENTS && fbo.colorTextures[db] != 0;
} else {
present = i == 0;
}
if (!present) continue;
final int fm = PixelOps.colorComponentMask(formats[i]);
final int m = colorWriteMask & fm;
if (m == 0) {
skipSlots |= 1 << i;
} else {
anyLive = true;
if (m != fm) colorPartial = true;
}
}
wantColor = anyLive;
}
if (target.width > 0 && target.height > 0) {
final int coverage = attachmentClear.coverage(st, target);
if (coverage == AttachmentClear.EMPTY) return;
final boolean partial = coverage == AttachmentClear.PARTIAL || colorPartial || (wantStencil && (st.pipeline.stencilWriteMask & 0xFF) != 0xFF);
if (partial && !(wantColor && attachmentClear.hasIntegerColorTarget(target)) && clearPartial(st, fbo, target, wantColor, wantDepth, wantStencil)) {
return;
}
}
// FBO0 path: just record pending state; ensureFbo0RenderPass consumes it lazily.
if (st.boundFboId == 0) {
@@ -994,15 +1092,9 @@ public class SDLGPURenderBackend extends RenderBackend {
return;
}
final FboState fbo = resourceManager.getFbo(st.boundFboId);
if (fbo == null) return;
if (!wantColor && (wantDepth || wantStencil)) {
final FrameState f = frameManager.frame();
if (attachmentClear.eligible(st, f, fbo, wantDepth, wantStencil)
&& attachmentClear.clearInPass(st, f, fbo, wantDepth, wantStencil)) {
return;
}
final FrameState f = frameManager.frame();
if (!wantColor && !SystemProperties.SDL_DISABLE_IN_PASS_CLEAR && f.renderPass != 0 && f.activeLayoutHash == fbo.structuralLayoutHash && !FBOClearTracker.fboHasPendingClear(st, fbo) && clearPartial(st, fbo, target, false, wantDepth, wantStencil)) {
return;
}
boolean affectsActivePass = false;
@@ -1011,7 +1103,10 @@ public class SDLGPURenderBackend extends RenderBackend {
final long activeDepth = passActive ? frameManager.getCurrentDepthTarget() : 0;
if (wantColor) {
for (int db : fbo.drawBuffers) {
final int[] drawBuffers = fbo.drawBuffers;
for (int i = 0; i < drawBuffers.length; i++) {
if ((skipSlots & (1 << i)) != 0) continue;
final int db = drawBuffers[i];
if (db < 0 || db >= ContextState.MAX_COLOR_ATTACHMENTS) continue;
final long tex = fbo.colorTextures[db];
if (tex == 0) continue;
@@ -1020,11 +1115,11 @@ public class SDLGPURenderBackend extends RenderBackend {
}
}
if (wantDepth && fbo.depthTexture != 0) {
FBOClearTracker.recordPendingDepthClear(st, fbo.depthTexture, cs.depthClearValue);
FBOClearTracker.recordPendingDepthClear(st, fbo.depthTexture, st.depthClearValue);
if (passActive && fbo.depthTexture == activeDepth) affectsActivePass = true;
}
if (wantStencil && fbo.depthTexture != 0 && PixelOps.isDepthStencilFormat(fbo.depthFormat)) {
FBOClearTracker.recordPendingStencilClear(st, fbo.depthTexture, cs.stencilClearValue);
FBOClearTracker.recordPendingStencilClear(st, fbo.depthTexture, st.stencilClearValue);
if (passActive && fbo.depthTexture == activeDepth) affectsActivePass = true;
}
@@ -1033,9 +1128,36 @@ public class SDLGPURenderBackend extends RenderBackend {
}
}
private boolean clearPartial(ContextState st, FboState fbo, AttachmentClear.Target t, boolean color, boolean depth, boolean stencil) {
final FrameState f = frameManager.frame();
pipelineApplier.ensureRenderPass(st, f);
final long expectedLayout = fbo == null ? FrameManager.FBO0_LAYOUT_HASH : fbo.structuralLayoutHash;
if (f.renderPass == 0 || f.activeLayoutHash != expectedLayout) {
return warnPartialClearFallback();
}
if (fbo == null) {
t.depthTexture = f.currentDepthTarget;
t.depthFormat = t.depthTexture != 0 ? resourceManager.getSwapchainDepthStencilFormat() : 0;
}
color &= t.colorFormats != null && (fbo == null || fbo.hasAnyColor);
depth &= t.depthTexture != 0;
stencil &= PixelOps.isDepthStencilFormat(t.depthFormat);
if (!color && !depth && !stencil) return true;
if (attachmentClear.clearInPass(st, f, t, color, depth, stencil)) return true;
return warnPartialClearFallback();
}
private boolean warnPartialClearFallback() {
if (!partialClearFallbackWarned) {
partialClearFallbackWarned = true;
LOG.error("clear: in-pass partial clear unavailable; falling back to the legacy clear path");
}
return false;
}
@Override public void lineWidth(float width) { /* no-op */ }
@Override public void pointSize(float size) { /* no-op */ }
@Override public void logicOp(int opcode) { /* no-op */ }
@Override public void logicOp(int opcode) { s().logicOpMode = opcode; }
@Override public void hint(int target, int mode) { /* no-op */ }
@Override public void flush() { /* no-op */ }
@@ -1047,16 +1169,17 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void drawArrays(int mode, int first, int count) {
if (count <= 0) return;
final ContextState st = s();
if (st.rasterizerDiscard) return;
final FrameState f = frameManager.frame();
if (!f.frameActive) { f.droppedDrawsThisFrame++; return; }
final ContextState st = s();
if (mode == GL11.GL_TRIANGLE_FAN && count >= 3) {
if (!drawDispatch.drawTriangleFanAsTriangleList(st, first, count)) { f.droppedDrawsThisFrame++; return; }
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysFan", mode, count, 0, first, 0);
return;
}
drawDispatch.setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
pipelineApplier.ensureRenderPass(st, f);
pipelineApplier.ensureDrawRenderPass(st, f);
if (f.renderPass == 0) { f.droppedDrawsThisFrame++; drawDispatch.warnDrawArraysNoRenderPass(mode, st.boundFboId); return; }
if (!pipelineApplier.applyPipelineAndState(st, f)) { f.droppedDrawsThisFrame++; return; }
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arrays", mode, count, 0, first, 0);
@@ -1092,6 +1215,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
private long prepareIndexedDrawBind(ContextState st, int mode, int type, String opName) {
if (st.rasterizerDiscard) return 0;
if (!drawDispatch.prepareIndexedDraw(st, mode, type)) return 0;
final int ebo = st.currentVao.elementBuffer;
final long eboHandle = resourceManager.getBufferHandle(ebo);
@@ -1420,7 +1544,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (cp == 0) return;
final ByteBuffer unpacked = PixelOps.applyUnpackPixelStore(pixels, width, height * depth, format, type, st.pixelStore);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
final ByteBuffer prepped = PixelOps.prepareUploadBuffer(format, meta.sdlFormat(), unpacked, width, height * depth);
final ByteBuffer prepped = PixelOps.prepareUploadBuffer(format, type, meta.sdlFormat(), unpacked, width, height * depth);
try {
resourceManager.uploadToTexture3D(cp, prepped, texHandle, 0, 0, 0, width, height, depth, level);
} finally {
@@ -1574,11 +1698,15 @@ public class SDLGPURenderBackend extends RenderBackend {
final ContextState.PixelStoreState ps = s().pixelStore;
switch (pname) {
case GL11.GL_UNPACK_ALIGNMENT -> ps.unpackAlignment = param;
case GL11.GL_PACK_ALIGNMENT -> ps.packAlignment = param;
case GL11.GL_PACK_ALIGNMENT -> { if (param == 1 || param == 2 || param == 4 || param == 8) ps.packAlignment = param; }
case GL11.GL_PACK_ROW_LENGTH -> { if (param >= 0) ps.packRowLength = param; }
case GL11.GL_PACK_SKIP_PIXELS -> { if (param >= 0) ps.packSkipPixels = param; }
case GL11.GL_PACK_SKIP_ROWS -> { if (param >= 0) ps.packSkipRows = param; }
case GL11.GL_PACK_SWAP_BYTES -> ps.packSwapBytes = param != 0;
case GL11.GL_UNPACK_ROW_LENGTH -> ps.unpackRowLength = param;
case GL11.GL_UNPACK_SKIP_PIXELS -> ps.unpackSkipPixels = param;
case GL11.GL_UNPACK_SKIP_ROWS -> ps.unpackSkipRows = param;
default -> { /* GL_PACK_* and GL_UNPACK_SWAP_BYTES etc are ignored */ }
default -> { /* GL_UNPACK_SWAP_BYTES etc are ignored */ }
}
}
@@ -1644,13 +1772,16 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void deleteFramebuffers(int framebuffer) {
final ContextState cs = s();
final boolean drawBound = cs.boundFboId == framebuffer;
final boolean readBound = cs.boundReadFboId == framebuffer;
resourceManager.deleteFbo(framebuffer);
if (cs.boundFboId == framebuffer) cs.boundFboId = 0;
if (cs.boundReadFboId == framebuffer) cs.boundReadFboId = 0;
if (drawBound) bindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, 0);
if (readBound) bindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0);
}
@Override public void bindFramebuffer(int target, int framebuffer) {
final ContextState cs = s();
if (framebuffer == 0) framebuffer = cs.defaultFboId;
final boolean isRead = target == GL30.GL_READ_FRAMEBUFFER;
final boolean isDraw = target == GL30.GL_DRAW_FRAMEBUFFER;
final boolean isBoth = !isRead && !isDraw;
@@ -1689,7 +1820,7 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void framebufferTexture2D(int target, int attachment, int textarget, int texture, int level) {
final ContextState cs = s();
if (cs.boundFboId == 0) return;
if (cs.boundFboId == cs.defaultFboId) return;
final FboState fbo = resourceManager.getFbo(cs.boundFboId);
if (fbo == null) return;
@@ -1770,7 +1901,7 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void drawBuffers(int buffer) {
final ContextState cs = s();
if (cs.boundFboId == 0) return;
if (cs.boundFboId == cs.defaultFboId) return;
final FboState fbo = resourceManager.getFbo(cs.boundFboId);
if (fbo == null) return;
@@ -1793,7 +1924,7 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public void drawBuffers(IntBuffer bufs) {
final ContextState cs = s();
if (cs.boundFboId == 0) return;
if (cs.boundFboId == cs.defaultFboId) return;
final FboState fbo = resourceManager.getFbo(cs.boundFboId);
if (fbo == null) return;
applyDrawBuffersFromIntBuffer(fbo, bufs, true);
@@ -1829,8 +1960,9 @@ public class SDLGPURenderBackend extends RenderBackend {
}
}
@Override public void readBuffer(int mode) {
final int fboId = s().boundReadFboId;
if (fboId == 0) return;
final ContextState cs = s();
final int fboId = cs.boundReadFboId;
if (fboId == cs.defaultFboId) return;
final FboState fbo = resourceManager.getFbo(fboId);
if (fbo == null) return;
if (mode >= GL30.GL_COLOR_ATTACHMENT0 && mode < GL30.GL_COLOR_ATTACHMENT0 + ContextState.MAX_COLOR_ATTACHMENTS) {
@@ -1839,122 +1971,217 @@ public class SDLGPURenderBackend extends RenderBackend {
}
private static final int READBACK_COLOR = 0;
private static final int READBACK_DEPTH = 1;
private static final int READBACK_STENCIL = 2;
private static final int READBACK_DEPTH_STENCIL = 3;
@Override public void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels) {
if (pixels == null || width <= 0 || height <= 0) return;
if (pixels == null) return;
readPixelsInto(x, y, width, height, format, type, MemoryUtil.memAddress(pixels), pixels.remaining(), 0, 0L);
}
@Override public void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
if (pixels == null) return;
readPixelsInto(x, y, width, height, format, type, MemoryUtil.memAddress(pixels), (long) pixels.remaining() << 2, 0, 0L);
}
@Override public void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels) {
if (pixels == null) return;
readPixelsInto(x, y, width, height, format, type, MemoryUtil.memAddress(pixels), (long) pixels.remaining() << 2, 0, 0L);
}
@Override public void readPixels(int x, int y, int width, int height, int format, int type, long pixelBufferOffset) {
final int pbo = s().boundPixelPackBuffer;
if (pbo == 0) return;
readPixelsInto(x, y, width, height, format, type, 0L, resourceManager.getBufferSize(pbo) - pixelBufferOffset, pbo, pixelBufferOffset);
}
@Override public void getTexImage(int target, int level, int format, int type, ByteBuffer pixels) {
if (pixels == null) return;
getTexImageInto(target, level, format, type, MemoryUtil.memAddress(pixels), pixels.remaining(), 0, 0L);
}
@Override public void getTexImage(int target, int level, int format, int type, IntBuffer pixels) {
if (pixels == null) return;
getTexImageInto(target, level, format, type, MemoryUtil.memAddress(pixels), (long) pixels.remaining() << 2, 0, 0L);
}
@Override public void getTexImage(int target, int level, int format, int type, long pixelBufferOffset) {
final int pbo = s().boundPixelPackBuffer;
if (pbo == 0) return;
getTexImageInto(target, level, format, type, 0L, resourceManager.getBufferSize(pbo) - pixelBufferOffset, pbo, pixelBufferOffset);
}
private static int readbackKind(int format) {
if (PixelPack.isDepthStencilFormat(format)) return READBACK_DEPTH_STENCIL;
if (PixelPack.isDepthFormat(format)) return READBACK_DEPTH;
if (PixelPack.isStencilFormat(format)) return READBACK_STENCIL;
return READBACK_COLOR;
}
private static PackState loadPackState(ContextState cs) {
final ContextState.PixelStoreState store = cs.pixelStore;
final PackState ps = cs.packState;
ps.alignment = store.packAlignment;
ps.rowLength = store.packRowLength;
ps.skipPixels = store.packSkipPixels;
ps.skipRows = store.packSkipRows;
ps.swapBytes = store.packSwapBytes;
return ps;
}
private static void checkReadbackCapacity(int format, int type, int width, int height, PackState ps, long capacity) {
final long needed = PixelPack.imageSize(format, type, width, height, ps);
if (capacity < needed) throw new IllegalArgumentException("readback of " + width + "x" + height + " format 0x" + Integer.toHexString(format) + " type 0x" + Integer.toHexString(type) + " needs " + needed + " bytes, destination has " + capacity);
}
private void readPixelsInto(int x, int y, int width, int height, int format, int type, long dst, long capacity, int pbo, long pboOffset) {
PixelPack.validate(format, type);
if (width <= 0 || height <= 0) return;
final ContextState cs = s();
checkReadbackCapacity(format, type, width, height, loadPackState(cs), capacity);
final int kind = readbackKind(format);
final boolean fromFbo0 = cs.boundReadFboId == 0;
final long texHandle;
final int srcSdlFormat;
long colorTex = 0L;
int colorFormat = 0;
int colorBase = GL11.GL_RGBA;
long depthTex = 0L;
int depthFormat = 0;
final int srcWidth;
final int srcHeight;
final int srcY;
if (fromFbo0) {
texHandle = frameManager.getFbo0Texture();
srcSdlFormat = frameManager.getSwapchainFormat();
srcWidth = frameManager.getFbo0Width();
srcHeight = frameManager.getFbo0Height();
srcY = srcHeight - y - height;
if (kind == READBACK_COLOR) {
colorTex = frameManager.getFbo0Texture();
colorFormat = frameManager.getSwapchainFormat();
} else {
depthFormat = resourceManager.getSwapchainDepthStencilFormat();
if (depthFormat != 0) depthTex = resourceManager.getSwapchainDepthStencil();
}
} else {
final FboState fbo = resourceManager.getFbo(cs.boundReadFboId);
if (fbo == null) return;
final int colorGlId = fbo.colorGlIds[fbo.readBufferIndex];
texHandle = resourceManager.getTextureHandle(colorGlId);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(colorGlId);
srcSdlFormat = meta != null ? meta.sdlFormat() : SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
srcWidth = meta != null ? meta.width() : fbo.width;
srcHeight = meta != null ? meta.height() : fbo.height;
srcY = y;
if (kind == READBACK_COLOR) {
final int colorGlId = fbo.colorGlIds[fbo.readBufferIndex];
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(colorGlId);
colorTex = resourceManager.getTextureHandle(colorGlId);
colorFormat = meta != null ? meta.sdlFormat() : SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
if (meta != null) colorBase = PixelPack.glBaseFormat(meta.glFormat());
srcWidth = meta != null ? meta.width() : fbo.width;
srcHeight = meta != null ? meta.height() : fbo.height;
} else {
depthTex = fbo.depthTexture;
depthFormat = fbo.depthFormat;
srcWidth = fbo.width;
srcHeight = fbo.height;
}
}
if (texHandle == 0 || srcWidth <= 0 || srcHeight <= 0) return;
if (srcWidth <= 0 || srcHeight <= 0) return;
final int srcY = fromFbo0 ? srcHeight - y - height : y;
final long clip = CopyRectClip.clipCopyRect(x, srcY, 0, 0, width, height, srcWidth, srcHeight, width, height);
if (clip == CopyRectClip.EMPTY) return;
final int sx = CopyRectClip.srcX(clip);
final int sy = CopyRectClip.srcY(clip);
final int w = CopyRectClip.width(clip);
final int h = CopyRectClip.height(clip);
final int colOffset = CopyRectClip.dstX(clip, x, 0);
final int dy = CopyRectClip.dstY(clip, srcY, 0);
final int rowOffset = fromFbo0 ? (height - h - dy) : dy;
readback(cs, true, kind, colorTex, colorFormat, colorBase, depthTex, depthFormat, 0, srcWidth, srcHeight,
CopyRectClip.srcX(clip), CopyRectClip.srcY(clip), w, h, fromFbo0,
width, CopyRectClip.dstX(clip, x, 0), fromFbo0 ? height - h - dy : dy, format, type, dst, pbo, pboOffset);
}
final int basePos = pixels.position();
final int baseLimit = pixels.limit();
final int stride = width * 4;
if (baseLimit - basePos < height * stride) return;
try {
if (w == width) {
final int start = basePos + rowOffset * stride;
pixels.position(0);
pixels.limit(start + h * stride);
pixels.position(start);
flushAndSubmitMidFrame();
textureOps.readbackTexture(texHandle, sx, sy, w, h, 0, pixels);
pixels.position(0);
pixels.limit(baseLimit);
pixels.position(start);
PixelOps.postProcessReadback(pixels, width, h, format, srcSdlFormat, fromFbo0);
} else {
final ByteBuffer staging = MemoryUtil.memAlloc(w * h * 4);
try {
flushAndSubmitMidFrame();
textureOps.readbackTexture(texHandle, sx, sy, w, h, 0, staging);
staging.rewind();
PixelOps.postProcessReadback(staging, w, h, format, srcSdlFormat, fromFbo0);
for (int row = 0; row < h; row++) {
staging.position(0);
staging.limit((row + 1) * w * 4);
staging.position(row * w * 4);
pixels.position(basePos + (rowOffset + row) * stride + colOffset * 4);
pixels.put(staging);
}
} finally {
MemoryUtil.memFree(staging);
}
}
} finally {
pixels.limit(baseLimit);
pixels.position(basePos);
}
}
@Override public void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
readPixels(x, y, width, height, format, type, MemoryUtil.memByteBuffer(pixels));
}
@Override public void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels) {
readPixels(x, y, width, height, format, type, MemoryUtil.memByteBuffer(pixels));
}
@Override public void getTexImage(int target, int level, int format, int type, ByteBuffer pixels) {
private void getTexImageInto(int target, int level, int format, int type, long dst, long capacity, int pbo, long pboOffset) {
PixelPack.validate(format, type);
final ContextState cs = s();
final int glId = boundTextureFor(cs, target);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
if (meta == null || level < 0 || level >= meta.levels()) return;
final int width = PixelOps.mipLevelSize(meta.width(), level);
final int height = PixelOps.mipLevelSize(meta.height(), level);
if (width <= 0 || height <= 0) return;
checkReadbackCapacity(format, type, width, height, loadPackState(cs), capacity);
final int kind = readbackKind(format);
final long texHandle = resourceManager.getTextureHandle(glId);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
if (meta == null) return;
flushAndSubmitMidFrame();
textureOps.readbackTexture(texHandle, 0, 0, meta.width(), meta.height(), level, pixels);
pixels.rewind();
PixelOps.postProcessReadback(pixels, meta.width(), meta.height(), format, meta.sdlFormat(), false);
final int sdlFormat = meta.sdlFormat();
if ((kind == READBACK_COLOR) == PixelOps.isDepthFormat(sdlFormat)) return;
if (kind != READBACK_COLOR && level != 0) throw new UnsupportedOperationException("glGetTexImage of depth/stencil level " + level + " is not supported on SDL-GPU");
readback(cs, false, kind, kind == READBACK_COLOR ? texHandle : 0L, sdlFormat, PixelPack.glBaseFormat(meta.glFormat()), kind == READBACK_COLOR ? 0L : texHandle, sdlFormat, level, width, height,
0, 0, width, height, false, width, 0, 0, format, type, dst, pbo, pboOffset);
}
@Override public void getTexImage(int target, int level, int format, int type, IntBuffer pixels) {
getTexImage(target, level, format, type, MemoryUtil.memByteBuffer(pixels));
}
@Override public void getTexImage(int target, int level, int format, int type, long pixelBufferOffset) {
final ContextState cs = s();
final int pbo = cs.boundPixelPackBuffer;
if (pbo == 0) return;
final int glId = boundTextureFor(cs, target);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
if (meta == null) return;
final ByteBuffer tmp = MemoryUtil.memAlloc(meta.width() * meta.height() * 4);
try {
getTexImage(target, level, format, type, tmp);
final long handle = resourceManager.getBufferHandle(pbo);
if (handle != 0 && frameManager.getCommandBuffer() != 0) {
tmp.rewind();
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), tmp, handle, pixelBufferOffset, false);
resourceManager.markBufferContentsDefined(pbo);
private void readback(ContextState cs, boolean readPixels, int kind, long colorTex, int colorFormat, int colorBase, long depthTex, int depthFormat, int level, int srcWidth, int srcHeight,
int sx, int sy, int w, int h, boolean flipRows, int width, int colOffset, int rowOffset, int format, int type, long dst, int pbo, long pboOffset) {
final boolean wantsStencil = kind == READBACK_STENCIL || kind == READBACK_DEPTH_STENCIL;
if (kind == READBACK_COLOR ? colorTex == 0L : depthTex == 0L) return;
if (wantsStencil && !PixelOps.isDepthStencilFormat(depthFormat)) return;
final int texelBytes = switch (kind) {
case READBACK_COLOR -> PixelOps.sdlFormatTexelBytes(colorFormat);
case READBACK_STENCIL -> 1;
default -> 4;
};
final int primaryBytes = w * h * texelBytes;
final int stencilOffset = alignReadback(primaryBytes);
final int downloadEnd = kind == READBACK_DEPTH_STENCIL ? stencilOffset + alignReadback(w * h) : stencilOffset;
final PackState ps = cs.packState;
final ContextState.PixelStoreState store = cs.pixelStore;
final int pixelBytes = PixelOps.glPixelSize(format, type);
ps.alignment = store.packAlignment;
ps.rowLength = store.packRowLength > 0 ? store.packRowLength : width;
ps.skipPixels = store.packSkipPixels + colOffset;
ps.skipRows = store.packSkipRows + rowOffset;
final long stride = PixelPack.rowStride(format, type, width, ps);
final long firstByte = ps.skipRows * stride + (long) ps.skipPixels * pixelBytes;
final long endByte = (long) (ps.skipRows + h - 1) * stride + (long) (ps.skipPixels + w) * pixelBytes;
final long base = textureOps.ensureReadbackStaging(pbo != 0 ? downloadEnd + endByte : downloadEnd);
final long packDst = pbo != 0 ? base + downloadEnd : dst;
switch (kind) {
case READBACK_COLOR -> {
fboClearTracker.materializePendingClearForRead(cs, frameManager.frame(), colorTex);
flushAndSubmitMidFrame();
textureOps.readbackTextureToStaging(colorTex, sx, sy, w, h, level, 0, primaryBytes);
PixelPack.packColor(base, colorFormat, colorBase, w * texelBytes, w, h, flipRows, readPixels, format, type, ps, packDst);
}
case READBACK_DEPTH -> {
final long depth = requireReadbackScratch(depthStencilReadback.renderDepth(cs, depthTex, depthFormat, srcWidth, srcHeight), "depth");
flushAndSubmitMidFrame();
textureOps.readbackTextureToStaging(depth, sx, sy, w, h, 0, 0, primaryBytes);
PixelPack.packDepth(base, w * 4, w, h, flipRows, type, ps, packDst);
}
case READBACK_STENCIL -> {
final long stencil = requireReadbackScratch(depthStencilReadback.renderStencil(cs, depthTex, depthFormat, srcWidth, srcHeight), "stencil");
flushAndSubmitMidFrame();
textureOps.readbackTextureToStaging(stencil, sx, sy, w, h, 0, 0, primaryBytes);
PixelPack.packStencil(base, w, w, h, flipRows, type, ps, packDst);
}
default -> {
final long depth = requireReadbackScratch(depthStencilReadback.renderDepth(cs, depthTex, depthFormat, srcWidth, srcHeight), "depth");
final long stencil = requireReadbackScratch(depthStencilReadback.renderStencil(cs, depthTex, depthFormat, srcWidth, srcHeight), "stencil");
flushAndSubmitMidFrame();
textureOps.readbackTextureToStaging(depth, sx, sy, w, h, 0, 0, primaryBytes);
textureOps.readbackTextureToStaging(stencil, sx, sy, w, h, 0, stencilOffset, w * h);
PixelPack.packDepthStencil(base, base + stencilOffset, w * 4, w, w, h, flipRows, type, ps, packDst);
}
} finally {
MemoryUtil.memFree(tmp);
}
if (pbo != 0) uploadReadbackToPixelPackBuffer(pbo, pboOffset, (int) (downloadEnd + firstByte), (int) (endByte - firstByte), firstByte);
}
private static long requireReadbackScratch(long scratch, String what) {
if (scratch == 0L) throw new IllegalStateException("SDL-GPU " + what + " readback pass unavailable (no active frame, or the attachment cannot be sampled)");
return scratch;
}
private static int alignReadback(int bytes) {
return (bytes + 15) & ~15;
}
private void uploadReadbackToPixelPackBuffer(int pbo, long pboOffset, int stagingOffset, int size, long firstByte) {
final long handle = resourceManager.getBufferHandle(pbo);
if (handle == 0 || frameManager.getCommandBuffer() == 0) return;
final ByteBuffer region = textureOps.readbackStagingRegion(stagingOffset, size);
try {
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), region, handle, pboOffset + firstByte, false);
} finally {
region.clear();
}
resourceManager.markBufferContentsDefined(pbo);
flushAndSubmitMidFrame();
}
@Override public int getFramebufferAttachmentParameteri(int target, int attachment, int pname) {
@@ -1964,6 +2191,10 @@ public class SDLGPURenderBackend extends RenderBackend {
final int colorFormat = cachedSwapchainFormatArray != null ? cachedSwapchainFormatArray[0] : 0;
return defaultFramebufferAttachmentParameteri(attachment, pname, colorFormat, resourceManager.getSwapchainDepthStencilFormat());
}
if (fboId == cs.defaultFboId) {
final FboState fbo = resourceManager.getFbo(fboId);
return defaultFramebufferAttachmentParameteri(attachment, pname, fbo.colorFormats[0], fbo.depthFormat);
}
return fboAttachmentParameteri(resourceManager.getFbo(fboId), attachment, pname);
}
@@ -2051,6 +2282,8 @@ public class SDLGPURenderBackend extends RenderBackend {
final ShaderManager.ProgramObject prog = shaderManager.getProgram(program);
if (prog == null) {
cs.pipeline.invalidateShaderHandles();
cs.appliedLogicOpKey = 0;
cs.activeLogicOpVariant = null;
return;
}
if (prog.sdlVertexShader == 0 && prog.vertexSpirv != null) {
@@ -2068,6 +2301,8 @@ public class SDLGPURenderBackend extends RenderBackend {
cs.pipeline.setVertexInputs(prog.vertexInputMask, prog.vertexInputVecSize, prog.vertexInputBaseType, prog.vertexInputName, prog.vertexInputSetId);
cs.pipeline.markInputDirty();
cs.pipeline.markShaderDirty();
cs.appliedLogicOpKey = 0;
cs.activeLogicOpVariant = null;
}
@Override public void useProgram(int program) {
final ContextState cs = s();
@@ -2083,6 +2318,8 @@ public class SDLGPURenderBackend extends RenderBackend {
}
}
st.boundProgram = program;
st.appliedLogicOpKey = 0;
st.activeLogicOpVariant = null;
if (program == 0) {
st.boundProgramObj = null;
cs.pipeline.vertexShader = 0;
@@ -3508,7 +3745,7 @@ public class SDLGPURenderBackend extends RenderBackend {
final int flipMode = (srcIsFbo0 != dstIsFbo0) ? SDL_FLIP_VERTICAL : SDL_FLIP_NONE;
if (blitColor) {
resolveBlitClears(cs, colorSrcTex, colorDstTex, colorDstGlId, dstX0, dstY, dstW, dstH);
resolveBlitClears(cs, colorSrcTex, colorDstTex, colorDstGlId, dstX0, dstY, dstW, dstH, true);
final ResourceManager.TextureMeta srcMeta = resourceManager.getTextureMeta(colorSrcGlId);
final ResourceManager.TextureMeta dstMeta = resourceManager.getTextureMeta(colorDstGlId);
if (flipMode == SDL_FLIP_NONE && TextureOps.canCopyInsteadOfBlit(srcMeta, dstMeta, srcW, srcH, dstW, dstH)) {
@@ -3518,14 +3755,15 @@ public class SDLGPURenderBackend extends RenderBackend {
}
}
if (blitDepth) {
resolveBlitClears(cs, depthSrcTex, depthDstTex, dstFbo.depthGlId, dstX0, dstY, dstW, dstH);
final boolean allAspects = !PixelOps.isDepthStencilFormat(dstFbo.depthFormat) || (mask & (GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT)) == (GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
resolveBlitClears(cs, depthSrcTex, depthDstTex, dstFbo.depthGlId, dstX0, dstY, dstW, dstH, allAspects);
textureOps.copyTexture(depthSrcTex, srcX0, srcY, depthDstTex, 0, dstX0, dstY, srcW, srcH);
}
}
private void resolveBlitClears(ContextState cs, long srcTex, long dstTex, int dstGlId, int dstX, int dstY, int dstW, int dstH) {
private void resolveBlitClears(ContextState cs, long srcTex, long dstTex, int dstGlId, int dstX, int dstY, int dstW, int dstH, boolean allAspects) {
fboClearTracker.materializePendingClearForTexture(cs, srcTex);
fboClearTracker.resolveDestinationForWrite(cs, dstTex, resourceManager.getTextureMeta(dstGlId), 0, dstX, dstY, 0, dstW, dstH);
fboClearTracker.resolveDestinationForWrite(cs, dstTex, resourceManager.getTextureMeta(dstGlId), 0, dstX, dstY, 0, dstW, dstH, allAspects);
}
@Override public int createBuffers() { return resourceManager.genBuffer(); }
@Override public void namedBufferData(int buffer, long size, int usage) {
@@ -3637,11 +3875,16 @@ public class SDLGPURenderBackend extends RenderBackend {
case GL43.GL_MAX_DEBUG_GROUP_STACK_DEPTH -> 64;
case GL43.GL_MAX_LABEL_LENGTH -> 256;
case GL43.GL_MAX_DEBUG_MESSAGE_LENGTH -> 1024;
case GL11.GL_DRAW_BUFFER -> drawBufferEnum(cs.boundFboId);
case GL11.GL_READ_BUFFER -> readBufferEnum(cs.boundReadFboId);
case GL11.GL_DRAW_BUFFER -> drawBufferEnum(cs.boundFboId, cs.defaultFboId);
case GL11.GL_READ_BUFFER -> readBufferEnum(cs.boundReadFboId, cs.defaultFboId);
case GL11.GL_TEXTURE_BINDING_2D -> boundTextureOf(cs.activeTextureUnit, cs.boundTextures);
case GL11.GL_DEPTH_BITS -> getFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_DEPTH, GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE);
case GL11.GL_STENCIL_BITS -> getFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_STENCIL, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE);
case GL11.GL_PACK_ALIGNMENT -> cs.pixelStore.packAlignment;
case GL11.GL_PACK_ROW_LENGTH -> cs.pixelStore.packRowLength;
case GL11.GL_PACK_SKIP_PIXELS -> cs.pixelStore.packSkipPixels;
case GL11.GL_PACK_SKIP_ROWS -> cs.pixelStore.packSkipRows;
case GL11.GL_PACK_SWAP_BYTES -> cs.pixelStore.packSwapBytes ? GL11.GL_TRUE : GL11.GL_FALSE;
default -> {
final int limit = deviceLimit(pname);
yield limit >= 0 ? limit : unknownGetInteger(pname);
@@ -3655,15 +3898,15 @@ public class SDLGPURenderBackend extends RenderBackend {
return GL30.GL_COLOR_ATTACHMENT0 + attachmentIndex;
}
private int drawBufferEnum(int fboId) {
if (fboId == 0) return bufferEnum(0, 0);
private int drawBufferEnum(int fboId, int defaultFboId) {
if (fboId == defaultFboId) return bufferEnum(0, 0);
final FboState fbo = resourceManager.getFbo(fboId);
final int idx = (fbo == null || fbo.drawBuffers.length == 0) ? -1 : fbo.drawBuffers[0];
return bufferEnum(fboId, idx);
}
private int readBufferEnum(int fboId) {
if (fboId == 0) return bufferEnum(0, 0);
private int readBufferEnum(int fboId, int defaultFboId) {
if (fboId == defaultFboId) return bufferEnum(0, 0);
final FboState fbo = resourceManager.getFbo(fboId);
return bufferEnum(fboId, fbo == null ? -1 : fbo.readBufferIndex);
}
@@ -3743,6 +3986,9 @@ public class SDLGPURenderBackend extends RenderBackend {
case GL11.GL_SCISSOR_TEST -> cs.scissorEnabled;
case GL11.GL_STENCIL_TEST -> cs.pipeline.stencilTestEnabled;
case GL11.GL_DEPTH_WRITEMASK -> cs.pipeline.depthWriteEnabled;
case GL30.GL_RASTERIZER_DISCARD -> cs.rasterizerDiscard;
case GL11.GL_COLOR_LOGIC_OP -> cs.logicOpEnabled;
case GL11.GL_PACK_SWAP_BYTES -> cs.pixelStore.packSwapBytes;
default -> false;
};
}
@@ -3880,7 +4126,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (srcTex == 0 || dstTex == 0) return;
final ContextState copySt = s();
fboClearTracker.materializePendingClearForTexture(copySt, srcTex);
fboClearTracker.resolveDestinationForWrite(copySt, dstTex, resourceManager.getTextureMeta(dstName), dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight);
fboClearTracker.resolveDestinationForWrite(copySt, dstTex, resourceManager.getTextureMeta(dstName), dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight, true);
final long cp = frameManager.ensureCopyPass();
if (cp == 0) return;
try (var stack = MemoryStack.stackPush()) {
@@ -4020,10 +4266,11 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
if (count <= 0 || primcount <= 0) return;
if (!frameManager.isFrameActive()) return;
final ContextState st = s();
if (st.rasterizerDiscard) return;
if (!frameManager.isFrameActive()) return;
drawDispatch.setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
pipelineApplier.ensureRenderPass(st);
pipelineApplier.ensureDrawRenderPass(st);
if (!frameManager.isRenderPassActive()) return;
if (!pipelineApplier.applyPipelineAndState(st)) return;
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysInstanced", mode, count, 0, first, 0);
@@ -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,20 @@ 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 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 +327,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 +352,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<>();
@@ -398,4 +422,51 @@ public final class ContextState {
cachedScissor = SDL_Rect.calloc();
cachedBlendColor = SDL_FColor.calloc();
}
public void invalidateMirror() {
boundFboId = -1;
boundReadFboId = -1;
viewportDirty = true;
scissorDirty = true;
blendColorDirty = true;
lastBoundPipeline = 0;
lastAppliedRenderPassGen = 0;
lastAppliedStencilRef = Integer.MIN_VALUE;
lastBoundEboHandle = 0;
lastBoundEboIndexSize = -1;
lastBoundEboOffset = 0;
lastAppliedSamplerBindGen = -1;
lastAppliedSamplerProgram = 0;
lastAppliedSamplerCb = 0;
lastFragSamplerProgram = 0;
lastFragSamplerVariant = null;
lastVertSamplerProgram = 0;
lastAppliedStorageTexBindGen = -1;
lastAppliedStorageTexProgram = 0;
lastAppliedStorageTexCb = 0;
lastFragStorageTexProgram = 0;
lastVertStorageTexProgram = 0;
lastAppliedStorageBufBindGen = -1;
lastAppliedStorageBufProgram = 0;
lastAppliedStorageBufCb = 0;
lastFragStorageBufProgram = 0;
lastVertStorageBufProgram = 0;
lastAppliedVboBindGen = -1;
lastAppliedVboBindProgram = 0;
lastAppliedVboBindCb = 0;
lastFlushedSamplerBindGen = -1;
lastFlushedProgram = -1;
lastFlushedPendingMutationGen = -1;
lastPushedCbVs = 0;
lastPushedCbFs = 0;
lastPushedProgramVs = -1;
lastPushedProgramFs = -1;
lastPushedUboGenVs = -1;
lastPushedUboGenFs = -1;
pipeline.markInputDirty();
pipeline.markOutputDirty();
pipeline.markShaderDirty();
currentVao.invalidateInputHash();
needsSeed = true;
}
}
@@ -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();
@@ -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;
}
}
@@ -92,7 +92,7 @@ public final class DrawDispatch {
return false;
}
setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
pipelineApplier.ensureRenderPass(st, f);
pipelineApplier.ensureDrawRenderPass(st, f);
if (!frameManager.isRenderPassActive(f)) {
f.droppedDrawsThisFrame++;
if (!prepareRpInactiveWarned) {
@@ -180,7 +180,7 @@ public final class DrawDispatch {
st.fanIndexBufferOffset += indexDataSize;
setPrimitiveTypeForDraw(st, SDL_GPU_PRIMITIVETYPE_TRIANGLELIST);
pipelineApplier.ensureRenderPass(st);
pipelineApplier.ensureDrawRenderPass(st);
if (!frameManager.isRenderPassActive()) return false;
if (!pipelineApplier.applyPipelineAndState(st)) return false;
Loaded 100 of 372 files, more files were not shown because too many files have changed in this diff. Show more