Compare commits

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

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

No files matched your search

+76 -17
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
# Builds the libTracyClient shared library for the tracy-client module.
# Builds the libTracyClient and AngelicaTracyCapture shared libraries for the tracy-client module.
set -euo pipefail
TRACY_VERSION="${TRACY_VERSION:-v0.14.0}"
TRACY_VERSION="${TRACY_VERSION:-v0.14.1}"
TRACY_VERSION_BARE="${TRACY_VERSION#v}"
OUT_DIR="${OUT_DIR:-dist/tracy-natives}"
TRACY_REPO="https://github.com/wolfpld/tracy"
@@ -31,36 +31,95 @@ else
git clone --depth 1 --branch "$TRACY_VERSION" "$TRACY_REPO" "$SRC_DIR"
fi
BUILD_DIR="$WORK_DIR/build"
export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-11.0}"
cmake -S "$REPO_ROOT/tracy-client/src/main/native" -B "$BUILD_DIR" \
CLIENT_BUILD_DIR="$WORK_DIR/build-client"
cmake -S "$REPO_ROOT/tracy-client/src/main/native" -B "$CLIENT_BUILD_DIR" \
-DTRACY_SRC_DIR="$SRC_DIR" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=ON \
-DTRACY_STATIC=OFF \
-DTRACY_ENABLE=ON \
-DTRACY_ON_DEMAND=ON \
-DTRACY_DELAYED_INIT=ON \
-DTRACY_MANUAL_LIFETIME=ON \
-DTRACY_NO_SAMPLING=ON \
-DTRACY_NO_SYSTEM_TRACING=ON \
-DTRACY_NO_CRASH_HANDLER=ON \
-DTRACY_LTO=OFF
cmake --build "$BUILD_DIR" --config Release --parallel
cmake --build "$CLIENT_BUILD_DIR" --config Release --parallel
mkdir -p "$OUT_DIR"
CPM_CACHE_DIR="$WORK_DIR/cpm"
CAPTURE_BUILD_DIR="$WORK_DIR/build-capture"
cmake -S "$REPO_ROOT/tracy-client/src/main/native-capture" -B "$CAPTURE_BUILD_DIR" \
-DTRACY_SRC_DIR="$SRC_DIR" \
-DCMAKE_BUILD_TYPE=Release \
-DCPM_SOURCE_CACHE="$CPM_CACHE_DIR"
cmake --build "$CAPTURE_BUILD_DIR" --config Release --parallel
find_one() {
find "$1" -name "$2" 2>/dev/null | head -1
}
CLIENT_LIB_NAME=""
CLIENT_LIB=""
for NAME in libTracyClient.so libTracyClient.dylib TracyClient.dll; do
LIB="$(find "$BUILD_DIR" -name "$NAME" | head -1)"
LIB="$(find_one "$CLIENT_BUILD_DIR" "$NAME")"
if [[ -n "$LIB" ]]; then
cp -L "$LIB" "$OUT_DIR/$NAME"
cp "$SRC_DIR/LICENSE" "$OUT_DIR/LICENSE"
mkdir -p "$RES_DIR"
cp -L "$LIB" "$RES_DIR/$NAME"
cp "$SRC_DIR/LICENSE" "$RES_DIR/LICENSE"
echo "built: $OUT_DIR/$NAME ($TRACY_VERSION), staged: $RES_DIR/$NAME"
exit 0
CLIENT_LIB="$LIB"
CLIENT_LIB_NAME="$NAME"
break
fi
done
echo "error: built library not found under $BUILD_DIR" >&2
exit 1
if [[ -z "$CLIENT_LIB" ]]; then
echo "error: libTracyClient not found under $CLIENT_BUILD_DIR" >&2
exit 1
fi
CAPTURE_LIB_NAME=""
CAPTURE_LIB=""
for NAME in libAngelicaTracyCapture.so libAngelicaTracyCapture.dylib AngelicaTracyCapture.dll; do
LIB="$(find_one "$CAPTURE_BUILD_DIR" "$NAME")"
if [[ -n "$LIB" ]]; then
CAPTURE_LIB="$LIB"
CAPTURE_LIB_NAME="$NAME"
break
fi
done
if [[ -z "$CAPTURE_LIB" ]]; then
echo "error: AngelicaTracyCapture not found under $CAPTURE_BUILD_DIR" >&2
exit 1
fi
STRIPPED_CAPTURE_LIB="$WORK_DIR/$CAPTURE_LIB_NAME"
cp -L "$CAPTURE_LIB" "$STRIPPED_CAPTURE_LIB"
case "$PLAT_OS" in
macos) strip -x "$STRIPPED_CAPTURE_LIB" ;;
linux) strip --strip-unneeded "$STRIPPED_CAPTURE_LIB" ;;
*) ;;
esac
LICENSE_ZSTD="$(find_one "$CPM_CACHE_DIR/zstd" LICENSE)"
LICENSE_CAPSTONE="$(find_one "$CPM_CACHE_DIR/capstone" LICENSE.TXT)"
LICENSE_PPQSORT="$(find_one "$CPM_CACHE_DIR/ppqsort" LICENSE)"
LICENSE_NAMES=(LICENSE LICENSE-zstd LICENSE-capstone LICENSE-ppqsort)
LICENSE_SRCS=("$SRC_DIR/LICENSE" "$LICENSE_ZSTD" "$LICENSE_CAPSTONE" "$LICENSE_PPQSORT")
for i in "${!LICENSE_NAMES[@]}"; do
if [[ -z "${LICENSE_SRCS[$i]}" || ! -f "${LICENSE_SRCS[$i]}" ]]; then
echo "error: ${LICENSE_NAMES[$i]} source not found under $CPM_CACHE_DIR" >&2
exit 1
fi
done
mkdir -p "$OUT_DIR" "$RES_DIR"
for DEST in "$OUT_DIR" "$RES_DIR"; do
cp -L "$CLIENT_LIB" "$DEST/$CLIENT_LIB_NAME"
cp -L "$STRIPPED_CAPTURE_LIB" "$DEST/$CAPTURE_LIB_NAME"
for i in "${!LICENSE_NAMES[@]}"; do
cp "${LICENSE_SRCS[$i]}" "$DEST/${LICENSE_NAMES[$i]}"
done
done
echo "built: $OUT_DIR/$CLIENT_LIB_NAME, $OUT_DIR/$CAPTURE_LIB_NAME ($TRACY_VERSION)"
echo "staged: $RES_DIR/$CLIENT_LIB_NAME, $RES_DIR/$CAPTURE_LIB_NAME"
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- name: Build libTracyClient
- name: Build Tracy natives
shell: bash
env:
OUT_DIR: dist/tracy-natives
+2
View File
@@ -162,6 +162,7 @@ DependencyHandlerScope.of(dependencies).apply {
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
compileOnly("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.49-GTNH:dev")
compileOnly("com.github.GTNewHorizons:OpenBlocks:1.12.19-GTNH:dev")
compileOnly("com.github.GTNewHorizons:Draconic-Evolution:1.5.30-GTNH:dev")
compileOnly("com.github.GTNewHorizons:Chisel:2.17.31-GTNH:dev")
compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
@@ -201,6 +202,7 @@ afterEvaluate {
testImplementation(libs.junit.platform.launcher)
testImplementation(libs.archunit.junit5)
testImplementation(libs.retrofuturabootstrap) { isTransitive = false }
glsmTestFixtures(testFixtures(project(":glsm")) as ModuleDependency) { isTransitive = false }
testCompileOnly(testFixtures(project(":glsm")))
@@ -40,10 +40,10 @@ public final class SystemProperties {
public static final boolean SDL_DISABLE_IN_PASS_CLEAR = Boolean.getBoolean("angelica.sdlgpu.disableInPassClear");
// Tracy
public static final boolean TRACY = Boolean.getBoolean("angelica.tracy");
public static final boolean TRACY_FINE_ZONES = Boolean.getBoolean("angelica.tracy.fineZones");
public static final Boolean TRACY_OVERRIDE = parseBooleanOverride("angelica.tracy");
public static final Boolean TRACY_FINE_ZONES_OVERRIDE = parseBooleanOverride("angelica.tracy.fineZones");
public static final String TRACY_DIR = System.getProperty("angelica.tracy.dir", "angelica" + File.separator + "natives" + File.separator + "tracy");
public static final int TRACY_MAX_SRC_LOCS = Math.max(16, Integer.getInteger("angelica.tracy.maxSrcLocs", 4096));
public static final Integer TRACY_MAX_SRC_LOCS_OVERRIDE = Integer.getInteger("angelica.tracy.maxSrcLocs");
// Profiling
public static final String PROFILE_OPTS = System.getProperty("angelica.profile.opts", "event=wall,interval=5ms,alloc=512k,lock=10ms");
@@ -152,6 +152,11 @@ public final class SystemProperties {
}
}
private static Boolean parseBooleanOverride(String key) {
final String raw = System.getProperty(key);
return raw == null ? null : Boolean.parseBoolean(raw);
}
private static double parseDouble(String key) {
final String raw = System.getProperty(key);
if (raw == null || raw.isEmpty()) return 0.0D;
@@ -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
) {}
@@ -175,7 +174,9 @@ public class DisplayListManager {
static void flushAll() {
drawBarrier();
flushMatrix();
for (int slot = 0; slot < DisplayListCallback.SLOT_COUNT; slot++) {
flushMatrix(slot);
}
}
@@ -190,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;
}
@@ -204,32 +213,48 @@ 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() {
@@ -319,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);
@@ -375,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();
}
@@ -390,6 +443,7 @@ public class DisplayListManager {
// Discard any transformations & flush any pending draw operations
drawBarrier();
resetRelativeTransform();
syncStreamMatrixMode(transformCallback.getMatrixMode());
currentRecorder.writePopMatrix();
}
@@ -439,6 +493,8 @@ public class DisplayListManager {
}
public static void recordActiveTexture(int texture) {
drawBarrier();
flushMatrix(DisplayListCallback.SLOT_TEXTURE);
currentRecorder.writeActiveTexture(texture);
}
@@ -447,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) {
@@ -575,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) {
@@ -598,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);
@@ -606,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();
}
@@ -649,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) {
@@ -662,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) {
@@ -680,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) {
@@ -702,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) {
@@ -720,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) {
@@ -735,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) {
@@ -753,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();
}
/**
@@ -829,6 +913,9 @@ public class DisplayListManager {
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 {
@@ -901,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
@@ -1399,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;
@@ -1416,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);
}
}
@@ -1467,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
@@ -1488,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);
}
@@ -1515,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;
}
}
}
@@ -107,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;
@@ -240,6 +242,9 @@ public class GLContextState {
public int internalGlDepth;
public int internalDrawDepth;
public int savedFilterTextureId;
public int savedFilterMin = -1;
public int savedFilterMag = -1;
public float savedFilterAniso = -1.0f;
public boolean wideLineEmulationActive = false;
public boolean lineStippleActive = false;
public Instancing ffpInstancing = Instancing.NONE;
@@ -10,7 +10,6 @@ import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.DisplayListManager.RecordMode;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
@@ -23,6 +22,7 @@ import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredDepthColorHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GlintColorHandler;
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
@@ -67,6 +67,7 @@ import com.gtnewhorizons.angelica.glsm.states.TextureBinding;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import com.gtnewhorizons.angelica.glsm.texture.TextureStaging;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import lombok.Getter;
@@ -118,6 +119,7 @@ import java.nio.ShortBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
@@ -146,6 +148,36 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
@SuppressWarnings("unused") // Used in ASM
public class GLStateManager {
/** Called before a mutation, while deferred draws can still use their original state. */
public static void beforeUncapturedStateChange() {
if (BatchStateGuard.isSuspended()) return;
if (DisplayListManager.getRecordMode() == RecordMode.COMPILE) return;
BatchStateGuard.beforeChange();
ctx().batchStateGeneration++;
}
public static void beforeUncapturedCapabilityChange(int cap) {
if (cap == GL11.GL_FOG || cap == GL11.GL_SCISSOR_TEST || cap == GL11.GL_STENCIL_TEST
|| cap == GL11.GL_COLOR_LOGIC_OP || cap == GL14.GL_COLOR_SUM || cap == GL30.GL_RASTERIZER_DISCARD
|| cap == GL13.GL_SAMPLE_ALPHA_TO_COVERAGE || cap == GL13.GL_SAMPLE_ALPHA_TO_ONE || cap == GL13.GL_SAMPLE_COVERAGE
|| (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + MAX_CLIP_PLANES)) {
beforeUncapturedStateChange();
}
}
public static void beforeUncapturedTextureUnitChange(int cap, int unit) {
if (cap == GL11.GL_TEXTURE_2D && unit <= 1) return;
beforeUncapturedStateChange();
}
private static void beforeMatrixChange() {
final GLContextState c = ctx();
if (c.matrixMode.getMode() == GL11.GL_PROJECTION
|| (c.matrixMode.getMode() == GL11.GL_TEXTURE && c.activeTextureUnit.getValue() != 0)) {
beforeUncapturedStateChange();
}
}
public static final Logger LOGGER = LogManager.getLogger("GLSM");
private static final Set<String> WARN_ONCE = ConcurrentHashMap.newKeySet();
@@ -167,7 +199,7 @@ public class GLStateManager {
return st != null ? st : primaryContext();
}
private static GLContextState primaryContext() {
public static GLContextState primaryContext() {
GLContextState p = primaryContext;
if (p != null) return p;
synchronized (GLStateManager.class) {
@@ -1387,6 +1419,7 @@ public class GLStateManager {
final boolean caching = isCachingEnabled();
final boolean bypass = !caching;
if (bypass || red != glCtx.blendState.getBlendColorR() || green != glCtx.blendState.getBlendColorG() || blue != glCtx.blendState.getBlendColorB() || alpha != glCtx.blendState.getBlendColorA()) {
beforeUncapturedStateChange();
glCtx.blendState.setBlendColorR(red);
glCtx.blendState.setBlendColorG(green);
glCtx.blendState.setBlendColorB(blue);
@@ -1624,6 +1657,7 @@ public class GLStateManager {
final boolean caching = isCachingEnabled();
final boolean bypass = !caching;
if (bypass || glCtx.blendState.getEquationRgb() != mode || glCtx.blendState.getEquationAlpha() != mode) {
beforeUncapturedStateChange();
glCtx.blendState.setEquationRgb(mode);
glCtx.blendState.setEquationAlpha(mode);
RENDER_BACKEND.blendEquation(mode);
@@ -1639,6 +1673,7 @@ public class GLStateManager {
final boolean caching = isCachingEnabled();
final boolean bypass = !caching;
if (bypass || glCtx.blendState.getEquationRgb() != modeRGB || glCtx.blendState.getEquationAlpha() != modeAlpha) {
beforeUncapturedStateChange();
glCtx.blendState.setEquationRgb(modeRGB);
glCtx.blendState.setEquationAlpha(modeAlpha);
RENDER_BACKEND.blendEquationSeparate(modeRGB, modeAlpha);
@@ -1936,6 +1971,7 @@ public class GLStateManager {
private static void changeSecondaryColor(float red, float green, float blue) {
final GLContextState glCtx = ctx();
if (!isCachingEnabled() || red != glCtx.secondaryColor.getRed() || green != glCtx.secondaryColor.getGreen() || blue != glCtx.secondaryColor.getBlue()) {
if (glCtx.colorSumState.isEnabled()) beforeUncapturedStateChange();
mod(glCtx.secondaryColor);
glCtx.secondaryColor.setRed(red);
glCtx.secondaryColor.setGreen(green);
@@ -2205,6 +2241,7 @@ public class GLStateManager {
final int cachedBinding = textureUnit.getBinding();
if (cachedBinding != texture || (texture == 0 && textureUnit.getTarget() != target)) {
if (activeUnit != 0 || target != GL11.GL_TEXTURE_2D) beforeUncapturedStateChange();
if (Tracy.ENABLED) texBindMisses++;
RENDER_BACKEND.bindTexture(target, texture);
mod(textureUnit).setBinding(texture, target);
@@ -2809,6 +2846,7 @@ public class GLStateManager {
return;
}
}
if (!isCachingEnabled() || ctx().drawBuffer.getValue() != mode) beforeUncapturedStateChange();
if (isCachingEnabled()) mod(ctx().drawBuffer).setValue(mode);
RENDER_BACKEND.drawBuffer(mode);
}
@@ -3121,18 +3159,24 @@ public class GLStateManager {
}
public static void glVertexAttrib2f(int index, float v0, float v1) {
RENDER_BACKEND.vertexAttrib2f(index, v0, v1);
glVertexAttrib4f(index, v0, v1, 0.0f, 1.0f);
}
public static void glVertexAttrib2s(int index, short v0, short v1) {
RENDER_BACKEND.vertexAttrib2f(index, v0, v1);
glVertexAttrib4f(index, v0, v1, 0.0f, 1.0f);
}
public static void glVertexAttrib3f(int index, float v0, float v1, float v2) {
RENDER_BACKEND.vertexAttrib3f(index, v0, v1, v2);
glVertexAttrib4f(index, v0, v1, v2, 1.0f);
}
public static void glVertexAttrib4f(int index, float v0, float v1, float v2, float v3) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordVertexAttrib(index, v0, v1, v2, v3);
if (mode == RecordMode.COMPILE) return;
}
beforeUncapturedStateChange();
RENDER_BACKEND.vertexAttrib4f(index, v0, v1, v2, v3);
}
@@ -3361,6 +3405,7 @@ public class GLStateManager {
return;
}
}
if (!isCachingEnabled() || ctx().logicOpMode.getValue() != opcode) beforeUncapturedStateChange();
if (isCachingEnabled()) mod(ctx().logicOpMode).setValue(opcode);
RENDER_BACKEND.logicOp(opcode);
}
@@ -3651,6 +3696,7 @@ public class GLStateManager {
final float green = param.get(1);
final float blue = param.get(2);
beforeUncapturedStateChange();
mod(glCtx.fogState);
glCtx.fogState.getFogColor().set(red, green, blue);
glCtx.fogState.setFogAlpha(param.get(3));
@@ -3674,6 +3720,7 @@ public class GLStateManager {
final GLContextState glCtx = ctx();
final boolean caching = isCachingEnabled();
if (!caching || red != glCtx.fogState.getFogColor().x || green != glCtx.fogState.getFogColor().y || blue != glCtx.fogState.getFogColor().z || alpha != glCtx.fogState.getFogAlpha()) {
beforeUncapturedStateChange();
mod(glCtx.fogState);
glCtx.fogState.getFogColor().set(red, green, blue);
glCtx.fogState.setFogAlpha(alpha);
@@ -3699,10 +3746,10 @@ public class GLStateManager {
if (isCachingEnabled()) {
boolean changed = false;
switch (pname) {
case GL11.GL_FOG_DENSITY -> { if (!isCachingEnabled() || glCtx.fogState.getDensity() != param) { mod(glCtx.fogState).setDensity(param); changed = true; } }
case GL11.GL_FOG_START -> { if (!isCachingEnabled() || glCtx.fogState.getStart() != param) { mod(glCtx.fogState).setStart(param); changed = true; } }
case GL11.GL_FOG_END -> { if (!isCachingEnabled() || glCtx.fogState.getEnd() != param) { mod(glCtx.fogState).setEnd(param); changed = true; } }
case GL11.GL_FOG_MODE -> { if (!isCachingEnabled() || glCtx.fogState.getFogMode() != (int) param) { mod(glCtx.fogState).setFogMode((int) param); changed = true; } }
case GL11.GL_FOG_DENSITY -> { if (!isCachingEnabled() || glCtx.fogState.getDensity() != param) { beforeUncapturedStateChange(); mod(glCtx.fogState).setDensity(param); changed = true; } }
case GL11.GL_FOG_START -> { if (!isCachingEnabled() || glCtx.fogState.getStart() != param) { beforeUncapturedStateChange(); mod(glCtx.fogState).setStart(param); changed = true; } }
case GL11.GL_FOG_END -> { if (!isCachingEnabled() || glCtx.fogState.getEnd() != param) { beforeUncapturedStateChange(); mod(glCtx.fogState).setEnd(param); changed = true; } }
case GL11.GL_FOG_MODE -> { if (!isCachingEnabled() || glCtx.fogState.getFogMode() != (int) param) { beforeUncapturedStateChange(); mod(glCtx.fogState).setFogMode((int) param); changed = true; } }
default -> {}
}
if (changed) {
@@ -3725,6 +3772,7 @@ public class GLStateManager {
}
// Update cached state (FFP shader reads from cache)
if (isCachingEnabled() && pname == GL11.GL_FOG_MODE && (!isCachingEnabled() || glCtx.fogState.getFogMode() != param)) {
beforeUncapturedStateChange();
mod(glCtx.fogState).setFogMode(param);
glCtx.fragmentGeneration++;
if (GLSMHooks.FOG_STATE_CHANGE.hasListeners()) {
@@ -3979,6 +4027,7 @@ public class GLStateManager {
}
final int depthBefore = glCtx.attribDepth;
glCtx.attribSets[depthBefore] = set;
glCtx.savedBatchStateGen[depthBefore] = glCtx.batchStateGeneration;
snapshotGenerations(glCtx, set.bump);
@@ -4027,6 +4076,9 @@ public class GLStateManager {
public static void popState() {
final GLContextState glCtx = ctx();
if (glCtx.attribDepth > 0 && glCtx.savedBatchStateGen[glCtx.attribDepth - 1] != glCtx.batchStateGeneration) {
beforeUncapturedStateChange();
}
final StateSet set = glCtx.attribSets[glCtx.attribDepth - 1];
final boolean inspectAlpha = GLSMHooks.ALPHA_STATE_CHANGE.hasListeners() && (set.contains(glCtx.alphaState.stackId()) || set.contains(glCtx.alphaTest.stackId()));
final AlphaState effectiveAlpha = inspectAlpha ? glCtx.alphaState.readEffective(glCtx.restoredAlphaScratch) : null;
@@ -4418,6 +4470,7 @@ public class GLStateManager {
}
public static void glLoadMatrix(FloatBuffer m) {
beforeMatrixChange();
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
final Matrix4f matrix = new Matrix4f().set(m);
@@ -4434,6 +4487,7 @@ public class GLStateManager {
}
public static void glLoadMatrix(DoubleBuffer m) {
beforeMatrixChange();
final GLContextState glCtx = ctx();
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
@@ -4464,6 +4518,7 @@ public class GLStateManager {
}
public static void setTextureMatrix(int unit, Matrix4fc m) {
if (unit != 0) beforeUncapturedStateChange();
if (isCachingEnabled()) {
final GLContextState glCtx = ctx();
glCtx.textures.getTextureUnitMatrix(unit).set(m);
@@ -4513,6 +4568,7 @@ public class GLStateManager {
}
public static void glLoadIdentity() {
beforeMatrixChange();
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordLoadIdentity();
@@ -4527,6 +4583,7 @@ public class GLStateManager {
}
public static void glTranslatef(float x, float y, float z) {
beforeMatrixChange();
if (DisplayListManager.isRecording()) {
DisplayListManager.applyMatrixTranslation(x, y, z);
return;
@@ -4538,6 +4595,7 @@ public class GLStateManager {
}
public static void glTranslated(double x, double y, double z) {
beforeMatrixChange();
if (DisplayListManager.isRecording()) {
DisplayListManager.applyMatrixTranslation((float) x, (float) y, (float) z);
return;
@@ -4549,6 +4607,7 @@ public class GLStateManager {
}
public static void glScalef(float x, float y, float z) {
beforeMatrixChange();
if (DisplayListManager.isRecording()) {
DisplayListManager.applyMatrixScale(x, y, z);
return;
@@ -4560,6 +4619,7 @@ public class GLStateManager {
}
public static void glScaled(double x, double y, double z) {
beforeMatrixChange();
if (DisplayListManager.isRecording()) {
DisplayListManager.applyMatrixScale((float) x, (float) y, (float) z);
return;
@@ -4571,6 +4631,7 @@ public class GLStateManager {
}
public static void glMultMatrix(FloatBuffer floatBuffer) {
beforeMatrixChange();
final GLContextState glCtx = ctx();
glCtx.multMatrix.set(floatBuffer);
final int currentMode = glCtx.matrixMode.getMode();
@@ -4586,6 +4647,7 @@ public class GLStateManager {
}
public static void applyMultMatrix(Matrix4f multMatrix) {
beforeMatrixChange();
if (isCachingEnabled()) {
getMatrixStack().mul(multMatrix);
bumpMatrixGeneration();
@@ -4593,6 +4655,7 @@ public class GLStateManager {
}
public static void glMultMatrix(DoubleBuffer matrix) {
beforeMatrixChange();
final GLContextState glCtx = ctx();
glCtx.conversionMatrix4d.set(matrix);
glCtx.conversionMatrix4f.set(glCtx.conversionMatrix4d);
@@ -4608,6 +4671,7 @@ public class GLStateManager {
}
public static void glRotatef(float angle, float x, float y, float z) {
beforeMatrixChange();
final float lenSq = x * x + y * y + z * z;
if (lenSq == 0.0f) return;
@@ -4630,6 +4694,7 @@ public class GLStateManager {
}
public static void glRotated(double angle, double x, double y, double z) {
beforeMatrixChange();
final double lenSq = x * x + y * y + z * z;
if (lenSq == 0.0) return;
@@ -4652,6 +4717,7 @@ public class GLStateManager {
}
public static void glOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
beforeMatrixChange();
if (DisplayListManager.isRecording()) {
DisplayListManager.updateRelativeTransformOrtho(left, right, bottom, top, zNear, zFar);
return;
@@ -4663,6 +4729,7 @@ public class GLStateManager {
}
public static void glFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
beforeMatrixChange();
if (DisplayListManager.isRecording()) {
DisplayListManager.updateRelativeTransformFrustum(left, right, bottom, top, zNear, zFar);
return;
@@ -4692,6 +4759,7 @@ public class GLStateManager {
}
public static void glPopMatrix() {
beforeMatrixChange();
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordPopMatrix(); // Handles flush + relativeTransform stack + lastRecordedTransform sync
@@ -4787,6 +4855,7 @@ public class GLStateManager {
return;
}
}
beforeUncapturedStateChange();
RENDER_BACKEND.viewport(x, y, width, height);
// Only update cached state when caching is enabled
if (isCachingEnabled()) {
@@ -4905,6 +4974,7 @@ public class GLStateManager {
return;
}
}
if (getActiveTextureUnit() != 0) beforeUncapturedStateChange();
if (target != GL11.GL_TEXTURE_2D) {
RENDER_BACKEND.texParameteri(target, pname, param);
return;
@@ -4948,6 +5018,7 @@ public class GLStateManager {
return;
}
}
if (getActiveTextureUnit() != 0) beforeUncapturedStateChange();
if (target != GL11.GL_TEXTURE_2D) {
RENDER_BACKEND.texParameterf(target, pname, param);
return;
@@ -5194,6 +5265,7 @@ public class GLStateManager {
public static void glBindSampler(int unit, int sampler) {
final SamplerUnitArray units = ctx().samplerUnits;
if (isCachingEnabled() && unit >= 0 && unit < units.size() && units.get(unit) == sampler) return;
beforeUncapturedStateChange();
if (!units.set(unit, sampler)) {
warnOnce("samplerUnitRange", "glBindSampler unit {} exceeds GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS {}", unit, units.size());
}
@@ -5609,7 +5681,9 @@ public class GLStateManager {
}
public static void glDepthRange(double near, double far) {
if (isCachingEnabled()) mod(ctx().viewportState).setDepthRange(near, far);
final GLContextState glCtx = ctx();
if (!isCachingEnabled() || glCtx.viewportState.depthRangeNear != near || glCtx.viewportState.depthRangeFar != far) beforeUncapturedStateChange();
if (isCachingEnabled()) mod(glCtx.viewportState).setDepthRange(near, far);
RENDER_BACKEND.depthRange(near, far);
}
@@ -5646,6 +5720,10 @@ public class GLStateManager {
}
// Non-zero program or FFP emulation not enabled
if (program == 0) {
warnOnce("useprogram-zero-ffp-disabled",
"[FFP] glUseProgram(0) reached the driver because emulation is disabled");
}
if (ffp.isActive()) {
ffp.deactivate();
}
@@ -5913,6 +5991,42 @@ public class GLStateManager {
maybeGenerateMipmap(target, level);
}
public static TextureStaging beginTextureStaging(int level, int x, int y, int width, int height) {
if (DisplayListManager.isRecording()) {
throw DisplayListManager.unsupportedInList("beginTextureStaging");
}
if (!ctx().pixelUnpackState.isDefault()) {
return null;
}
return RENDER_BACKEND.beginTextureStaging(level, x, y, width, height);
}
public static boolean commitTextureStaging(TextureStaging staging) {
if (DisplayListManager.isRecording()) {
throw DisplayListManager.unsupportedInList("commitTextureStaging");
}
suspendPixelUnpackBuffer();
final boolean committed;
try {
committed = RENDER_BACKEND.commitTextureStaging(staging);
} finally {
restorePixelUnpackBuffer();
}
if (committed) {
TextureInfoCache.INSTANCE.onTexSubImage2D(GL11.GL_TEXTURE_2D, staging.level());
maybeGenerateMipmap(GL11.GL_TEXTURE_2D, staging.level());
}
return committed;
}
public static void abandonTextureStaging(TextureStaging staging) {
RENDER_BACKEND.abandonTextureStaging(staging);
}
public static void trimTextureStaging() {
RENDER_BACKEND.trimTextureStaging();
}
public static void glTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, ByteBuffer pixels) {
if (DisplayListManager.isRecording()) {
throw DisplayListManager.unsupportedInList("glTexSubImage3D");
@@ -6052,24 +6166,19 @@ public class GLStateManager {
}
// Track front/back separately in cache (Mesa compat semantics), but always issue GL_FRONT_AND_BACK to the driver (core profile constraint).
final boolean caching = isCachingEnabled();
final boolean needsUpdate;
if (face == GL11.GL_FRONT) {
needsUpdate = !caching || glCtx.polygonState.getFrontMode() != polygonMode;
if (caching && needsUpdate) mod(glCtx.polygonState).setFrontMode(polygonMode);
} else if (face == GL11.GL_BACK) {
needsUpdate = !caching || glCtx.polygonState.getBackMode() != polygonMode;
if (caching && needsUpdate) mod(glCtx.polygonState).setBackMode(polygonMode);
} else { // GL_FRONT_AND_BACK
needsUpdate = !caching || glCtx.polygonState.getFrontMode() != polygonMode || glCtx.polygonState.getBackMode() != polygonMode;
if (caching && needsUpdate) {
mod(glCtx.polygonState);
glCtx.polygonState.setFrontMode(polygonMode);
glCtx.polygonState.setBackMode(polygonMode);
}
}
if (needsUpdate) {
RENDER_BACKEND.polygonMode(GL11.GL_FRONT_AND_BACK, polygonMode);
final boolean front = face != GL11.GL_BACK;
final boolean back = face != GL11.GL_FRONT;
final boolean needsUpdate = !caching
|| (front && glCtx.polygonState.getFrontMode() != polygonMode)
|| (back && glCtx.polygonState.getBackMode() != polygonMode);
if (!needsUpdate) return;
beforeUncapturedStateChange();
if (caching) {
mod(glCtx.polygonState);
if (front) glCtx.polygonState.setFrontMode(polygonMode);
if (back) glCtx.polygonState.setBackMode(polygonMode);
}
RENDER_BACKEND.polygonMode(GL11.GL_FRONT_AND_BACK, polygonMode);
}
public static void glPolygonOffset(float factor, float units) {
@@ -6138,6 +6247,7 @@ public class GLStateManager {
return;
}
}
beforeUncapturedStateChange();
RENDER_BACKEND.scissor(x, y, width, height);
if (isCachingEnabled()) {
mod(ctx().scissorState).setScissor(x, y, width, height);
@@ -6156,6 +6266,7 @@ public class GLStateManager {
final int clampedMask = mask & glCtx.stencilBitMask;
final boolean caching = isCachingEnabled();
if (!caching || glCtx.stencilState.getFuncFront() != func || glCtx.stencilState.getRefFront() != ref || glCtx.stencilState.getValueMaskFront() != clampedMask) {
beforeUncapturedStateChange();
mod(glCtx.stencilState).setFunc(func, ref, clampedMask);
RENDER_BACKEND.stencilFunc(func, ref, clampedMask);
}
@@ -6173,6 +6284,7 @@ public class GLStateManager {
final int clampedMask = mask & glCtx.stencilBitMask;
final boolean caching = isCachingEnabled();
if (!caching || glCtx.stencilState.getWriteMaskFront() != clampedMask) {
beforeUncapturedStateChange();
mod(glCtx.stencilState).setWriteMask(clampedMask);
RENDER_BACKEND.stencilMask(clampedMask);
}
@@ -6189,6 +6301,7 @@ public class GLStateManager {
}
final boolean caching = isCachingEnabled();
if (!caching || glCtx.stencilState.getFailOpFront() != fail || glCtx.stencilState.getZFailOpFront() != zfail || glCtx.stencilState.getZPassOpFront() != zpass) {
beforeUncapturedStateChange();
mod(glCtx.stencilState).setOp(fail, zfail, zpass);
RENDER_BACKEND.stencilOp(fail, zfail, zpass);
}
@@ -6244,6 +6357,7 @@ public class GLStateManager {
final int index = plane - GL11.GL_CLIP_PLANE0;
if (index < 0 || index >= MAX_CLIP_PLANES) return;
final int pos = equation.position();
beforeUncapturedStateChange();
glCtx.clipPlaneState.setPlane(index, equation.get(pos), equation.get(pos + 1), equation.get(pos + 2), equation.get(pos + 3), glCtx.modelViewMatrix);
glCtx.clipPlaneGeneration++;
}
@@ -6283,6 +6397,7 @@ public class GLStateManager {
return;
}
}
beforeUncapturedStateChange();
RENDER_BACKEND.drawBuffers(buffer);
}
@@ -6294,6 +6409,7 @@ public class GLStateManager {
return;
}
}
beforeUncapturedStateChange();
RENDER_BACKEND.drawBuffers(bufs);
}
@@ -6302,6 +6418,7 @@ public class GLStateManager {
if (DisplayListManager.isRecording()) {
throw DisplayListManager.unsupportedInList("glSampleCoverage");
}
beforeUncapturedStateChange();
RENDER_BACKEND.sampleCoverage(value, invert);
}
@@ -6315,6 +6432,7 @@ public class GLStateManager {
return;
}
}
beforeUncapturedStateChange();
final int clampedMask = mask & glCtx.stencilBitMask;
final boolean caching = isCachingEnabled();
boolean needsUpdate = !caching;
@@ -6351,6 +6469,7 @@ public class GLStateManager {
return;
}
}
beforeUncapturedStateChange();
final int clampedMask = mask & glCtx.stencilBitMask;
final boolean caching = isCachingEnabled();
boolean needsUpdate = !caching;
@@ -6383,6 +6502,7 @@ public class GLStateManager {
return;
}
}
beforeUncapturedStateChange();
final boolean caching = isCachingEnabled();
boolean needsUpdate = !caching;
if (!needsUpdate) {
@@ -7149,7 +7269,7 @@ public class GLStateManager {
glCtx.ffp.setTexCoord(s, t, 0.0f, 1.0f);
glCtx.dirtyTexCoordAttrib = true;
} else if (target == GL13.GL_TEXTURE1) {
if (DisplayListManager.isRecording() || ImmediateModeRecorder.isDrawing()) {
if (ImmediateModeRecorder.isDrawing()) {
ImmediateModeRecorder.setLightmapCoord(s, t);
return;
}
@@ -7160,6 +7280,7 @@ public class GLStateManager {
if (DisplayListManager.isRecording() || ImmediateModeRecorder.isDrawing()) {
glCtx.unit23TexCoordSetDuringDraw = true;
}
beforeUncapturedStateChange();
glCtx.ffp.setTexCoord(unit, s, t, 0.0f, 1.0f);
}
}
@@ -7175,6 +7296,11 @@ public class GLStateManager {
public static void setLightmapTextureCoords(int unit, float x, float y) {
if (unit == GL13.GL_TEXTURE1) {
final RecordMode mode = DisplayListManager.getRecordMode();
if (mode != RecordMode.NONE) {
DisplayListManager.recordStateCommand(() -> setLightmapTextureCoords(unit, x, y));
if (mode == RecordMode.COMPILE) return;
}
final GLContextState glCtx = ctx();
glCtx.lastBrightnessX = x;
glCtx.lastBrightnessY = y;
@@ -7253,6 +7379,7 @@ public class GLStateManager {
public static void glTexEnv(int target, int pname, FloatBuffer params) {
final GLContextState glCtx = ctx();
if (target == GL11.GL_TEXTURE_ENV && pname == GL11.GL_TEXTURE_ENV_COLOR && params.remaining() >= 4) {
beforeUncapturedStateChange();
final int pos = params.position();
final var envState = glCtx.textures.getTexEnvState(glCtx.activeTextureUnit.getValue());
envState.beforeModify();
@@ -7283,6 +7410,7 @@ public class GLStateManager {
* GL_TEXTURE_ENV parameters including GL_COMBINE sub-params are tracked in per-unit TexEnvState.
*/
private static void handleTexEnvScalar(int target, int pname, float param) {
beforeUncapturedStateChange();
final GLContextState glCtx = ctx();
if (target == GL14.GL_TEXTURE_FILTER_CONTROL) {
// LOD bias via legacy glTexEnv path - remap to core-profile glTexParameterf
@@ -7429,6 +7557,7 @@ public class GLStateManager {
public static void glTexGeni(int coord, int pname, int param) {
beforeUncapturedStateChange();
final GLContextState glCtx = ctx();
if (!isCachingEnabled()) return;
if (pname == GL11.GL_TEXTURE_GEN_MODE) {
@@ -7448,6 +7577,7 @@ public class GLStateManager {
}
public static void glTexGen(int coord, int pname, FloatBuffer params) {
beforeUncapturedStateChange();
final GLContextState glCtx = ctx();
if (!isCachingEnabled()) return;
final int unit = glCtx.activeTextureUnit.getValue();
@@ -7478,6 +7608,7 @@ public class GLStateManager {
}
public static void glTexGen(int coord, int pname, DoubleBuffer params) {
beforeUncapturedStateChange();
final GLContextState glCtx = ctx();
if (!isCachingEnabled()) return;
final int pos = params.position();
@@ -7504,6 +7635,7 @@ public class GLStateManager {
}
public static void glTexGen(int coord, int pname, IntBuffer params) {
beforeUncapturedStateChange();
final GLContextState glCtx = ctx();
if (!isCachingEnabled()) return;
if (pname == GL11.GL_TEXTURE_GEN_MODE) {
@@ -7672,6 +7804,14 @@ public class GLStateManager {
RENDER_BACKEND.deleteShader(vertShader);
}
@Getter
private static long programLifetimeGeneration;
private static final IntOpenHashSet programsPendingDeletion = new IntOpenHashSet();
public static boolean isProgramPendingDeletion(int program) {
return programsPendingDeletion.contains(program);
}
public static void glDeleteProgram(int program) {
if (program == 0) return;
CompatUniformManager.onDeleteProgram(program);
@@ -7683,6 +7823,9 @@ public class GLStateManager {
GLSMHooks.PROGRAM_DELETE.post(GLSMHooks.programDeleteEvent);
}
RENDER_BACKEND.deleteProgram(program);
programsPendingDeletion.add(program);
programsPendingDeletion.removeIf((int id) -> !RENDER_BACKEND.isProgram(id));
programLifetimeGeneration++;
}
public static int glCreateShader(int type) {
@@ -7694,7 +7837,10 @@ public class GLStateManager {
}
public static int glCreateProgram() {
return RENDER_BACKEND.createProgram();
final int program = RENDER_BACKEND.createProgram();
programsPendingDeletion.remove(program);
programLifetimeGeneration++;
return program;
}
public static void glAttachShader(int program, int shader) {
@@ -7742,7 +7888,9 @@ public class GLStateManager {
}
public static boolean glIsProgram(int obj) {
return RENDER_BACKEND.isProgram(obj);
final boolean valid = RENDER_BACKEND.isProgram(obj);
if (!valid) programsPendingDeletion.remove(obj);
return valid;
}
public static boolean glIsShader(int obj) {
@@ -8009,6 +8157,9 @@ public class GLStateManager {
public static void glGetTexImage(int target, int level, int format, int type, long pixels) {
RENDER_BACKEND.getTexImage(target, level, format, type, pixels);
}
public static void glReadPixels(int x, int y, int width, int height, int format, int type, long pixels) {
RENDER_BACKEND.readPixels(x, y, width, height, format, remapPixelTypeForGLES(format, type), pixels);
}
/**
* Draw modes that rely on the previous vertex data cannot be merged currently.
@@ -8404,6 +8555,56 @@ public class GLStateManager {
return ctx().backMaterial;
}
public static void captureLighting(LightingSnapshot out) {
final GLContextState glCtx = ctx();
for (int i = 0; i < out.lights.length; i++) {
out.lightEnabled[i] = glCtx.lightStates[i].isEnabled();
out.lights[i].set(glCtx.lightDataStates[i]);
}
out.lightModel.set(glCtx.lightModel);
out.frontMaterial.set(glCtx.frontMaterial);
out.backMaterial.set(glCtx.backMaterial);
out.colorMaterial = glCtx.colorMaterial.isEnabled();
out.colorMaterialFace = glCtx.colorMaterialFace.getValue();
out.colorMaterialParameter = glCtx.colorMaterialParameter.getValue();
out.normalize = glCtx.normalizeState.isEnabled();
out.rescaleNormal = glCtx.rescaleNormalState.isEnabled();
}
public static void applyLighting(LightingSnapshot in) {
final GLContextState glCtx = ctx();
boolean changed = false;
for (int i = 0; i < in.lights.length; i++) {
glCtx.lightStates[i].setEnabled(in.lightEnabled[i]);
if (!glCtx.lightDataStates[i].sameAs(in.lights[i])) {
mod(glCtx.lightDataStates[i]).set(in.lights[i]);
changed = true;
}
}
if (!glCtx.lightModel.sameAs(in.lightModel)) {
mod(glCtx.lightModel).set(in.lightModel);
changed = true;
}
if (!glCtx.frontMaterial.sameAs(in.frontMaterial)) {
mod(glCtx.frontMaterial).set(in.frontMaterial);
changed = true;
}
if (!glCtx.backMaterial.sameAs(in.backMaterial)) {
mod(glCtx.backMaterial).set(in.backMaterial);
changed = true;
}
if (glCtx.colorMaterialFace.getValue() != in.colorMaterialFace || glCtx.colorMaterialParameter.getValue() != in.colorMaterialParameter) {
mod(glCtx.colorMaterialFace).setValue(in.colorMaterialFace);
mod(glCtx.colorMaterialParameter).setValue(in.colorMaterialParameter);
changed = true;
}
// Direct set: glEnable(GL_COLOR_MATERIAL) would bake the current color over the captured material.
glCtx.colorMaterial.setEnabled(in.colorMaterial);
glCtx.normalizeState.setEnabled(in.normalize);
glCtx.rescaleNormalState.setEnabled(in.rescaleNormal);
if (changed) glCtx.lightingGeneration++;
}
public static ViewPortStateStack getViewportState() {
return ctx().viewportState;
}
@@ -0,0 +1,26 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.VertexKey;
import com.gtnewhorizons.angelica.glsm.states.LightModelState;
import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import org.lwjgl.opengl.GL11;
public final class LightingSnapshot {
final boolean[] lightEnabled = new boolean[VertexKey.FFP_LIGHT_COUNT];
final LightState[] lights = new LightState[VertexKey.FFP_LIGHT_COUNT];
final LightModelState lightModel = new LightModelState();
final MaterialState frontMaterial = new MaterialState(GL11.GL_FRONT);
final MaterialState backMaterial = new MaterialState(GL11.GL_BACK);
boolean colorMaterial;
int colorMaterialFace;
int colorMaterialParameter;
boolean normalize;
boolean rescaleNormal;
public LightingSnapshot() {
for (int i = 0; i < lights.length; i++) {
lights[i] = new LightState(GL11.GL_LIGHT0 + i);
}
}
}
@@ -496,10 +496,12 @@ 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);
}
@@ -88,10 +88,15 @@ public final class StateSet {
GLStateManager.getActiveTextureUnitStack(),
GLStateManager.getBlendMode(), GLStateManager.getAlphaTest(), GLStateManager.getDepthTest(),
GLStateManager.getLightingState(), GLStateManager.getCullState(), GLStateManager.getPolygonOffsetFillState(),
GLStateManager.getTextures().getTextureUnitStates(0)
GLStateManager.getTextures().getTextureUnitStates(0), GLStateManager.getTextures().getTextureUnitStates(1),
GLStateManager.getLightStates()[0], GLStateManager.getLightStates()[1],
GLStateManager.getLightDataStates()[0], GLStateManager.getLightDataStates()[1],
GLStateManager.getColorMaterial(), GLStateManager.getColorMaterialFace(), GLStateManager.getColorMaterialParameter(),
GLStateManager.getLightModel(), GLStateManager.getFrontMaterial(), GLStateManager.getBackMaterial(),
GLStateManager.getNormalizeState(), GLStateManager.getRescaleNormalState()
),
R_BLEND | R_DEPTH | R_COLOR_MASK | R_POLYGON | R_TEXTURE | R_ACTIVE_UNIT,
B_FRAG_COLORBUF,
B_FRAG_COLORBUF | B_LIGHTING,
true);
}
@@ -652,6 +652,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL11.glReadPixels(x, y, width, height, format, type, pixels);
}
@Override
public void readPixels(int x, int y, int width, int height, int format, int type, long pixelBufferOffset) {
GL11.glReadPixels(x, y, width, height, format, type, pixelBufferOffset);
}
@Override
public void getTexImage(int target, int level, int format, int type, ByteBuffer pixels) {
GL11.glGetTexImage(target, level, format, type, pixels);
@@ -1,10 +1,14 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLESFormatRemap;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.texture.TextureStaging;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL20;
import java.nio.ByteBuffer;
@@ -66,6 +70,7 @@ public abstract class RenderBackend {
public abstract void flush();
public abstract void finish();
private final ByteBuffer[] clientStagingCache = new ByteBuffer[16];
private VSyncMode effectiveVSyncMode = VSyncMode.ON;
private VSyncMode preferredTearFreeMode;
@@ -351,6 +356,71 @@ public abstract class RenderBackend {
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, IntBuffer pixels) {}
public void texSubImage1D(int target, int level, int xoffset, int width, int format, int type, ByteBuffer pixels) {}
public void texSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, long pboOffset) {}
public TextureStaging beginTextureStaging(int level, int x, int y, int width, int height) {
final int size = Math.multiplyExact(Math.multiplyExact(width, height), 4);
ByteBuffer buffer = null;
for (int i = 0; i < clientStagingCache.length; i++) {
final ByteBuffer cached = clientStagingCache[i];
if (cached != null && cached.capacity() == size) {
clientStagingCache[i] = null;
buffer = cached;
break;
}
}
if (buffer == null) buffer = MemoryUtilities.memAlloc(size);
buffer.clear();
return new ClientTextureStaging(buffer, level, x, y, width, height);
}
public boolean commitTextureStaging(TextureStaging staging) {
final int type = RenderSystem.isGLES() ? GLESFormatRemap.remapPixelType(GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV) : GL12.GL_UNSIGNED_INT_8_8_8_8_REV;
try {
final ClientTextureStaging s = (ClientTextureStaging) staging;
texSubImage2D(GL11.GL_TEXTURE_2D, s.level(), s.x(), s.y(), s.width(), s.height(), GL12.GL_BGRA, type, s.buffer());
} finally {
recycleClientStaging(staging.buffer());
}
return true;
}
public void abandonTextureStaging(TextureStaging staging) {
recycleClientStaging(staging.buffer());
}
public void trimTextureStaging() {
for (int i = 0; i < clientStagingCache.length; i++) {
MemoryUtilities.memFree(clientStagingCache[i]);
clientStagingCache[i] = null;
}
}
private record ClientTextureStaging(ByteBuffer buffer, int level, int x, int y, int width, int height) implements TextureStaging {
@Override
public boolean bgra() {
return true;
}
}
private void recycleClientStaging(ByteBuffer buffer) {
int victim = -1;
for (int i = 0; i < clientStagingCache.length; i++) {
final ByteBuffer cached = clientStagingCache[i];
if (cached == null) {
clientStagingCache[i] = buffer;
return;
}
if (cached.capacity() < buffer.capacity() && (victim < 0 || cached.capacity() < clientStagingCache[victim].capacity())) {
victim = i;
}
}
if (victim < 0) {
MemoryUtilities.memFree(buffer);
return;
}
MemoryUtilities.memFree(clientStagingCache[victim]);
clientStagingCache[victim] = buffer;
}
public void texSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, ByteBuffer pixels) {}
public void copyTexImage1D(int target, int level, int internalFormat, int x, int y, int width, int border) {}
public void copyTexImage2D(int target, int level, int internalFormat, int x, int y, int width, int height, int border) {}
@@ -393,6 +463,7 @@ public abstract class RenderBackend {
public abstract void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, long pixelBufferOffset);
public abstract void getTexImage(int target, int level, int format, int type, ByteBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, IntBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, long pixelBufferOffset);
@@ -4,6 +4,7 @@ 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;
@@ -226,8 +227,13 @@ 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(GLContextState glCtx, int vboId) {
@@ -55,13 +55,18 @@ public final class InstancedAttribs {
return (entity & 0xFFFFL) | ((blockEntity & 0xFFFFL) << 16) | ((item & 0xFFFFL) << 32);
}
public static int unpackId(long info, int shift) {
final int id = (int) (info >>> shift) & 0xFFFF;
return id == 0xFFFF ? -1 : id;
}
public static void pointHead(long base, int stride) {
for (int r = 0; r < 3; r++) {
GLStateManager.glVertexAttribPointer(LOC_ROW0 + r, 4, GL11.GL_FLOAT, false, stride, base + OFFSET_ROWS + r * 16L);
}
GLStateManager.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_COLOR);
GLStateManager.glVertexAttribPointer(LOC_OVERLAY, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_OVERLAY);
GLStateManager.glVertexAttribIPointer(LOC_ENTITY, 4, GL11.GL_SHORT, stride, base + OFFSET_ENTITY);
GLStateManager.glVertexAttribIPointer(LOC_ENTITY, 4, GL11.GL_UNSIGNED_SHORT, stride, base + OFFSET_ENTITY);
}
public static void pointTemplate(long base) {
@@ -102,7 +102,11 @@ public class Program {
for (int i = 0; i < 4; i++) {
if (ffpProgram.locSampler[i] != -1) backend.uniform1i(ffpProgram.locSampler[i], i);
}
backend.useProgram(previousProgram);
if (previousProgram != 0) {
backend.useProgram(previousProgram);
} else {
ShaderManager.getInstance().invalidateProgram();
}
return ffpProgram;
} catch (RuntimeException e) {
@@ -116,9 +116,10 @@ public final class ShaderManager {
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;
@@ -170,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++;
@@ -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);
}
}
}
@@ -18,6 +18,7 @@ public final class GLSMConfig {
public static boolean extendedAttribsExpected;
public static volatile boolean expandVertexFormats;
public static int workerThreadCount;
private GLSMConfig() {}
}
@@ -11,7 +11,6 @@ public final class GLSMHooks {
public static DeferredDepthColorHandler depthColorHandler;
public static GlintColorHandler glintColorHandler;
public static ImmediateExtendedAttribHandler immediateExtendedHandler;
public static ShaderWorkSubmitter shaderWorkSubmitter;
public static ShaderTransformPostProcessor postTransformProcessor;
public static PerFrameUniformBlock perFrameUniformBlock;
public static PerFrameUniformBlock perPassUniformBlock;
@@ -1,8 +0,0 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
public interface ShaderWorkSubmitter {
<T> CompletableFuture<T> submit(Supplier<T> work);
}
@@ -1,14 +1,28 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLContextState;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
public final class TextureUtilHooks {
private static int pausedTexture;
private static int pausedMin;
private static int pausedMag;
private static float pausedAniso;
private static int nearestTexture;
private static int nearestMin;
private static int nearestMag;
private static float nearestAniso;
private TextureUtilHooks() {}
public static void setMaxLevel(int mipmapLevels) {
@@ -16,7 +30,17 @@ public final class TextureUtilHooks {
}
public static void captureBoundFilterTexture() {
GLStateManager.ctx().savedFilterTextureId = GLStateManager.getBoundTextureForServerState();
final GLContextState ctx = GLStateManager.ctx();
final int id = GLStateManager.getBoundTextureForServerState();
ctx.savedFilterTextureId = id;
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(id);
if (info != null) {
ctx.savedFilterMin = info.getMinFilter();
ctx.savedFilterMag = info.getMagFilter();
ctx.savedFilterAniso = info.getMaxAnisotropy();
} else {
ctx.savedFilterMin = -1;
}
}
public static void clearSavedFilterTexture() {
@@ -42,4 +66,61 @@ public final class TextureUtilHooks {
GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
}
}
public static boolean pauseTemporaryFilter() {
final GLContextState ctx = GLStateManager.ctx();
final int id = ctx.savedFilterTextureId;
if (id == 0 || pausedTexture != 0 || ctx.savedFilterMin < 0
|| DisplayListManager.getRecordMode() != DisplayListManager.RecordMode.NONE) return false;
final TextureInfo info = TextureInfoCache.INSTANCE.getInfo(id);
if (info == null) return false;
pausedTexture = id;
pausedMin = info.getMinFilter();
pausedMag = info.getMagFilter();
pausedAniso = info.getMaxAnisotropy();
setFilter(id, ctx.savedFilterMin, ctx.savedFilterMag, ctx.savedFilterAniso);
return true;
}
public static void resumeTemporaryFilter() {
if (pausedTexture == 0) return;
final int id = pausedTexture;
pausedTexture = 0;
setFilter(id, pausedMin, pausedMag, pausedAniso);
}
/** TextureUtil.func_152777_a(false, false, 1) without using vanilla's single save slot, which the caller may hold. */
public static void forceNearestFilter() {
final int id = GLStateManager.getBoundTextureForServerState();
final TextureInfo info = id != 0 ? TextureInfoCache.INSTANCE.getInfo(id) : null;
if (info == null) {
nearestTexture = 0;
return;
}
nearestTexture = id;
nearestMin = info.getMinFilter();
nearestMag = info.getMagFilter();
nearestAniso = info.getMaxAnisotropy();
setFilter(id, GL11.GL_NEAREST, GL11.GL_NEAREST, 1.0f);
}
public static void restoreFilterAfterNearest() {
if (nearestTexture == 0) return;
final int id = nearestTexture;
nearestTexture = 0;
setFilter(id, nearestMin, nearestMag, nearestAniso);
}
private static void setFilter(int id, int min, int mag, float aniso) {
final boolean anisotropic = BackendManager.RENDER_BACKEND.isAnisotropicSupported();
if (DisplayListManager.getRecordMode() == DisplayListManager.RecordMode.NONE) {
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
RenderSystem.texParameteri(id, GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
if (anisotropic) RenderSystem.texParameterf(id, GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
} else {
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, min);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, mag);
if (anisotropic) GLStateManager.glTexParameterf(GL11.GL_TEXTURE_2D, EXTTextureFilterAnisotropic.GL_TEXTURE_MAX_ANISOTROPY_EXT, aniso);
}
}
}
@@ -38,6 +38,10 @@ public class ModRedirector {
return allExclusions;
}
public String[] getConstantPoolSearchStrings() {
return core.getConstantPoolSearchStrings();
}
public boolean shouldTransform(byte[] basicClass) {
return core.shouldTransform(basicClass);
}
@@ -1,5 +1,7 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.retrofuturabootstrap.api.BytePatternMatcher;
import com.gtnewhorizons.retrofuturabootstrap.api.ClassHeaderMetadata;
import com.gtnewhorizons.retrofuturabootstrap.api.ClassNodeHandle;
import com.gtnewhorizons.retrofuturabootstrap.api.ExtensibleClassLoader;
import com.gtnewhorizons.retrofuturabootstrap.api.RfbClassTransformer;
@@ -16,10 +18,12 @@ public abstract class RfbModRedirector implements RfbClassTransformer {
private final String id;
private final ModRedirector inner;
private final BytePatternMatcher cstPoolMatcher;
protected RfbModRedirector(@Pattern("[a-z0-9-]+") String id, ModRedirector inner) {
this.id = id;
this.inner = inner;
this.cstPoolMatcher = new BytePatternMatcher(inner.getConstantPoolSearchStrings(), BytePatternMatcher.Mode.StartsWith);
}
@Override
@@ -53,7 +57,11 @@ public abstract class RfbModRedirector implements RfbClassTransformer {
if (!classNode.isPresent()) {
return false;
}
return inner.shouldTransform(classNode.getOriginalBytes());
final ClassHeaderMetadata metadata = classNode.getOriginalMetadata();
if (metadata == null) {
return inner.shouldTransform(classNode.getOriginalBytes());
}
return metadata.matchesBytes(classNode.getOriginalBytes(), cstPoolMatcher);
}
@Override
@@ -1,6 +1,5 @@
package com.gtnewhorizons.angelica.glsm.profiling;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CaptureGate;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.sun.management.ThreadMXBean;
@@ -66,12 +65,12 @@ public final class Tracy {
static {
TracyBackend backend = null;
if (SystemProperties.TRACY) {
if (TracyOptions.enabled()) {
backend = loadAndInit();
}
BACKEND = backend;
ENABLED = backend != null;
FINE_ZONES = ENABLED && SystemProperties.TRACY_FINE_ZONES;
FINE_ZONES = ENABLED && TracyOptions.fineZones();
if (ENABLED) {
Runtime.getRuntime().addShutdownHook(new Thread(Tracy::shutdown, "Tracy-Shutdown"));
SECTIONS.registerCategory(SECTION_WORLD, "World");
@@ -86,6 +85,18 @@ public final class Tracy {
private static void shutdown() {
SHUTTING_DOWN = true;
if (isCaptureActive(BACKEND.captureState())) {
BACKEND.captureStop();
final long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);
while (isCaptureActive(BACKEND.captureState()) && System.nanoTime() < deadline) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
final Thread worker = new Thread(BACKEND::shutdown, "Tracy-Shutdown-Worker");
worker.setDaemon(true);
worker.start();
@@ -96,6 +107,10 @@ public final class Tracy {
}
}
private static boolean isCaptureActive(int state) {
return state == TracyBackend.CAPTURE_CONNECTING || state == TracyBackend.CAPTURE_RECORDING || state == TracyBackend.CAPTURE_SAVING;
}
private static TracyBackend loadAndInit() {
TracyBackend found = null;
try {
@@ -104,11 +119,11 @@ public final class Tracy {
found = it.next();
}
} catch (ServiceConfigurationError | LinkageError e) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but backend unavailable: {}", e.getMessage());
LOGGER.warn("Tracy: backend unavailable: {}", e.getMessage());
return null;
}
if (found == null) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but no TracyBackend service present");
LOGGER.warn("Tracy: enabled but the angelica-tracy jar is not installed");
return null;
}
if (!implementsCurrentAbi(found)) return null;
@@ -120,7 +135,7 @@ public final class Tracy {
init.start();
try {
if (!done.await(15, TimeUnit.SECONDS) && result.compareAndSet(null, Boolean.FALSE)) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but init timed out; profiling disabled");
LOGGER.warn("Tracy: init timed out; profiling disabled");
return null;
}
} catch (InterruptedException e) {
@@ -131,7 +146,7 @@ public final class Tracy {
return null;
}
if (!Boolean.TRUE.equals(result.get())) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but native init failed; profiling disabled");
LOGGER.warn("Tracy: native init failed; profiling disabled");
return null;
}
return backend;
@@ -388,6 +403,31 @@ public final class Tracy {
return ENABLED && BACKEND.isConnected();
}
public static String captureStart(String path, int seconds) {
if (!ENABLED) return "Tracy not enabled";
return BACKEND.captureStart(path, seconds);
}
public static void captureStop() {
if (!ENABLED) return;
BACKEND.captureStop();
}
public static int captureState() {
if (!ENABLED) return 0;
return BACKEND.captureState();
}
public static long captureElapsedMs() {
if (!ENABLED) return 0;
return BACKEND.captureElapsedMs();
}
public static String captureError() {
if (!ENABLED) return "";
return BACKEND.captureError();
}
private static boolean gpuInitAttempted;
private static boolean gpuReady;
@Setter private static boolean gpuZonesEnabled = true;
@@ -12,6 +12,13 @@ public interface TracyBackend {
int SEVERITY_ERROR = 4;
int SEVERITY_FATAL = 5;
int CAPTURE_IDLE = 0;
int CAPTURE_CONNECTING = 1;
int CAPTURE_RECORDING = 2;
int CAPTURE_SAVING = 3;
int CAPTURE_DONE = 4;
int CAPTURE_FAILED = 5;
boolean init();
long internSrcLoc(String name, int color);
@@ -47,4 +54,12 @@ public interface TracyBackend {
boolean gpuBeginZone(long srcLoc);
void gpuEndZone();
void gpuCollect();
default String captureStart(String path, int seconds) {
return "Tracy capture unsupported by this angelica-tracy build";
}
default void captureStop() {}
default int captureState() { return CAPTURE_IDLE; }
default long captureElapsedMs() { return 0; }
default String captureError() { return ""; }
}
@@ -0,0 +1,64 @@
package com.gtnewhorizons.angelica.glsm.profiling;
import com.gtnewhorizons.angelica.config.SystemProperties;
public final class TracyOptions {
private static boolean configEnabled;
private static boolean configRemote;
private static boolean configFineZones;
private static int configMaxSrcLocs = 4096;
private TracyOptions() {}
public static void latch(boolean enabled, boolean allowRemote, boolean fineZones, int maxSrcLocs) {
configEnabled = enabled;
configRemote = allowRemote;
configFineZones = fineZones;
configMaxSrcLocs = maxSrcLocs;
}
public static boolean enabled() {
return resolveBoolean(SystemProperties.TRACY_OVERRIDE, configEnabled);
}
public static boolean allowRemote() {
return configRemote;
}
public static boolean fineZones() {
return resolveBoolean(SystemProperties.TRACY_FINE_ZONES_OVERRIDE, configFineZones);
}
public static int maxSrcLocs() {
return resolveMaxSrcLocs(SystemProperties.TRACY_MAX_SRC_LOCS_OVERRIDE, configMaxSrcLocs);
}
public static boolean configEnabled() {
return configEnabled;
}
public static boolean configRemote() {
return configRemote;
}
public static boolean configFineZones() {
return configFineZones;
}
public static int configMaxSrcLocs() {
return configMaxSrcLocs;
}
public static boolean backendPresent() {
return TracyOptions.class.getResource("/com/gtnewhorizons/angelica/tracy/TracyClientBackend.class") != null;
}
static boolean resolveBoolean(Boolean override, boolean config) {
return override != null ? override : config;
}
static int resolveMaxSrcLocs(Integer override, int config) {
return Math.max(16, override != null ? override : config);
}
}
@@ -97,7 +97,8 @@ public final class CommandBufferExecutor {
ptr += 4;
}
case GLCommand.USE_PROGRAM -> {
GLStateManager.glUseProgram(memGetInt(ptr));
final int recordedProgram = memGetInt(ptr);
GLStateManager.glUseProgram(compiledDisplayList.resolveProgram(recordedProgram));
ptr += 4;
}
case GLCommand.PUSH_ATTRIB -> {
@@ -252,6 +253,11 @@ public final class CommandBufferExecutor {
}
// === Float commands ===
case GLCommand.VERTEX_ATTRIB -> {
GLStateManager.glVertexAttrib4f(memGetInt(ptr), memGetFloat(ptr + 4), memGetFloat(ptr + 8),
memGetFloat(ptr + 12), memGetFloat(ptr + 16));
ptr += 20;
}
case GLCommand.POINT_SIZE -> {
GLStateManager.glPointSize(memGetFloat(ptr));
ptr += 4;
@@ -10,6 +10,8 @@ import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
@@ -21,6 +23,13 @@ public final class CommandRecorder {
private long writePointer;
private final ArrayList<DisplayListCommand> complexObjects = new ArrayList<>();
private final IndexedDrawBatchBuilder indexedDraws = new IndexedDrawBatchBuilder();
private Set<DisplayListCommand> onceCommands;
private DisplayListCommand lastComplexCommand;
private int lastComplexCommandEnd = -1;
public boolean isLastCommand(DisplayListCommand command) {
return size() == lastComplexCommandEnd && command.equals(lastComplexCommand);
}
public CommandRecorder() {
this(DEFAULT_CAPACITY);
@@ -32,6 +41,11 @@ public final class CommandRecorder {
this.writePointer = basePointer;
}
public boolean markRecordedOnce(DisplayListCommand command) {
if (onceCommands == null) onceCommands = new HashSet<>();
return onceCommands.add(command);
}
// === Low-level write operations ===
private void writeInt(int value) {
@@ -364,6 +378,16 @@ public final class CommandRecorder {
writeFloat(b);
}
public void writeVertexAttrib(int index, float x, float y, float z, float w) {
ensureCapacity(24);
writeInt(GLCommand.VERTEX_ATTRIB);
writeInt(index);
writeFloat(x);
writeFloat(y);
writeFloat(z);
writeFloat(w);
}
public void writeClearColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.CLEAR_COLOR);
@@ -649,6 +673,8 @@ public final class CommandRecorder {
complexObjects.add(cmd);
writeInt(GLCommand.COMPLEX_REF);
writeInt(index);
lastComplexCommand = cmd;
lastComplexCommandEnd = size();
}
// === Lifecycle ===
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
@@ -35,6 +36,24 @@ public final class CompiledDisplayList {
private final DisplayListCommand[] complexObjects; // Complex commands (TexImage2D, etc.)
private final DisplayListVBO ownedVbos; // GPU resources referenced by index
private final List<IndexedDrawBatch> indexedBatches; // Shared VAO/VBO/EBO triples per layout group
private Int2IntMap replayPrograms;
private long programLifetimeGeneration = -1;
int resolveProgram(int program) {
if (program == 0) return 0;
final long generation = GLStateManager.getProgramLifetimeGeneration();
if (replayPrograms == null) replayPrograms = new Int2IntOpenHashMap();
if (programLifetimeGeneration != generation) {
replayPrograms.clear();
programLifetimeGeneration = generation;
}
if (replayPrograms.containsKey(program)) return replayPrograms.get(program);
final boolean valid = GLStateManager.glIsProgram(program);
if (!valid || !GLStateManager.isProgramPendingDeletion(program)) {
replayPrograms.put(program, valid ? program : 0);
}
return valid ? program : 0;
}
public CompiledDisplayList(ByteBuffer commandBuffer, DisplayListCommand[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches) {
this.commandBuffer = commandBuffer;
@@ -24,9 +24,9 @@ public final class GLCommand {
public static final int USE_PROGRAM = 11; // [cmd:4][program:4]
public static final int PUSH_ATTRIB = 12; // [cmd:4][mask:4]
public static final int POP_ATTRIB = 13; // [cmd:4][unused:4]
public static final int LOAD_IDENTITY = 14; // [cmd:4][matrixMode:4]
public static final int PUSH_MATRIX = 15; // [cmd:4][matrixMode:4]
public static final int POP_MATRIX = 16; // [cmd:4][matrixMode:4]
public static final int LOAD_IDENTITY = 14; // [cmd:4]
public static final int PUSH_MATRIX = 15; // [cmd:4]
public static final int POP_MATRIX = 16; // [cmd:4]
public static final int STENCIL_MASK = 17; // [cmd:4][mask:4]
public static final int DEPTH_MASK = 18; // [cmd:4][flag:4] (0 or 1)
public static final int FRONT_FACE = 19; // [cmd:4][mode:4]
@@ -62,6 +62,7 @@ public final class GLCommand {
public static final int CLEAR_COLOR = 55; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int BLEND_COLOR = 56; // [cmd:4][r:4f][g:4f][b:4f][a:4f]
public static final int SECONDARY_COLOR = 57; // [cmd:4][r:4f][g:4f][b:4f]
public static final int VERTEX_ATTRIB = 58; // [cmd:4][index:4][x:4f][y:4f][z:4f][w:4f]
// === Mixed int+float commands ===
public static final int ALPHA_FUNC = 60; // [cmd:4][func:4][ref:4f]
@@ -73,14 +74,13 @@ public final class GLCommand {
public static final int MATERIALF = 66; // [cmd:4][face:4][pname:4][param:4f]
public static final int TEX_PARAMETERF = 67; // [cmd:4][target:4][pname:4][param:4f]
// === Double commands ===
public static final int TRANSLATE = 70; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int SCALE = 72; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int TRANSLATE = 70; // [cmd:4][x:4f][y:4f][z:4f] = 16 bytes
public static final int SCALE = 72; // [cmd:4][x:4f][y:4f][z:4f] = 16 bytes
public static final int CLEAR_DEPTH = 75; // [cmd:4][depth:8d] = 12 bytes
// === Matrix commands (72 bytes) ===
public static final int MULT_MATRIX = 80; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
public static final int LOAD_MATRIX = 81; // [cmd:4][mode:4][m00-m33:64f] = 72 bytes
// === Matrix commands (68 bytes) ===
public static final int MULT_MATRIX = 80; // [cmd:4][m00-m33:64f] = 68 bytes
public static final int LOAD_MATRIX = 81; // [cmd:4][m00-m33:64f] = 68 bytes
// === Buffer commands (inline 4 floats max) ===
public static final int FOG = 90; // [cmd:4][pname:4][count:4][params:16f] = 28 bytes
@@ -157,6 +157,7 @@ public final class GLCommand {
case CLEAR_COLOR -> "CLEAR_COLOR";
case BLEND_COLOR -> "BLEND_COLOR";
case SECONDARY_COLOR -> "SECONDARY_COLOR";
case VERTEX_ATTRIB -> "VERTEX_ATTRIB";
case ALPHA_FUNC -> "ALPHA_FUNC";
case FOGF -> "FOGF";
case LIGHTF -> "LIGHTF";
@@ -221,6 +222,8 @@ public final class GLCommand {
GLCommand.SCISSOR,
GLCommand.COLOR, GLCommand.CLEAR_COLOR, GLCommand.BLEND_COLOR -> 20;
case GLCommand.VERTEX_ATTRIB -> 24;
// DRAW_RANGE_RESTORE: 28 bytes
case GLCommand.DRAW_RANGE_RESTORE -> 28;
@@ -176,6 +176,7 @@ public class GLSMRedirector {
private static final Map<Integer, String> glCapRedirects = new HashMap<>();
private static final Map<String, String> typeRedirects = new HashMap<>();
private static final ClassConstantPoolParser cstPoolParser;
private static final String[] CONSTANT_POOL_SEARCH_STRINGS;
private static final String[] CORE_EXCLUSIONS = {
"org.lwjgl",
@@ -826,13 +827,18 @@ public class GLSMRedirector {
stringsToSearch.add(Display);
stringsToSearch.addAll(typeRedirects.keySet());
stringsToSearch.addAll(methodRedirects.keySet());
cstPoolParser = new ClassConstantPoolParser(stringsToSearch.toArray(new String[0]));
CONSTANT_POOL_SEARCH_STRINGS = stringsToSearch.toArray(new String[0]);
cstPoolParser = new ClassConstantPoolParser(CONSTANT_POOL_SEARCH_STRINGS);
}
public String[] getCoreExclusions() {
return CORE_EXCLUSIONS.clone();
}
public String[] getConstantPoolSearchStrings() {
return CONSTANT_POOL_SEARCH_STRINGS.clone();
}
public static String getGLPrefix() { return GL_PREFIX; }
public static String getTargetClassName() { return GLStateManager; }
@@ -24,6 +24,9 @@ public final class TextureUnitBooleanStateStack extends BooleanStateStack {
@Override
public void setEnabled(boolean enabled) {
if (!GLStateManager.isCachingEnabled() || enabled != this.enabled) {
GLStateManager.beforeUncapturedTextureUnitChange(glCap, unitIndex);
}
beforeModify();
setEnabledWithUnitSwitch(enabled);
}
@@ -46,6 +46,7 @@ public class BooleanState implements ISettableState<BooleanState> {
public void setEnabled(boolean enabled) {
final boolean bypass = !GLStateManager.isCachingEnabled();
if (stateUnknown || bypass || enabled != this.enabled || (this.glCap == GL11.GL_BLEND && GLStateManager.vendorIsAMD() && GLStateManager.isPoppingAttributes())) {
GLStateManager.beforeUncapturedCapabilityChange(glCap);
stateUnknown = false;
this.enabled = enabled;
onEnabledChanged();
@@ -0,0 +1,9 @@
package com.gtnewhorizons.angelica.glsm.texture;
import java.nio.ByteBuffer;
public interface TextureStaging {
ByteBuffer buffer();
boolean bgra();
int level();
}
@@ -0,0 +1,186 @@
package com.gtnewhorizons.angelica.glsm.threading;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
public final class AngelicaWorkers {
private static final Logger LOGGER = LogManager.getLogger("Angelica");
private static final long IDLE_TIMEOUT_SECONDS = 120;
private static final Object lock = new Object();
private static final List<Runnable> idleShutdownHooks = new ArrayList<>();
private static ExecutorService executor;
private static ScheduledExecutorService scheduler;
private static volatile long lastActivityTime;
private static final AtomicInteger inFlight = new AtomicInteger(0);
private static boolean idleCheckScheduled;
private static int resolvedThreads;
private AngelicaWorkers() {}
private static final class Worker extends Thread {
Worker(Runnable r, String name) {
super(r, name);
setDaemon(true);
}
}
public static boolean isWorkerThread() {
return Thread.currentThread() instanceof Worker;
}
public static void onIdleShutdown(Runnable hook) {
Objects.requireNonNull(hook);
synchronized (lock) {
idleShutdownHooks.add(hook);
}
}
public static void prestart() {
executor();
}
public static int threads() {
synchronized (lock) {
resolveThreads();
return resolvedThreads;
}
}
private static void resolveThreads() {
if (resolvedThreads == 0) {
final int configured = GLSMConfig.workerThreadCount;
resolvedThreads = configured > 0 ? configured : Math.max(2, Math.min(12, Runtime.getRuntime().availableProcessors() / 2));
}
}
private static void noteActivity() {
lastActivityTime = System.nanoTime();
}
private static ExecutorService executor() {
synchronized (lock) {
noteActivity();
if (executor != null && !executor.isShutdown()) {
return executor;
}
resolveThreads();
final AtomicInteger threadCounter = new AtomicInteger();
final ThreadFactory factory = r -> new Worker(r, "Angelica-Worker-" + threadCounter.getAndIncrement());
final ThreadPoolExecutor tpe = new ThreadPoolExecutor(resolvedThreads, resolvedThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), factory);
tpe.prestartAllCoreThreads();
executor = tpe;
LOGGER.debug("Created Angelica worker pool with {} prestarted threads", resolvedThreads);
if (scheduler == null || scheduler.isShutdown()) {
scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
final Thread t = new Thread(r, "Angelica-Worker-Scheduler");
t.setDaemon(true);
return t;
});
}
if (!idleCheckScheduled) {
idleCheckScheduled = true;
scheduleIdleCheck(IDLE_TIMEOUT_SECONDS);
}
return executor;
}
}
private static void scheduleIdleCheck(long delaySeconds) {
try {
scheduler.schedule(AngelicaWorkers::checkIdleShutdown, delaySeconds, TimeUnit.SECONDS);
} catch (Exception e) {
idleCheckScheduled = false;
LOGGER.warn("Failed to schedule idle check", e);
}
}
public static <T> CompletableFuture<T> submit(Supplier<T> work) {
Objects.requireNonNull(work);
noteActivity();
inFlight.incrementAndGet();
try {
return CompletableFuture.supplyAsync(() -> {
try {
return work.get();
} finally {
noteActivity();
inFlight.decrementAndGet();
}
}, executor());
} catch (Exception e) {
inFlight.decrementAndGet();
throw e;
}
}
public static CompletableFuture<Void> run(Runnable work) {
Objects.requireNonNull(work);
noteActivity();
inFlight.incrementAndGet();
try {
return CompletableFuture.runAsync(() -> {
try {
work.run();
} finally {
noteActivity();
inFlight.decrementAndGet();
}
}, executor());
} catch (Exception e) {
inFlight.decrementAndGet();
throw e;
}
}
private static void checkIdleShutdown() {
synchronized (lock) {
final ExecutorService current = executor;
if (current == null || current.isShutdown()) {
if (scheduler != null && !scheduler.isShutdown()) {
scheduler.shutdown();
scheduler = null;
}
idleCheckScheduled = false;
return;
}
final long idleSeconds = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - lastActivityTime);
if (idleSeconds >= IDLE_TIMEOUT_SECONDS && inFlight.get() == 0) {
LOGGER.debug("Shutting down idle Angelica worker pool after {} seconds", idleSeconds);
current.shutdown();
executor = null;
scheduler.shutdown();
scheduler = null;
idleCheckScheduled = false;
for (int i = 0; i < idleShutdownHooks.size(); i++) {
idleShutdownHooks.get(i).run();
}
} else {
scheduleIdleCheck(Math.max(1, IDLE_TIMEOUT_SECONDS - idleSeconds + 1));
}
}
}
}
@@ -0,0 +1,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));
}
}
@@ -706,4 +706,48 @@ public class GLSM_StateSet_GLTest {
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
}
}
@Test
void batchAppliesPassLightingAndRestoresLiveLighting() {
final boolean lightmapWas = GLStateManager.getTextures().getTextureUnitStates(1).isEnabled();
final LightingSnapshot pass = new LightingSnapshot();
try {
GLStateManager.glEnable(GL11.GL_LIGHT0);
GLStateManager.glEnable(GL11.GL_LIGHT1);
GLStateManager.enableColorMaterial();
GLStateManager.glLightf(GL11.GL_LIGHT0, GL11.GL_CONSTANT_ATTENUATION, 2.0f);
GLStateManager.captureLighting(pass);
GLStateManager.glDisable(GL11.GL_LIGHT0);
GLStateManager.glDisable(GL11.GL_LIGHT1);
GLStateManager.disableColorMaterial();
GLStateManager.glLightf(GL11.GL_LIGHT0, GL11.GL_CONSTANT_ATTENUATION, 1.0f);
GLStateManager.getTextures().getTextureUnitStates(1).setEnabled(false);
final int liveGeneration = GLStateManager.getLightingGeneration();
final int d = GLStateManager.pushState(StateSet.BATCH);
GLStateManager.applyLighting(pass);
GLStateManager.getTextures().getTextureUnitStates(1).setEnabled(true);
assertTrue(GLStateManager.getLightStates()[0].isEnabled(), "light 0 from the pass");
assertTrue(GLStateManager.getLightStates()[1].isEnabled(), "light 1 from the pass");
assertTrue(GLStateManager.getColorMaterial().isEnabled(), "color material from the pass");
assertEquals(2.0f, GLStateManager.getLightDataStates()[0].constantAttenuation, "light 0 data from the pass");
assertTrue(GLStateManager.getLightingGeneration() != liveGeneration, "lighting uniforms marked dirty");
final int passGeneration = GLStateManager.getLightingGeneration();
GLStateManager.popStateTo(d);
assertFalse(GLStateManager.getLightStates()[0].isEnabled(), "light 0 restored by BATCH pop");
assertFalse(GLStateManager.getLightStates()[1].isEnabled(), "light 1 restored by BATCH pop");
assertFalse(GLStateManager.getColorMaterial().isEnabled(), "color material restored by BATCH pop");
assertEquals(1.0f, GLStateManager.getLightDataStates()[0].constantAttenuation, "light 0 data restored by BATCH pop");
assertFalse(GLStateManager.getTextures().getTextureUnitStates(1).isEnabled(), "lightmap enable restored by BATCH pop");
assertTrue(GLStateManager.getLightingGeneration() != passGeneration, "lighting uniforms marked dirty after pop");
} finally {
GLStateManager.glDisable(GL11.GL_LIGHT0);
GLStateManager.glDisable(GL11.GL_LIGHT1);
GLStateManager.disableColorMaterial();
GLStateManager.glLightf(GL11.GL_LIGHT0, GL11.GL_CONSTANT_ATTENUATION, 1.0f);
GLStateManager.getTextures().getTextureUnitStates(1).setEnabled(lightmapWas);
}
}
}
@@ -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();
}
}
@@ -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) {
@@ -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");
}
}
@@ -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());
}
}
@@ -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");
@@ -0,0 +1,275 @@
package com.gtnewhorizons.angelica.glsm;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public final class ReadbackFixture {
public static final int SIZE = 4;
private ReadbackFixture() {}
public static int colorFbo(int internalFormat, int format, int type) {
return colorFbo(internalFormat, format, type, null);
}
public static int colorFbo(int internalFormat, int format, int type, ByteBuffer data) {
final int tex = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, internalFormat, SIZE, SIZE, 0, format, type, data);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
final int fbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, tex, 0);
GLStateManager.glViewport(0, 0, SIZE, SIZE);
return fbo;
}
public static int depthStencilFbo() {
final int fbo = colorFbo(GL11.GL_RGBA8, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
final int rb = GLStateManager.glGenRenderbuffers();
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, rb);
GLStateManager.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_DEPTH24_STENCIL8, SIZE, SIZE);
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, rb);
return fbo;
}
public static void release(int fbo) {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glDeleteFramebuffers(fbo);
resetPack();
}
public static void clearColor(float r, float g, float b, float a) {
GLStateManager.glClearColor(r, g, b, a);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
}
public static void clearTopHalf(float r, float g, float b, float a) {
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(0, SIZE / 2, SIZE, SIZE / 2);
clearColor(r, g, b, a);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
}
public static void resetPack() {
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 0);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_PIXELS, 0);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 0);
GLStateManager.glPixelStorei(GL11.GL_PACK_SWAP_BYTES, GL11.GL_FALSE);
}
public static ByteBuffer read(int format, int type, int bytes) {
final ByteBuffer out = BufferUtils.createByteBuffer(bytes);
for (int i = 0; i < bytes; i++) out.put(i, (byte) 0x7E);
GLStateManager.glReadPixels(0, 0, SIZE, SIZE, format, type, out);
return out.order(ByteOrder.nativeOrder());
}
public static void assertBytes(ByteBuffer buf, int offset, int[] expected, String label) {
for (int i = 0; i < expected.length; i++) {
final int idx = i;
assertEquals(expected[i], buf.get(offset + i) & 0xFF, () -> label + " byte " + idx);
}
}
public static void assertFloats(ByteBuffer buf, int offset, float[] expected, String label) {
for (int i = 0; i < expected.length; i++) {
final int idx = i;
assertEquals(expected[i], buf.getFloat(offset + i * 4), () -> label + " float " + idx);
}
}
public static void assertFloatsNear(ByteBuffer buf, int offset, float[] expected, String label) {
for (int i = 0; i < expected.length; i++) {
final int idx = i;
assertEquals(expected[i], buf.getFloat(offset + i * 4), 1f / 4096f, () -> label + " float " + idx);
}
}
public static void assertShorts(ByteBuffer buf, int offset, int[] expected, String label) {
for (int i = 0; i < expected.length; i++) {
final int idx = i;
assertEquals(expected[i], buf.getShort(offset + i * 2) & 0xFFFF, () -> label + " short " + idx);
}
}
public static void rgba8() {
final int fbo = colorFbo(GL11.GL_RGBA8, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
clearColor(51 / 255f, 102 / 255f, 153 / 255f, 204 / 255f);
clearTopHalf(1f, 0f, 0f, 1f);
final ByteBuffer rgba = read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4);
assertBytes(rgba, 0, new int[] { 51, 102, 153, 204 }, "RGBA8 as RGBA/UBYTE bottom row");
assertBytes(rgba, (SIZE - 1) * SIZE * 4, new int[] { 255, 0, 0, 255 }, "RGBA8 as RGBA/UBYTE top row");
assertBytes(read(GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 153, 102, 51, 204 }, "RGBA8 as BGRA/UBYTE");
final ByteBuffer rev = read(GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, SIZE * SIZE * 4);
assertEquals(0xCC996633, rev.getInt(0), "RGBA8 as RGBA/8_8_8_8_REV");
final ByteBuffer f = read(GL11.GL_RGBA, GL11.GL_FLOAT, SIZE * SIZE * 16);
assertFloatsNear(f, 0, new float[] { 51 / 255f, 102 / 255f, 153 / 255f, 204 / 255f }, "RGBA8 as RGBA/FLOAT");
final ByteBuffer rgb = read(GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, SIZE * 12);
assertBytes(rgb, 0, new int[] { 51, 102, 153, 51, 102, 153 }, "RGBA8 as RGB/UBYTE");
assertBytes(rgb, 12 * (SIZE - 1), new int[] { 255, 0, 0 }, "RGBA8 as RGB/UBYTE top row at 4-aligned stride");
assertBytes(read(GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE), 0, new int[] { 51, 51, 51, 51 }, "RGBA8 as RED/UBYTE");
release(fbo);
}
public static void packState() {
final int fbo = colorFbo(GL11.GL_RGBA8, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
clearColor(51 / 255f, 102 / 255f, 153 / 255f, 204 / 255f);
clearTopHalf(1f, 0f, 0f, 1f);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
final ByteBuffer rgb = read(GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 3);
assertBytes(rgb, (SIZE - 1) * SIZE * 3, new int[] { 255, 0, 0 }, "PACK_ALIGNMENT 1 tightly packs RGB rows");
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 6);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_PIXELS, 1);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 2);
final int rowBytes = 6 * 4;
final ByteBuffer skipped = read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, rowBytes * (SIZE + 2));
assertBytes(skipped, 0, new int[] { 0x7E, 0x7E, 0x7E, 0x7E }, "skipped rows untouched");
assertBytes(skipped, 2 * rowBytes, new int[] { 0x7E, 0x7E, 0x7E, 0x7E }, "skipped pixel untouched");
assertBytes(skipped, 2 * rowBytes + 4, new int[] { 51, 102, 153, 204 }, "first pixel after skips");
assertBytes(skipped, (2 + SIZE - 1) * rowBytes + 4, new int[] { 255, 0, 0, 255 }, "top row at ROW_LENGTH stride");
assertBytes(skipped, 2 * rowBytes + 4 + SIZE * 4, new int[] { 0x7E, 0x7E, 0x7E, 0x7E }, "row padding untouched");
resetPack();
GLStateManager.glPixelStorei(GL11.GL_PACK_SWAP_BYTES, GL11.GL_TRUE);
final ByteBuffer swapped = read(GL11.GL_RGBA, GL30.GL_HALF_FLOAT, SIZE * SIZE * 8);
assertBytes(swapped, 0, new int[] { 0x32, 0x66 }, "SWAP_BYTES on HALF_FLOAT");
resetPack();
release(fbo);
}
public static void pixelPackBuffer() {
final int fbo = colorFbo(GL11.GL_RGBA8, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
clearColor(51 / 255f, 102 / 255f, 153 / 255f, 204 / 255f);
final int pbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, pbo);
GLStateManager.glBufferData(GL21.GL_PIXEL_PACK_BUFFER, 16 + SIZE * SIZE * 4, GL15.GL_STREAM_READ);
GLStateManager.glReadPixels(0, 0, SIZE, SIZE, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, 16L);
final ByteBuffer mapped = GLStateManager.glMapBuffer(GL21.GL_PIXEL_PACK_BUFFER, GL15.GL_READ_ONLY);
assertTrue(mapped != null, "PBO map");
assertBytes(mapped, 16, new int[] { 51, 102, 153, 204 }, "PBO readPixels at offset 16");
GLStateManager.glUnmapBuffer(GL21.GL_PIXEL_PACK_BUFFER);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
GLStateManager.glDeleteBuffers(pbo);
release(fbo);
}
public static void rgb8() {
final int fbo = colorFbo(GL11.GL_RGB8, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE);
clearColor(51 / 255f, 102 / 255f, 153 / 255f, 0.5f);
assertBytes(read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 51, 102, 153, 255 }, "RGB8 as RGBA reads alpha 1");
release(fbo);
}
public static void rgba16f() {
final int fbo = colorFbo(GL30.GL_RGBA16F, GL11.GL_RGBA, GL11.GL_FLOAT);
clearColor(0.25f, 0.625f, 0.75f, 2.0f);
assertFloats(read(GL11.GL_RGBA, GL11.GL_FLOAT, SIZE * SIZE * 16), 0, new float[] { 0.25f, 0.625f, 0.75f, 2.0f }, "RGBA16F as RGBA/FLOAT keeps >1");
assertShorts(read(GL11.GL_RGBA, GL30.GL_HALF_FLOAT, SIZE * SIZE * 8), 0, new int[] { 0x3400, 0x3900, 0x3A00, 0x4000 }, "RGBA16F as RGBA/HALF_FLOAT");
assertBytes(read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 64, 159, 191, 255 }, "RGBA16F as RGBA/UBYTE clamps");
release(fbo);
}
public static void rgba32f() {
final int fbo = colorFbo(GL30.GL_RGBA32F, GL11.GL_RGBA, GL11.GL_FLOAT);
clearColor(0.2f, -0.5f, 3.0f, 1.0f);
assertFloats(read(GL11.GL_RGBA, GL11.GL_FLOAT, SIZE * SIZE * 16), 0, new float[] { 0.2f, -0.5f, 3.0f, 1.0f }, "RGBA32F as RGBA/FLOAT");
assertBytes(read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 51, 0, 255, 255 }, "RGBA32F as RGBA/UBYTE clamps both ends");
release(fbo);
}
public static void r8() {
final int fbo = colorFbo(GL30.GL_R8, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE);
clearColor(0.2f, 0.9f, 0.9f, 0.9f);
assertBytes(read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 51, 0, 0, 255 }, "R8 as RGBA rebases G/B=0 A=1");
release(fbo);
}
public static void rg16f() {
final int fbo = colorFbo(GL30.GL_RG16F, GL30.GL_RG, GL11.GL_FLOAT);
clearColor(0.25f, 0.625f, 0.9f, 0.9f);
assertFloats(read(GL11.GL_RGBA, GL11.GL_FLOAT, SIZE * SIZE * 16), 0, new float[] { 0.25f, 0.625f, 0f, 1f }, "RG16F as RGBA/FLOAT");
release(fbo);
}
public static void rgb10a2() {
final int texel = (1 << 30) | (614 << 20) | (409 << 10) | 205;
final ByteBuffer data = BufferUtils.createByteBuffer(SIZE * SIZE * 4).order(ByteOrder.nativeOrder());
for (int i = 0; i < SIZE * SIZE; i++) data.putInt(i * 4, texel);
final int fbo = colorFbo(GL11.GL_RGB10_A2, GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_2_10_10_10_REV, data);
final ByteBuffer raw = read(GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_2_10_10_10_REV, SIZE * SIZE * 4);
assertEquals(texel, raw.getInt(0), "RGB10A2 as RGBA/2_10_10_10_REV");
assertBytes(read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 51, 102, 153, 85 }, "RGB10A2 as RGBA/UBYTE");
release(fbo);
}
public static void r11g11b10f() {
final int fbo = colorFbo(GL30.GL_R11F_G11F_B10F, GL11.GL_RGB, GL11.GL_FLOAT);
clearColor(0.25f, 0.625f, 1.5f, 1f);
assertFloats(read(GL11.GL_RGBA, GL11.GL_FLOAT, SIZE * SIZE * 16), 0, new float[] { 0.25f, 0.625f, 1.5f, 1f }, "R11G11B10F as RGBA/FLOAT");
release(fbo);
}
public static void depthStencil() {
final int fbo = depthStencilFbo();
GLStateManager.glDepthMask(true);
GLStateManager.glStencilMask(0xFF);
GLStateManager.glClearDepth(0.25);
GLStateManager.glClearStencil(0xC3);
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(0, SIZE / 2, SIZE, SIZE / 2);
GLStateManager.glClearDepth(0.75);
GLStateManager.glClearStencil(0x5A);
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
GLStateManager.glClearDepth(1.0);
GLStateManager.glClearStencil(0);
final ByteBuffer depth = read(GL11.GL_DEPTH_COMPONENT, GL11.GL_FLOAT, SIZE * SIZE * 4);
assertEquals(0.25f, depth.getFloat(0), 1e-6f, "depth bottom row as FLOAT");
assertEquals(0.75f, depth.getFloat((SIZE - 1) * SIZE * 4), 1e-6f, "depth top row as FLOAT");
final ByteBuffer depthUs = read(GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_SHORT, SIZE * SIZE * 2);
assertEquals(16384, depthUs.getShort(0) & 0xFFFF, 1, "depth as UNSIGNED_SHORT");
final ByteBuffer depthUi = read(GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_INT, SIZE * SIZE * 4);
assertEquals(0x40000000L >>> 8, (depthUi.getInt(0) & 0xFFFFFFFFL) >>> 8, 1, "depth as UNSIGNED_INT (top 24 bits)");
final ByteBuffer stencil = read(GL11.GL_STENCIL_INDEX, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE);
assertBytes(stencil, 0, new int[] { 0xC3, 0xC3, 0xC3, 0xC3 }, "stencil bottom row");
assertBytes(stencil, (SIZE - 1) * SIZE, new int[] { 0x5A, 0x5A, 0x5A, 0x5A }, "stencil top row");
final ByteBuffer ds = read(GL30.GL_DEPTH_STENCIL, GL30.GL_UNSIGNED_INT_24_8, SIZE * SIZE * 4);
final int packed = ds.getInt(0);
assertEquals(0xC3, packed & 0xFF, "DEPTH_STENCIL stencil byte");
assertEquals(0x400000, (packed >>> 8) & 0xFFFFFF, 1, "DEPTH_STENCIL depth 24 bits");
release(fbo);
}
}
@@ -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() {
@@ -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];
}
}
+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
@@ -15,6 +15,7 @@ import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import com.gtnewhorizons.angelica.glsm.texture.TextureStaging;
import com.gtnewhorizons.angelica.sdlgpu.compute.ComputeBinder;
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
import com.gtnewhorizons.angelica.sdlgpu.device.SDLDrawable;
@@ -25,6 +26,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;
@@ -39,8 +41,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;
@@ -110,6 +114,8 @@ import com.gtnewhorizons.angelica.sdlgpu.compat.Lwjgl3GLCapabilitiesShim;
import me.eigenraven.lwjgl3ify.api.GLCapabilitiesOverride;
import static org.lwjgl.sdl.SDLGPU.*;
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_HORIZONTAL;
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_HORIZONTAL_AND_VERTICAL;
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_NONE;
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_VERTICAL;
@@ -154,6 +160,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;
@@ -494,6 +501,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) {
@@ -505,6 +520,8 @@ public class SDLGPURenderBackend extends RenderBackend {
drawDispatch.clearWarnedState();
attachmentClear.shutdown();
depthStencilReadback.shutdown();
textureOps.shutdown();
pipelineStore.shutdown();
shaderManager.shutdown();
resourceManager.shutdown();
@@ -542,11 +559,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);
@@ -601,6 +619,7 @@ 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);
@@ -647,8 +666,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();
@@ -728,6 +745,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();
@@ -740,6 +758,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();
@@ -833,6 +852,13 @@ 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;
}
@@ -1087,17 +1113,26 @@ public class SDLGPURenderBackend extends RenderBackend {
if (db < 0 || db >= ContextState.MAX_COLOR_ATTACHMENTS) continue;
final long tex = fbo.colorTextures[db];
if (tex == 0) continue;
if (fbo.colorLevels[db] != 0) {
fboClearTracker.clearColorLevel(tex, fbo.colorLevels[db], st.clearR, st.clearG, st.clearB, st.clearA);
continue;
}
FBOClearTracker.recordPendingColorClear(st, tex, st.clearR, st.clearG, st.clearB, st.clearA);
if (passActive && tex == activeColor) affectsActivePass = true;
}
}
if (wantDepth && fbo.depthTexture != 0) {
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, st.stencilClearValue);
if (passActive && fbo.depthTexture == activeDepth) affectsActivePass = true;
final boolean clearStencilAspect = wantStencil && fbo.depthTexture != 0 && PixelOps.isDepthStencilFormat(fbo.depthFormat);
if (fbo.depthTexture != 0 && fbo.depthLevel != 0) {
fboClearTracker.clearDepthStencilLevel(fbo.depthTexture, fbo.depthLevel, wantDepth, st.depthClearValue, clearStencilAspect, st.stencilClearValue);
} else {
if (wantDepth && fbo.depthTexture != 0) {
FBOClearTracker.recordPendingDepthClear(st, fbo.depthTexture, st.depthClearValue);
if (passActive && fbo.depthTexture == activeDepth) affectsActivePass = true;
}
if (clearStencilAspect) {
FBOClearTracker.recordPendingStencilClear(st, fbo.depthTexture, st.stencilClearValue);
if (passActive && fbo.depthTexture == activeDepth) affectsActivePass = true;
}
}
if (affectsActivePass) {
@@ -1134,7 +1169,7 @@ public class SDLGPURenderBackend extends RenderBackend {
@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 */ }
@@ -1156,7 +1191,7 @@ public class SDLGPURenderBackend extends RenderBackend {
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);
@@ -1521,7 +1556,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 {
@@ -1599,6 +1634,37 @@ public class SDLGPURenderBackend extends RenderBackend {
resourceManager.getTextureHandle(glId),
staging.slice(), xoffset, yoffset, width, height, level, format, type);
}
@Override public TextureStaging beginTextureStaging(int level, int x, int y, int width, int height) {
final int glId = boundTextureFor(s(), GL11.GL_TEXTURE_2D);
if (glId == 0) return null;
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
if (meta == null || level >= meta.levels()) return null;
final long texHandle = resourceManager.getTextureHandle(glId);
if (texHandle == 0 || frameManager.getCommandBuffer() == 0) return null;
return resourceManager.beginTextureStaging(glId, texHandle, PixelOps.isBgraSdlFormat(meta.sdlFormat()), level, x, y, width, height);
}
@Override public boolean commitTextureStaging(TextureStaging staging) {
final ResourceManager.TextureStagingRegion region = (ResourceManager.TextureStagingRegion) staging;
final ContextState st = s();
final boolean defer = resourceManager.shouldDeferTextureUpload(st, region.glId());
if (!defer || !resourceManager.commitTextureStagingDeferred(st, region)) {
final long cp = frameManager.ensureCopyPass();
if (cp == 0) {
resourceManager.abandonTextureStaging(region);
return false;
}
resourceManager.commitTextureStagingInline(cp, region);
}
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(region.glId());
if (meta != null) textureOps.trackMipUploadForTexture(region.glId(), region.level(), meta.levels());
return true;
}
@Override public void abandonTextureStaging(TextureStaging staging) {
resourceManager.abandonTextureStaging((ResourceManager.TextureStagingRegion) staging);
}
@Override public void copyTexSubImage2D(int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
final ContextState cs = s();
textureOps.copyTexSubImageImpl(cs, boundTextureFor(cs, target), level, xoffset, yoffset, x, y, width, height);
@@ -1675,11 +1741,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 */ }
}
}
@@ -1809,13 +1879,14 @@ public class SDLGPURenderBackend extends RenderBackend {
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
fbo.colorTextures[colorIdx] = handle;
fbo.colorGlIds[colorIdx] = texture;
fbo.colorLevels[colorIdx] = level;
resourceManager.markFboAttachment(texture);
fbo.targetsDirty = true;
if (meta != null) {
fbo.colorFormats[colorIdx] = meta.sdlFormat();
if (colorIdx == 0) {
fbo.width = meta.width();
fbo.height = meta.height();
fbo.width = PixelOps.mipLevelSize(meta.width(), level);
fbo.height = PixelOps.mipLevelSize(meta.height(), level);
}
}
fbo.colorAttachmentCount = Math.max(fbo.colorAttachmentCount, colorIdx + 1);
@@ -1843,6 +1914,7 @@ public class SDLGPURenderBackend extends RenderBackend {
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
fbo.depthTexture = handle;
fbo.depthGlId = texture;
fbo.depthLevel = level;
resourceManager.markFboAttachment(texture);
fbo.targetsDirty = true;
if (meta != null) {
@@ -1944,123 +2016,220 @@ 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;
int srcLevel = 0;
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 (frameManager.isFrameActive()) fboClearTracker.materializePendingClearForTexture(cs, texHandle);
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());
srcLevel = fbo.colorLevels[fbo.readBufferIndex];
srcWidth = meta != null ? PixelOps.mipLevelSize(meta.width(), srcLevel) : fbo.width;
srcHeight = meta != null ? PixelOps.mipLevelSize(meta.height(), srcLevel) : fbo.height;
} else {
if (fbo.depthLevel != 0) throw new UnsupportedOperationException("glReadPixels of a depth/stencil attachment at level " + fbo.depthLevel + " is not supported on SDL-GPU");
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, srcLevel, 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) {
@@ -2091,6 +2260,9 @@ public class SDLGPURenderBackend extends RenderBackend {
if (pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE) {
return (colorPresent || depthPresent) ? GL11.GL_TEXTURE : GL11.GL_NONE;
}
if (pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL) {
return colorPresent ? fbo.colorLevels[colorIdx] : depthPresent ? fbo.depthLevel : 0;
}
if (colorPresent) return colorAttachmentSize(fbo.colorFormats[colorIdx], pname);
if (depthPresent) return depthAttachmentSize(fbo.depthFormat, pname);
return 0;
@@ -2161,6 +2333,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) {
@@ -2178,6 +2352,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();
@@ -2193,6 +2369,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;
@@ -3580,62 +3758,126 @@ public class SDLGPURenderBackend extends RenderBackend {
final int fbo0Height = frameManager.getFbo0Height();
if (anyFbo0 && fbo0Height <= 0) return;
final long colorSrcTex;
final long colorDstTex;
final int colorSrcGlId;
final int colorDstGlId;
if ((mask & GL11.GL_COLOR_BUFFER_BIT) != 0) {
colorSrcTex = srcIsFbo0 ? frameManager.getFbo0Texture() : srcFbo.colorTextures[srcFbo.readBufferIndex];
colorDstTex = dstIsFbo0 ? frameManager.getFbo0Texture() : dstFbo.colorTextures[0];
colorSrcGlId = srcIsFbo0 ? 0 : srcFbo.colorGlIds[srcFbo.readBufferIndex];
colorDstGlId = dstIsFbo0 ? 0 : dstFbo.colorGlIds[0];
} else {
colorSrcTex = 0L;
colorDstTex = 0L;
colorSrcGlId = 0;
colorDstGlId = 0;
}
final boolean blitColor = colorSrcTex != 0 && colorDstTex != 0 && colorSrcTex != colorDstTex;
final boolean colorRequested = (mask & GL11.GL_COLOR_BUFFER_BIT) != 0;
final long colorSrcTex = !colorRequested ? 0L : srcIsFbo0 ? frameManager.getFbo0Texture() : srcFbo.colorTextures[srcFbo.readBufferIndex];
final int colorSrcGlId = (!colorRequested || srcIsFbo0) ? 0 : srcFbo.colorGlIds[srcFbo.readBufferIndex];
final int colorSrcLevel = (!colorRequested || srcIsFbo0) ? 0 : srcFbo.colorLevels[srcFbo.readBufferIndex];
final boolean blitColor = colorSrcTex != 0 && (dstIsFbo0 ? frameManager.getFbo0Texture() != 0 : dstFbo.drawBuffers.length > 0);
final boolean depthRequested = (mask & (GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT)) != 0;
final long depthSrcTex = (depthRequested && !anyFbo0) ? srcFbo.depthTexture : 0L;
final long depthDstTex = (depthRequested && !anyFbo0) ? dstFbo.depthTexture : 0L;
final boolean blitDepth = depthSrcTex != 0 && depthDstTex != 0 && depthSrcTex != depthDstTex;
final int depthSrcLevel = depthSrcTex != 0 ? srcFbo.depthLevel : 0;
final int depthDstLevel = depthDstTex != 0 ? dstFbo.depthLevel : 0;
final boolean blitDepth = depthSrcTex != 0 && depthDstTex != 0 && (depthSrcTex != depthDstTex || depthSrcLevel != depthDstLevel);
if (!blitColor && !blitDepth) return;
final int srcX = Math.min(srcX0, srcX1);
final int dstX = Math.min(dstX0, dstX1);
final int srcW = Math.abs(srcX1 - srcX0);
final int srcH = Math.abs(srcY1 - srcY0);
final int dstW = Math.abs(dstX1 - dstX0);
final int dstH = Math.abs(dstY1 - dstY0);
if (srcW == 0 || srcH == 0 || dstW == 0 || dstH == 0) return;
final boolean mirrorX = (srcX1 < srcX0) != (dstX1 < dstX0);
final boolean mirrorY = (srcY1 < srcY0) != (dstY1 < dstY0);
if (blitDepth && (mirrorX || mirrorY)) {
throw new UnsupportedOperationException("glBlitFramebuffer: mirrored depth/stencil blit is not supported on SDL-GPU (src " + srcX0 + "," + srcY0 + "," + srcX1 + "," + srcY1 + " dst " + dstX0 + "," + dstY0 + "," + dstX1 + "," + dstY1 + ")");
}
final ContextState cs = s();
if (dstIsFbo0 || (srcIsFbo0 && (cs.pendingSwapchainClear || cs.pendingSwapchainDepthClear || cs.pendingSwapchainStencilClear))) {
frameManager.ensureFbo0RenderPass(frameManager.frame(), cs);
}
final int srcW = srcX1 - srcX0;
final int srcH = srcY1 - srcY0;
final int dstW = dstX1 - dstX0;
final int dstH = dstY1 - dstY0;
final int srcY = srcIsFbo0 ? (fbo0Height - srcY0 - srcH) : srcY0;
final int dstY = dstIsFbo0 ? (fbo0Height - dstY0 - dstH) : dstY0;
final int flipMode = (srcIsFbo0 != dstIsFbo0) ? SDL_FLIP_VERTICAL : SDL_FLIP_NONE;
final int srcY = srcIsFbo0 ? (fbo0Height - Math.max(srcY0, srcY1)) : Math.min(srcY0, srcY1);
final int dstY = dstIsFbo0 ? (fbo0Height - Math.max(dstY0, dstY1)) : Math.min(dstY0, dstY1);
final boolean flipV = mirrorY != (srcIsFbo0 != dstIsFbo0);
final int flipMode = mirrorX ? (flipV ? SDL_FLIP_HORIZONTAL_AND_VERTICAL : SDL_FLIP_HORIZONTAL) : (flipV ? SDL_FLIP_VERTICAL : SDL_FLIP_NONE);
if (blitColor) {
resolveBlitClears(cs, colorSrcTex, colorDstTex, colorDstGlId, dstX0, dstY, dstW, dstH);
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)) {
textureOps.copyTexture(colorSrcTex, srcX0, srcY, colorDstTex, 0, dstX0, dstY, srcW, srcH);
if (dstIsFbo0) {
blitColorTarget(cs, srcMeta, colorSrcTex, colorSrcLevel, srcX, srcY, srcW, srcH, frameManager.getFbo0Texture(), 0, 0, dstX, dstY, dstW, dstH, filter, flipMode, srcIsFbo0, true);
} else {
textureOps.blitTexture(colorSrcTex, srcX0, srcY, srcW, srcH, colorDstTex, 0, dstX0, dstY, dstW, dstH, filter, flipMode);
final int[] drawBuffers = dstFbo.drawBuffers;
for (int i = 0; i < drawBuffers.length; i++) {
final int db = drawBuffers[i];
if (db < 0 || db >= ContextState.MAX_COLOR_ATTACHMENTS) continue;
final long dstTex = dstFbo.colorTextures[db];
if (dstTex == 0) continue;
blitColorTarget(cs, srcMeta, colorSrcTex, colorSrcLevel, srcX, srcY, srcW, srcH, dstTex, dstFbo.colorGlIds[db], dstFbo.colorLevels[db], dstX, dstY, dstW, dstH, filter, flipMode, srcIsFbo0, false);
}
}
}
if (blitDepth) {
resolveBlitClears(cs, depthSrcTex, depthDstTex, dstFbo.depthGlId, dstX0, dstY, dstW, dstH);
textureOps.copyTexture(depthSrcTex, srcX0, srcY, depthDstTex, 0, dstX0, dstY, srcW, srcH);
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);
copyClipped(cs, depthSrcTex, resourceManager.getTextureMeta(srcFbo.depthGlId), depthSrcLevel, srcX, srcY, depthDstTex, resourceManager.getTextureMeta(dstFbo.depthGlId), depthDstLevel, dstX, dstY, srcW, srcH, allAspects);
}
}
private void resolveBlitClears(ContextState cs, long srcTex, long dstTex, int dstGlId, int dstX, int dstY, int dstW, int dstH) {
private void copyClipped(ContextState cs, long srcTex, ResourceManager.TextureMeta srcMeta, int srcLevel, int sx, int sy, long dstTex, ResourceManager.TextureMeta dstMeta, int dstLevel, int dx, int dy, int w, int h, boolean allAspects) {
final long clip = CopyRectClip.clipCopyRect(sx, sy, dx, dy, w, h, TextureOps.levelWidth(srcMeta, srcLevel), TextureOps.levelHeight(srcMeta, srcLevel), TextureOps.levelWidth(dstMeta, dstLevel), TextureOps.levelHeight(dstMeta, dstLevel));
if (clip == CopyRectClip.EMPTY) return;
final int cw = CopyRectClip.width(clip);
final int ch = CopyRectClip.height(clip);
final int cdx = CopyRectClip.dstX(clip, sx, dx);
final int cdy = CopyRectClip.dstY(clip, sy, dy);
resolveBlitClears(cs, srcTex, dstTex, dstMeta, dstLevel, cdx, cdy, cw, ch, allAspects);
textureOps.copyTexture(srcTex, srcLevel, CopyRectClip.srcX(clip), CopyRectClip.srcY(clip), dstTex, dstLevel, cdx, cdy, cw, ch);
}
private void blitColorTarget(ContextState cs, ResourceManager.TextureMeta srcMeta, long srcTex, int srcLevel, int sx, int sy, int sw, int sh, long dstTex, int dstGlId, int dstLevel, int dx, int dy, int dw, int dh, int filter, int flipMode, boolean srcIsFbo0, boolean dstIsFbo0) {
if (srcTex == dstTex && srcLevel == dstLevel) return;
final ResourceManager.TextureMeta dstMeta = resourceManager.getTextureMeta(dstGlId);
if (flipMode == SDL_FLIP_NONE && TextureOps.canCopyInsteadOfBlit(srcMeta, dstMeta, sw, sh, dw, dh)) {
copyClipped(cs, srcTex, srcMeta, srcLevel, sx, sy, dstTex, dstMeta, dstLevel, dx, dy, sw, sh, true);
return;
}
final boolean flipH = (flipMode & SDL_FLIP_HORIZONTAL) != 0;
final boolean flipV = (flipMode & SDL_FLIP_VERTICAL) != 0;
final int[] r = cs.blitRect;
r[0] = sx; r[1] = sy; r[2] = sw; r[3] = sh;
r[4] = dx; r[5] = dy; r[6] = dw; r[7] = dh;
if (!clipScaled(r, 0, srcIsFbo0 ? frameManager.getFbo0Width() : TextureOps.levelWidth(srcMeta, srcLevel), srcIsFbo0 ? frameManager.getFbo0Height() : TextureOps.levelHeight(srcMeta, srcLevel), flipH, flipV)) return;
if (!clipScaled(r, 4, dstIsFbo0 ? frameManager.getFbo0Width() : TextureOps.levelWidth(dstMeta, dstLevel), dstIsFbo0 ? frameManager.getFbo0Height() : TextureOps.levelHeight(dstMeta, dstLevel), flipH, flipV)) return;
resolveBlitClears(cs, srcTex, dstTex, dstMeta, dstLevel, r[4], r[5], r[6], r[7], true);
textureOps.blitTexture(srcTex, srcLevel, r[0], r[1], r[2], r[3], dstTex, dstLevel, r[4], r[5], r[6], r[7], filter, flipMode);
}
static boolean clipScaled(int[] r, int a, int limW, int limH, boolean flipH, boolean flipV) {
final int b = 4 - a;
final int aw = r[a + 2];
final int ah = r[a + 3];
final int bw = r[b + 2];
final int bh = r[b + 3];
if (aw <= 0 || ah <= 0 || bw <= 0 || bh <= 0 || limW <= 0 || limH <= 0) return true;
final int left = Math.max(0, -r[a]);
final int right = Math.max(0, r[a] + aw - limW);
final int top = Math.max(0, -r[a + 1]);
final int bottom = Math.max(0, r[a + 1] + ah - limH);
if (left + right >= aw || top + bottom >= ah) return false;
if ((left | right | top | bottom) == 0) return true;
final int bLeft = (int) ((long) left * bw / aw);
final int bRight = (int) ((long) right * bw / aw);
final int bTop = (int) ((long) top * bh / ah);
final int bBottom = (int) ((long) bottom * bh / ah);
r[a] += left;
r[a + 2] -= left + right;
r[a + 1] += top;
r[a + 3] -= top + bottom;
r[b] += flipH ? bRight : bLeft;
r[b + 2] -= bLeft + bRight;
r[b + 1] += flipV ? bBottom : bTop;
r[b + 3] -= bTop + bBottom;
return r[b + 2] > 0 && r[b + 3] > 0;
}
private void resolveBlitClears(ContextState cs, long srcTex, long dstTex, ResourceManager.TextureMeta dstMeta, int dstLevel, 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, dstMeta, dstLevel, dstX, dstY, 0, dstW, dstH, allAspects);
}
@Override public int createBuffers() { return resourceManager.genBuffer(); }
@Override public void namedBufferData(int buffer, long size, int usage) {
@@ -3752,6 +3994,11 @@ public class SDLGPURenderBackend extends RenderBackend {
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);
@@ -3854,6 +4101,8 @@ public class SDLGPURenderBackend extends RenderBackend {
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;
};
}
@@ -3991,9 +4240,10 @@ 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;
resourceManager.flushBatchedUploads(cp);
try (var stack = MemoryStack.stackPush()) {
final var src = SDL_GPUTextureLocation.calloc(stack)
.texture(srcTex).mip_level(srcLevel).x(srcX).y(srcY).z(srcZ);
@@ -4135,7 +4385,7 @@ public class SDLGPURenderBackend extends RenderBackend {
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);
@@ -3,6 +3,8 @@ 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;
@@ -13,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;
@@ -49,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;
@@ -126,6 +134,21 @@ public final class ContextState {
public int ssboBinds;
public float blendColorR, blendColorG, blendColorB, blendColorA;
public boolean logicOpEnabled;
public int logicOpMode = GL11.GL_COPY;
public long appliedLogicOpKey;
public LogicOpVariant activeLogicOpVariant;
public final int[] blitRect = new int[8];
public final long[] logicOpScratch = new long[MAX_COLOR_ATTACHMENTS];
public final int[] logicOpScratchFormat = new int[MAX_COLOR_ATTACHMENTS];
public final int[] logicOpScratchWidth = new int[MAX_COLOR_ATTACHMENTS];
public final int[] logicOpScratchHeight = new int[MAX_COLOR_ATTACHMENTS];
public long readbackDepthScratch;
public int readbackDepthScratchWidth;
public int readbackDepthScratchHeight;
public long readbackStencilScratch;
public int readbackStencilScratchWidth;
public int readbackStencilScratchHeight;
public int stencilRef;
public float depthClearValue = 1.0f;
public int stencilClearValue;
@@ -305,6 +328,7 @@ public final class ContextState {
public final long[] lastVertSamplerTex = new long[MAX_SAMPLERS];
public final long[] lastVertSamplerSmp = new long[MAX_SAMPLERS];
public int lastFragSamplerProgram;
public LogicOpVariant lastFragSamplerVariant;
public int lastVertSamplerProgram;
public final PointerBuffer fragStorageTexBindings = PointerBuffer.allocateDirect(MAX_IMAGE_UNITS);
@@ -332,9 +356,6 @@ public final class ContextState {
public int defaultFboId;
public GLContextState mirroredContext;
public final LongOpenHashSet clearedTexturesThisFrame = new LongOpenHashSet();
public final LongOpenHashSet clearedStencilTexturesThisFrame = new LongOpenHashSet();
public final LongOpenHashSet pendingColorTextures = new LongOpenHashSet();
public final Long2ObjectOpenHashMap<float[]> pendingColorValues = new Long2ObjectOpenHashMap<>();
public final LongOpenHashSet pendingDepthTextures = new LongOpenHashSet();
@@ -419,6 +440,7 @@ public final class ContextState {
lastAppliedSamplerProgram = 0;
lastAppliedSamplerCb = 0;
lastFragSamplerProgram = 0;
lastFragSamplerVariant = null;
lastVertSamplerProgram = 0;
lastAppliedStorageTexBindGen = -1;
lastAppliedStorageTexProgram = 0;
@@ -55,7 +55,9 @@ public final class FrameManager {
public static final int PASS_END_TARGET = 3;
public static final int PASS_END_UNIFORM_BLOCK = 4;
public static final int PASS_END_FRAME_END = 5;
public static final int PASS_END_CAUSE_COUNT = 6;
public static final int PASS_END_LOGIC_OP = 6;
public static final int PASS_END_READBACK = 7;
public static final int PASS_END_CAUSE_COUNT = 8;
public static final class FrameState {
public final Thread owner = Thread.currentThread();
@@ -647,7 +649,11 @@ public final class FrameManager {
public void submitMidFrame() {
final FrameState f = frame();
if (f.commandBuffer == 0) return;
if (f.commandBuffer == 0) {
if (f.wantFenceOnNextSubmit) getOrCreatePendingUploadCommandBuffer(f);
flushPendingUploadCommandBuffer(f);
return;
}
endCopyPassIfActive(f);
endRenderPassIfActive(f, PASS_END_FRAME_END);
Tracy.beginZone(Z_SDL_SUBMIT);
@@ -747,7 +753,14 @@ public final class FrameManager {
Tracy.beginZone(Z_SDL_SUBMIT);
final boolean submitted;
try {
submitted = SDL_SubmitGPUCommandBuffer(f.pendingUploadCommandBuffer);
if (f.wantFenceOnNextSubmit) {
f.wantFenceOnNextSubmit = false;
if (f.lastAcquiredFence != 0) SDL_ReleaseGPUFence(device.getDevice(), f.lastAcquiredFence);
f.lastAcquiredFence = SDL_SubmitGPUCommandBufferAndAcquireFence(f.pendingUploadCommandBuffer);
submitted = f.lastAcquiredFence != 0;
} else {
submitted = SDL_SubmitGPUCommandBuffer(f.pendingUploadCommandBuffer);
}
} finally {
Tracy.endZone();
}
@@ -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]);
}
}
@@ -7,21 +7,9 @@ import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
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;
@@ -34,9 +22,6 @@ 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;
@@ -58,13 +43,11 @@ public final class AttachmentClear {
private final FrameManager frameManager;
private final PipelineStore pipelineStore;
private final ShaderManager shaderManager;
private final Target fboTarget = new Target();
private final Target fbo0Target = new Target();
private int vertexShaderId;
private long sdlVertexShader;
private final FullscreenPass fullscreen;
private final long[] sdlFragmentShaders = new long[MAX_COLOR_TARGETS + 1];
private final boolean[] fragmentFailed = new boolean[MAX_COLOR_TARGETS + 1];
@@ -85,7 +68,7 @@ public final class AttachmentClear {
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 {
@@ -160,21 +143,8 @@ public final class AttachmentClear {
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, t.width);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.H, t.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);
@@ -194,16 +164,7 @@ public final class AttachmentClear {
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 (t.depthTexture != 0) {
if (depth) st.clearedTexturesThisFrame.add(t.depthTexture);
if (stencil) st.clearedStencilTexturesThisFrame.add(t.depthTexture);
}
FullscreenPass.markStateClobbered(st);
frameManager.noteInPassClear();
return true;
@@ -258,7 +219,6 @@ public final class AttachmentClear {
.enable_color_write_mask(true);
}
final boolean hasDepthTarget = t.depthFormat != 0;
final SDL_GPUDepthStencilState depthStencil = SDL_GPUDepthStencilState.calloc(stack)
.enable_depth_test(buildDepthFlag)
.enable_depth_write(buildDepthFlag)
@@ -279,38 +239,7 @@ 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(t.depthFormat)
.has_depth_stencil_target(hasDepthTarget);
final SDL_GPUVertexInputState vertexInput = SDL_GPUVertexInputState.calloc(stack);
final SDL_GPUGraphicsPipelineCreateInfo ci = SDL_GPUGraphicsPipelineCreateInfo.calloc(stack)
.vertex_shader(sdlVertexShader)
.fragment_shader(sdlFragmentShaders[buildColorTargets])
.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");
}
}
@@ -320,47 +249,10 @@ public final class AttachmentClear {
if (fragmentFailed[colorTargets]) return false;
fragmentFailed[colorTargets] = true;
if (vertexShaderId == 0) {
vertexShaderId = shaderManager.createShader(GL20.GL_VERTEX_SHADER);
shaderManager.shaderSource(vertexShaderId, ShaderLoader.getShaderSource(VERTEX_SOURCE));
shaderManager.compileShader(vertexShaderId);
}
final String baseSource = ShaderLoader.getShaderSource(FRAGMENT_SOURCE);
final String fragmentSource = colorTargets == 0 ? baseSource : withColorOutputs(baseSource, colorTargets);
final int fs = shaderManager.createShader(GL20.GL_FRAGMENT_SHADER);
shaderManager.shaderSource(fs, fragmentSource);
shaderManager.compileShader(fs);
final int prog = shaderManager.createProgram();
shaderManager.attachShader(prog, vertexShaderId);
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 (COLOR_TARGETS={}) failed to link: {}", colorTargets, po == null ? "no program object" : po.infoLog);
return false;
}
if (colorTargets > 0 && po.fragmentUboSize != 16) {
LOG.error("AttachmentClear: clear_attachment fragment UBO size {} != 16 for COLOR_TARGETS={}", po.fragmentUboSize, colorTargets);
return false;
}
if (sdlVertexShader == 0) {
sdlVertexShader = shaderManager.createSDLShader(po.vertexSpirv, SDL_GPU_SHADERSTAGE_VERTEX, po.vertexResources.numSamplers(), po.vertexResources.numUBOs(), po.vertexResources.numStorageBuffers(), po.vertexResources.numStorageTextures());
if (sdlVertexShader == 0) {
LOG.error("AttachmentClear: clear_attachment vertex SDL shader creation failed");
return false;
}
}
final long fragmentShader = shaderManager.createSDLShader(po.fragmentSpirv, SDL_GPU_SHADERSTAGE_FRAGMENT, po.fragmentResources.numSamplers(), po.fragmentResources.numUBOs(), po.fragmentResources.numStorageBuffers(), po.fragmentResources.numStorageTextures());
if (fragmentShader == 0) {
LOG.error("AttachmentClear: clear_attachment fragment SDL shader creation failed (COLOR_TARGETS={})", colorTargets);
return false;
}
final long fragmentShader = fullscreen.compileFragment(fragmentSource, colorTargets > 0 ? 16 : -1, "AttachmentClear: clear_attachment COLOR_TARGETS=" + colorTargets);
if (fragmentShader == 0) return false;
sdlFragmentShaders[colorTargets] = fragmentShader;
fragmentFailed[colorTargets] = false;
@@ -382,8 +274,7 @@ public final class AttachmentClear {
}
public void shutdown() {
vertexShaderId = 0;
sdlVertexShader = 0;
fullscreen.shutdown();
Arrays.fill(sdlFragmentShaders, 0L);
Arrays.fill(fragmentFailed, false);
Arrays.fill(cacheKeys, 0L);
@@ -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;
@@ -0,0 +1,151 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
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 static org.lwjgl.sdl.SDLGPU.*;
final class FullscreenPass {
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
private static final String VERTEX_SOURCE = "angelica:sdlgpu/clear_attachment.vsh";
private final ShaderManager shaderManager;
private int vertexShaderId;
private long sdlVertexShader;
FullscreenPass(ShaderManager shaderManager) {
this.shaderManager = shaderManager;
}
long vertexShader() {
return sdlVertexShader;
}
long compileFragment(String fragmentSource, int requiredUboSize, String label) {
if (vertexShaderId == 0) {
vertexShaderId = shaderManager.createShader(GL20.GL_VERTEX_SHADER);
shaderManager.shaderSource(vertexShaderId, ShaderLoader.getShaderSource(VERTEX_SOURCE));
shaderManager.compileShader(vertexShaderId);
}
final int fs = shaderManager.createShader(GL20.GL_FRAGMENT_SHADER);
shaderManager.shaderSource(fs, fragmentSource);
shaderManager.compileShader(fs);
final int prog = shaderManager.createProgram();
shaderManager.attachShader(prog, vertexShaderId);
shaderManager.attachShader(prog, fs);
shaderManager.linkProgram(prog);
final ShaderManager.ProgramObject po = shaderManager.getProgram(prog);
if (po == null || !po.linked) {
LOG.error("{}: program failed to link: {}", label, po == null ? "no program object" : po.infoLog);
return 0;
}
if (requiredUboSize >= 0 && po.fragmentUboSize != requiredUboSize) {
LOG.error("{}: fragment UBO size {} != {}", label, po.fragmentUboSize, requiredUboSize);
return 0;
}
if (sdlVertexShader == 0) {
sdlVertexShader = shaderManager.createSDLShader(po.vertexSpirv, SDL_GPU_SHADERSTAGE_VERTEX, po.vertexResources.numSamplers(), po.vertexResources.numUBOs(), po.vertexResources.numStorageBuffers(), po.vertexResources.numStorageTextures());
if (sdlVertexShader == 0) {
LOG.error("{}: vertex SDL shader creation failed", label);
return 0;
}
}
final long fragmentShader = shaderManager.createSDLShader(po.fragmentSpirv, SDL_GPU_SHADERSTAGE_FRAGMENT, po.fragmentResources.numSamplers(), po.fragmentResources.numUBOs(), po.fragmentResources.numStorageBuffers(), po.fragmentResources.numStorageTextures());
if (fragmentShader == 0) {
LOG.error("{}: fragment SDL shader creation failed", label);
return 0;
}
return fragmentShader;
}
long createPipeline(PipelineStore pipelineStore, MemoryStack stack, long fragmentShader, SDL_GPUColorTargetDescription.Buffer colorDesc, int numColorTargets, SDL_GPUDepthStencilState depthStencil, int depthFormat, String label) {
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(depthFormat)
.has_depth_stencil_target(depthFormat != 0);
final SDL_GPUVertexInputState vertexInput = SDL_GPUVertexInputState.calloc(stack);
final SDL_GPUGraphicsPipelineCreateInfo ci = SDL_GPUGraphicsPipelineCreateInfo.calloc(stack)
.vertex_shader(sdlVertexShader)
.fragment_shader(fragmentShader)
.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("{}: pipeline creation failed: {}", label, SDLError.SDL_GetError());
return PipelineStore.BAD_PIPELINE_SENTINEL;
}
return pipeline;
}
static void setViewport(long renderPass, ContextState st, int width, int height, float depth) {
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, width);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.H, height);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.MIN_DEPTH, depth);
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.MAX_DEPTH, depth);
SDL_SetGPUViewport(renderPass, st.cachedViewport);
}
static void setScissor(long renderPass, ContextState st, int x, int y, int width, int height) {
final long scAddr = st.cachedScissor.address();
MemoryAccess.putInt(scAddr + SDL_Rect.X, x);
MemoryAccess.putInt(scAddr + SDL_Rect.Y, y);
MemoryAccess.putInt(scAddr + SDL_Rect.W, width);
MemoryAccess.putInt(scAddr + SDL_Rect.H, height);
SDL_SetGPUScissor(renderPass, st.cachedScissor);
}
static void markStateClobbered(ContextState st) {
st.lastBoundPipeline = 0;
st.viewportDirty = true;
st.scissorDirty = true;
st.lastAppliedStencilRef = Integer.MIN_VALUE;
st.lastAppliedVboBindCb = 0;
}
void shutdown() {
vertexShaderId = 0;
sdlVertexShader = 0;
}
}
@@ -0,0 +1,133 @@
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.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.TextureOps;
import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpFormats;
import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpVariant;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
public final class LogicOpEmulator {
private final FrameManager frameManager;
private final ResourceManager resourceManager;
private final ShaderManager shaderManager;
private final PipelineApplier pipelineApplier;
LogicOpEmulator(FrameManager frameManager, ResourceManager resourceManager, ShaderManager shaderManager, PipelineApplier pipelineApplier) {
this.frameManager = frameManager;
this.resourceManager = resourceManager;
this.shaderManager = shaderManager;
this.pipelineApplier = pipelineApplier;
}
public void beforeDraw(ContextState st, FrameState f) {
if (!st.logicOpEnabled && st.appliedLogicOpKey == 0) return;
final FboState fbo = st.boundFboId != 0 ? resourceManager.getFbo(st.boundFboId) : null;
final long key = st.logicOpEnabled ? computeKey(st, fbo) : 0L;
if (key != 0L) copyDestinations(st, f, fbo, key);
if (key != st.appliedLogicOpKey) applyVariant(st, key);
}
private long computeKey(ContextState st, FboState fbo) {
final int op = LogicOpFormats.mesaOp(st.logicOpMode);
if (op == LogicOpFormats.OP_COPY || st.boundProgram == 0) return 0L;
if (st.boundFboId != 0 && fbo == null) return 0L;
final ShaderManager.ProgramObject prog = shaderManager.getProgram(st.boundProgram);
if (prog == null || !prog.linked) return 0L;
final int limit = Math.min(targetCount(fbo), prog.maxFragOutputLocation + 1);
long key = op;
boolean any = false;
for (int loc = 0; loc < limit; loc++) {
if (targetTexture(fbo, loc) == 0L) continue;
final int cls = LogicOpFormats.classOf(targetFormat(fbo, loc));
if (cls == 0) continue;
key = LogicOpFormats.withClass(key, loc, cls);
any = true;
}
return any ? key : 0L;
}
private void copyDestinations(ContextState st, FrameState f, FboState fbo, long key) {
pipelineApplier.ensureRenderPass(st, f);
if (f.renderPass == 0) return;
long cp = 0L;
for (int loc = 0; loc < ContextState.MAX_COLOR_ATTACHMENTS; loc++) {
if (LogicOpFormats.classAt(key, loc) == 0) continue;
final long tex = targetTexture(fbo, loc);
final int w = targetWidth(fbo, loc);
final int h = targetHeight(fbo, loc);
final int fmt = targetFormat(fbo, loc);
long scratch = st.logicOpScratch[loc];
if (scratch == 0L || st.logicOpScratchFormat[loc] != fmt || st.logicOpScratchWidth[loc] != w || st.logicOpScratchHeight[loc] != h) {
if (scratch != 0L) resourceManager.releaseTextureDeferred(scratch);
scratch = resourceManager.createLogicOpScratch(fmt, w, h);
st.logicOpScratch[loc] = scratch;
st.logicOpScratchFormat[loc] = fmt;
st.logicOpScratchWidth[loc] = w;
st.logicOpScratchHeight[loc] = h;
st.samplerBindGen++;
}
if (cp == 0L) {
cp = frameManager.ensureCopyPass(FrameManager.PASS_END_LOGIC_OP);
resourceManager.flushBatchedUploads(cp);
}
TextureOps.copyTexture(cp, tex, targetLevel(fbo, loc), 0, 0, scratch, 0, 0, 0, w, h);
}
}
private void applyVariant(ContextState st, long key) {
if (key == 0L) {
final ShaderManager.ProgramObject prog = shaderManager.getProgram(st.boundProgram);
st.pipeline.fragmentShader = prog != null ? prog.sdlFragmentShader : 0L;
st.activeLogicOpVariant = null;
} else {
final LogicOpVariant v = shaderManager.getOrBuildLogicOpVariant(st.boundProgram, key);
st.pipeline.fragmentShader = v.sdlShader;
st.activeLogicOpVariant = v;
}
st.pipeline.markShaderDirty();
st.appliedLogicOpKey = key;
st.samplerBindGen++;
}
private static int targetCount(FboState fbo) {
return fbo == null ? 1 : Math.min(fbo.drawBuffers.length, ContextState.MAX_COLOR_ATTACHMENTS);
}
private static int drawBuffer(FboState fbo, int loc) {
final int db = fbo.drawBuffers[loc];
return db >= 0 && db < ContextState.MAX_COLOR_ATTACHMENTS && fbo.colorTextures[db] != 0L ? db : -1;
}
private long targetTexture(FboState fbo, int loc) {
if (fbo == null) return frameManager.getFbo0Texture();
final int db = drawBuffer(fbo, loc);
return db >= 0 ? fbo.colorTextures[db] : 0L;
}
private int targetFormat(FboState fbo, int loc) {
return fbo == null ? frameManager.getSwapchainFormat() : fbo.colorFormats[drawBuffer(fbo, loc)];
}
private static int targetLevel(FboState fbo, int loc) {
return fbo == null ? 0 : fbo.colorLevels[drawBuffer(fbo, loc)];
}
private int targetWidth(FboState fbo, int loc) {
if (fbo == null) return frameManager.getFbo0Width();
final int db = drawBuffer(fbo, loc);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(fbo.colorGlIds[db]);
return meta != null ? PixelOps.mipLevelSize(meta.width(), fbo.colorLevels[db]) : fbo.width;
}
private int targetHeight(FboState fbo, int loc) {
if (fbo == null) return frameManager.getFbo0Height();
final int db = drawBuffer(fbo, loc);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(fbo.colorGlIds[db]);
return meta != null ? PixelOps.mipLevelSize(meta.height(), fbo.colorLevels[db]) : fbo.height;
}
}
@@ -58,6 +58,7 @@ public final class PipelineApplier {
private final SamplerBinder samplerBinder;
private final StorageTextureBinder storageTextureBinder;
private final StorageBufferBinder storageBufferBinder;
private final LogicOpEmulator logicOpEmulator;
private DrawDispatch.FanUploadSink deferredUploadSink;
public void setDeferredUploadSink(DrawDispatch.FanUploadSink sink) { this.deferredUploadSink = sink; }
@@ -84,6 +85,16 @@ public final class PipelineApplier {
this.samplerBinder = samplerBinder;
this.storageTextureBinder = storageTextureBinder;
this.storageBufferBinder = storageBufferBinder;
this.logicOpEmulator = new LogicOpEmulator(frameManager, resourceManager, shaderManager, this);
}
public void ensureDrawRenderPass(ContextState st) {
ensureDrawRenderPass(st, frameManager.frame());
}
public void ensureDrawRenderPass(ContextState st, FrameState f) {
logicOpEmulator.beforeDraw(st, f);
ensureRenderPass(st, f);
}
public void ensureRenderPass(ContextState st) {
@@ -132,6 +143,8 @@ public final class PipelineApplier {
return;
}
fboClearTracker.materializePendingClearsForMipTargets(st, fbo);
final int totalTargets = fbo.drawBuffers.length;
final long dummyTex = totalTargets > 0 ? resourceManager.getOrCreateDummyColorTarget() : 0L;
@@ -140,17 +153,21 @@ public final class PipelineApplier {
for (int i = 0; i < totalTargets; i++) {
final int db = fbo.drawBuffers[i];
final long tex;
final int level;
if (db >= 0 && db < ContextState.MAX_COLOR_ATTACHMENTS && fbo.colorTextures[db] != 0) {
tex = fbo.colorTextures[db];
level = fbo.colorLevels[db];
} else {
tex = dummyTex;
level = 0;
}
final boolean pendingClear = tex != dummyTex && st.pendingColorTextures.contains(tex);
final boolean defined = tex != dummyTex && resourceManager.isTextureContentDefined(tex);
final boolean firstUse = !pendingClear && tex != dummyTex && !defined;
final boolean firstUse = !pendingClear && tex != dummyTex && !defined && level == 0;
if (pendingClear || firstUse) proposedClearOps |= 1 << i;
layoutHash = Hashing.fmix64(layoutHash, tex);
layoutHash = Hashing.fmix64(layoutHash, db);
layoutHash = Hashing.fmix64(layoutHash, level);
if (pendingClear || firstUse) {
final float[] c = pendingClear ? st.pendingColorValues.get(tex) : null;
layoutHash = foldClearColor(layoutHash, c != null ? c[0] : st.clearR, c != null ? c[1] : st.clearG, c != null ? c[2] : st.clearB, c != null ? c[3] : st.clearA);
@@ -160,17 +177,14 @@ public final class PipelineApplier {
final boolean proposedStencilClear;
final boolean depthHasStencil = fbo.depthTexture != 0 && PixelOps.isDepthStencilFormat(fbo.depthFormat);
if (fbo.depthTexture != 0) {
final boolean defined = resourceManager.isTextureContentDefined(fbo.depthTexture);
final boolean pendingClear = st.pendingDepthTextures.contains(fbo.depthTexture);
final boolean firstUse = !pendingClear && !st.clearedTexturesThisFrame.contains(fbo.depthTexture);
final boolean firstUse = !defined && fbo.depthLevel == 0;
proposedDepthClear = pendingClear || firstUse;
final boolean pendingStencil = depthHasStencil && st.pendingStencilTextures.contains(fbo.depthTexture);
if (depthHasStencil) {
final boolean firstStencilUse = !pendingStencil && !st.clearedStencilTexturesThisFrame.contains(fbo.depthTexture);
proposedStencilClear = pendingStencil || firstStencilUse;
} else {
proposedStencilClear = false;
}
proposedStencilClear = depthHasStencil && (pendingStencil || firstUse);
layoutHash = Hashing.fmix64(layoutHash, fbo.depthTexture);
layoutHash = Hashing.fmix64(layoutHash, fbo.depthLevel);
if (proposedDepthClear) {
layoutHash = foldClearDepth(layoutHash, pendingClear ? st.pendingDepthValues.get(fbo.depthTexture) : st.depthClearValue);
}
@@ -198,15 +212,19 @@ public final class PipelineApplier {
for (int i = 0; i < totalTargets; i++) {
final int db = fbo.drawBuffers[i];
final long tex;
final int level;
if (db >= 0 && db < ContextState.MAX_COLOR_ATTACHMENTS && fbo.colorTextures[db] != 0) {
tex = fbo.colorTextures[db];
level = fbo.colorLevels[db];
} else {
tex = dummyTex;
level = 0;
}
final boolean pendingClear = tex != dummyTex && st.pendingColorTextures.contains(tex);
final boolean clearThis = (proposedClearOps & (1 << i)) != 0;
final long ctAddr = colorTargets.address() + (long) i * SDL_GPUColorTargetInfo.SIZEOF;
MemoryAccess.putAddress(ctAddr + SDL_GPUColorTargetInfo.TEXTURE, tex);
MemoryAccess.putInt(ctAddr + SDL_GPUColorTargetInfo.MIP_LEVEL, level);
MemoryAccess.putInt(ctAddr + SDL_GPUColorTargetInfo.LOAD_OP, clearThis ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
MemoryAccess.putInt(ctAddr + SDL_GPUColorTargetInfo.STORE_OP, db >= 0 ? SDL_GPU_STOREOP_STORE : SDL_GPU_STOREOP_DONT_CARE);
if (clearThis) {
@@ -231,6 +249,7 @@ public final class PipelineApplier {
depthTarget = fbo.cachedDepthTarget;
final long dtAddr = depthTarget.address();
MemoryAccess.putAddress(dtAddr + SDL_GPUDepthStencilTargetInfo.TEXTURE, fbo.depthTexture);
MemoryAccess.putByte(dtAddr + SDL_GPUDepthStencilTargetInfo.MIP_LEVEL, (byte) fbo.depthLevel);
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, proposedDepthClear ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
if (proposedDepthClear) {
@@ -274,11 +293,8 @@ public final class PipelineApplier {
}
if (fbo.depthTexture == 0) return;
st.pendingDepthTextures.remove(fbo.depthTexture);
if (depthClear) st.clearedTexturesThisFrame.add(fbo.depthTexture);
if (depthHasStencil) {
st.pendingStencilTextures.remove(fbo.depthTexture);
if (stencilClear) st.clearedStencilTexturesThisFrame.add(fbo.depthTexture);
}
if (depthHasStencil) st.pendingStencilTextures.remove(fbo.depthTexture);
if (depthClear || stencilClear) rm.markTextureContentDefined(fbo.depthTexture);
}
static long foldClearColor(long h, float r, float g, float b, float a) {
@@ -61,6 +61,7 @@ public final class PipelineCache {
public int primitiveType = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
public final boolean[] blendEnabledPerDrawBuffer = new boolean[ContextState.MAX_COLOR_ATTACHMENTS];
public boolean logicOpEnabled;
public final int[] srcColorFactors = new int[ContextState.MAX_COLOR_ATTACHMENTS];
public final int[] dstColorFactors = new int[ContextState.MAX_COLOR_ATTACHMENTS];
public final int[] srcAlphaFactors = new int[ContextState.MAX_COLOR_ATTACHMENTS];
@@ -162,7 +163,7 @@ public final class PipelineCache {
}
private boolean blendEnabledForSlot(int slot) {
return slot >= 0 && slot < ContextState.MAX_COLOR_ATTACHMENTS && blendEnabledPerDrawBuffer[slot];
return !logicOpEnabled && slot >= 0 && slot < ContextState.MAX_COLOR_ATTACHMENTS && blendEnabledPerDrawBuffer[slot];
}
public boolean setBlendFactors(int sc, int dc, int sa, int da) {
@@ -259,7 +260,7 @@ public final class PipelineCache {
private boolean computeUsesBlendConstants() {
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
if (!blendEnabledPerDrawBuffer[i]) continue;
if (!blendEnabledForSlot(i)) continue;
if (isBlendConstantFactor(srcColorFactors[i]) || isBlendConstantFactor(dstColorFactors[i])
|| isBlendConstantFactor(srcAlphaFactors[i]) || isBlendConstantFactor(dstAlphaFactors[i])) {
return true;
@@ -38,7 +38,6 @@ public final class FBOClearTracker {
if (st.pendingColorTextures.add(tex)) {
st.pendingMutationGen++;
}
st.clearedTexturesThisFrame.remove(tex);
}
public static void recordPendingDepthClear(ContextState st, long tex, float value) {
@@ -46,7 +45,6 @@ public final class FBOClearTracker {
if (st.pendingDepthTextures.add(tex)) {
st.pendingMutationGen++;
}
st.clearedTexturesThisFrame.remove(tex);
}
public static void recordPendingStencilClear(ContextState st, long tex, int value) {
@@ -54,7 +52,6 @@ public final class FBOClearTracker {
if (st.pendingStencilTextures.add(tex)) {
st.pendingMutationGen++;
}
st.clearedStencilTexturesThisFrame.remove(tex);
}
public static boolean fboHasPendingClear(ContextState st, FboState fbo) {
@@ -170,16 +167,7 @@ public final class FBOClearTracker {
final int idx = batchIdx[k];
final long handle = colorHandles.getLong(idx);
final float[] color = st.pendingColorValues.get(handle);
final long addr = targets.get(k).address();
MemoryAccess.putAddress(addr + SDL_GPUColorTargetInfo.TEXTURE, handle);
MemoryAccess.putInt(addr + SDL_GPUColorTargetInfo.LOAD_OP, SDL_GPU_LOADOP_CLEAR);
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, color[0]);
MemoryAccess.putFloat(ccAddr + SDL_FColor.G, color[1]);
MemoryAccess.putFloat(ccAddr + SDL_FColor.B, color[2]);
MemoryAccess.putFloat(ccAddr + SDL_FColor.A, color[3]);
st.clearedTexturesThisFrame.add(handle);
putColorClear(targets.get(k).address(), handle, 0, color[0], color[1], color[2], color[3]);
resourceManager.markTextureContentDefined(handle);
consumed[idx] = true;
}
@@ -198,54 +186,48 @@ public final class FBOClearTracker {
private void emitDepthStencilClearPass(ContextState st, long handle, boolean clearDepth, boolean clearStencil, boolean materialized) {
if (!clearDepth && !clearStencil) return;
try (var stack = MemoryStack.stackPush()) {
final SDL_GPUDepthStencilTargetInfo dt = SDL_GPUDepthStencilTargetInfo.calloc(stack);
final long addr = dt.address();
MemoryAccess.putAddress(addr + SDL_GPUDepthStencilTargetInfo.TEXTURE, handle);
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, clearDepth ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
if (clearDepth) {
MemoryAccess.putFloat(addr + SDL_GPUDepthStencilTargetInfo.CLEAR_DEPTH, st.pendingDepthValues.get(handle));
}
if (clearStencil) {
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STENCIL_LOAD_OP, SDL_GPU_LOADOP_CLEAR);
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STENCIL_STORE_OP, SDL_GPU_STOREOP_STORE);
MemoryAccess.putByte(addr + SDL_GPUDepthStencilTargetInfo.CLEAR_STENCIL, (byte) st.pendingStencilValues.get(handle));
}
frameManager.noteClearPass();
if (materialized) frameManager.noteMaterializedClearPass();
frameManager.beginRenderPass(null, dt);
frameManager.endRenderPassIfActive(FrameManager.PASS_END_CLEAR);
}
if (clearDepth) {
st.pendingDepthValues.remove(handle);
st.clearedTexturesThisFrame.add(handle);
}
if (clearStencil) {
st.pendingStencilValues.remove(handle);
st.clearedStencilTexturesThisFrame.add(handle);
}
if (materialized) frameManager.noteMaterializedClearPass();
depthStencilClearPass(handle, 0, clearDepth, clearDepth ? st.pendingDepthValues.get(handle) : 0f, clearStencil, clearStencil ? st.pendingStencilValues.get(handle) : 0);
if (clearDepth) st.pendingDepthValues.remove(handle);
if (clearStencil) st.pendingStencilValues.remove(handle);
if (clearDepth && clearStencil) resourceManager.markTextureContentDefined(handle);
}
public boolean discardPendingClearIfFullyCovered(ContextState st, long handle, int dstX, int dstY, int dstLevel, int width, int height, ResourceManager.TextureMeta meta) {
public boolean discardPendingClearIfFullyCovered(ContextState st, long handle, int dstX, int dstY, int dstLevel, int width, int height, ResourceManager.TextureMeta meta, boolean allAspects) {
if (handle == 0 || meta == null) return false;
if (dstLevel != 0 || dstX != 0 || dstY != 0) return false;
if (width != meta.width() || height != meta.height() || meta.depth() > 1) return false;
final boolean depth = st.pendingDepthTextures.remove(handle);
final boolean color = !depth && st.pendingColorTextures.remove(handle);
if (!depth && !color) return false;
if (depth) st.pendingDepthValues.remove(handle);
else st.pendingColorValues.remove(handle);
st.clearedTexturesThisFrame.add(handle);
resourceManager.markTextureContentDefined(handle);
if (st.pendingDepthTextures.contains(handle) || st.pendingStencilTextures.contains(handle)) {
if (!allAspects) return false;
st.pendingDepthTextures.remove(handle);
st.pendingDepthValues.remove(handle);
st.pendingStencilTextures.remove(handle);
st.pendingStencilValues.remove(handle);
} else if (st.pendingColorTextures.remove(handle)) {
st.pendingColorValues.remove(handle);
} else {
return false;
}
st.pendingMutationGen++;
return true;
}
public void resolveDestinationForWrite(ContextState st, long destTex, ResourceManager.TextureMeta destMeta, int level, int dx, int dy, int dz, int w, int h) {
if (dz != 0 || !discardPendingClearIfFullyCovered(st, destTex, dx, dy, level, w, h, destMeta)) {
public void resolveDestinationForWrite(ContextState st, long destTex, ResourceManager.TextureMeta destMeta, int level, int dx, int dy, int dz, int w, int h, boolean allAspects) {
if (dz != 0 || !discardPendingClearIfFullyCovered(st, destTex, dx, dy, level, w, h, destMeta, allAspects)) {
materializePendingClearForTexture(st, destTex);
}
resourceManager.markTextureContentDefined(destTex);
}
public void materializePendingClearForRead(ContextState st, FrameManager.FrameState f, long texture) {
if (texture == 0L || !f.frameActive) return;
if (texture == frameManager.getFbo0Texture()) {
if (st.pendingSwapchainClear || !f.clearedThisFrame) frameManager.ensureFbo0RenderPass(f, st);
} else if (texture == resourceManager.getSwapchainDepthStencil()) {
if (st.pendingSwapchainDepthClear || st.pendingSwapchainStencilClear || !f.depthClearedThisFrame) frameManager.ensureFbo0RenderPass(f, st);
} else {
materializePendingClearForTexture(st, texture);
}
}
public void materializePendingClearForTexture(ContextState st, long handle) {
@@ -263,26 +245,92 @@ public final class FBOClearTracker {
frameManager.endRenderPassIfActive(FrameManager.PASS_END_CLEAR);
try (var stack = MemoryStack.stackPush()) {
final SDL_GPUColorTargetInfo.Buffer targets = SDL_GPUColorTargetInfo.calloc(1, stack);
final long addr = targets.get(0).address();
MemoryAccess.putAddress(addr + SDL_GPUColorTargetInfo.TEXTURE, handle);
MemoryAccess.putInt(addr + SDL_GPUColorTargetInfo.LOAD_OP, SDL_GPU_LOADOP_CLEAR);
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, color != null ? color[0] : 0f);
MemoryAccess.putFloat(ccAddr + SDL_FColor.G, color != null ? color[1] : 0f);
MemoryAccess.putFloat(ccAddr + SDL_FColor.B, color != null ? color[2] : 0f);
MemoryAccess.putFloat(ccAddr + SDL_FColor.A, color != null ? color[3] : 0f);
putColorClear(targets.get(0).address(), handle, 0, color != null ? color[0] : 0f, color != null ? color[1] : 0f, color != null ? color[2] : 0f, color != null ? color[3] : 0f);
frameManager.noteClearPass();
frameManager.noteMaterializedClearPass();
frameManager.beginRenderPass(targets, null);
frameManager.endRenderPassIfActive(FrameManager.PASS_END_CLEAR);
st.clearedTexturesThisFrame.add(handle);
resourceManager.markTextureContentDefined(handle);
}
st.pendingMutationGen++;
}
}
public void materializePendingClearsForMipTargets(ContextState st, FboState fbo) {
for (int db : fbo.drawBuffers) {
if (db < 0 || db >= ContextState.MAX_COLOR_ATTACHMENTS || fbo.colorLevels[db] == 0) continue;
final long tex = fbo.colorTextures[db];
if (tex != 0 && st.pendingColorTextures.contains(tex)) materializePendingClearForTexture(st, tex);
}
if (fbo.depthLevel != 0 && fbo.depthTexture != 0 && (st.pendingDepthTextures.contains(fbo.depthTexture) || st.pendingStencilTextures.contains(fbo.depthTexture))) {
materializePendingClearForTexture(st, fbo.depthTexture);
}
}
public void clearColorLevel(long handle, int level, float r, float g, float b, float a) {
frameManager.endRenderPassIfActive(FrameManager.PASS_END_CLEAR);
try (var stack = MemoryStack.stackPush()) {
final SDL_GPUColorTargetInfo.Buffer targets = SDL_GPUColorTargetInfo.calloc(1, stack);
putColorClear(targets.get(0).address(), handle, level, r, g, b, a);
frameManager.noteClearPass();
frameManager.beginRenderPass(targets, null);
frameManager.endRenderPassIfActive(FrameManager.PASS_END_CLEAR);
}
}
public void clearDepthStencilLevel(long handle, int level, boolean clearDepth, float depth, boolean clearStencil, int stencil) {
if (!clearDepth && !clearStencil) return;
frameManager.endRenderPassIfActive(FrameManager.PASS_END_CLEAR);
depthStencilClearPass(handle, level, clearDepth, depth, clearStencil, stencil);
}
private void depthStencilClearPass(long handle, int level, boolean clearDepth, float depth, boolean clearStencil, int stencil) {
try (var stack = MemoryStack.stackPush()) {
final SDL_GPUDepthStencilTargetInfo dt = SDL_GPUDepthStencilTargetInfo.calloc(stack);
final long addr = dt.address();
MemoryAccess.putAddress(addr + SDL_GPUDepthStencilTargetInfo.TEXTURE, handle);
MemoryAccess.putByte(addr + SDL_GPUDepthStencilTargetInfo.MIP_LEVEL, (byte) level);
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, clearDepth ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
if (clearDepth) {
MemoryAccess.putFloat(addr + SDL_GPUDepthStencilTargetInfo.CLEAR_DEPTH, depth);
}
if (clearStencil) {
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STENCIL_LOAD_OP, SDL_GPU_LOADOP_CLEAR);
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STENCIL_STORE_OP, SDL_GPU_STOREOP_STORE);
MemoryAccess.putByte(addr + SDL_GPUDepthStencilTargetInfo.CLEAR_STENCIL, (byte) stencil);
}
frameManager.noteClearPass();
frameManager.beginRenderPass(null, dt);
frameManager.endRenderPassIfActive(FrameManager.PASS_END_CLEAR);
}
}
private static void putColorClear(long addr, long handle, int level, float r, float g, float b, float a) {
MemoryAccess.putAddress(addr + SDL_GPUColorTargetInfo.TEXTURE, handle);
MemoryAccess.putInt(addr + SDL_GPUColorTargetInfo.MIP_LEVEL, level);
MemoryAccess.putInt(addr + SDL_GPUColorTargetInfo.LOAD_OP, SDL_GPU_LOADOP_CLEAR);
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, r);
MemoryAccess.putFloat(ccAddr + SDL_FColor.G, g);
MemoryAccess.putFloat(ccAddr + SDL_FColor.B, b);
MemoryAccess.putFloat(ccAddr + SDL_FColor.A, a);
}
public void materializeAllPendingClears(ContextState st) {
if (st.pendingColorTextures.isEmpty() && st.pendingDepthTextures.isEmpty() && st.pendingStencilTextures.isEmpty()) return;
final LongArrayList handles = st.samplerFlushColorHandles;
handles.clear();
handles.addAll(st.pendingColorTextures);
handles.addAll(st.pendingDepthTextures);
handles.addAll(st.pendingStencilTextures);
for (int i = 0; i < handles.size(); i++) {
materializePendingClearForTexture(st, handles.getLong(i));
}
handles.clear();
}
public void scrubPendingClearsForTexture(ContextState st, int glId) {
final long handle = resourceManager.getTextureHandle(glId);
if (handle == 0) return;
@@ -292,8 +340,6 @@ public final class FBOClearTracker {
st.pendingDepthValues.remove(handle);
st.pendingStencilTextures.remove(handle);
st.pendingStencilValues.remove(handle);
st.clearedTexturesThisFrame.remove(handle);
st.clearedStencilTexturesThisFrame.remove(handle);
}
public boolean fbosHaveSameAttachments(ContextState st, int a, int b) {
@@ -302,6 +348,7 @@ public final class FBOClearTracker {
if (fa == null || fb == null) return false;
if (fa.depthTexture != fb.depthTexture) return false;
if (fa.depthFormat != fb.depthFormat) return false;
if (fa.depthLevel != fb.depthLevel) return false;
if (fa.colorAttachmentCount != fb.colorAttachmentCount) return false;
if (fa.drawBuffers.length != fb.drawBuffers.length) return false;
for (int i = 0; i < fa.drawBuffers.length; i++) {
@@ -310,6 +357,7 @@ public final class FBOClearTracker {
for (int i = 0; i < ContextState.MAX_COLOR_ATTACHMENTS; i++) {
if (fa.colorTextures[i] != fb.colorTextures[i]) return false;
if (fa.colorFormats[i] != fb.colorFormats[i]) return false;
if (fa.colorLevels[i] != fb.colorLevels[i]) return false;
}
if (fboHasPendingClear(st, fb)) return false;
return true;
@@ -11,6 +11,7 @@ public final class FboState {
public final long[] colorTextures = new long[MAX_COLOR_ATTACHMENTS];
public final int[] colorGlIds = new int[MAX_COLOR_ATTACHMENTS];
public final int[] colorFormats = new int[MAX_COLOR_ATTACHMENTS];
public final int[] colorLevels = new int[MAX_COLOR_ATTACHMENTS];
public int colorAttachmentCount;
public int[] drawBuffers = {0};
@@ -19,6 +20,7 @@ public final class FboState {
public long depthTexture;
public int depthGlId;
public int depthFormat;
public int depthLevel;
public int width, height;
public int[] cachedColorFormats;
@@ -62,8 +64,10 @@ public final class FboState {
final long tex = (db >= 0 && db < MAX_COLOR_ATTACHMENTS) ? colorTextures[db] : 0L;
h = Hashing.fmix64(h, tex);
h = Hashing.fmix64(h, db);
h = Hashing.fmix64(h, tex != 0L ? colorLevels[db] : 0);
}
h = Hashing.fmix64(h, depthTexture);
h = Hashing.fmix64(h, depthLevel);
if (h == 0L || h == FrameManager.FBO0_LAYOUT_HASH) h = 0xD1CE5EEDL;
structuralLayoutHash = h;
}
@@ -73,6 +77,7 @@ public final class FboState {
colorTextures[colorIdx] = 0L;
colorGlIds[colorIdx] = 0;
colorFormats[colorIdx] = 0;
colorLevels[colorIdx] = 0;
int newCount = 0;
for (int i = 0; i < MAX_COLOR_ATTACHMENTS; i++) {
if (colorTextures[i] != 0L) newCount = i + 1;
@@ -86,6 +91,7 @@ public final class FboState {
depthTexture = 0L;
depthGlId = 0;
depthFormat = 0;
depthLevel = 0;
targetsDirty = true;
}
@@ -0,0 +1,9 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
public final class PackState {
public int alignment = 4;
public int rowLength;
public int skipPixels;
public int skipRows;
public boolean swapBytes;
}
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
@@ -131,15 +132,22 @@ public final class PixelOps {
final int packed = GLTypes.packedTexelBytes(type);
if (packed != 0) return packed;
final int components = switch (format) {
case GL11.GL_RED, GL11.GL_ALPHA, GL11.GL_LUMINANCE -> 1;
case GL11.GL_LUMINANCE_ALPHA -> 2;
case GL11.GL_RGB -> 3;
case GL11.GL_RGBA, GL12.GL_BGRA -> 4;
case GL11.GL_RED, GL11.GL_GREEN, GL11.GL_BLUE, GL11.GL_ALPHA, GL11.GL_LUMINANCE,
GL30.GL_RED_INTEGER, GL30.GL_GREEN_INTEGER, GL30.GL_BLUE_INTEGER, GL30.GL_ALPHA_INTEGER,
GL11.GL_DEPTH_COMPONENT, GL11.GL_STENCIL_INDEX -> 1;
case GL11.GL_LUMINANCE_ALPHA, GL30.GL_RG, GL30.GL_RG_INTEGER -> 2;
case GL11.GL_RGB, GL12.GL_BGR, GL30.GL_RGB_INTEGER, GL30.GL_BGR_INTEGER -> 3;
default -> 4;
};
return components * GLTypes.sizeBytes(type);
}
public static int alignedRowStride(int rowPixels, int pixelBytes, int alignment) {
final int unaligned = rowPixels * pixelBytes;
final int align = Math.max(1, alignment);
return ((unaligned + align - 1) / align) * align;
}
public static int sdlFormatTexelBytes(int sdlFormat) {
return switch (sdlFormat) {
case SDL_GPU_TEXTUREFORMAT_R8_UNORM, SDL_GPU_TEXTUREFORMAT_R8_SNORM,
@@ -258,8 +266,9 @@ public final class PixelOps {
pixels.position(pos);
}
public static ByteBuffer prepareUploadBuffer(int format, int dstSdlFormat, ByteBuffer src, int width, int height) {
public static ByteBuffer prepareUploadBuffer(int format, int type, int dstSdlFormat, ByteBuffer src, int width, int height) {
if (src == null) return null;
if (dstSdlFormat == SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM) return prepareRgb10A2Upload(format, type, src, width, height);
final boolean storageBgra = isBgraSdlFormat(dstSdlFormat);
final ByteBuffer expanded = expandToRGBA(format, src, width, height);
if (expanded != null) {
@@ -273,14 +282,37 @@ public final class PixelOps {
return src;
}
private static ByteBuffer prepareRgb10A2Upload(int format, int type, ByteBuffer src, int width, int height) {
if (format == GL11.GL_RGBA && type == GL12.GL_UNSIGNED_INT_2_10_10_10_REV) return src;
if ((format == GL11.GL_RGBA || format == GL12.GL_BGRA) && type == GL11.GL_UNSIGNED_BYTE) {
final int pixelCount = width * height;
final ByteBuffer dst = MemoryUtil.memAlloc(pixelCount * 4);
final int srcPos = src.position();
final int rOff = format == GL12.GL_BGRA ? 2 : 0;
final int bOff = 2 - rOff;
for (int i = 0; i < pixelCount; i++) {
final int o = i * 4;
final int r = src.get(srcPos + o + rOff) & 0xFF;
final int g = src.get(srcPos + o + 1) & 0xFF;
final int b = src.get(srcPos + o + bOff) & 0xFF;
final int a = src.get(srcPos + o + 3) & 0xFF;
dst.putInt(o, unorm8ToBits(r, 1023) | unorm8ToBits(g, 1023) << 10 | unorm8ToBits(b, 1023) << 20 | unorm8ToBits(a, 3) << 30);
}
dst.position(0).limit(pixelCount * 4);
return dst;
}
throw new UnsupportedOperationException("GL_RGB10_A2 upload from format 0x" + Integer.toHexString(format) + " type 0x" + Integer.toHexString(type) + " is not supported on SDL-GPU");
}
private static int unorm8ToBits(int v, int max) {
return (v * max + 127) / 255;
}
public static ByteBuffer applyUnpackPixelStore(ByteBuffer src, int width, int height, int format, int type, ContextState.PixelStoreState ps) {
if (src == null || ps == null || ps.isDefault()) return src;
final int pixelBytes = glPixelSize(format, type);
if (pixelBytes <= 0) return src;
final int rowPixels = ps.unpackRowLength > 0 ? ps.unpackRowLength : width;
final int unalignedRowBytes = rowPixels * pixelBytes;
final int align = Math.max(1, ps.unpackAlignment);
final int srcStride = ((unalignedRowBytes + align - 1) / align) * align;
final int srcStride = alignedRowStride(ps.unpackRowLength > 0 ? ps.unpackRowLength : width, pixelBytes, ps.unpackAlignment);
final int dstStride = width * pixelBytes;
final int totalDst = dstStride * height;
final ByteBuffer dst = MemoryUtil.memAlloc(totalDst);
@@ -296,31 +328,4 @@ public final class PixelOps {
dst.position(0).limit(totalDst);
return dst;
}
public static void postProcessReadback(ByteBuffer pixels, int width, int height, int format, int srcSdlFormat, boolean flipRows) {
final int rowBytes = width * 4;
final int pos = pixels.position();
if (flipRows) {
final byte[] rowA = new byte[rowBytes];
final byte[] rowB = new byte[rowBytes];
for (int top = 0, bot = height - 1; top < bot; top++, bot--) {
pixels.position(pos + top * rowBytes);
pixels.get(rowA);
pixels.position(pos + bot * rowBytes);
pixels.get(rowB);
pixels.position(pos + top * rowBytes);
pixels.put(rowB);
pixels.position(pos + bot * rowBytes);
pixels.put(rowA);
}
}
pixels.position(pos);
final boolean storageBgra = isBgraSdlFormat(srcSdlFormat);
final boolean requestBgra = (format == GL12.GL_BGRA);
swapRedBlueIfNeeded(pixels, width, height, storageBgra != requestBgra);
pixels.position(pos);
}
}
@@ -0,0 +1,956 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GL41;
import org.lwjgl.opengl.ARBTextureFloat;
import org.lwjgl.opengl.EXTTextureInteger;
import org.lwjgl.opengl.EXTTextureSRGBR8;
import org.lwjgl.opengl.EXTTextureSRGBRG8;
import org.lwjgl.opengl.EXTTextureSnorm;
import org.lwjgl.opengl.EXTTextureStorage;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteOrder;
import static org.lwjgl.sdl.SDLGPU.*;
public final class PixelPack {
private PixelPack() {}
private static final boolean LITTLE_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN;
private static final int K_UNORM = 0;
private static final int K_SNORM = 1;
private static final int K_FLOAT = 2;
private static final int K_UINT = 3;
private static final int K_SINT = 4;
private static final int CH_R = 0;
private static final int CH_G = 1;
private static final int CH_B = 2;
private static final int CH_A = 3;
private static final int CH_L = 4;
private static final int CH_ZERO = 5;
private static final int CH_ONE = 6;
private static final int CH_L_RG = 7;
private static final int REBASE_IDENTITY = CH_R | (CH_G << 4) | (CH_B << 8) | (CH_A << 12);
private static final int OK = 0;
private static final int INVALID_ENUM = 1;
private static final int INVALID_OPERATION = 2;
private static final float MAX_RGB9E5 = 65408.0f;
public static void validate(int format, int type) {
final int err = checkFormatAndType(format, type);
if (err == INVALID_ENUM) {
throw new IllegalArgumentException("GL_INVALID_ENUM: pack format 0x" + Integer.toHexString(format) + " type 0x" + Integer.toHexString(type));
}
if (err == INVALID_OPERATION) {
throw new IllegalArgumentException("GL_INVALID_OPERATION: pack format 0x" + Integer.toHexString(format) + " type 0x" + Integer.toHexString(type));
}
}
public static boolean isDepthFormat(int glFormat) {
return glFormat == GL11.GL_DEPTH_COMPONENT;
}
public static boolean isStencilFormat(int glFormat) {
return glFormat == GL11.GL_STENCIL_INDEX;
}
public static boolean isDepthStencilFormat(int glFormat) {
return glFormat == GL30.GL_DEPTH_STENCIL;
}
public static int rowStride(int format, int type, int width, PackState ps) {
return PixelOps.alignedRowStride(ps.rowLength > 0 ? ps.rowLength : width, PixelOps.glPixelSize(format, type), ps.alignment);
}
public static long imageSize(int format, int type, int width, int height, PackState ps) {
if (width <= 0 || height <= 0) return 0L;
final long stride = rowStride(format, type, width, ps);
final long pixelBytes = PixelOps.glPixelSize(format, type);
return (ps.skipRows + height - 1L) * stride + (ps.skipPixels + (long) width) * pixelBytes;
}
public static void packColor(long src, int srcSdlFormat, int srcBaseFormat, int srcRowBytes, int width, int height, boolean flipRows, boolean readPixels, int format, int type, PackState ps, long dst) {
validate(format, type);
if (!isColorFormat(format)) {
throw new IllegalArgumentException("GL_INVALID_OPERATION: packColor with non-color format 0x" + Integer.toHexString(format));
}
final int kind = sourceKind(srcSdlFormat);
final boolean srcInteger = kind == K_UINT || kind == K_SINT;
final boolean dstInteger = isIntegerFormat(format);
if (srcInteger != dstInteger) {
throw new IllegalArgumentException("GL_INVALID_OPERATION: integer / non-integer mismatch, source 0x" + Integer.toHexString(srcSdlFormat) + " format 0x" + Integer.toHexString(format));
}
final int rebase = rebaseSwizzle(srcBaseFormat);
if (width <= 0 || height <= 0) return;
final int texelBytes = PixelOps.sdlFormatTexelBytes(srcSdlFormat);
final int pixelBytes = PixelOps.glPixelSize(format, type);
final int stride = rowStride(format, type, width, ps);
final long base = dst + (long) ps.skipRows * stride + (long) ps.skipPixels * pixelBytes;
final int rowBytes = width * pixelBytes;
final int swapSize = ps.swapBytes ? swapUnit(type) : 0;
final boolean lumConversion = isLuminanceFormat(format)
&& (srcBaseFormat == GL30.GL_RG || srcBaseFormat == GL11.GL_RGB || srcBaseFormat == GL11.GL_RGBA);
final int n = formatComponents(format);
final int requested = formatSwizzle(format, false);
final boolean rebaseChanges = rebaseChangesRequested(requested, n, rebase);
final boolean clamp = !dstInteger && clampNeeded(kind, readPixels, format, type, lumConversion);
if (!clamp && !lumConversion && !rebaseChanges && layoutMatches(srcSdlFormat, format, type)) {
for (int j = 0; j < height; j++) {
final long d = base + (long) j * stride;
MemoryUtil.memCopy(src + (long) srcRow(j, height, flipRows) * srcRowBytes, d, rowBytes);
if (swapSize != 0) swapRow(d, rowBytes, swapSize);
}
return;
}
final int swizzle = applyRebase(formatSwizzle(format, lumConversion && readPixels), n, rebase, srcBaseFormat == GL30.GL_RG);
if (LITTLE_ENDIAN && !clamp && !lumConversion && !rebaseChanges && isUnorm8x4(srcSdlFormat) && byteShuffleType(format, type)) {
final boolean reversed = type == GL12.GL_UNSIGNED_INT_8_8_8_8;
final int o0 = shuffleOffset(srcSdlFormat, swizzle, n, reversed, 0);
final int o1 = shuffleOffset(srcSdlFormat, swizzle, n, reversed, 1);
final int o2 = shuffleOffset(srcSdlFormat, swizzle, n, reversed, 2);
final int o3 = shuffleOffset(srcSdlFormat, swizzle, n, reversed, 3);
for (int j = 0; j < height; j++) {
final long s = src + (long) srcRow(j, height, flipRows) * srcRowBytes;
final long d = base + (long) j * stride;
for (int x = 0; x < width; x++) {
final long t = s + ((long) x << 2);
final long o = d + (long) x * n;
MemoryUtil.memPutByte(o, MemoryUtil.memGetByte(t + o0));
if (n > 1) MemoryUtil.memPutByte(o + 1, MemoryUtil.memGetByte(t + o1));
if (n > 2) MemoryUtil.memPutByte(o + 2, MemoryUtil.memGetByte(t + o2));
if (n > 3) MemoryUtil.memPutByte(o + 3, MemoryUtil.memGetByte(t + o3));
}
if (swapSize != 0) swapRow(d, rowBytes, swapSize);
}
return;
}
final boolean packed = GLTypes.packedTexelBytes(type) != 0;
final int compBytes = packed ? 0 : GLTypes.sizeBytes(type);
final boolean direct = (kind == K_UNORM || kind == K_SNORM) && !clamp && !lumConversion;
for (int j = 0; j < height; j++) {
final long s = src + (long) srcRow(j, height, flipRows) * srcRowBytes;
final long d = base + (long) j * stride;
for (int x = 0; x < width; x++) {
final long t = s + (long) x * texelBytes;
final long o = d + (long) x * pixelBytes;
if (packed) {
putSized(o, pixelBytes, packedTexel(t, srcSdlFormat, kind, swizzle, n, type, direct, clamp, dstInteger));
} else if (dstInteger) {
for (int i = 0; i < n; i++) {
putSized(o + (long) i * compBytes, compBytes, clampInteger(integerChannel(t, srcSdlFormat, (swizzle >> (i << 2)) & 15), type));
}
} else {
for (int i = 0; i < n; i++) {
putSized(o + (long) i * compBytes, compBytes, normComponent(t, srcSdlFormat, kind, (swizzle >> (i << 2)) & 15, type, direct, clamp));
}
}
}
if (swapSize != 0) swapRow(d, rowBytes, swapSize);
}
}
public static void packDepth(long srcR32f, int srcRowBytes, int width, int height, boolean flipRows, int type, PackState ps, long dst) {
validate(GL11.GL_DEPTH_COMPONENT, type);
if (width <= 0 || height <= 0) return;
final int size = GLTypes.sizeBytes(type);
final int stride = rowStride(GL11.GL_DEPTH_COMPONENT, type, width, ps);
final long base = dst + (long) ps.skipRows * stride + (long) ps.skipPixels * size;
for (int j = 0; j < height; j++) {
final long s = srcR32f + (long) srcRow(j, height, flipRows) * srcRowBytes;
final long d = base + (long) j * stride;
for (int x = 0; x < width; x++) {
putSized(d + (long) x * size, size, encodeDepth(MemoryUtil.memGetFloat(s + ((long) x << 2)), type));
}
if (ps.swapBytes && size > 1) swapRow(d, width * size, size);
}
}
public static void packStencil(long srcR8, int srcRowBytes, int width, int height, boolean flipRows, int type, PackState ps, long dst) {
validate(GL11.GL_STENCIL_INDEX, type);
if (width <= 0 || height <= 0) return;
final int size = GLTypes.sizeBytes(type);
final int stride = rowStride(GL11.GL_STENCIL_INDEX, type, width, ps);
final long base = dst + (long) ps.skipRows * stride + (long) ps.skipPixels * size;
for (int j = 0; j < height; j++) {
final long s = srcR8 + (long) srcRow(j, height, flipRows) * srcRowBytes;
final long d = base + (long) j * stride;
for (int x = 0; x < width; x++) {
putSized(d + (long) x * size, size, encodeStencil(MemoryUtil.memGetByte(s + x) & 0xFF, type));
}
if (ps.swapBytes && size > 1) swapRow(d, width * size, size);
}
}
public static void packDepthStencil(long srcR32f, long srcR8, int srcRowBytes32, int srcRowBytes8, int width, int height, boolean flipRows, int type, PackState ps, long dst) {
validate(GL30.GL_DEPTH_STENCIL, type);
if (width <= 0 || height <= 0) return;
final int size = GLTypes.packedTexelBytes(type);
final int stride = rowStride(GL30.GL_DEPTH_STENCIL, type, width, ps);
final long base = dst + (long) ps.skipRows * stride + (long) ps.skipPixels * size;
final boolean float32 = type == GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
for (int j = 0; j < height; j++) {
final int row = srcRow(j, height, flipRows);
final long sd = srcR32f + (long) row * srcRowBytes32;
final long ss = srcR8 + (long) row * srcRowBytes8;
final long d = base + (long) j * stride;
for (int x = 0; x < width; x++) {
final float depth = MemoryUtil.memGetFloat(sd + ((long) x << 2));
final int stencil = MemoryUtil.memGetByte(ss + x) & 0xFF;
final long o = d + (long) x * size;
if (float32) {
MemoryUtil.memPutFloat(o, depth);
MemoryUtil.memPutInt(o + 4, stencil);
} else {
MemoryUtil.memPutInt(o, ((int) (depth * 16777215.0f) << 8) | stencil);
}
}
if (ps.swapBytes) swapRow(d, width * size, 4);
}
}
private static float halfToFloat(int h) {
final int sign = (h & 0x8000) << 16;
final int exp = (h >>> 10) & 0x1F;
final int mant = h & 0x3FF;
if (exp == 0) {
final float v = mant * (1.0f / 16777216.0f);
return sign != 0 ? -v : v;
}
if (exp == 31) return Float.intBitsToFloat(sign | 0x7F800000 | (mant << 13));
return Float.intBitsToFloat(sign | ((exp - 15 + 127) << 23) | (mant << 13));
}
private static int floatToHalf(float f) {
final int x = Float.floatToRawIntBits(f);
final int sign = (x >>> 16) & 0x8000;
final int exp = (x >>> 23) & 0xFF;
int mant = x & 0x7FFFFF;
if (exp == 0xFF) return sign | 0x7C00 | (mant != 0 ? 0x200 | (mant >>> 13) : 0);
final int he = exp - 127 + 15;
if (he >= 0x1F) return sign | 0x7C00;
if (he <= 0) {
if (he < -10) return sign;
mant |= 0x800000;
final int shift = 14 - he;
int hm = mant >>> shift;
final int rem = mant & ((1 << shift) - 1);
final int half = 1 << (shift - 1);
if (rem > half || (rem == half && (hm & 1) != 0)) hm++;
return sign | hm;
}
int out = sign | (he << 10) | (mant >>> 13);
final int rem = mant & 0x1FFF;
if (rem > 0x1000 || (rem == 0x1000 && (out & 1) != 0)) out++;
return out;
}
private static float uf11ToFloat(int v) {
return packedUfloatToFloat(v, 6);
}
private static float uf10ToFloat(int v) {
return packedUfloatToFloat(v, 5);
}
private static float packedUfloatToFloat(int v, int mantBits) {
final int exp = (v >>> mantBits) & 0x1F;
final int mant = v & ((1 << mantBits) - 1);
if (exp == 0) return mant == 0 ? 0.0f : mant * (1.0f / (1 << 14)) / (1 << mantBits);
if (exp == 31) return Float.intBitsToFloat(0x7F800000 | mant);
final int e = exp - 15;
final float scale = e < 0 ? 1.0f / (1 << -e) : (float) (1 << e);
return scale * (1.0f + (float) mant / (1 << mantBits));
}
private static int floatToUf11(float val) {
return floatToPackedUfloat(val, 6, 65024.0f);
}
private static int floatToUf10(float val) {
return floatToPackedUfloat(val, 5, 64512.0f);
}
private static int floatToPackedUfloat(float val, int mantBits, float maxFinite) {
final int bits = Float.floatToRawIntBits(val);
final boolean negative = bits < 0;
int exponent = ((bits >>> 23) & 0xFF) - 127;
final int f32Mant = bits & 0x7FFFFF;
final int maxExponent = 0x1F << mantBits;
if (exponent == 128) {
if (f32Mant != 0) return maxExponent | 1;
return negative ? 0 : maxExponent;
}
if (negative) return 0;
if (val > maxFinite) return (30 << mantBits) | ((1 << mantBits) - 1);
if (exponent > -15) {
int mant = (int) Math.rint(Math.scalb((double) val, mantBits - exponent));
if (mant >= 2 << mantBits) {
mant >>= 1;
exponent++;
}
mant &= (1 << mantBits) - 1;
return ((exponent + 15) << mantBits) | mant;
}
final int mant = (int) Math.rint(Math.scalb((double) val, mantBits + 14));
if ((mant >> mantBits) != 0) return 1 << mantBits;
return mant;
}
private static int floatToRgb9e5(float r, float g, float b) {
final int rc = rgb9e5ClampRange(r);
final int gc = rgb9e5ClampRange(g);
final int bc = rgb9e5ClampRange(b);
int maxrgb = Math.max(rc, Math.max(gc, bc));
maxrgb += maxrgb & (1 << (23 - 9));
final int expShared = Math.max(maxrgb >>> 23, -15 - 1 + 127) + 1 + 15 - 127;
final int revdenomBiasedExp = 127 - (expShared - 15 - 9) + 1;
final float revdenom = Float.intBitsToFloat(revdenomBiasedExp << 23);
int rm = (int) (Float.intBitsToFloat(rc) * revdenom);
int gm = (int) (Float.intBitsToFloat(gc) * revdenom);
int bm = (int) (Float.intBitsToFloat(bc) * revdenom);
rm = (rm & 1) + (rm >> 1);
gm = (gm & 1) + (gm >> 1);
bm = (bm & 1) + (bm >> 1);
return (expShared << 27) | (bm << 18) | (gm << 9) | rm;
}
private static int rgb9e5ClampRange(float x) {
final int u = Float.floatToRawIntBits(x);
final int max = Float.floatToRawIntBits(MAX_RGB9E5);
if (Integer.compareUnsigned(u, 0x7F800000) > 0) return 0;
if (Integer.compareUnsigned(u, max) >= 0) return max;
return u;
}
private static int srcRow(int j, int height, boolean flipRows) {
return flipRows ? height - 1 - j : j;
}
private static int swapUnit(int type) {
final int packed = GLTypes.packedTexelBytes(type);
final int size = packed != 0 ? packed : GLTypes.sizeBytes(type);
return (size == 2 || size == 4) ? size : 0;
}
private static void swapRow(long p, int bytes, int size) {
if (size == 2) {
for (int i = 0; i + 1 < bytes; i += 2) {
MemoryUtil.memPutShort(p + i, Short.reverseBytes(MemoryUtil.memGetShort(p + i)));
}
} else if (size == 4) {
for (int i = 0; i + 3 < bytes; i += 4) {
MemoryUtil.memPutInt(p + i, Integer.reverseBytes(MemoryUtil.memGetInt(p + i)));
}
}
}
private static void putSized(long p, int size, int v) {
switch (size) {
case 1 -> MemoryUtil.memPutByte(p, (byte) v);
case 2 -> MemoryUtil.memPutShort(p, (short) v);
default -> MemoryUtil.memPutInt(p, v);
}
}
private static boolean clampNeeded(int kind, boolean readPixels, int format, int type, boolean lumConversion) {
if (readPixels) {
final boolean clampRead = kind == K_UNORM || kind == K_SNORM;
final boolean floatType = type == GL11.GL_FLOAT || type == GL30.GL_HALF_FLOAT || type == GL30.GL_UNSIGNED_INT_10F_11F_11F_REV;
final boolean clamp = clampRead || !floatType;
return clamp && !(kind == K_UNORM && !lumConversion);
}
final boolean typeNeedsClamping = switch (type) {
case GL11.GL_BYTE, GL11.GL_SHORT, GL11.GL_INT, GL11.GL_FLOAT, GL30.GL_HALF_FLOAT,
GL30.GL_UNSIGNED_INT_10F_11F_11F_REV, GL30.GL_UNSIGNED_INT_5_9_9_9_REV -> false;
default -> true;
};
return typeNeedsClamping && (kind == K_FLOAT || kind == K_SNORM || isLuminanceFormat(format));
}
private static int packedTexel(long t, int fmt, int kind, int swizzle, int n, int type, boolean direct, boolean clamp, boolean dstInteger) {
if (type == GL30.GL_UNSIGNED_INT_10F_11F_11F_REV) {
return floatToUf11(channelFloat(t, fmt, kind, swizzle & 15, clamp))
| (floatToUf11(channelFloat(t, fmt, kind, (swizzle >> 4) & 15, clamp)) << 11)
| (floatToUf10(channelFloat(t, fmt, kind, (swizzle >> 8) & 15, clamp)) << 22);
}
if (type == GL30.GL_UNSIGNED_INT_5_9_9_9_REV) {
return floatToRgb9e5(channelFloat(t, fmt, kind, swizzle & 15, clamp),
channelFloat(t, fmt, kind, (swizzle >> 4) & 15, clamp),
channelFloat(t, fmt, kind, (swizzle >> 8) & 15, clamp));
}
int word = 0;
for (int i = 0; i < n; i++) {
final int ch = (swizzle >> (i << 2)) & 15;
final int bits = packedBits(type, i);
final int v;
if (dstInteger) {
v = (int) clampRange(integerChannel(t, fmt, ch), 0L, (1L << bits) - 1L);
} else {
v = normField(t, fmt, kind, ch, bits, direct, clamp);
}
word |= (v & ((1 << bits) - 1)) << packedShift(type, i);
}
return word;
}
private static int normField(long t, int fmt, int kind, int ch, int dstBits, boolean direct, boolean clamp) {
final int bits = PixelOps.colorChannelBits(fmt, ch);
if (bits == 0) return floatToUnorm((ch == CH_A || ch == CH_ONE) ? 1.0f : 0.0f, dstBits);
if (direct) {
final long raw = rawChannel(t, fmt, ch);
return (int) (kind == K_SNORM ? snormToUnorm(raw, bits, dstBits) : unormToUnorm(raw, bits, dstBits));
}
float f = floatChannel(t, fmt, kind, ch, bits);
if (clamp) f = clamp01(f);
return floatToUnorm(f, dstBits);
}
private static int normComponent(long t, int fmt, int kind, int ch, int type, boolean direct, boolean clamp) {
if (ch == CH_L || ch == CH_L_RG) return encodeFloat(channelFloat(t, fmt, kind, ch, clamp), type);
final int bits = PixelOps.colorChannelBits(fmt, ch);
if (bits == 0) return encodeFloat((ch == CH_A || ch == CH_ONE) ? 1.0f : 0.0f, type);
if (direct) {
return encodeDirect(rawChannel(t, fmt, ch), bits, kind == K_SNORM, type);
}
float f = floatChannel(t, fmt, kind, ch, bits);
if (clamp) f = clamp01(f);
return encodeFloat(f, type);
}
private static float channelFloat(long t, int fmt, int kind, int ch, boolean clamp) {
if (ch == CH_L || ch == CH_L_RG) {
float sum = channelFloat(t, fmt, kind, CH_R, clamp) + channelFloat(t, fmt, kind, CH_G, clamp);
if (ch == CH_L) sum += channelFloat(t, fmt, kind, CH_B, clamp);
return clamp ? clamp01(sum) : sum;
}
final int bits = PixelOps.colorChannelBits(fmt, ch);
if (bits == 0) return (ch == CH_A || ch == CH_ONE) ? 1.0f : 0.0f;
final float f = floatChannel(t, fmt, kind, ch, bits);
return clamp ? clamp01(f) : f;
}
private static float clamp01(float f) {
return f < 0.0f ? 0.0f : (f > 1.0f ? 1.0f : f);
}
private static long integerChannel(long t, int fmt, int ch) {
if (PixelOps.colorChannelBits(fmt, ch) == 0) return (ch == CH_A || ch == CH_ONE) ? 1L : 0L;
return rawChannel(t, fmt, ch);
}
private static int clampInteger(long v, int type) {
return switch (type) {
case GL11.GL_UNSIGNED_BYTE -> (int) clampRange(v, 0L, 0xFFL);
case GL11.GL_BYTE -> (int) clampRange(v, Byte.MIN_VALUE, Byte.MAX_VALUE);
case GL11.GL_UNSIGNED_SHORT -> (int) clampRange(v, 0L, 0xFFFFL);
case GL11.GL_SHORT -> (int) clampRange(v, Short.MIN_VALUE, Short.MAX_VALUE);
case GL11.GL_UNSIGNED_INT -> (int) clampRange(v, 0L, 0xFFFFFFFFL);
default -> (int) clampRange(v, Integer.MIN_VALUE, Integer.MAX_VALUE);
};
}
private static long clampRange(long v, long lo, long hi) {
return v < lo ? lo : (v > hi ? hi : v);
}
private static int encodeFloat(float f, int type) {
return switch (type) {
case GL11.GL_UNSIGNED_BYTE -> floatToUnorm(f, 8);
case GL11.GL_UNSIGNED_SHORT -> floatToUnorm(f, 16);
case GL11.GL_UNSIGNED_INT -> floatToUnorm(f, 32);
case GL11.GL_BYTE -> floatToSnorm(f, 8);
case GL11.GL_SHORT -> floatToSnorm(f, 16);
case GL11.GL_INT -> floatToSnorm(f, 32);
case GL30.GL_HALF_FLOAT -> floatToHalf(f);
default -> Float.floatToRawIntBits(f);
};
}
private static int encodeDirect(long raw, int bits, boolean signed, int type) {
return switch (type) {
case GL11.GL_UNSIGNED_BYTE -> (int) (signed ? snormToUnorm(raw, bits, 8) : unormToUnorm(raw, bits, 8));
case GL11.GL_UNSIGNED_SHORT -> (int) (signed ? snormToUnorm(raw, bits, 16) : unormToUnorm(raw, bits, 16));
case GL11.GL_UNSIGNED_INT -> (int) (signed ? snormToUnorm(raw, bits, 32) : unormToUnorm(raw, bits, 32));
case GL11.GL_BYTE -> (int) (signed ? snormToSnorm(raw, bits, 8) : unormToUnorm(raw, bits, 7));
case GL11.GL_SHORT -> (int) (signed ? snormToSnorm(raw, bits, 16) : unormToUnorm(raw, bits, 15));
case GL11.GL_INT -> (int) (signed ? snormToSnorm(raw, bits, 32) : unormToUnorm(raw, bits, 31));
default -> encodeFloat(signed ? snormToFloat(raw, bits) : unormToFloat(raw, bits), type);
};
}
private static long maxUnsigned(int bits) {
return (1L << bits) - 1L;
}
private static long unormToUnorm(long x, int srcBits, int dstBits) {
if (srcBits == dstBits) return x;
final long srcMax = maxUnsigned(srcBits);
return (x * maxUnsigned(dstBits) * 2L + srcMax) / (srcMax * 2L);
}
private static long snormToUnorm(long x, int srcBits, int dstBits) {
return x < 0 ? 0L : unormToUnorm(x, srcBits - 1, dstBits);
}
private static long snormToSnorm(long x, int srcBits, int dstBits) {
if (srcBits == dstBits) return x;
final long srcMax = maxUnsigned(srcBits - 1);
final long dstMax = maxUnsigned(dstBits - 1);
if (x <= -srcMax) return -dstMax;
final long magnitude = ((x < 0 ? -x : x) * dstMax * 2L + srcMax) / (srcMax * 2L);
return x < 0 ? -magnitude : magnitude;
}
private static float unormToFloat(long x, int bits) {
return x * (1.0f / (float) maxUnsigned(bits));
}
private static float snormToFloat(long x, int bits) {
final long max = maxUnsigned(bits - 1);
if (x <= -max) return -1.0f;
return x * (1.0f / (float) max);
}
private static int floatToUnorm(float x, int bits) {
if (x < 0.0f) return 0;
if (bits >= 32) {
if (x > 1.0f) return -1;
return (int) (long) Math.rint(x * 4294967295.0);
}
final long max = maxUnsigned(bits);
if (x > 1.0f) return (int) max;
return (int) (long) Math.rint(x * (float) max);
}
private static int floatToSnorm(float x, int bits) {
final long max = maxUnsigned(bits - 1);
if (x < -1.0f) return (int) -max;
if (x > 1.0f) return (int) max;
if (bits >= 32) return (int) (long) Math.rint(x * 2147483647.0);
return (int) (long) Math.rint(x * (float) max);
}
private static int encodeDepth(float z, int type) {
return switch (type) {
case GL11.GL_UNSIGNED_BYTE -> (int) (z * 255.0f);
case GL11.GL_BYTE -> (((int) (255.0f * z)) - 1) / 2;
case GL11.GL_UNSIGNED_SHORT -> (int) (long) Math.rint(z * 65535.0f);
case GL11.GL_SHORT -> (((int) (65535.0f * z)) - 1) / 2;
case GL11.GL_UNSIGNED_INT -> (int) (long) (z * 4294967295.0);
case GL11.GL_INT -> (int) (2147483647.0 * z);
case GL30.GL_HALF_FLOAT -> floatToHalf(z);
default -> Float.floatToRawIntBits(z);
};
}
private static int encodeStencil(int s, int type) {
return switch (type) {
case GL11.GL_BYTE -> s & 0x7F;
case GL11.GL_FLOAT -> Float.floatToRawIntBits((float) s);
case GL30.GL_HALF_FLOAT -> floatToHalf((float) s);
default -> s;
};
}
private static int sourceKind(int fmt) {
return switch (fmt) {
case SDL_GPU_TEXTUREFORMAT_R8_UNORM, SDL_GPU_TEXTUREFORMAT_R8G8_UNORM, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB, SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM,
SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB, SDL_GPU_TEXTUREFORMAT_A8_UNORM,
SDL_GPU_TEXTUREFORMAT_R16_UNORM, SDL_GPU_TEXTUREFORMAT_R16G16_UNORM,
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM, SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM,
SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM, SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM,
SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM -> K_UNORM;
case SDL_GPU_TEXTUREFORMAT_R8_SNORM, SDL_GPU_TEXTUREFORMAT_R8G8_SNORM, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM,
SDL_GPU_TEXTUREFORMAT_R16_SNORM, SDL_GPU_TEXTUREFORMAT_R16G16_SNORM,
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM -> K_SNORM;
case SDL_GPU_TEXTUREFORMAT_R16_FLOAT, SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT,
SDL_GPU_TEXTUREFORMAT_R32_FLOAT, SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT, SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT,
SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT -> K_FLOAT;
case SDL_GPU_TEXTUREFORMAT_R8_UINT, SDL_GPU_TEXTUREFORMAT_R8G8_UINT, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT,
SDL_GPU_TEXTUREFORMAT_R16_UINT, SDL_GPU_TEXTUREFORMAT_R16G16_UINT, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT,
SDL_GPU_TEXTUREFORMAT_R32_UINT, SDL_GPU_TEXTUREFORMAT_R32G32_UINT, SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT -> K_UINT;
case SDL_GPU_TEXTUREFORMAT_R8_INT, SDL_GPU_TEXTUREFORMAT_R8G8_INT, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT,
SDL_GPU_TEXTUREFORMAT_R16_INT, SDL_GPU_TEXTUREFORMAT_R16G16_INT, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT,
SDL_GPU_TEXTUREFORMAT_R32_INT, SDL_GPU_TEXTUREFORMAT_R32G32_INT, SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT -> K_SINT;
default -> throw new IllegalArgumentException("Unsupported color readback source format " + fmt);
};
}
private static long rawChannel(long t, int fmt, int ch) {
return switch (fmt) {
case SDL_GPU_TEXTUREFORMAT_R8_UNORM, SDL_GPU_TEXTUREFORMAT_R8G8_UNORM, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB, SDL_GPU_TEXTUREFORMAT_R8_UINT, SDL_GPU_TEXTUREFORMAT_R8G8_UINT,
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT -> MemoryUtil.memGetByte(t + ch) & 0xFFL;
case SDL_GPU_TEXTUREFORMAT_R8_SNORM, SDL_GPU_TEXTUREFORMAT_R8G8_SNORM, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM,
SDL_GPU_TEXTUREFORMAT_R8_INT, SDL_GPU_TEXTUREFORMAT_R8G8_INT, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT -> MemoryUtil.memGetByte(t + ch);
case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB -> MemoryUtil.memGetByte(t + bgraOffset(ch)) & 0xFFL;
case SDL_GPU_TEXTUREFORMAT_A8_UNORM -> MemoryUtil.memGetByte(t) & 0xFFL;
case SDL_GPU_TEXTUREFORMAT_R16_UNORM, SDL_GPU_TEXTUREFORMAT_R16G16_UNORM, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM,
SDL_GPU_TEXTUREFORMAT_R16_UINT, SDL_GPU_TEXTUREFORMAT_R16G16_UINT,
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT -> MemoryUtil.memGetShort(t + ((long) ch << 1)) & 0xFFFFL;
case SDL_GPU_TEXTUREFORMAT_R16_SNORM, SDL_GPU_TEXTUREFORMAT_R16G16_SNORM, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM,
SDL_GPU_TEXTUREFORMAT_R16_INT, SDL_GPU_TEXTUREFORMAT_R16G16_INT,
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT -> MemoryUtil.memGetShort(t + ((long) ch << 1));
case SDL_GPU_TEXTUREFORMAT_R32_UINT, SDL_GPU_TEXTUREFORMAT_R32G32_UINT,
SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT -> MemoryUtil.memGetInt(t + ((long) ch << 2)) & 0xFFFFFFFFL;
case SDL_GPU_TEXTUREFORMAT_R32_INT, SDL_GPU_TEXTUREFORMAT_R32G32_INT,
SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT -> MemoryUtil.memGetInt(t + ((long) ch << 2));
case SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM -> {
final int w = MemoryUtil.memGetInt(t);
yield ch == CH_A ? (w >>> 30) : ((w >>> (10 * ch)) & 0x3FF);
}
case SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM -> {
final int w = MemoryUtil.memGetShort(t) & 0xFFFF;
yield switch (ch) {
case CH_R -> (w >>> 11) & 0x1F;
case CH_G -> (w >>> 5) & 0x3F;
default -> w & 0x1F;
};
}
case SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM -> {
final int w = MemoryUtil.memGetShort(t) & 0xFFFF;
yield switch (ch) {
case CH_R -> (w >>> 10) & 0x1F;
case CH_G -> (w >>> 5) & 0x1F;
case CH_B -> w & 0x1F;
default -> (w >>> 15) & 1;
};
}
case SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM -> {
final int w = MemoryUtil.memGetShort(t) & 0xFFFF;
yield switch (ch) {
case CH_R -> (w >>> 8) & 0xF;
case CH_G -> (w >>> 4) & 0xF;
case CH_B -> w & 0xF;
default -> (w >>> 12) & 0xF;
};
}
default -> throw new IllegalArgumentException("Unsupported color readback source format " + fmt);
};
}
private static float floatChannel(long t, int fmt, int kind, int ch, int bits) {
if (kind == K_UNORM) return unormToFloat(rawChannel(t, fmt, ch), bits);
if (kind == K_SNORM) return snormToFloat(rawChannel(t, fmt, ch), bits);
return switch (fmt) {
case SDL_GPU_TEXTUREFORMAT_R16_FLOAT, SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT,
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT -> halfToFloat(MemoryUtil.memGetShort(t + ((long) ch << 1)) & 0xFFFF);
case SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT -> {
final int w = MemoryUtil.memGetInt(t);
yield switch (ch) {
case CH_R -> uf11ToFloat(w & 0x7FF);
case CH_G -> uf11ToFloat((w >>> 11) & 0x7FF);
default -> uf10ToFloat((w >>> 22) & 0x3FF);
};
}
default -> MemoryUtil.memGetFloat(t + ((long) ch << 2));
};
}
private static int bgraOffset(int ch) {
return ch == CH_R ? 2 : (ch == CH_B ? 0 : ch);
}
private static boolean isUnorm8x4(int fmt) {
return fmt == SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM || fmt == SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB
|| fmt == SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM || fmt == SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB;
}
private static boolean byteShuffleType(int format, int type) {
if (type == GL11.GL_UNSIGNED_BYTE) return !isLuminanceFormat(format);
return (type == GL12.GL_UNSIGNED_INT_8_8_8_8 || type == GL12.GL_UNSIGNED_INT_8_8_8_8_REV)
&& (format == GL11.GL_RGBA || format == GL12.GL_BGRA);
}
private static int shuffleOffset(int fmt, int swizzle, int n, boolean reversed, int outByte) {
if (outByte >= n) return 0;
final int comp = reversed ? 3 - outByte : outByte;
final int ch = (swizzle >> (comp << 2)) & 15;
final boolean bgra = fmt == SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM || fmt == SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB;
return bgra ? bgraOffset(ch) : ch;
}
private static boolean layoutMatches(int fmt, int format, int type) {
return switch (fmt) {
case SDL_GPU_TEXTUREFORMAT_R8_UNORM -> format == GL11.GL_RED && type == GL11.GL_UNSIGNED_BYTE;
case SDL_GPU_TEXTUREFORMAT_R8G8_UNORM -> format == GL30.GL_RG && type == GL11.GL_UNSIGNED_BYTE;
case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB ->
format == GL11.GL_RGBA && (type == GL11.GL_UNSIGNED_BYTE || (LITTLE_ENDIAN && type == GL12.GL_UNSIGNED_INT_8_8_8_8_REV));
case SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB ->
format == GL12.GL_BGRA && (type == GL11.GL_UNSIGNED_BYTE || (LITTLE_ENDIAN && type == GL12.GL_UNSIGNED_INT_8_8_8_8_REV));
case SDL_GPU_TEXTUREFORMAT_A8_UNORM -> format == GL11.GL_ALPHA && type == GL11.GL_UNSIGNED_BYTE;
case SDL_GPU_TEXTUREFORMAT_R16_UNORM -> format == GL11.GL_RED && type == GL11.GL_UNSIGNED_SHORT;
case SDL_GPU_TEXTUREFORMAT_R16G16_UNORM -> format == GL30.GL_RG && type == GL11.GL_UNSIGNED_SHORT;
case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM -> format == GL11.GL_RGBA && type == GL11.GL_UNSIGNED_SHORT;
case SDL_GPU_TEXTUREFORMAT_R8_SNORM -> format == GL11.GL_RED && type == GL11.GL_BYTE;
case SDL_GPU_TEXTUREFORMAT_R8G8_SNORM -> format == GL30.GL_RG && type == GL11.GL_BYTE;
case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM -> format == GL11.GL_RGBA && type == GL11.GL_BYTE;
case SDL_GPU_TEXTUREFORMAT_R16_SNORM -> format == GL11.GL_RED && type == GL11.GL_SHORT;
case SDL_GPU_TEXTUREFORMAT_R16G16_SNORM -> format == GL30.GL_RG && type == GL11.GL_SHORT;
case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM -> format == GL11.GL_RGBA && type == GL11.GL_SHORT;
case SDL_GPU_TEXTUREFORMAT_R16_FLOAT -> format == GL11.GL_RED && type == GL30.GL_HALF_FLOAT;
case SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT -> format == GL30.GL_RG && type == GL30.GL_HALF_FLOAT;
case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT -> format == GL11.GL_RGBA && type == GL30.GL_HALF_FLOAT;
case SDL_GPU_TEXTUREFORMAT_R32_FLOAT -> format == GL11.GL_RED && type == GL11.GL_FLOAT;
case SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT -> format == GL30.GL_RG && type == GL11.GL_FLOAT;
case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT -> format == GL11.GL_RGBA && type == GL11.GL_FLOAT;
case SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT -> format == GL11.GL_RGB && type == GL30.GL_UNSIGNED_INT_10F_11F_11F_REV;
case SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM -> format == GL11.GL_RGBA && type == GL12.GL_UNSIGNED_INT_2_10_10_10_REV;
case SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM -> format == GL11.GL_RGB && type == GL12.GL_UNSIGNED_SHORT_5_6_5;
case SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM -> format == GL12.GL_BGRA && type == GL12.GL_UNSIGNED_SHORT_1_5_5_5_REV;
case SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM -> format == GL12.GL_BGRA && type == GL12.GL_UNSIGNED_SHORT_4_4_4_4_REV;
case SDL_GPU_TEXTUREFORMAT_R8_UINT -> format == GL30.GL_RED_INTEGER && type == GL11.GL_UNSIGNED_BYTE;
case SDL_GPU_TEXTUREFORMAT_R8G8_UINT -> format == GL30.GL_RG_INTEGER && type == GL11.GL_UNSIGNED_BYTE;
case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT -> format == GL30.GL_RGBA_INTEGER && type == GL11.GL_UNSIGNED_BYTE;
case SDL_GPU_TEXTUREFORMAT_R16_UINT -> format == GL30.GL_RED_INTEGER && type == GL11.GL_UNSIGNED_SHORT;
case SDL_GPU_TEXTUREFORMAT_R16G16_UINT -> format == GL30.GL_RG_INTEGER && type == GL11.GL_UNSIGNED_SHORT;
case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT -> format == GL30.GL_RGBA_INTEGER && type == GL11.GL_UNSIGNED_SHORT;
case SDL_GPU_TEXTUREFORMAT_R32_UINT -> format == GL30.GL_RED_INTEGER && type == GL11.GL_UNSIGNED_INT;
case SDL_GPU_TEXTUREFORMAT_R32G32_UINT -> format == GL30.GL_RG_INTEGER && type == GL11.GL_UNSIGNED_INT;
case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT -> format == GL30.GL_RGBA_INTEGER && type == GL11.GL_UNSIGNED_INT;
case SDL_GPU_TEXTUREFORMAT_R8_INT -> format == GL30.GL_RED_INTEGER && type == GL11.GL_BYTE;
case SDL_GPU_TEXTUREFORMAT_R8G8_INT -> format == GL30.GL_RG_INTEGER && type == GL11.GL_BYTE;
case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT -> format == GL30.GL_RGBA_INTEGER && type == GL11.GL_BYTE;
case SDL_GPU_TEXTUREFORMAT_R16_INT -> format == GL30.GL_RED_INTEGER && type == GL11.GL_SHORT;
case SDL_GPU_TEXTUREFORMAT_R16G16_INT -> format == GL30.GL_RG_INTEGER && type == GL11.GL_SHORT;
case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT -> format == GL30.GL_RGBA_INTEGER && type == GL11.GL_SHORT;
case SDL_GPU_TEXTUREFORMAT_R32_INT -> format == GL30.GL_RED_INTEGER && type == GL11.GL_INT;
case SDL_GPU_TEXTUREFORMAT_R32G32_INT -> format == GL30.GL_RG_INTEGER && type == GL11.GL_INT;
case SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT -> format == GL30.GL_RGBA_INTEGER && type == GL11.GL_INT;
default -> false;
};
}
private static int packedBits(int type, int i) {
return switch (type) {
case GL12.GL_UNSIGNED_BYTE_3_3_2 -> i == 2 ? 2 : 3;
case GL12.GL_UNSIGNED_BYTE_2_3_3_REV -> i == 2 ? 2 : 3;
case GL12.GL_UNSIGNED_SHORT_5_6_5, GL12.GL_UNSIGNED_SHORT_5_6_5_REV -> i == 1 ? 6 : 5;
case GL12.GL_UNSIGNED_SHORT_4_4_4_4, GL12.GL_UNSIGNED_SHORT_4_4_4_4_REV -> 4;
case GL12.GL_UNSIGNED_SHORT_5_5_5_1, GL12.GL_UNSIGNED_SHORT_1_5_5_5_REV -> i == 3 ? 1 : 5;
case GL12.GL_UNSIGNED_INT_8_8_8_8, GL12.GL_UNSIGNED_INT_8_8_8_8_REV -> 8;
default -> i == 3 ? 2 : 10;
};
}
private static int packedShift(int type, int i) {
return switch (type) {
case GL12.GL_UNSIGNED_BYTE_3_3_2 -> i == 0 ? 5 : (i == 1 ? 2 : 0);
case GL12.GL_UNSIGNED_BYTE_2_3_3_REV -> i * 3;
case GL12.GL_UNSIGNED_SHORT_5_6_5 -> i == 0 ? 11 : (i == 1 ? 5 : 0);
case GL12.GL_UNSIGNED_SHORT_5_6_5_REV -> i == 0 ? 0 : (i == 1 ? 5 : 11);
case GL12.GL_UNSIGNED_SHORT_4_4_4_4 -> 12 - 4 * i;
case GL12.GL_UNSIGNED_SHORT_4_4_4_4_REV -> 4 * i;
case GL12.GL_UNSIGNED_SHORT_5_5_5_1 -> i == 3 ? 0 : 11 - 5 * i;
case GL12.GL_UNSIGNED_SHORT_1_5_5_5_REV -> 5 * i;
case GL12.GL_UNSIGNED_INT_8_8_8_8 -> 24 - 8 * i;
case GL12.GL_UNSIGNED_INT_8_8_8_8_REV -> 8 * i;
case GL12.GL_UNSIGNED_INT_10_10_10_2 -> i == 3 ? 0 : 22 - 10 * i;
default -> 10 * i;
};
}
private static boolean isIntegerFormat(int format) {
return switch (format) {
case GL30.GL_RED_INTEGER, GL30.GL_GREEN_INTEGER, GL30.GL_BLUE_INTEGER, GL30.GL_ALPHA_INTEGER,
GL30.GL_RG_INTEGER, GL30.GL_RGB_INTEGER, GL30.GL_BGR_INTEGER, GL30.GL_RGBA_INTEGER,
GL30.GL_BGRA_INTEGER -> true;
default -> false;
};
}
private static boolean isLuminanceFormat(int format) {
return format == GL11.GL_LUMINANCE || format == GL11.GL_LUMINANCE_ALPHA;
}
private static boolean isColorFormat(int format) {
return format != GL11.GL_DEPTH_COMPONENT && format != GL11.GL_STENCIL_INDEX && format != GL30.GL_DEPTH_STENCIL;
}
private static int formatComponents(int format) {
return switch (format) {
case GL30.GL_RG, GL30.GL_RG_INTEGER, GL11.GL_LUMINANCE_ALPHA -> 2;
case GL11.GL_RGB, GL12.GL_BGR, GL30.GL_RGB_INTEGER, GL30.GL_BGR_INTEGER -> 3;
case GL11.GL_RGBA, GL12.GL_BGRA, GL30.GL_RGBA_INTEGER, GL30.GL_BGRA_INTEGER -> 4;
default -> 1;
};
}
public static int glBaseFormat(int internalFormat) {
return switch (internalFormat) {
case GL11.GL_ALPHA, GL11.GL_ALPHA4, GL11.GL_ALPHA8, GL11.GL_ALPHA12, GL11.GL_ALPHA16,
GL13.GL_COMPRESSED_ALPHA, ARBTextureFloat.GL_ALPHA16F_ARB, ARBTextureFloat.GL_ALPHA32F_ARB,
EXTTextureSnorm.GL_ALPHA_SNORM, EXTTextureSnorm.GL_ALPHA8_SNORM, EXTTextureSnorm.GL_ALPHA16_SNORM,
EXTTextureInteger.GL_ALPHA8UI_EXT, EXTTextureInteger.GL_ALPHA16UI_EXT, EXTTextureInteger.GL_ALPHA32UI_EXT,
EXTTextureInteger.GL_ALPHA8I_EXT, EXTTextureInteger.GL_ALPHA16I_EXT, EXTTextureInteger.GL_ALPHA32I_EXT -> GL11.GL_ALPHA;
case 1, GL11.GL_LUMINANCE, GL11.GL_LUMINANCE4, GL11.GL_LUMINANCE8, GL11.GL_LUMINANCE12, GL11.GL_LUMINANCE16,
GL13.GL_COMPRESSED_LUMINANCE, ARBTextureFloat.GL_LUMINANCE16F_ARB, ARBTextureFloat.GL_LUMINANCE32F_ARB,
EXTTextureSnorm.GL_LUMINANCE_SNORM, EXTTextureSnorm.GL_LUMINANCE8_SNORM, EXTTextureSnorm.GL_LUMINANCE16_SNORM,
GL21.GL_SLUMINANCE, GL21.GL_SLUMINANCE8, GL21.GL_COMPRESSED_SLUMINANCE,
EXTTextureInteger.GL_LUMINANCE8UI_EXT, EXTTextureInteger.GL_LUMINANCE16UI_EXT, EXTTextureInteger.GL_LUMINANCE32UI_EXT,
EXTTextureInteger.GL_LUMINANCE8I_EXT, EXTTextureInteger.GL_LUMINANCE16I_EXT, EXTTextureInteger.GL_LUMINANCE32I_EXT -> GL11.GL_LUMINANCE;
case 2, GL11.GL_LUMINANCE_ALPHA, GL11.GL_LUMINANCE4_ALPHA4, GL11.GL_LUMINANCE6_ALPHA2, GL11.GL_LUMINANCE8_ALPHA8,
GL11.GL_LUMINANCE12_ALPHA4, GL11.GL_LUMINANCE12_ALPHA12, GL11.GL_LUMINANCE16_ALPHA16,
GL13.GL_COMPRESSED_LUMINANCE_ALPHA, ARBTextureFloat.GL_LUMINANCE_ALPHA16F_ARB, ARBTextureFloat.GL_LUMINANCE_ALPHA32F_ARB,
EXTTextureSnorm.GL_LUMINANCE_ALPHA_SNORM, EXTTextureSnorm.GL_LUMINANCE8_ALPHA8_SNORM, EXTTextureSnorm.GL_LUMINANCE16_ALPHA16_SNORM,
GL21.GL_SLUMINANCE_ALPHA, GL21.GL_SLUMINANCE8_ALPHA8, GL21.GL_COMPRESSED_SLUMINANCE_ALPHA,
EXTTextureInteger.GL_LUMINANCE_ALPHA8UI_EXT, EXTTextureInteger.GL_LUMINANCE_ALPHA16UI_EXT, EXTTextureInteger.GL_LUMINANCE_ALPHA32UI_EXT,
EXTTextureInteger.GL_LUMINANCE_ALPHA8I_EXT, EXTTextureInteger.GL_LUMINANCE_ALPHA16I_EXT, EXTTextureInteger.GL_LUMINANCE_ALPHA32I_EXT -> GL11.GL_LUMINANCE_ALPHA;
case GL11.GL_INTENSITY, GL11.GL_INTENSITY4, GL11.GL_INTENSITY8, GL11.GL_INTENSITY12, GL11.GL_INTENSITY16,
GL13.GL_COMPRESSED_INTENSITY, ARBTextureFloat.GL_INTENSITY16F_ARB, ARBTextureFloat.GL_INTENSITY32F_ARB,
EXTTextureSnorm.GL_INTENSITY_SNORM, EXTTextureSnorm.GL_INTENSITY8_SNORM, EXTTextureSnorm.GL_INTENSITY16_SNORM,
EXTTextureInteger.GL_INTENSITY8UI_EXT, EXTTextureInteger.GL_INTENSITY16UI_EXT, EXTTextureInteger.GL_INTENSITY32UI_EXT,
EXTTextureInteger.GL_INTENSITY8I_EXT, EXTTextureInteger.GL_INTENSITY16I_EXT, EXTTextureInteger.GL_INTENSITY32I_EXT -> GL11.GL_INTENSITY;
case 3, GL11.GL_RGB, GL11.GL_R3_G3_B2, GL11.GL_RGB4, GL11.GL_RGB5, GL11.GL_RGB8, GL11.GL_RGB10, GL11.GL_RGB12, GL11.GL_RGB16,
GL41.GL_RGB565, GL13.GL_COMPRESSED_RGB, GL21.GL_SRGB, GL21.GL_SRGB8, GL21.GL_COMPRESSED_SRGB,
GL30.GL_RGB16F, GL30.GL_RGB32F, EXTTextureSnorm.GL_RGB_SNORM, GL31.GL_RGB8_SNORM, GL31.GL_RGB16_SNORM,
GL30.GL_RGB8UI, GL30.GL_RGB16UI, GL30.GL_RGB32UI, GL30.GL_RGB8I, GL30.GL_RGB16I, GL30.GL_RGB32I,
GL30.GL_RGB9_E5, GL30.GL_R11F_G11F_B10F -> GL11.GL_RGB;
case 4, GL11.GL_RGBA, GL11.GL_RGBA2, GL11.GL_RGBA4, GL11.GL_RGB5_A1, GL11.GL_RGBA8, GL11.GL_RGB10_A2, GL11.GL_RGBA12, GL11.GL_RGBA16,
GL12.GL_BGRA, EXTTextureStorage.GL_BGRA8_EXT, GL13.GL_COMPRESSED_RGBA,
GL21.GL_SRGB_ALPHA, GL21.GL_SRGB8_ALPHA8, GL21.GL_COMPRESSED_SRGB_ALPHA,
GL30.GL_RGBA16F, GL30.GL_RGBA32F, EXTTextureSnorm.GL_RGBA_SNORM, GL31.GL_RGBA8_SNORM, GL31.GL_RGBA16_SNORM,
GL30.GL_RGBA8UI, GL30.GL_RGBA16UI, GL30.GL_RGBA32UI, GL30.GL_RGBA8I, GL30.GL_RGBA16I, GL30.GL_RGBA32I,
GL33.GL_RGB10_A2UI -> GL11.GL_RGBA;
case GL11.GL_RED, GL30.GL_R8, GL30.GL_R16, GL30.GL_R16F, GL30.GL_R32F, GL30.GL_COMPRESSED_RED,
GL30.GL_R8I, GL30.GL_R8UI, GL30.GL_R16I, GL30.GL_R16UI, GL30.GL_R32I, GL30.GL_R32UI,
EXTTextureSnorm.GL_RED_SNORM, GL31.GL_R8_SNORM, GL31.GL_R16_SNORM, EXTTextureSRGBR8.GL_SR8_EXT -> GL11.GL_RED;
case GL30.GL_RG, GL30.GL_RG8, GL30.GL_RG16, GL30.GL_RG16F, GL30.GL_RG32F, GL30.GL_COMPRESSED_RG,
GL30.GL_RG8I, GL30.GL_RG8UI, GL30.GL_RG16I, GL30.GL_RG16UI, GL30.GL_RG32I, GL30.GL_RG32UI,
EXTTextureSnorm.GL_RG_SNORM, GL31.GL_RG8_SNORM, GL31.GL_RG16_SNORM, EXTTextureSRGBRG8.GL_SRG8_EXT -> GL30.GL_RG;
case GL11.GL_DEPTH_COMPONENT, GL14.GL_DEPTH_COMPONENT16, GL14.GL_DEPTH_COMPONENT24, GL14.GL_DEPTH_COMPONENT32,
GL30.GL_DEPTH_COMPONENT32F -> GL11.GL_DEPTH_COMPONENT;
case GL30.GL_DEPTH_STENCIL, GL30.GL_DEPTH24_STENCIL8, GL30.GL_DEPTH32F_STENCIL8 -> GL30.GL_DEPTH_STENCIL;
case GL11.GL_STENCIL_INDEX, GL30.GL_STENCIL_INDEX1, GL30.GL_STENCIL_INDEX4, GL30.GL_STENCIL_INDEX8,
GL30.GL_STENCIL_INDEX16 -> GL11.GL_STENCIL_INDEX;
default -> -1;
};
}
private static int rebaseSwizzle(int baseFormat) {
return switch (baseFormat) {
case GL11.GL_RGBA -> REBASE_IDENTITY;
case GL11.GL_RGB -> CH_R | (CH_G << 4) | (CH_B << 8) | (CH_ONE << 12);
case GL30.GL_RG -> CH_R | (CH_G << 4) | (CH_ZERO << 8) | (CH_ONE << 12);
case GL11.GL_RED, GL11.GL_LUMINANCE, GL11.GL_INTENSITY -> CH_R | (CH_ZERO << 4) | (CH_ZERO << 8) | (CH_ONE << 12);
case GL11.GL_GREEN -> CH_ZERO | (CH_G << 4) | (CH_ZERO << 8) | (CH_ONE << 12);
case GL11.GL_BLUE -> CH_ZERO | (CH_ZERO << 4) | (CH_B << 8) | (CH_ONE << 12);
case GL11.GL_ALPHA -> CH_ZERO | (CH_ZERO << 4) | (CH_ZERO << 8) | (CH_A << 12);
case GL11.GL_LUMINANCE_ALPHA -> CH_R | (CH_ZERO << 4) | (CH_ZERO << 8) | (CH_A << 12);
default -> throw new IllegalArgumentException("GL_INVALID_OPERATION: color readback from base format 0x" + Integer.toHexString(baseFormat));
};
}
private static boolean rebaseChangesRequested(int swizzle, int n, int rebase) {
for (int i = 0; i < n; i++) {
final int ch = (swizzle >> (i << 2)) & 15;
if (ch <= CH_A && ((rebase >> (ch << 2)) & 15) != ch) return true;
}
return false;
}
private static int applyRebase(int swizzle, int n, int rebase, boolean rgBase) {
int out = 0;
for (int i = 0; i < n; i++) {
int ch = (swizzle >> (i << 2)) & 15;
if (ch <= CH_A) ch = (rebase >> (ch << 2)) & 15;
else if (ch == CH_L && rgBase) ch = CH_L_RG;
out |= ch << (i << 2);
}
return out;
}
private static int formatSwizzle(int format, boolean luminanceSum) {
final int l = luminanceSum ? CH_L : CH_R;
return switch (format) {
case GL11.GL_GREEN, GL30.GL_GREEN_INTEGER -> CH_G;
case GL11.GL_BLUE, GL30.GL_BLUE_INTEGER -> CH_B;
case GL11.GL_ALPHA, GL30.GL_ALPHA_INTEGER -> CH_A;
case GL11.GL_LUMINANCE -> l;
case GL11.GL_LUMINANCE_ALPHA -> l | (CH_A << 4);
case GL30.GL_RG, GL30.GL_RG_INTEGER -> CH_R | (CH_G << 4);
case GL11.GL_RGB, GL30.GL_RGB_INTEGER -> CH_R | (CH_G << 4) | (CH_B << 8);
case GL12.GL_BGR, GL30.GL_BGR_INTEGER -> CH_B | (CH_G << 4) | (CH_R << 8);
case GL11.GL_RGBA, GL30.GL_RGBA_INTEGER -> CH_R | (CH_G << 4) | (CH_B << 8) | (CH_A << 12);
case GL12.GL_BGRA, GL30.GL_BGRA_INTEGER -> CH_B | (CH_G << 4) | (CH_R << 8) | (CH_A << 12);
default -> CH_R;
};
}
private static boolean isBaseType(int type) {
return switch (type) {
case GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE, GL11.GL_SHORT, GL11.GL_UNSIGNED_SHORT,
GL11.GL_INT, GL11.GL_UNSIGNED_INT, GL11.GL_FLOAT, GL30.GL_HALF_FLOAT -> true;
default -> false;
};
}
private static boolean isIntegerBaseType(int type) {
return isBaseType(type) && type != GL11.GL_FLOAT && type != GL30.GL_HALF_FLOAT;
}
private static boolean isKnownFormat(int format) {
return switch (format) {
case GL11.GL_RED, GL11.GL_GREEN, GL11.GL_BLUE, GL11.GL_ALPHA, GL30.GL_RG, GL11.GL_RGB, GL12.GL_BGR,
GL11.GL_RGBA, GL12.GL_BGRA, GL11.GL_LUMINANCE, GL11.GL_LUMINANCE_ALPHA,
GL11.GL_DEPTH_COMPONENT, GL11.GL_STENCIL_INDEX, GL30.GL_DEPTH_STENCIL -> true;
default -> isIntegerFormat(format);
};
}
private static boolean isKnownType(int type) {
return isBaseType(type) || GLTypes.packedTexelBytes(type) != 0;
}
private static int checkFormatAndType(int format, int type) {
if (!isKnownFormat(format) || !isKnownType(type)) return INVALID_ENUM;
if (format == GL30.GL_DEPTH_STENCIL && type != GL30.GL_UNSIGNED_INT_24_8 && type != GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV) {
return INVALID_ENUM;
}
switch (type) {
case GL12.GL_UNSIGNED_BYTE_3_3_2, GL12.GL_UNSIGNED_BYTE_2_3_3_REV,
GL12.GL_UNSIGNED_SHORT_5_6_5, GL12.GL_UNSIGNED_SHORT_5_6_5_REV -> {
return (format == GL11.GL_RGB || format == GL30.GL_RGB_INTEGER) ? OK : INVALID_OPERATION;
}
case GL12.GL_UNSIGNED_SHORT_4_4_4_4, GL12.GL_UNSIGNED_SHORT_4_4_4_4_REV,
GL12.GL_UNSIGNED_INT_8_8_8_8, GL12.GL_UNSIGNED_INT_8_8_8_8_REV,
GL12.GL_UNSIGNED_SHORT_5_5_5_1, GL12.GL_UNSIGNED_SHORT_1_5_5_5_REV,
GL12.GL_UNSIGNED_INT_10_10_10_2, GL12.GL_UNSIGNED_INT_2_10_10_10_REV -> {
return (format == GL11.GL_RGBA || format == GL12.GL_BGRA
|| format == GL30.GL_RGBA_INTEGER || format == GL30.GL_BGRA_INTEGER) ? OK : INVALID_OPERATION;
}
case GL30.GL_UNSIGNED_INT_24_8, GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV -> {
return format == GL30.GL_DEPTH_STENCIL ? OK : INVALID_OPERATION;
}
case GL30.GL_UNSIGNED_INT_10F_11F_11F_REV -> {
return format == GL11.GL_RGB ? OK : INVALID_OPERATION;
}
case GL30.GL_UNSIGNED_INT_5_9_9_9_REV -> {
return format == GL11.GL_RGB ? OK : INVALID_ENUM;
}
default -> {
if (isIntegerFormat(format)) return isIntegerBaseType(type) ? OK : INVALID_ENUM;
return isBaseType(type) ? OK : INVALID_ENUM;
}
}
}
}
@@ -45,6 +45,7 @@ import static org.lwjgl.system.MemoryUtil.memAlloc;
import static org.lwjgl.system.MemoryUtil.memFree;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.glsm.texture.TextureStaging;
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
@@ -125,10 +126,13 @@ public final class ResourceManager {
public void markFboAttachment(int glId) { fboAttachedGlIds.add(glId); }
public boolean isFboAttachment(int glId) { return fboAttachedGlIds.contains(glId); }
public boolean shouldDeferTextureUpload(ContextState st, int glId) { return st.deferUploads && !isFboAttachment(glId); }
private int preferredD24 = SDL_GPU_TEXTUREFORMAT_D24_UNORM;
private int preferredD24S8 = SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT;
private volatile int swapchainDepthStencilFormat = SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT;
private long swapchainDepthStencil;
private int swapchainDepthStencilUsage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET;
private int swapchainDepthStencilWidth;
private int swapchainDepthStencilHeight;
@@ -159,18 +163,34 @@ public final class ResourceManager {
LOG.info("D24_UNORM_S8_UINT not supported for depth+sampler, using D32_FLOAT_S8_UINT");
}
swapchainDepthStencilFormat = preferredD24S8;
if (!SDL_GPUTextureSupportsFormat(dev, swapchainDepthStencilFormat, texType, SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)) {
final int alt = swapchainDepthStencilFormat == SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT ? SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT : SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT;
if (SDL_GPUTextureSupportsFormat(dev, alt, texType, SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)) {
swapchainDepthStencilFormat = alt;
final int alt = preferredD24S8 == SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT ? SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT : SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT;
swapchainDepthStencilUsage = depthUsage;
swapchainDepthStencilFormat = pickDepthStencilFormat(dev, texType, preferredD24S8, alt, depthUsage);
if (swapchainDepthStencilFormat == 0) {
swapchainDepthStencilUsage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET;
swapchainDepthStencilFormat = pickDepthStencilFormat(dev, texType, preferredD24S8, alt, swapchainDepthStencilUsage);
if (swapchainDepthStencilFormat != 0) {
LOG.warn("No depth+stencil format is both sampleable and a render target; default framebuffer depth/stencil cannot be read back");
} else {
swapchainDepthStencilFormat = 0;
LOG.warn("No depth+stencil format usable as a render target; default framebuffer will have no depth or stencil");
}
}
}
private static int pickDepthStencilFormat(long dev, int texType, int preferred, int alt, int usage) {
if (SDL_GPUTextureSupportsFormat(dev, preferred, texType, usage)) return preferred;
if (SDL_GPUTextureSupportsFormat(dev, alt, texType, usage)) return alt;
return 0;
}
public long getSwapchainDepthStencil() {
return swapchainDepthStencil;
}
public boolean isSwapchainDepthStencilSampleable() {
return (swapchainDepthStencilUsage & SDL_GPU_TEXTUREUSAGE_SAMPLER) != 0;
}
public int getSwapchainDepthStencilFormat() {
return swapchainDepthStencilFormat;
}
@@ -364,6 +384,7 @@ public final class ResourceManager {
public void markTextureContentDefined(long handle) {
if (handle == 0) return;
if (isTextureContentDefined(handle)) return;
wLock.lock();
try { definedContentTextures.add(handle); } finally { wLock.unlock(); }
}
@@ -568,6 +589,7 @@ public final class ResourceManager {
private boolean copyAllMips(long srcTex, long dstTex, int glTarget, int width, int height, int depth, int levels) {
final long cp = frameManager.ensureCopyPass();
if (cp == 0) return false;
flushBatchedUploads(cp);
final boolean volume = glTarget == GL12.GL_TEXTURE_3D;
final int layers = volume ? 1 : Math.max(1, depth);
try (var stack = stackPush()) {
@@ -1722,22 +1744,7 @@ public final class ResourceManager {
}
copyMappedFromData(mapped, data, (int) size, COPY_CALLSITE_UPLOAD_TEX_DIRECT);
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
try (var stack = stackPush()) {
final SDL_GPUTextureTransferInfo src = SDL_GPUTextureTransferInfo.calloc(stack)
.transfer_buffer(xfer)
.offset(0);
final SDL_GPUTextureRegion dst = SDL_GPUTextureRegion.calloc(stack)
.texture(gpuTexture)
.mip_level(level)
.x(x).y(y).z(z)
.w(w).h(h).d(d);
SDL_UploadToGPUTexture(copyPass, src, dst, false);
}
markTextureContentDefined(gpuTexture);
frameManager.recordUploadCommands(size, 1);
returnTransferBuffer(xfer, size);
uploadRegionFromTransfer(copyPass, xfer, size, gpuTexture, level, x, y, z, w, h, d);
}
private void directUploadToTexture(long copyPass, ByteBuffer data, long gpuTexture, int x, int y, int w, int h, int level) {
@@ -1754,24 +1761,28 @@ public final class ResourceManager {
}
copyMappedFromData(mapped, data, (int) size, COPY_CALLSITE_UPLOAD_TEX_DIRECT);
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
uploadRegionFromTransfer(copyPass, xfer, size, gpuTexture, level, x, y, 0, w, h, 1);
}
try (var stack = stackPush()) {
final SDL_GPUTextureTransferInfo src = SDL_GPUTextureTransferInfo.calloc(stack)
.transfer_buffer(xfer)
.offset(0);
final SDL_GPUTextureRegion dst = SDL_GPUTextureRegion.calloc(stack)
.texture(gpuTexture)
.mip_level(level)
.x(x).y(y).z(0)
.w(w).h(h).d(1);
SDL_UploadToGPUTexture(copyPass, src, dst, false);
private void uploadRegionFromTransfer(long copyPass, long xfer, long size, long tex, int level, int x, int y, int z, int w, int h, int d) {
try {
flushBatchedUploads(copyPass);
try (var stack = stackPush()) {
final SDL_GPUTextureTransferInfo src = SDL_GPUTextureTransferInfo.calloc(stack)
.transfer_buffer(xfer)
.offset(0);
final SDL_GPUTextureRegion dst = SDL_GPUTextureRegion.calloc(stack)
.texture(tex)
.mip_level(level)
.x(x).y(y).z(z)
.w(w).h(h).d(d);
SDL_UploadToGPUTexture(copyPass, src, dst, false);
}
markTextureContentDefined(tex);
frameManager.recordUploadCommands(size, 1);
} finally {
returnTransferBufferThreadSafe(xfer, size);
}
markTextureContentDefined(gpuTexture);
frameManager.recordUploadCommands(size, 1);
returnTransferBuffer(xfer, size);
}
public boolean enqueueDeferredTextureUpload(ContextState st, ByteBuffer prepped, long texHandle, int x, int y, int w, int h, int level) {
@@ -1788,15 +1799,54 @@ public final class ResourceManager {
prepped.position(prevPos);
}
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
enqueueMappedRegion(tt, st, xfer, size, texHandle, level, x, y, w, h);
return true;
}
private void enqueueMappedRegion(TransferThread tt, ContextState st, long xfer, long size, long tex, int level, int x, int y, int w, int h) {
final long seq = TransferThread.nextSeq();
st.frameHighestEnqueuedSeq = seq;
lastTextureUploadSeq = seq;
markTextureContentDefined(texHandle);
tt.enqueue(TransferThread.TextureRegionUpload.acquire(xfer, texHandle, x, y, w, h, level, size, seq));
markTextureContentDefined(tex);
tt.enqueue(TransferThread.TextureRegionUpload.acquire(xfer, tex, x, y, w, h, level, size, seq));
tt.wake();
}
public TextureStagingRegion beginTextureStaging(int glId, long texHandle, boolean bgra, int level, int x, int y, int w, int h) {
final long size = (long) w * h * 4;
if (size <= 0 || size > Integer.MAX_VALUE) return null;
final long xfer = acquireTransferBufferThreadSafe(size);
if (xfer == 0) return null;
final ByteBuffer mapped = SDL_MapGPUTransferBuffer(device.getDevice(), xfer, true, getTransferBufferMapSize(size));
if (mapped == null || mapped.capacity() < size) {
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
returnTransferBufferThreadSafe(xfer, size);
return null;
}
mapped.clear().limit((int) size);
return new TextureStagingRegion(mapped.slice(), bgra, level, x, y, w, h, glId, texHandle, xfer, size);
}
public boolean commitTextureStagingDeferred(ContextState st, TextureStagingRegion s) {
final TransferThread tt = transferThread;
if (tt == null) return false;
SDL_UnmapGPUTransferBuffer(device.getDevice(), s.transferBuffer());
enqueueMappedRegion(tt, st, s.transferBuffer(), s.size(), s.texHandle(), s.level(), s.x(), s.y(), s.width(), s.height());
return true;
}
public void commitTextureStagingInline(long copyPass, TextureStagingRegion s) {
SDL_UnmapGPUTransferBuffer(device.getDevice(), s.transferBuffer());
uploadRegionFromTransfer(copyPass, s.transferBuffer(), s.size(), s.texHandle(), s.level(), s.x(), s.y(), 0, s.width(), s.height(), 1);
}
public void abandonTextureStaging(TextureStagingRegion s) {
SDL_UnmapGPUTransferBuffer(device.getDevice(), s.transferBuffer());
returnTransferBufferThreadSafe(s.transferBuffer(), s.size());
}
public record TextureStagingRegion(ByteBuffer buffer, boolean bgra, int level, int x, int y, int width, int height, int glId, long texHandle, long transferBuffer, long size) implements TextureStaging {}
public void downloadFromTexture(long commandBuffer, long gpuTexture, int x, int y, int w, int h, int level, ByteBuffer output) {
downloadFromTexture(commandBuffer, gpuTexture, x, y, 0, w, h, 1, level, output);
}
@@ -2134,7 +2184,7 @@ public final class ResourceManager {
final SDL_GPUTextureCreateInfo ci = SDL_GPUTextureCreateInfo.calloc(stack)
.type(SDL_GPU_TEXTURETYPE_2D)
.format(swapchainDepthStencilFormat)
.usage(SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)
.usage(swapchainDepthStencilUsage)
.width(width).height(height).layer_count_or_depth(1).num_levels(1);
swapchainDepthStencil = SDL_CreateGPUTexture(device.getDevice(), ci);
}
@@ -2149,6 +2199,27 @@ public final class ResourceManager {
return swapchainDepthStencil;
}
public long createLogicOpScratch(int sdlFormat, int width, int height) {
return createScratchTexture(sdlFormat, width, height, SDL_GPU_TEXTUREUSAGE_SAMPLER);
}
public long createScratchTexture(int sdlFormat, int width, int height, int usage) {
final long handle;
try (var stack = stackPush()) {
final SDL_GPUTextureCreateInfo ci = SDL_GPUTextureCreateInfo.calloc(stack)
.type(SDL_GPU_TEXTURETYPE_2D)
.format(sdlFormat)
.usage(usage)
.width(width).height(height).layer_count_or_depth(1).num_levels(1);
handle = SDL_CreateGPUTexture(device.getDevice(), ci);
}
if (handle == 0) {
throw new IllegalStateException("Failed to create scratch texture (" + width + "x" + height + " format " + sdlFormat + " usage " + usage + "): " + SDLError.SDL_GetError());
}
trackTextureHandle(handle);
return handle;
}
private static final int DUMMY_VBO_SIZE = 16 * 16;
public static final int ATTRIB_RING_BLOCKS = 2048 * Device.MAX_FRAMES_IN_FLIGHT;
public static final int ATTRIB_RING_CHUNK_BLOCKS = 64;
@@ -2308,6 +2379,7 @@ public final class ResourceManager {
case GL30.GL_RGB16I -> SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT;
case GL30.GL_RGB32I -> SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT;
case GL30.GL_R11F_G11F_B10F -> SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT;
case GL11.GL_RGB10_A2 -> SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM;
case GL31.GL_R8_SNORM -> SDL_GPU_TEXTUREFORMAT_R8_SNORM;
case GL31.GL_RG8_SNORM -> SDL_GPU_TEXTUREFORMAT_R8G8_SNORM;
case GL31.GL_RGB8_SNORM -> SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM; // RGB SNORM -> RGBA SNORM promotion
@@ -29,6 +29,8 @@ public final class TextureOps {
private final FrameManager frameManager;
private final ResourceManager resourceManager;
private final FBOClearTracker fboClearTracker;
private ByteBuffer readbackStaging;
private long readbackStagingAddress;
public TextureOps(Device device, FrameManager frameManager, ResourceManager resourceManager, FBOClearTracker fboClearTracker) {
this.device = device;
@@ -45,11 +47,11 @@ public final class TextureOps {
public boolean uploadTextureRegion(ContextState st, int glId, ResourceManager.TextureMeta meta, long texHandle, ByteBuffer src, int x, int y, int w, int h, int level, int srcFormat, int srcType) {
if (meta == null || level >= meta.levels()) return false;
if (src == null || texHandle == 0 || frameManager.getCommandBuffer() == 0) return false;
final boolean defer = st.deferUploads && !resourceManager.isFboAttachment(glId);
final boolean defer = resourceManager.shouldDeferTextureUpload(st, glId);
final long cp = defer ? 0L : frameManager.ensureCopyPass();
if (!defer && cp == 0) return false;
final ByteBuffer unpacked = PixelOps.applyUnpackPixelStore(src, w, h, srcFormat, srcType, st.pixelStore);
final ByteBuffer prepped = PixelOps.prepareUploadBuffer(srcFormat, meta.sdlFormat(), unpacked, w, h);
final ByteBuffer prepped = PixelOps.prepareUploadBuffer(srcFormat, srcType, meta.sdlFormat(), unpacked, w, h);
try {
final boolean handled = defer && resourceManager.enqueueDeferredTextureUpload(st, prepped, texHandle, x, y, w, h, level);
if (!handled) {
@@ -111,6 +113,35 @@ public final class TextureOps {
resourceManager.downloadFromTexture(cb, texHandle, x, y, w, h, level, output);
}
public long ensureReadbackStaging(long bytes) {
if (bytes > Integer.MAX_VALUE) throw new IllegalArgumentException("readback of " + bytes + " bytes exceeds the staging limit");
if (readbackStaging == null || readbackStaging.capacity() < bytes) {
if (readbackStaging != null) MemoryUtil.memFree(readbackStaging);
readbackStaging = MemoryUtil.memAlloc((int) Math.max(bytes, 64 * 1024));
readbackStagingAddress = MemoryUtil.memAddress(readbackStaging);
}
return readbackStagingAddress;
}
public ByteBuffer readbackStagingRegion(int offset, int size) {
readbackStaging.limit(offset + size);
readbackStaging.position(offset);
return readbackStaging;
}
public void readbackTextureToStaging(long texHandle, int x, int y, int w, int h, int level, int offset, int size) {
readbackTexture(texHandle, x, y, w, h, level, readbackStagingRegion(offset, size));
readbackStaging.clear();
}
public void shutdown() {
if (readbackStaging != null) {
MemoryUtil.memFree(readbackStaging);
readbackStaging = null;
readbackStagingAddress = 0L;
}
}
public void copyTexSubImageImpl(ContextState st, int destGlId, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
if (destGlId == 0) return;
final long destTex = resourceManager.getTextureHandle(destGlId);
@@ -130,8 +161,9 @@ public final class TextureOps {
if (srcTex == 0) return;
final int srcGlId = isDepthDest ? fbo.depthGlId : fbo.colorGlIds[fbo.readBufferIndex];
final int srcLevel = isDepthDest ? fbo.depthLevel : fbo.colorLevels[fbo.readBufferIndex];
final ResourceManager.TextureMeta srcMeta = srcGlId != 0 ? resourceManager.getTextureMeta(srcGlId) : null;
final long clip = CopyRectClip.clipCopyRect(x, y, xoffset, yoffset, width, height, srcMeta != null ? srcMeta.width() : fbo.width, srcMeta != null ? srcMeta.height() : fbo.height, levelWidth(destMeta, level), levelHeight(destMeta, level));
final long clip = CopyRectClip.clipCopyRect(x, y, xoffset, yoffset, width, height, srcMeta != null ? levelWidth(srcMeta, srcLevel) : fbo.width, srcMeta != null ? levelHeight(srcMeta, srcLevel) : fbo.height, levelWidth(destMeta, level), levelHeight(destMeta, level));
if (clip == CopyRectClip.EMPTY) return;
final int sx = CopyRectClip.srcX(clip);
final int sy = CopyRectClip.srcY(clip);
@@ -141,20 +173,20 @@ public final class TextureOps {
final int h = CopyRectClip.height(clip);
fboClearTracker.materializePendingClearForTexture(st, srcTex);
fboClearTracker.resolveDestinationForWrite(st, destTex, destMeta, level, dx, dy, 0, w, h);
fboClearTracker.resolveDestinationForWrite(st, destTex, destMeta, level, dx, dy, 0, w, h, true);
if (isDepthDest || (srcMeta != null && destMeta != null && srcMeta.sdlFormat() == destMeta.sdlFormat())) {
copyTexture(srcTex, sx, sy, destTex, level, dx, dy, w, h);
copyTexture(srcTex, srcLevel, sx, sy, destTex, level, dx, dy, w, h);
} else {
blitTexture(srcTex, sx, sy, w, h, destTex, level, dx, dy, w, h, GL11.GL_NEAREST);
blitTexture(srcTex, srcLevel, sx, sy, w, h, destTex, level, dx, dy, w, h, GL11.GL_NEAREST);
}
}
private static int levelWidth(ResourceManager.TextureMeta meta, int level) {
public static int levelWidth(ResourceManager.TextureMeta meta, int level) {
return meta != null ? PixelOps.mipLevelSize(meta.width(), level) : 0;
}
private static int levelHeight(ResourceManager.TextureMeta meta, int level) {
public static int levelHeight(ResourceManager.TextureMeta meta, int level) {
return meta != null ? PixelOps.mipLevelSize(meta.height(), level) : 0;
}
@@ -180,30 +212,36 @@ public final class TextureOps {
if (st.pendingSwapchainClear || st.pendingSwapchainDepthClear || st.pendingSwapchainStencilClear) {
frameManager.ensureFbo0RenderPass(frameManager.frame(), st);
}
fboClearTracker.resolveDestinationForWrite(st, destTex, destMeta, level, dx, dy, 0, w, h);
fboClearTracker.resolveDestinationForWrite(st, destTex, destMeta, level, dx, dy, 0, w, h, true);
blitTexture(srcTex, sx, srcFullH - sy - h, w, h, destTex, level, dx, dy, w, h, GL11.GL_NEAREST, SDL_FLIP_VERTICAL);
blitTexture(srcTex, 0, sx, srcFullH - sy - h, w, h, destTex, level, dx, dy, w, h, GL11.GL_NEAREST, SDL_FLIP_VERTICAL);
}
public static boolean canCopyInsteadOfBlit(ResourceManager.TextureMeta src, ResourceManager.TextureMeta dst, int srcW, int srcH, int dstW, int dstH) {
return src != null && dst != null && src.sdlFormat() == dst.sdlFormat() && srcW > 0 && srcH > 0 && srcW == dstW && srcH == dstH;
}
public void copyTexture(long srcTex, int srcX, int srcY, long dstTex, int dstLevel, int dstX, int dstY, int w, int h) {
public void copyTexture(long srcTex, int srcLevel, int srcX, int srcY, long dstTex, int dstLevel, int dstX, int dstY, int w, int h) {
final long cp = frameManager.ensureCopyPass();
if (cp == 0) return;
resourceManager.flushBatchedUploads(cp);
copyTexture(cp, srcTex, srcLevel, srcX, srcY, dstTex, dstLevel, dstX, dstY, w, h);
}
public static void copyTexture(long cp, long srcTex, int srcLevel, int srcX, int srcY, long dstTex, int dstLevel, int dstX, int dstY, int w, int h) {
if (cp == 0) return;
try (var stack = MemoryStack.stackPush()) {
final var src = SDL_GPUTextureLocation.calloc(stack).texture(srcTex).x(srcX).y(srcY);
final var src = SDL_GPUTextureLocation.calloc(stack).texture(srcTex).mip_level(srcLevel).x(srcX).y(srcY);
final var dst = SDL_GPUTextureLocation.calloc(stack).texture(dstTex).mip_level(dstLevel).x(dstX).y(dstY);
SDL_CopyGPUTextureToTexture(cp, src, dst, w, h, 1, false);
}
}
public void blitTexture(long srcTex, int srcX, int srcY, int srcW, int srcH, long dstTex, int dstLevel, int dstX, int dstY, int dstW, int dstH, int glFilter) {
blitTexture(srcTex, srcX, srcY, srcW, srcH, dstTex, dstLevel, dstX, dstY, dstW, dstH, glFilter, SDL_FLIP_NONE);
public void blitTexture(long srcTex, int srcLevel, int srcX, int srcY, int srcW, int srcH, long dstTex, int dstLevel, int dstX, int dstY, int dstW, int dstH, int glFilter) {
blitTexture(srcTex, srcLevel, srcX, srcY, srcW, srcH, dstTex, dstLevel, dstX, dstY, dstW, dstH, glFilter, SDL_FLIP_NONE);
}
public void blitTexture(long srcTex, int srcX, int srcY, int srcW, int srcH, long dstTex, int dstLevel, int dstX, int dstY, int dstW, int dstH, int glFilter, int flipMode) {
public void blitTexture(long srcTex, int srcLevel, int srcX, int srcY, int srcW, int srcH, long dstTex, int dstLevel, int dstX, int dstY, int dstW, int dstH, int glFilter, int flipMode) {
frameManager.endCopyPassIfActive();
frameManager.endRenderPassIfActive(FrameManager.PASS_END_COPY);
final long cb = frameManager.getCommandBuffer();
@@ -211,8 +249,8 @@ public final class TextureOps {
try (var stack = MemoryStack.stackPush()) {
final var info = SDL_GPUBlitInfo.calloc(stack);
info.source(s -> s.texture(srcTex).x(srcX).y(srcY).w(srcW).h(srcH));
info.destination(d -> d.texture(dstTex).mip_level(dstLevel).x(dstX).y(dstY).w(dstW).h(dstH));
info.source().texture(srcTex).mip_level(srcLevel).x(srcX).y(srcY).w(srcW).h(srcH);
info.destination().texture(dstTex).mip_level(dstLevel).x(dstX).y(dstY).w(dstW).h(dstH);
info.filter(glFilter == GL11.GL_LINEAR ? SDL_GPU_FILTER_LINEAR : SDL_GPU_FILTER_NEAREST);
info.flip_mode(flipMode);
frameManager.noteBlit();
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.resource.FormatMap;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.TextureSamplerState;
import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpVariant;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
@@ -112,7 +113,9 @@ public final class SamplerBinder {
}
private void bindStageSamplers(long renderPass, ContextState st, ShaderManager.ProgramObject prog, long fallbackTexture, boolean fragment) {
final int raw = fragment ? prog.fragmentResources.numSamplers() : prog.vertexResources.numSamplers();
final LogicOpVariant variant = fragment ? st.activeLogicOpVariant : null;
final int raw = variant != null ? variant.numSamplers : fragment ? prog.fragmentResources.numSamplers() : prog.vertexResources.numSamplers();
final int baseCount = variant != null ? variant.baseSamplers : raw;
if (raw > ContextState.MAX_SAMPLERS) {
throw new IllegalStateException("program " + st.boundProgram + " has " + raw + " " + (fragment ? "fragment" : "vertex") + " samplers; MAX=" + ContextState.MAX_SAMPLERS);
}
@@ -123,9 +126,22 @@ public final class SamplerBinder {
final long[] lastTex = fragment ? st.lastFragSamplerTex : st.lastVertSamplerTex;
final long[] lastSmp = fragment ? st.lastFragSamplerSmp : st.lastVertSamplerSmp;
final int lastProg = fragment ? st.lastFragSamplerProgram : st.lastVertSamplerProgram;
final boolean programChanged = st.boundProgram != lastProg;
final boolean programChanged = st.boundProgram != lastProg || fragment && variant != st.lastFragSamplerVariant;
boolean anyChanged = programChanged;
for (int i = 0; i < raw; i++) {
if (i >= baseCount) {
final long dst = st.logicOpScratch[variant.dstLocation[i - baseCount]];
final long dstSampler = resourceManager.getOrCreateDefaultSampler();
if (programChanged || dst != lastTex[i] || dstSampler != lastSmp[i]) {
anyChanged = true;
final long elemAddr = bindingsBase + (long) i * SDL_GPUTextureSamplerBinding.SIZEOF;
MemoryAccess.putAddress(elemAddr + SDL_GPUTextureSamplerBinding.TEXTURE, dst);
MemoryAccess.putAddress(elemAddr + SDL_GPUTextureSamplerBinding.SAMPLER, dstSampler);
lastTex[i] = dst;
lastSmp[i] = dstSampler;
}
continue;
}
final int glUnit = samplerUnits[i];
final int glTexId = (glUnit >= 0 && glUnit < st.boundTextures.length) ? st.boundTextures[glUnit] : 0;
long texHandle = (glTexId != 0) ? resourceManager.getTextureHandle(glTexId) : 0;
@@ -149,6 +165,7 @@ public final class SamplerBinder {
if (fragment) {
SDL_BindGPUFragmentSamplers(renderPass, 0, bindings);
st.lastFragSamplerProgram = st.boundProgram;
st.lastFragSamplerVariant = variant;
} else {
SDL_BindGPUVertexSamplers(renderPass, 0, bindings);
st.lastVertSamplerProgram = st.boundProgram;
@@ -173,6 +190,7 @@ public final class SamplerBinder {
if (fragment) {
SDL_BindGPUFragmentSamplers(renderPass, 0, bindings);
st.lastFragSamplerProgram = 0;
st.lastFragSamplerVariant = null;
} else {
SDL_BindGPUVertexSamplers(renderPass, 0, bindings);
st.lastVertSamplerProgram = 0;
@@ -0,0 +1,86 @@
package com.gtnewhorizons.angelica.sdlgpu.shader;
import static org.lwjgl.sdl.SDLGPU.*;
public final class LogicOpFormats {
public static final int OP_COPY = 12;
private static final int CLASS_RGBA8 = 1;
private static final int CLASS_R8 = 2;
private static final int CLASS_RG8 = 3;
private static final int CLASS_RGBA16 = 4;
private static final int CLASS_R16 = 5;
private static final int CLASS_RG16 = 6;
private static final int CLASS_RGB10A2 = 7;
private static final int CLASS_R5G6B5 = 8;
private static final int CLASS_RGB5A1 = 9;
private static final int CLASS_RGBA4 = 10;
private static final int CLASS_A8 = 11;
private static final int[] MESA_OP = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
private static final byte[][] BITS = {
{ 0, 0, 0, 0 },
{ 8, 8, 8, 8 },
{ 8, 0, 0, 0 },
{ 8, 8, 0, 0 },
{ 16, 16, 16, 16 },
{ 16, 0, 0, 0 },
{ 16, 16, 0, 0 },
{ 10, 10, 10, 2 },
{ 5, 6, 5, 0 },
{ 5, 5, 5, 1 },
{ 4, 4, 4, 4 },
{ 0, 0, 0, 8 },
};
private LogicOpFormats() {}
public static int mesaOp(int glOpcode) {
return MESA_OP[glOpcode & 0xF];
}
public static int classOf(int sdlTextureFormat) {
return switch (sdlTextureFormat) {
case SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM -> CLASS_RGBA8;
case SDL_GPU_TEXTUREFORMAT_R8_UNORM -> CLASS_R8;
case SDL_GPU_TEXTUREFORMAT_R8G8_UNORM -> CLASS_RG8;
case SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM -> CLASS_RGBA16;
case SDL_GPU_TEXTUREFORMAT_R16_UNORM -> CLASS_R16;
case SDL_GPU_TEXTUREFORMAT_R16G16_UNORM -> CLASS_RG16;
case SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM -> CLASS_RGB10A2;
case SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM -> CLASS_R5G6B5;
case SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM -> CLASS_RGB5A1;
case SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM -> CLASS_RGBA4;
case SDL_GPU_TEXTUREFORMAT_A8_UNORM -> CLASS_A8;
case SDL_GPU_TEXTUREFORMAT_R8_SNORM, SDL_GPU_TEXTUREFORMAT_R8G8_SNORM, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM,
SDL_GPU_TEXTUREFORMAT_R16_SNORM, SDL_GPU_TEXTUREFORMAT_R16G16_SNORM, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM,
SDL_GPU_TEXTUREFORMAT_R8_UINT, SDL_GPU_TEXTUREFORMAT_R8G8_UINT, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT,
SDL_GPU_TEXTUREFORMAT_R16_UINT, SDL_GPU_TEXTUREFORMAT_R16G16_UINT, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT,
SDL_GPU_TEXTUREFORMAT_R32_UINT, SDL_GPU_TEXTUREFORMAT_R32G32_UINT, SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT,
SDL_GPU_TEXTUREFORMAT_R8_INT, SDL_GPU_TEXTUREFORMAT_R8G8_INT, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT,
SDL_GPU_TEXTUREFORMAT_R16_INT, SDL_GPU_TEXTUREFORMAT_R16G16_INT, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT,
SDL_GPU_TEXTUREFORMAT_R32_INT, SDL_GPU_TEXTUREFORMAT_R32G32_INT, SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT ->
throw new UnsupportedOperationException("glLogicOp on snorm/integer color attachment format " + sdlTextureFormat + " is not emulated");
default -> 0;
};
}
public static float channelMax(int cls, int channel) {
final int bits = BITS[cls][channel];
return bits == 0 ? 0.0f : (float) ((1 << bits) - 1);
}
public static long withClass(long key, int location, int cls) {
return key | ((long) cls << (4 + 4 * location));
}
public static int classAt(long key, int location) {
return (int) (key >>> (4 + 4 * location)) & 0xF;
}
public static int opOf(long key) {
return (int) (key & 0xF);
}
}
@@ -0,0 +1,189 @@
package com.gtnewhorizons.angelica.sdlgpu.shader;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
import org.antlr.v4.runtime.Token;
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
import org.taumc.glsl.grammar.GLSLParser;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.applyEdits;
public final class LogicOpLowering {
public static final int MAX_LOCATIONS = 8;
public static final String DST_SAMPLER_PREFIX = "angelica_LogicOpDst";
private static final String MAIN_RENAME = "angelica_logicOpMain";
private static final String SNIPPET = ShaderLoader.getShaderSource("angelica:sdlgpu/logic_op.glsl");
private record Output(String name, int location, int arrayLen, int vecSize) {}
private LogicOpLowering() {}
public static String lower(String preprocessedFragmentSource, long key, int program) {
final GLSLParser.Translation_unitContext root = GlslTransformUtils.parseFullQuiet(preprocessedFragmentSource);
final List<Edit> edits = new ArrayList<>();
final List<Output> outputs = new ArrayList<>();
boolean renamed = false;
for (GLSLParser.External_declarationContext ext : root.external_declaration()) {
final GLSLParser.Function_definitionContext fn = ext.function_definition();
if (fn != null) {
renamed |= renameMain(fn.function_prototype(), edits);
continue;
}
final GLSLParser.DeclarationContext decl = ext.declaration();
if (decl == null) continue;
if (decl.function_prototype() != null) {
renameMain(decl.function_prototype(), edits);
continue;
}
collectOutputs(decl, outputs, program);
}
if (!renamed) throw new IllegalStateException("glLogicOp lowering: program " + program + " fragment shader has no main()");
final String[] exprByLoc = new String[MAX_LOCATIONS];
final int[] sizeByLoc = new int[MAX_LOCATIONS];
resolveLocations(outputs, exprByLoc, sizeByLoc, program);
final int op = LogicOpFormats.opOf(key);
final StringBuilder sb = new StringBuilder(applyEdits(preprocessedFragmentSource, edits));
sb.append("\n#define ANGELICA_LOGIC_OP ").append(op).append("u\n");
sb.append(SNIPPET).append('\n');
for (int loc = 0; loc < MAX_LOCATIONS; loc++) {
if (LogicOpFormats.classAt(key, loc) == 0) continue;
if (exprByLoc[loc] == null) continue;
if (sizeByLoc[loc] == 0) throw new IllegalStateException("glLogicOp lowering: program " + program + " output at location " + loc + " is not a float vector");
sb.append("uniform sampler2D ").append(DST_SAMPLER_PREFIX).append(loc).append(";\n");
}
sb.append("void main() {\n ").append(MAIN_RENAME).append("();\n");
for (int loc = 0; loc < MAX_LOCATIONS; loc++) {
final int cls = LogicOpFormats.classAt(key, loc);
if (cls == 0 || exprByLoc[loc] == null) continue;
final String out = exprByLoc[loc];
final int n = sizeByLoc[loc];
sb.append(" ").append(out).append(" = ");
sb.append("angelica_logicOp(").append(widen(out, n)).append(", ").append(DST_SAMPLER_PREFIX).append(loc).append(", vec4(");
for (int c = 0; c < 4; c++) {
if (c > 0) sb.append(", ");
sb.append(LogicOpFormats.channelMax(cls, c));
}
sb.append("))").append(narrow(n)).append(";\n");
}
sb.append("}\n");
return sb.toString();
}
private static boolean renameMain(GLSLParser.Function_prototypeContext proto, List<Edit> edits) {
if (proto == null || proto.IDENTIFIER() == null || !"main".equals(proto.IDENTIFIER().getText())) return false;
final Token t = proto.IDENTIFIER().getSymbol();
edits.add(new Edit(t.getStartIndex(), t.getStopIndex(), MAIN_RENAME));
return true;
}
private static void collectOutputs(GLSLParser.DeclarationContext decl, List<Output> outputs, int program) {
final GLSLParser.Init_declarator_listContext idl = decl.init_declarator_list();
if (idl == null) return;
final GLSLParser.Single_declarationContext single = idl.single_declaration();
if (single == null || single.fully_specified_type() == null) return;
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
if (fst.type_qualifier() == null) return;
boolean isOut = false;
int location = -1;
int index = 0;
for (GLSLParser.Single_type_qualifierContext stq : fst.type_qualifier().single_type_qualifier()) {
if (stq.storage_qualifier() != null && stq.storage_qualifier().OUT() != null) isOut = true;
final GLSLParser.Layout_qualifierContext lq = stq.layout_qualifier();
if (lq == null) continue;
for (GLSLParser.Layout_qualifier_idContext id : lq.layout_qualifier_id_list().layout_qualifier_id()) {
if (id.IDENTIFIER() == null || id.constant_expression() == null) continue;
final String qname = id.IDENTIFIER().getText();
if ("location".equals(qname)) location = parseInt(id.constant_expression().getText(), program);
else if ("index".equals(qname)) index = parseInt(id.constant_expression().getText(), program);
}
}
if (!isOut || index != 0) return;
final GLSLParser.Type_specifierContext ts = fst.type_specifier();
final int vecSize = floatVecSize(ts.type_specifier_nonarray().getText());
final int typeArrayLen = ts.array_specifier() != null ? arrayLen(ts.array_specifier(), program) : 0;
final List<GLSLParser.Typeless_declarationContext> names = new ArrayList<>();
if (single.typeless_declaration() != null) names.add(single.typeless_declaration());
names.addAll(idl.typeless_declaration());
int nextLocation = location;
for (GLSLParser.Typeless_declarationContext td : names) {
final int len = td.array_specifier() != null ? arrayLen(td.array_specifier(), program) : typeArrayLen;
outputs.add(new Output(td.IDENTIFIER().getText(), nextLocation, len, vecSize));
if (nextLocation >= 0) nextLocation += Math.max(len, 1);
}
}
private static void resolveLocations(List<Output> outputs, String[] exprByLoc, int[] sizeByLoc, int program) {
int unlocated = 0;
for (Output o : outputs) if (o.location() < 0) unlocated++;
if (unlocated > 0 && outputs.size() > 1) {
throw new IllegalStateException("glLogicOp lowering: program " + program + " declares " + outputs.size() + " fragment outputs without explicit locations");
}
for (Output o : outputs) {
final int base = Math.max(o.location(), 0);
final int count = Math.max(o.arrayLen(), 1);
for (int k = 0; k < count; k++) {
final int loc = base + k;
if (loc >= MAX_LOCATIONS) throw new IllegalStateException("glLogicOp lowering: program " + program + " output " + o.name() + " exceeds location " + (MAX_LOCATIONS - 1));
if (exprByLoc[loc] != null) throw new IllegalStateException("glLogicOp lowering: program " + program + " has two outputs at location " + loc);
exprByLoc[loc] = o.arrayLen() > 0 ? o.name() + "[" + k + "]" : o.name();
sizeByLoc[loc] = o.vecSize();
}
}
}
private static int arrayLen(GLSLParser.Array_specifierContext spec, int program) {
final List<GLSLParser.DimensionContext> dims = spec.dimension();
if (dims.size() != 1 || dims.get(0).constant_expression() == null) {
throw new IllegalStateException("glLogicOp lowering: program " + program + " has an unsized or multidimensional fragment output array");
}
return parseInt(dims.get(0).constant_expression().getText(), program);
}
private static int parseInt(String text, int program) {
final String t = text.endsWith("u") || text.endsWith("U") ? text.substring(0, text.length() - 1) : text;
try {
return Integer.decode(t);
} catch (NumberFormatException e) {
throw new IllegalStateException("glLogicOp lowering: program " + program + " uses non-literal constant '" + text + "' in a fragment output declaration");
}
}
private static int floatVecSize(String type) {
return switch (type) {
case "float" -> 1;
case "vec2" -> 2;
case "vec3" -> 3;
case "vec4" -> 4;
default -> 0;
};
}
private static String widen(String expr, int n) {
return switch (n) {
case 1 -> "vec4(" + expr + ", 0.0, 0.0, 0.0)";
case 2 -> "vec4(" + expr + ", 0.0, 0.0)";
case 3 -> "vec4(" + expr + ", 0.0)";
default -> expr;
};
}
private static String narrow(int n) {
return switch (n) {
case 1 -> ".x";
case 2 -> ".xy";
case 3 -> ".xyz";
default -> "";
};
}
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.sdlgpu.shader;
public final class LogicOpVariant {
public long sdlShader;
public int numSamplers;
public int baseSamplers;
public byte[] dstLocation;
}
@@ -4,10 +4,10 @@ import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.PerFrameUniformBlock;
import com.gtnewhorizons.angelica.glsm.hooks.ShaderWorkSubmitter;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
import com.gtnewhorizons.angelica.glsm.threading.AngelicaWorkers;
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.taumc.glsl.grammar.GLSLParser;
@@ -146,6 +146,7 @@ public final class ShaderManager {
public void shaderSource(int shader, CharSequence source) {
final ShaderObject obj = shaderObjects.get(shader);
if (obj == null) return;
discardSpirvFuture(obj);
String raw = source.toString();
if (!raw.isEmpty() && raw.charAt(raw.length() - 1) == '\0') {
@@ -158,7 +159,7 @@ public final class ShaderManager {
obj.reflection = hit.reflection();
obj.graphicsBindingMap = hit.graphicsBindingMap();
obj.boolUniforms = hit.boolUniforms();
obj.spirvFuture = CompletableFuture.completedFuture(new SpirvCompiler.Result(hit.spirv(), null, null));
obj.spirvFuture = CompletableFuture.completedFuture(new AsyncCompile(new SpirvCompiler.Result(hit.spirv(), null, null), hit.graphicsBindingMap(), hit.reflection()));
return;
}
@@ -186,22 +187,16 @@ public final class ShaderManager {
obj.boolUniforms = transformed.boolUniforms();
obj.source = transformed.source();
final ShaderWorkSubmitter submitter = GLSMHooks.shaderWorkSubmitter;
if (submitter != null) {
final int shaderKind = shaderKindFor(obj);
final int glType = obj.type;
final String finalSrc = obj.source;
obj.spirvFuture = submitter.submit(() -> {
final SpirvCompiler.Result r = SpirvCompiler.compile(finalSrc, shaderKind, "shader" + shader, SpirvCompiler.Options.vulkanForced460Core());
if (r.spirv() != null && (glType == GL20.GL_VERTEX_SHADER || glType == GL20.GL_FRAGMENT_SHADER)) {
obj.graphicsBindingMap = remapSpirvForSDLGPU(r.spirv(), glType);
obj.reflection = reflectStage(r.spirv(), glType == GL20.GL_VERTEX_SHADER);
}
return r;
});
} else {
obj.spirvFuture = null;
}
final int shaderKind = shaderKindFor(obj);
final int glType = obj.type;
final String finalSrc = obj.source;
obj.spirvFuture = AngelicaWorkers.submit(() -> {
final SpirvCompiler.Result r = SpirvCompiler.compile(finalSrc, shaderKind, "shader" + shader, SpirvCompiler.Options.vulkanForced460Core());
if (r.spirv() != null && (glType == GL20.GL_VERTEX_SHADER || glType == GL20.GL_FRAGMENT_SHADER)) {
return new AsyncCompile(r, remapSpirvForSDLGPU(r.spirv(), glType), reflectStage(r.spirv(), glType == GL20.GL_VERTEX_SHADER));
}
return new AsyncCompile(r, null, null);
});
}
private static int shaderKindFor(ShaderObject obj) {
@@ -312,8 +307,11 @@ public final class ShaderManager {
final SpirvCompiler.Result r;
final boolean alreadyRemapped;
if (obj.spirvFuture != null) {
r = obj.spirvFuture.join();
final AsyncCompile compiled = obj.spirvFuture.join();
obj.spirvFuture = null;
r = compiled.result();
if (compiled.graphicsBindingMap() != null) obj.graphicsBindingMap = compiled.graphicsBindingMap();
if (compiled.reflection() != null) obj.reflection = compiled.reflection();
alreadyRemapped = true;
} else {
r = SpirvCompiler.compile(obj.source, shaderKindFor(obj), "shader" + shader, SpirvCompiler.Options.vulkanForced460Core());
@@ -325,6 +323,7 @@ public final class ShaderManager {
LOG.error("Shader {} compilation failed: {} (dump={})", shader, obj.infoLog, r.dumpPath());
return;
}
if (obj.spirv != null && obj.spirv != r.spirv()) memFree(obj.spirv);
obj.spirv = r.spirv();
if (!alreadyRemapped && (obj.type == GL20.GL_VERTEX_SHADER || obj.type == GL20.GL_FRAGMENT_SHADER)) {
obj.graphicsBindingMap = remapSpirvForSDLGPU(obj.spirv, obj.type);
@@ -342,6 +341,7 @@ public final class ShaderManager {
return;
}
shaderObjects.remove(shader);
discardSpirvFuture(obj);
if (obj.spirv != null) memFree(obj.spirv);
}
@@ -352,10 +352,24 @@ public final class ShaderManager {
if (obj.attachCount > 0) obj.attachCount--;
if (obj.deletePending && obj.attachCount == 0) {
shaderObjects.remove(shader);
discardSpirvFuture(obj);
if (obj.spirv != null) memFree(obj.spirv);
}
}
private static void discardSpirvFuture(ShaderObject obj) {
final CompletableFuture<AsyncCompile> future = obj.spirvFuture;
if (future == null) return;
obj.spirvFuture = null;
future.thenAccept(ShaderManager::freeSpirvResult);
}
private static void freeSpirvResult(AsyncCompile compiled) {
if (compiled != null && compiled.result().spirv() != null) memFree(compiled.result().spirv());
}
private record AsyncCompile(SpirvCompiler.Result result, GraphicsBindingMap graphicsBindingMap, StageReflection reflection) {}
public boolean isCompiled(int shader) {
final ShaderObject obj = shaderObjects.get(shader);
return obj != null && obj.compiled;
@@ -431,11 +445,12 @@ public final class ShaderManager {
prog.vertexSpirv = copyBuffer(vs.spirv);
prog.fragmentSpirv = copyBuffer(fs.spirv);
prog.vertexSource = vs.source;
prog.fragmentSource = fs.source;
prog.vertexReflection = vs.reflection;
prog.maxFragOutputLocation = fs.reflection.maxOutputLocation();
dumpLinkedSource(program, vs.source, fs.source);
applyVaryingMatch(prog, vs.reflection, fs.reflection);
applyVaryingMatch(prog.fragmentSpirv, vs.reflection, fs.reflection);
applyAttribLocationsAndInputMask(prog, vs.reflection);
prog.vertexInputSetId = internVertexInputSet(prog.vertexInputMask, prog.vertexInputVecSize, prog.vertexInputBaseType);
@@ -615,11 +630,11 @@ public final class ShaderManager {
return loc;
}
private static void applyVaryingMatch(ProgramObject prog, StageReflection vs, StageReflection fs) {
if (vs.vsOutputs().isEmpty() || fs.fsInputs().isEmpty() || prog.fragmentSpirv == null) return;
private static void applyVaryingMatch(ByteBuffer fragmentSpirv, StageReflection vs, StageReflection fs) {
if (vs.vsOutputs().isEmpty() || fs.fsInputs().isEmpty() || fragmentSpirv == null) return;
final HashMap<String, Integer> vsOutLocs = new HashMap<>(vs.vsOutputs().size() * 2);
for (VsOutput o : vs.vsOutputs()) vsOutLocs.put(o.name(), o.originalLocation());
final IntBuffer fsBuf = prog.fragmentSpirv.asIntBuffer();
final IntBuffer fsBuf = fragmentSpirv.asIntBuffer();
for (FsInput fi : fs.fsInputs()) {
final Integer vsLoc = vsOutLocs.get(fi.name());
if (vsLoc == null || vsLoc == fi.originalLocation()) continue;
@@ -785,6 +800,79 @@ public final class ShaderManager {
}
}
public LogicOpVariant getOrBuildLogicOpVariant(int program, long key) {
final ProgramObject prog = programObjects.get(program);
if (prog == null || !prog.linked || prog.fragmentSource.isEmpty()) {
throw new IllegalStateException("glLogicOp variant: program " + program + " is not a linked graphics program");
}
final LogicOpVariant hit = prog.logicOpVariants.get(key);
if (hit != null) return hit;
final LogicOpVariant built = buildLogicOpVariant(program, prog, key);
prog.logicOpVariants.put(key, built);
return built;
}
private LogicOpVariant buildLogicOpVariant(int program, ProgramObject prog, long key) {
final String preprocessed = SpirvCompiler.preprocess(prog.fragmentSource, Shaderc.shaderc_fragment_shader, "logicop" + program, SpirvCompiler.Options.vulkanForced460Core());
if (preprocessed == null) throw new IllegalStateException("glLogicOp variant: program " + program + " fragment preprocessing failed");
final String src = LogicOpLowering.lower(preprocessed, key, program);
if (SystemProperties.dumpShaders()) dumpLogicOpSource(program, key, src);
final SpirvCompiler.Result r = SpirvCompiler.compile(src, Shaderc.shaderc_fragment_shader, "logicop" + program, SpirvCompiler.Options.vulkanForced460Core());
if (r.spirv() == null) throw new IllegalStateException("glLogicOp variant: program " + program + " failed to compile: " + r.error());
try {
final GraphicsBindingMap bindingMap = remapSpirvForSDLGPU(r.spirv(), GL20.GL_FRAGMENT_SHADER);
final StageReflection refl = reflectStage(r.spirv(), false);
final ResourceCounts base = prog.fragmentResources;
final ResourceCounts counts = refl.counts();
if (counts.numUBOs() != base.numUBOs() || counts.numStorageBuffers() != base.numStorageBuffers() || counts.numStorageTextures() != base.numStorageTextures()
|| !Arrays.equals(bindingMap.roStorageTextureGlSlots(), prog.fragmentGraphicsBindingMap.roStorageTextureGlSlots())
|| !Arrays.equals(bindingMap.roSsboGlSlots(), prog.fragmentGraphicsBindingMap.roSsboGlSlots())) {
throw new IllegalStateException("glLogicOp variant: program " + program + " variant resources " + counts + " differ from base " + base);
}
final List<String> names = refl.samplerNames();
final List<String> baseNames = prog.fragmentSamplerNames;
final int extra = names.size() - baseNames.size();
if (extra < 0 || !names.subList(0, baseNames.size()).equals(baseNames)) {
throw new IllegalStateException("glLogicOp variant: program " + program + " sampler order " + names + " does not extend base " + baseNames);
}
final byte[] dstLocation = new byte[extra];
int prev = -1;
for (int i = 0; i < extra; i++) {
final String name = names.get(baseNames.size() + i);
if (!name.startsWith(LogicOpLowering.DST_SAMPLER_PREFIX)) {
throw new IllegalStateException("glLogicOp variant: program " + program + " unexpected appended sampler " + name);
}
final int loc = Integer.parseInt(name.substring(LogicOpLowering.DST_SAMPLER_PREFIX.length()));
if (loc <= prev || LogicOpFormats.classAt(key, loc) == 0) {
throw new IllegalStateException("glLogicOp variant: program " + program + " appended samplers " + names.subList(baseNames.size(), names.size()) + " are not in location order");
}
prev = loc;
dstLocation[i] = (byte) loc;
}
applyVaryingMatch(r.spirv(), prog.vertexReflection, refl);
final long shader = createSDLShader(r.spirv(), SDL_GPU_SHADERSTAGE_FRAGMENT, counts.numSamplers(), counts.numUBOs(), counts.numStorageBuffers(), counts.numStorageTextures());
if (shader == 0) throw new IllegalStateException("glLogicOp variant: program " + program + " shader creation failed: " + SDLError.SDL_GetError());
final LogicOpVariant v = new LogicOpVariant();
v.sdlShader = shader;
v.numSamplers = names.size();
v.baseSamplers = baseNames.size();
v.dstLocation = dstLocation;
return v;
} finally {
memFree(r.spirv());
}
}
private static void dumpLogicOpSource(int program, long key, String src) {
try {
final Path dir = Path.of("patched_shaders");
Files.createDirectories(dir);
Files.writeString(dir.resolve("logicop_" + program + "_" + Long.toHexString(key) + ".fsh"), src);
} catch (IOException e) {
LOG.warn("Failed to dump glLogicOp variant for program {}: {}", program, e.toString());
}
}
private static boolean linkDumpDirCleared;
private static void dumpLinkedSource(int program, String vertexSrc, String fragmentSrc) {
@@ -839,6 +927,10 @@ public final class ShaderManager {
}
prog.vertexVariants.clear();
prog.vertexVariantFailed.clear();
for (LogicOpVariant v : prog.logicOpVariants.values()) {
if (v.sdlShader != 0) SDL_ReleaseGPUShader(dev, v.sdlShader);
}
prog.logicOpVariants.clear();
if (prog.sdlVertexShader != 0) { SDL_ReleaseGPUShader(dev, prog.sdlVertexShader); prog.sdlVertexShader = 0; }
if (prog.sdlFragmentShader != 0) { SDL_ReleaseGPUShader(dev, prog.sdlFragmentShader); prog.sdlFragmentShader = 0; }
if (prog.sdlComputePipeline != 0) { SDL_ReleaseGPUComputePipeline(dev, prog.sdlComputePipeline); prog.sdlComputePipeline = 0; }
@@ -1295,6 +1387,7 @@ public final class ShaderManager {
}
programObjects.clear();
for (ShaderObject obj : shaderObjects.values()) {
discardSpirvFuture(obj);
if (obj.spirv != null) memFree(obj.spirv);
}
shaderObjects.clear();
@@ -1308,7 +1401,7 @@ public final class ShaderManager {
public String infoLog = "";
public boolean deletePending;
public int attachCount;
public CompletableFuture<SpirvCompiler.Result> spirvFuture;
public CompletableFuture<AsyncCompile> spirvFuture;
public StageReflection reflection = StageReflection.EMPTY;
public GraphicsBindingMap graphicsBindingMap = GraphicsBindingMap.EMPTY;
public Set<String> boolUniforms = Set.of();
@@ -1745,6 +1838,8 @@ public final class ShaderManager {
public StageReflection vertexReflection = StageReflection.EMPTY;
public final Long2ObjectOpenHashMap<VertexVariant> vertexVariants = new Long2ObjectOpenHashMap<>();
public final LongOpenHashSet vertexVariantFailed = new LongOpenHashSet();
public String fragmentSource = "";
public final Long2ObjectOpenHashMap<LogicOpVariant> logicOpVariants = new Long2ObjectOpenHashMap<>();
public final Object2IntOpenHashMap<String> attribLocationBindings = newLocMap();
public final Object2IntOpenHashMap<String> resolvedAttribLocations = newLocMap();
public final Object2IntOpenHashMap<String> nameToLocation = newLocMap();
@@ -0,0 +1,10 @@
vec4 angelica_logicOp(vec4 src, sampler2D dstTex, vec4 maxv) {
uvec4 s = uvec4(roundEven(clamp(src, 0.0, 1.0) * maxv));
uvec4 d = uvec4(roundEven(clamp(texelFetch(dstTex, ivec2(gl_FragCoord.xy), 0), 0.0, 1.0) * maxv));
uvec4 t0 = uvec4(0u - (ANGELICA_LOGIC_OP & 1u));
uvec4 t1 = uvec4(0u - ((ANGELICA_LOGIC_OP >> 1u) & 1u));
uvec4 t2 = uvec4(0u - ((ANGELICA_LOGIC_OP >> 2u) & 1u));
uvec4 t3 = uvec4(0u - ((ANGELICA_LOGIC_OP >> 3u) & 1u));
uvec4 r = ((~s & ~d & t0) | (~s & d & t1) | (s & ~d & t2) | (s & d & t3)) & uvec4(maxv);
return mix(src, vec4(r) / max(maxv, vec4(1.0)), greaterThan(maxv, vec4(0.0)));
}
@@ -0,0 +1,4 @@
#version 460 core
uniform sampler2D depthSampler;
layout(location = 0) out vec4 depthOut;
void main() { depthOut = vec4(texelFetch(depthSampler, ivec2(gl_FragCoord.xy), 0).r, 0.0, 0.0, 1.0); }
@@ -0,0 +1,4 @@
#version 460 core
uniform vec4 bitValue;
layout(location = 0) out vec4 stencilOut;
void main() { stencilOut = bitValue; }
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Disabled;
@@ -29,6 +30,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class GlsmSdlAttachmentClearTest {
private static final int SIZE = GlsmSdlHeadlessRig.SIZE;
private static final int RED = 0xFFFF0000;
private static final int BLACK = 0xFF000000;
@BeforeAll
static void boot() {
@@ -570,4 +573,31 @@ class GlsmSdlAttachmentClearTest {
GLStateManager.glDeleteTextures(tex);
GlsmSdlHeadlessRig.bindTarget();
}
@Test
void stencilWrittenLastFramePersists() {
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.writeStencilOneInBottomHalf();
GlsmSdlHeadlessRig.endFrame();
GlsmSdlHeadlessRig.beginFrameAndReset();
GlsmSdlHeadlessRig.drawRedWhereStencilIsOne();
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, 4, RED, "stencil written last frame");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE - 5, BLACK, "outside last frame's stencil");
}
@Test
void aSingleAspectClearLeavesTheTextureUndefined() {
final int fbo = GlsmSdlHeadlessRig.createDepthStencilFbo();
GLStateManager.glDepthMask(true);
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT);
final ResourceManager resourceManager = Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
final long depthTexture = resourceManager.getFbo(fbo).depthTexture;
GlsmSdlHeadlessRig.endFrame();
assertFalse(resourceManager.isTextureContentDefined(depthTexture), "the stencil aspect was never written, so the first pass must still clear it");
}
}
@@ -19,6 +19,7 @@ import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -155,32 +156,13 @@ class GlsmSdlCopyBlitTest {
assertRow(pixels, SIZE - 1, SENTINEL, "last out-of-range row");
}
private static int createSecondaryFbo() {
final int color = GLStateManager.glGenTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, color);
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, GL11.GL_RGBA8, SIZE, SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
final int depth = GLStateManager.glGenRenderbuffers();
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depth);
GLStateManager.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_DEPTH24_STENCIL8, SIZE, SIZE);
final int fbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, color, 0);
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, depth);
return fbo;
}
@Test
void blitFramebufferWithColorAndDepthBitsMovesColor() {
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.clearTo(0.0f, 1.0f, 0.0f, 1.0f);
GlsmSdlHeadlessRig.solidQuad(0.0f, 1.0f, 0.0f);
final int destination = createSecondaryFbo();
final int destination = GlsmSdlHeadlessRig.createDepthStencilFbo();
GLStateManager.glViewport(0, 0, SIZE, SIZE);
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
@@ -197,6 +179,44 @@ class GlsmSdlCopyBlitTest {
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 4, SIZE - 5, GREEN, "color aspect of a color+depth blit");
}
private static void blitLevelOneInto(int dstSize, int dstLevel) {
final int source = GlsmSdlHeadlessRig.createSolidMipTexture(SIZE, 1, RED);
uploadLevel(1, SIZE / 2, GREEN);
final int destination = GlsmSdlHeadlessRig.createSolidMipTexture(dstSize, dstLevel, BLUE);
final int readFbo = GlsmSdlHeadlessRig.fboWithColor(source, 1);
final int drawFbo = GlsmSdlHeadlessRig.fboWithColor(destination, dstLevel);
final int dstExtent = Math.max(1, dstSize >> dstLevel);
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, readFbo);
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, drawFbo);
GLStateManager.glBlitFramebuffer(0, 0, SIZE / 2, SIZE / 2, 0, 0, dstExtent, dstExtent, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST);
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTextureLevel(destination, dstLevel), GREEN, "destination level " + dstLevel + " after blitting source level 1");
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTextureLevel(source, 0), RED, "source level 0 is not the blit source");
if (dstLevel != 0) GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTextureLevel(destination, 0), BLUE, "destination level 0 is not the blit target");
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glDeleteFramebuffers(readFbo);
GLStateManager.glDeleteFramebuffers(drawFbo);
GLStateManager.glDeleteTextures(source);
GLStateManager.glDeleteTextures(destination);
}
@Test
void blitFramebufferReadsTheSourceAttachmentLevel() {
blitLevelOneInto(SIZE / 2, 0);
}
@Test
void scaledBlitFramebufferReadsTheSourceAttachmentLevel() {
blitLevelOneInto(SIZE, 0);
}
@Test
void blitFramebufferWritesTheDestinationAttachmentLevel() {
blitLevelOneInto(SIZE, 1);
}
@Test
void blitFramebufferIntoFbo0SurvivesTheFramesFirstFbo0Pass() {
GlsmSdlHeadlessRig.bindTarget();
@@ -243,4 +263,159 @@ class GlsmSdlCopyBlitTest {
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2 - 4, GREEN, "blitted fbo0 below the split");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE / 2 + 4, RED, "blitted fbo0 above the split");
}
@Test
void blitFramebufferStencilSurvivesTheDestinationsFirstDraw() {
GlsmSdlHeadlessRig.bindTarget();
GlsmSdlHeadlessRig.writeStencilOneInBottomHalf();
final int destination = GlsmSdlHeadlessRig.createDepthStencilFbo();
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
GlsmSdlHeadlessRig.bindTarget();
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, destination);
GLStateManager.glBlitFramebuffer(0, 0, SIZE, SIZE, 0, 0, SIZE, SIZE, GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT, GL11.GL_NEAREST);
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, destination);
GlsmSdlHeadlessRig.drawRedWhereStencilIsOne();
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, 4, RED, "stencil carried by the blit");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 2, SIZE - 5, BLUE, "outside the blitted stencil");
}
private static final int UNTOUCHED = 0xFF101010;
private static final int CELL = 8;
private static int pattern(int x, int y) {
return 0xFF000040 | ((x / CELL) * 32 << 16) | ((y / CELL) * 32 << 8);
}
private static int createPatternTexture() {
final int id = GlsmSdlHeadlessRig.newNearestTexture();
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 0);
final ByteBuffer texels = MemoryUtil.memAlloc(SIZE * SIZE * 4);
try {
for (int y = 0; y < SIZE; y++) {
for (int x = 0; x < SIZE; x++) {
final int argb = pattern(x, y);
final int i = (y * SIZE + x) * 4;
texels.put(i, (byte) ((argb >> 16) & 0xFF));
texels.put(i + 1, (byte) ((argb >> 8) & 0xFF));
texels.put(i + 2, (byte) (argb & 0xFF));
texels.put(i + 3, (byte) ((argb >>> 24) & 0xFF));
}
}
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SIZE, SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
} finally {
MemoryUtil.memFree(texels);
}
return id;
}
private static int[] blitPattern(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1) {
final int source = createPatternTexture();
final int destination = GlsmSdlHeadlessRig.createSolidMipTexture(SIZE, 0, UNTOUCHED);
final int readFbo = GlsmSdlHeadlessRig.fboWithColor(source, 0);
final int drawFbo = GlsmSdlHeadlessRig.fboWithColor(destination, 0);
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, readFbo);
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, drawFbo);
GLStateManager.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST);
final int[] pixels = GlsmSdlHeadlessRig.readTextureLevel(destination, 0);
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glDeleteFramebuffers(readFbo);
GLStateManager.glDeleteFramebuffers(drawFbo);
GLStateManager.glDeleteTextures(source);
GLStateManager.glDeleteTextures(destination);
return pixels;
}
@Test
void blitFramebufferWithReversedSourceXMirrorsHorizontally() {
final int[] pixels = blitPattern(SIZE, 0, 0, SIZE, 0, 0, SIZE, SIZE);
for (int y = 0; y < SIZE; y++) {
for (int x = 0; x < SIZE; x++) {
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, x, y, pattern(SIZE - 1 - x, y), "horizontal mirror");
}
}
}
@Test
void blitFramebufferWithReversedDestinationYMirrorsVertically() {
final int[] pixels = blitPattern(0, 0, SIZE, SIZE, 0, SIZE, SIZE, 0);
for (int y = 0; y < SIZE; y++) {
for (int x = 0; x < SIZE; x++) {
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, x, y, pattern(x, SIZE - 1 - y), "vertical mirror");
}
}
}
@Test
void blitFramebufferWithBothRangesReversedDoesNotMirror() {
final int[] pixels = blitPattern(SIZE, SIZE, 0, 0, SIZE, SIZE, 0, 0);
for (int y = 0; y < SIZE; y++) {
for (int x = 0; x < SIZE; x++) {
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, x, y, pattern(x, y), "both ranges reversed");
}
}
}
@Test
void mirroredScaledBlitClipsTheOutOfBoundsSourceOnTheMirroredSide() {
final int[] pixels = blitPattern(SIZE + SIZE / 2, 0, SIZE / 2, SIZE / 2, 0, 0, SIZE / 2, SIZE);
for (int y = 0; y < SIZE; y++) {
for (int x = 0; x < SIZE; x++) {
if (x < SIZE / 4 || x >= SIZE / 2) {
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, x, y, UNTOUCHED, "outside the in-bounds part of the mirrored source");
continue;
}
final int sx = SIZE + SIZE / 2 - 1 - 2 * x;
final int sy = y / 2;
if ((sx - 1) / CELL != (sx + 1) / CELL || (sy - 1) / CELL != (sy + 1) / CELL) continue;
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, x, y, pattern(sx, sy), "mirrored scaled blit");
}
}
}
@Test
void blitFramebufferWritesEveryDrawBuffer() {
final int source = createPatternTexture();
final int first = GlsmSdlHeadlessRig.createSolidMipTexture(SIZE, 0, UNTOUCHED);
final int skipped = GlsmSdlHeadlessRig.createSolidMipTexture(SIZE, 0, UNTOUCHED);
final int third = GlsmSdlHeadlessRig.createSolidMipTexture(SIZE, 0, UNTOUCHED);
final int readFbo = GlsmSdlHeadlessRig.fboWithColor(source, 0);
final int drawFbo = GlsmSdlHeadlessRig.fboWithColor(first, 0);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT1, GL11.GL_TEXTURE_2D, skipped, 0);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT2, GL11.GL_TEXTURE_2D, third, 0);
final IntBuffer bufs = MemoryUtil.memAllocInt(3);
try {
bufs.put(0, GL30.GL_COLOR_ATTACHMENT0).put(1, GL11.GL_NONE).put(2, GL30.GL_COLOR_ATTACHMENT2);
GLStateManager.glDrawBuffers(bufs);
} finally {
MemoryUtil.memFree(bufs);
}
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, readFbo);
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, drawFbo);
GLStateManager.glBlitFramebuffer(0, 0, SIZE, SIZE, 0, 0, SIZE, SIZE, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST);
final int[] firstPixels = GlsmSdlHeadlessRig.readTextureLevel(first, 0);
final int[] thirdPixels = GlsmSdlHeadlessRig.readTextureLevel(third, 0);
for (int y = 0; y < SIZE; y++) {
for (int x = 0; x < SIZE; x++) {
GlsmSdlHeadlessRig.assertPixel(firstPixels, SIZE, x, y, pattern(x, y), "draw buffer 0");
GlsmSdlHeadlessRig.assertPixel(thirdPixels, SIZE, x, y, pattern(x, y), "draw buffer 2");
}
}
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTextureLevel(skipped, 0), UNTOUCHED, "attachment 1 behind a GL_NONE draw buffer");
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glDeleteFramebuffers(readFbo);
GLStateManager.glDeleteFramebuffers(drawFbo);
GLStateManager.glDeleteTextures(source);
GLStateManager.glDeleteTextures(first);
GLStateManager.glDeleteTextures(skipped);
GLStateManager.glDeleteTextures(third);
}
}
@@ -228,7 +228,7 @@ class GlsmSdlFfpTest {
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 13, SIZE / 2, 0xFFFF0000, "entity -1 must render red");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 32, SIZE / 2, 0xFF00FF00, "entity 7 must render green");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 51, SIZE / 2, 0xFF0000FF, "entity 42 must render blue");
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 51, SIZE / 2, 0xFF0000FF, "entity 50020 must render blue");
}
private static void enableGuiStandardItemLighting() {
@@ -116,6 +116,44 @@ public final class GlsmSdlHeadlessRig {
GLStateManager.glViewport(0, 0, SIZE, SIZE);
}
public static int createDepthStencilFbo() {
final int color = GLStateManager.glGenTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, color);
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, GL11.GL_RGBA8, SIZE, SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
final int depth = GLStateManager.glGenRenderbuffers();
GLStateManager.glBindRenderbuffer(GL30.GL_RENDERBUFFER, depth);
GLStateManager.glRenderbufferStorage(GL30.GL_RENDERBUFFER, GL30.GL_DEPTH24_STENCIL8, SIZE, SIZE);
final int fbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, color, 0);
GLStateManager.glFramebufferRenderbuffer(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_STENCIL_ATTACHMENT, GL30.GL_RENDERBUFFER, depth);
return fbo;
}
public static void writeStencilOneInBottomHalf() {
GLStateManager.glClearStencil(0);
GLStateManager.glStencilMask(0xFF);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
GLStateManager.glStencilFunc(GL11.GL_ALWAYS, 1, 0xFF);
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE);
halfQuad(-1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
}
public static void drawRedWhereStencilIsOne() {
GLStateManager.glEnable(GL11.GL_STENCIL_TEST);
GLStateManager.glStencilFunc(GL11.GL_EQUAL, 1, 0xFF);
GLStateManager.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
solidQuad(1.0f, 0.0f, 0.0f);
GLStateManager.glDisable(GL11.GL_STENCIL_TEST);
}
public static void clearTo(float r, float g, float b, float a) {
GLStateManager.glClearColor(r, g, b, a);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
@@ -175,12 +213,24 @@ public final class GlsmSdlHeadlessRig {
GLStateManager.glFogf(GL11.GL_FOG_END, 1.0e7f);
}
public static int createSolidTexture(int argb) {
public static int newNearestTexture() {
final int id = GLStateManager.glGenTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
return id;
}
public static int fboWithColor(int texture, int level) {
final int fbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, texture, level);
return fbo;
}
public static int createSolidTexture(int argb) {
final int id = newNearestTexture();
final ByteBuffer texels = MemoryUtil.memAlloc(2 * 2 * 4);
try {
for (int i = 0; i < 4; i++) {
@@ -275,20 +325,63 @@ public final class GlsmSdlHeadlessRig {
try {
for (int i = 0; i < w * h * 4; i++) pixels.put(i, fill);
GLStateManager.glReadPixels(x, y, w, h, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
final int[] out = new int[w * h];
for (int i = 0; i < out.length; i++) {
final int r = pixels.get(i * 4) & 0xFF;
final int g = pixels.get(i * 4 + 1) & 0xFF;
final int b = pixels.get(i * 4 + 2) & 0xFF;
final int a = pixels.get(i * 4 + 3) & 0xFF;
out[i] = (a << 24) | (r << 16) | (g << 8) | b;
}
return out;
return toArgb(pixels, w * h);
} finally {
MemoryUtil.memFree(pixels);
}
}
public static int createSolidMipTexture(int size, int maxLevel, int argb) {
final int id = GLStateManager.glGenTextures();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST_MIPMAP_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, maxLevel);
for (int level = 0; level <= maxLevel; level++) {
final int levelSize = Math.max(1, size >> level);
final ByteBuffer texels = MemoryUtil.memAlloc(levelSize * levelSize * 4);
try {
for (int i = 0; i < levelSize * levelSize; i++) {
texels.put(i * 4, (byte) ((argb >> 16) & 0xFF));
texels.put(i * 4 + 1, (byte) ((argb >> 8) & 0xFF));
texels.put(i * 4 + 2, (byte) (argb & 0xFF));
texels.put(i * 4 + 3, (byte) ((argb >>> 24) & 0xFF));
}
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA8, levelSize, levelSize, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
} finally {
MemoryUtil.memFree(texels);
}
}
return id;
}
public static int[] readTextureLevel(int texture, int level) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
final int w = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_WIDTH);
final int h = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, level, GL11.GL_TEXTURE_HEIGHT);
final ByteBuffer pixels = MemoryUtil.memAlloc(w * h * 4);
try {
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, level, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
return toArgb(pixels, w * h);
} finally {
MemoryUtil.memFree(pixels);
}
}
public static int[] toArgb(ByteBuffer pixels, int count) {
final int[] out = new int[count];
for (int i = 0; i < count; i++) {
final int r = pixels.get(i * 4) & 0xFF;
final int g = pixels.get(i * 4 + 1) & 0xFF;
final int b = pixels.get(i * 4 + 2) & 0xFF;
final int a = pixels.get(i * 4 + 3) & 0xFF;
out[i] = (a << 24) | (r << 16) | (g << 8) | b;
}
return out;
}
public static int pixelAt(int[] pixels, int rowStride, int x, int y) {
return pixels[y * rowStride + x];
}
@@ -0,0 +1,218 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.LogicOpFixture;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.describe;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.pixelAt;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlLogicOpTest {
private static final String VERT = """
#version 330 core
layout(location = 0) in vec3 a_Position;
void main() {
gl_Position = vec4(a_Position, 1.0);
}
""";
private static final String FRAG_SINGLE = """
#version 330 core
out vec4 fragColor;
void main() {
if (gl_FragCoord.x < 0.0) return;
fragColor = vec4(51.0, 153.0, 230.0, 128.0) / 255.0;
}
""";
private static final String FRAG_MRT = """
#version 330 core
layout(location = 0) out vec4 color0;
layout(location = 1) out vec4 color1;
void main() {
color0 = vec4(51.0, 153.0, 230.0, 128.0) / 255.0;
color1 = vec4(51.0, 102.0, 204.0, 255.0) / 255.0;
}
""";
private static final int[] PROGRAM_SRC = LogicOpFixture.SOURCES[1];
private static int singleProgram;
private static int mrtProgram;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
@AfterEach
void cleanup() {
GLStateManager.glDisable(GL11.GL_COLOR_LOGIC_OP);
GLStateManager.glLogicOp(GL11.GL_COPY);
GLStateManager.glDisable(GL11.GL_BLEND);
GLStateManager.glUseProgram(0);
LogicOpFixture.deleteResources();
}
@Test
void everyOpMatchesTheBitwiseReference() {
for (int op : LogicOpFixture.OPS) {
for (int[] src : LogicOpFixture.SOURCES) {
GlsmSdlHeadlessRig.beginFrame();
GlsmSdlHeadlessRig.bindTarget();
LogicOpFixture.clearToDst();
LogicOpFixture.drawQuad(op, src);
assertCenter(op, src, LogicOpFixture.label(op, src));
}
}
}
@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);
assertCenter(GL11.GL_XOR, src, "blend enabled under XOR");
}
@Test
void disablingLogicOpRestoresBaseShaderAndBlending() {
final int[] src = LogicOpFixture.SOURCES[1];
LogicOpFixture.clearToDst();
LogicOpFixture.drawQuad(GL11.GL_XOR, src);
GLStateManager.glEnable(GL11.GL_BLEND);
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
LogicOpFixture.quad(src);
final int got = centerPixel();
final int r = Math.min(255, LogicOpFixture.expected(GL11.GL_XOR, src[0], LogicOpFixture.DST_R) + src[0]);
final int g = Math.min(255, LogicOpFixture.expected(GL11.GL_XOR, src[1], LogicOpFixture.DST_G) + src[1]);
final int b = Math.min(255, LogicOpFixture.expected(GL11.GL_XOR, src[2], LogicOpFixture.DST_B) + src[2]);
final int a = Math.min(255, LogicOpFixture.expected(GL11.GL_XOR, src[3], LogicOpFixture.DST_A) + src[3]);
assertEquals((a << 24) | (r << 16) | (g << 8) | b, got, () -> "additive after XOR: " + describe(got));
}
@Test
void userProgramIsLowered() {
singleProgram = buildProgram(singleProgram, FRAG_SINGLE);
for (int op : new int[] { GL11.GL_OR_REVERSE, GL11.GL_COPY_INVERTED, GL11.GL_OR_INVERTED, GL11.GL_XOR }) {
GlsmSdlHeadlessRig.beginFrame();
GlsmSdlHeadlessRig.bindTarget();
LogicOpFixture.clearToDst();
GLStateManager.glUseProgram(singleProgram);
LogicOpFixture.drawQuad(op, PROGRAM_SRC);
GLStateManager.glUseProgram(0);
assertCenter(op, PROGRAM_SRC, "user program " + LogicOpFixture.label(op, PROGRAM_SRC));
}
}
@Test
void floatAttachmentPassesThroughWhileUnormAttachmentIsLowered() {
mrtProgram = buildProgram(mrtProgram, FRAG_MRT);
final int unorm = colorTexture(GL11.GL_RGBA8, GL11.GL_UNSIGNED_BYTE);
final int half = colorTexture(GL30.GL_RGBA16F, GL11.GL_FLOAT);
final int fbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, unorm, 0);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT1, GL11.GL_TEXTURE_2D, half, 0);
final IntBuffer bufs = BufferUtils.createIntBuffer(2);
bufs.put(GL30.GL_COLOR_ATTACHMENT0).put(GL30.GL_COLOR_ATTACHMENT1).flip();
GLStateManager.glDrawBuffers(bufs);
GLStateManager.glViewport(0, 0, SIZE, SIZE);
GLStateManager.glEnable(GL11.GL_BLEND);
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
LogicOpFixture.clearToDst();
GLStateManager.glUseProgram(mrtProgram);
LogicOpFixture.drawQuad(GL11.GL_OR_REVERSE, PROGRAM_SRC);
GLStateManager.glUseProgram(0);
GLStateManager.glDisable(GL11.GL_BLEND);
GlsmSdlHeadlessRig.endFrame();
GLStateManager.glReadBuffer(GL30.GL_COLOR_ATTACHMENT0);
final ByteBuffer px = BufferUtils.createByteBuffer(4);
GLStateManager.glReadPixels(SIZE / 2, SIZE / 2, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, px);
assertEquals(LogicOpFixture.expected(GL11.GL_OR_REVERSE, PROGRAM_SRC[0], LogicOpFixture.DST_R), px.get(0) & 0xFF, "unorm red");
assertEquals(LogicOpFixture.expected(GL11.GL_OR_REVERSE, PROGRAM_SRC[1], LogicOpFixture.DST_G), px.get(1) & 0xFF, "unorm green");
assertEquals(LogicOpFixture.expected(GL11.GL_OR_REVERSE, PROGRAM_SRC[2], LogicOpFixture.DST_B), px.get(2) & 0xFF, "unorm blue");
assertEquals(LogicOpFixture.expected(GL11.GL_OR_REVERSE, PROGRAM_SRC[3], LogicOpFixture.DST_A), px.get(3) & 0xFF, "unorm alpha");
GLStateManager.glReadBuffer(GL30.GL_COLOR_ATTACHMENT1);
final ByteBuffer fpx = BufferUtils.createByteBuffer(16).order(ByteOrder.nativeOrder());
GLStateManager.glReadPixels(SIZE / 2, SIZE / 2, 1, 1, GL11.GL_RGBA, GL11.GL_FLOAT, fpx);
assertEquals(0.2f, fpx.getFloat(0), 1e-3f, "float red passes through unblended");
assertEquals(0.4f, fpx.getFloat(4), 1e-3f, "float green passes through unblended");
assertEquals(0.8f, fpx.getFloat(8), 1e-3f, "float blue passes through unblended");
assertEquals(1.0f, fpx.getFloat(12), 1e-3f, "float alpha passes through unblended");
GLStateManager.glReadBuffer(GL30.GL_COLOR_ATTACHMENT0);
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glDeleteFramebuffers(fbo);
GLStateManager.glDeleteTextures(unorm);
GLStateManager.glDeleteTextures(half);
}
private static int colorTexture(int internalFormat, int type) {
final int id = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
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, GL11.GL_RGBA, type, (ByteBuffer) null);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
return id;
}
private static int buildProgram(int cached, String frag) {
if (cached != 0) return cached;
final int vs = compile(GL20.GL_VERTEX_SHADER, VERT);
final int fs = compile(GL20.GL_FRAGMENT_SHADER, frag);
final int p = GLStateManager.glCreateProgram();
GLStateManager.glAttachShader(p, vs);
GLStateManager.glAttachShader(p, fs);
GLStateManager.glLinkProgram(p);
assertTrue(GLStateManager.glGetProgrami(p, GL20.GL_LINK_STATUS) != 0, "program did not link");
return p;
}
private static int compile(int type, String src) {
final int s = GLStateManager.glCreateShader(type);
GLStateManager.glShaderSource(s, src);
GLStateManager.glCompileShader(s);
assertTrue(GLStateManager.glGetShaderi(s, GL20.GL_COMPILE_STATUS) != 0, () -> "shader: " + GLStateManager.glGetShaderInfoLog(s));
return s;
}
private static int centerPixel() {
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
return pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2);
}
private static void assertCenter(int op, int[] src, String label) {
final int got = centerPixel();
final int r = LogicOpFixture.expected(op, src[0], LogicOpFixture.DST_R);
final int g = LogicOpFixture.expected(op, src[1], LogicOpFixture.DST_G);
final int b = LogicOpFixture.expected(op, src[2], LogicOpFixture.DST_B);
final int a = LogicOpFixture.expected(op, src[3], LogicOpFixture.DST_A);
assertEquals((a << 24) | (r << 16) | (g << 8) | b, got, () -> label + ": " + describe(got));
}
}
@@ -0,0 +1,91 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.assertUniform;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.createSolidMipTexture;
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.readTextureLevel;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlMipAttachmentTest {
private static final int TEX_SIZE = 64;
private static final int MAX_LEVEL = 6;
private static final int CLEAR = 0xFF204080;
private static final int BASE = 0xFF10C030;
private static final int RED = 0xFFFF0000;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
private static void setClearColor(int argb) {
GLStateManager.glClearColor(((argb >> 16) & 0xFF) / 255.0f, ((argb >> 8) & 0xFF) / 255.0f, (argb & 0xFF) / 255.0f, ((argb >>> 24) & 0xFF) / 255.0f);
}
private static int levelColor(int level) {
return (CLEAR + level) | 0xFF000000;
}
@Test
void clearingEachLevelWritesOnlyThatLevel() {
final int texture = createSolidMipTexture(TEX_SIZE, MAX_LEVEL, BASE);
final int fbo = GlsmSdlHeadlessRig.fboWithColor(texture, 0);
for (int level = 1; level <= MAX_LEVEL; level++) {
final int extent = Math.max(1, TEX_SIZE >> level);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, texture, level);
GLStateManager.glViewport(0, 0, extent, extent);
setClearColor(levelColor(level));
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
}
for (int level = 1; level <= MAX_LEVEL; level++) {
assertUniform(readTextureLevel(texture, level), levelColor(level), "level " + level + " after clearing its own attachment");
}
assertUniform(readTextureLevel(texture, 0), BASE, "level 0 untouched by clearing the mip levels");
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, texture, 1);
final int w = Math.max(1, TEX_SIZE >> 1);
final ByteBuffer pixels = MemoryUtil.memAlloc(w * w * 4);
try {
GLStateManager.glReadPixels(0, 0, w, w, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
assertUniform(GlsmSdlHeadlessRig.toArgb(pixels, w * w), levelColor(1), "readPixels of the level 1 attachment");
} finally {
MemoryUtil.memFree(pixels);
}
GLStateManager.glDeleteFramebuffers(fbo);
GLStateManager.glDeleteTextures(texture);
}
@Test
void pendingLevelZeroClearIsNotAppliedToALevelOnePass() {
final int texture = createSolidMipTexture(TEX_SIZE, MAX_LEVEL, BASE);
final int fbo = GlsmSdlHeadlessRig.fboWithColor(texture, 0);
GLStateManager.glViewport(0, 0, TEX_SIZE, TEX_SIZE);
setClearColor(CLEAR);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, texture, 1);
GLStateManager.glViewport(0, 0, TEX_SIZE / 2, TEX_SIZE / 2);
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
assertUniform(readTextureLevel(texture, 1), RED, "level 1 after drawing into it");
assertUniform(readTextureLevel(texture, 0), CLEAR, "level 0 keeps its own pending clear");
GLStateManager.glDeleteFramebuffers(fbo);
GLStateManager.glDeleteTextures(texture);
}
}
@@ -0,0 +1,27 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public final class GlsmSdlOutOfFrameExtension implements BeforeEachCallback, AfterEachCallback {
@Override
public void beforeEach(ExtensionContext context) {
GlsmSdlHeadlessRig.endFrame();
final FrameManager fm = Reflect.get(BackendManager.RENDER_BACKEND, "frameManager");
assertFalse(fm.isFrameActive(), "test must start with no active frame");
}
@Override
public void afterEach(ExtensionContext context) {
GlsmSdlHeadlessRig.endFrame();
assertTrue(GlsmSdlRedirectAgent.failures().isEmpty(), () -> "redirect agent recorded failures: " + GlsmSdlRedirectAgent.failures());
}
}
@@ -0,0 +1,100 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.nio.ByteBuffer;
import static org.junit.jupiter.api.Assertions.fail;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlOutOfFrameExtension.class)
class GlsmSdlOutOfFrameReadbackTest {
private static final int TEX_SIZE = 64;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
@Test
void outOfFrameTexImageReadback() {
final ByteBuffer pattern = pattern(TEX_SIZE, TEX_SIZE, 1);
final int tex = GlsmSdlHeadlessRig.newNearestTexture();
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, TEX_SIZE, TEX_SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pattern);
assertReadback(tex, TEX_SIZE, TEX_SIZE, pattern);
GLStateManager.glDeleteTextures(tex);
}
@Test
void inFrameCopyImageSubDataReadback() {
GlsmSdlHeadlessRig.beginFrame();
copyAndReadBack(3);
}
@Test
void outOfFrameCopyImageSubDataReadback() {
copyAndReadBack(4);
}
private static void copyAndReadBack(int seed) {
final ByteBuffer pattern = pattern(TEX_SIZE, TEX_SIZE, seed);
final int src = GlsmSdlHeadlessRig.newNearestTexture();
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, TEX_SIZE, TEX_SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pattern);
final int dst = GlsmSdlHeadlessRig.newNearestTexture();
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, TEX_SIZE, TEX_SIZE, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
GLStateManager.glCopyImageSubData(src, GL11.GL_TEXTURE_2D, 0, 0, 0, 0, dst, GL11.GL_TEXTURE_2D, 0, 0, 0, 0, TEX_SIZE, TEX_SIZE, 1);
assertReadback(dst, TEX_SIZE, TEX_SIZE, pattern);
GLStateManager.glDeleteTextures(src);
GLStateManager.glDeleteTextures(dst);
}
private static ByteBuffer pattern(int w, int h, int seed) {
final ByteBuffer buf = BufferUtils.createByteBuffer(w * h * 4);
for (int i = 0; i < w * h; i++) {
buf.put((byte) (i * 7 + seed));
buf.put((byte) (i * 13 + 0x40));
buf.put((byte) (i * 29 + seed * 3));
buf.put((byte) (0x80 | i));
}
return buf.flip();
}
private static void assertReadback(int tex, int w, int h, ByteBuffer expected) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
final ByteBuffer got = BufferUtils.createByteBuffer(w * h * 4);
for (int i = 0; i < got.capacity(); i++) got.put(i, (byte) 0xCD);
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, got);
final int n = w * h * 4;
int mismatches = 0;
int zeros = 0;
int untouched = 0;
int first = -1;
for (int i = 0; i < n; i++) {
final int g = got.get(i) & 0xFF;
if (g == 0) zeros++;
if (g == 0xCD) untouched++;
if (g != (expected.get(i) & 0xFF)) {
mismatches++;
if (first < 0) first = i;
}
}
if (mismatches == 0) return;
fail("readback mismatch: " + mismatches + "/" + n + " bytes differ, zeroBytes=" + zeros + " untouched0xCD=" + untouched + ", firstMismatch=" + first + "\n expected[0..16]=" + hex(expected, 16) + "\n actual[0..16] =" + hex(got, 16));
}
private static String hex(ByteBuffer buf, int count) {
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) sb.append(String.format(" %02x", buf.get(i) & 0xFF));
return sb.toString();
}
}
@@ -0,0 +1,173 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ReadbackFixture;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlReadbackMatrixTest {
private static final int FBO0_W = 8;
private static final int FBO0_H = 6;
private static FrameManager frameManager;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
frameManager = Reflect.get(BackendManager.RENDER_BACKEND, "frameManager");
final ResourceManager resourceManager = Reflect.get(BackendManager.RENDER_BACKEND, "resourceManager");
frameManager.destroyFinalTarget();
frameManager.finalTarget().create(SDLGPUGate.device(), resourceManager, FBO0_W, FBO0_H, resourceManager.mapTextureFormat(GL11.GL_RGBA8));
}
@AfterAll
static void dropFinalTarget() {
GlsmSdlHeadlessRig.endFrame();
if (frameManager != null) frameManager.destroyFinalTarget();
}
@AfterEach
void resetPack() {
ReadbackFixture.resetPack();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
}
@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();
}
@Test
void fbo0FloatReadIsBottomUp() {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glViewport(0, 0, FBO0_W, FBO0_H);
GLStateManager.glClearColor(0f, 0f, 1f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(0, 0, FBO0_W, 1);
GLStateManager.glClearColor(1f, 0f, 0f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
final ByteBuffer out = BufferUtils.createByteBuffer(FBO0_W * FBO0_H * 16).order(ByteOrder.nativeOrder());
GLStateManager.glReadPixels(0, 0, FBO0_W, FBO0_H, GL11.GL_RGBA, GL11.GL_FLOAT, out);
assertEquals(1f, out.getFloat(0), "GL row 0 is the scissored bottom row (red)");
assertEquals(0f, out.getFloat(8), "GL row 0 blue");
final int top = (FBO0_H - 1) * FBO0_W * 16;
assertEquals(0f, out.getFloat(top), "top row red");
assertEquals(1f, out.getFloat(top + 8), "top row blue");
}
@Test
void fbo0ReadAfterBareClearSeesTheClear() {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glViewport(0, 0, FBO0_W, FBO0_H);
GLStateManager.glClearColor(0.2f, 0.4f, 0.6f, 1f);
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
final ByteBuffer out = BufferUtils.createByteBuffer(FBO0_W * FBO0_H * 4);
GLStateManager.glReadPixels(0, 0, FBO0_W, FBO0_H, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, out);
assertEquals(51, out.get(0) & 0xFF, "red after bare clear");
assertEquals(102, out.get(1) & 0xFF, "green after bare clear");
assertEquals(153, out.get(2) & 0xFF, "blue after bare clear");
}
@Test
void fbo0DepthAndStencilAreBottomUp() {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glViewport(0, 0, FBO0_W, FBO0_H);
GLStateManager.glDepthMask(true);
GLStateManager.glStencilMask(0xFF);
GLStateManager.glClearDepth(0.75);
GLStateManager.glClearStencil(0x11);
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(0, 0, FBO0_W, 1);
GLStateManager.glClearDepth(0.25);
GLStateManager.glClearStencil(0x22);
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 = BufferUtils.createByteBuffer(FBO0_W * FBO0_H * 4).order(ByteOrder.nativeOrder());
GLStateManager.glReadPixels(0, 0, FBO0_W, FBO0_H, GL11.GL_DEPTH_COMPONENT, GL11.GL_FLOAT, depth);
assertEquals(0.25f, depth.getFloat(0), 1e-6f, "fbo0 depth bottom row");
assertEquals(0.75f, depth.getFloat((FBO0_H - 1) * FBO0_W * 4), 1e-6f, "fbo0 depth top row");
final ByteBuffer stencil = BufferUtils.createByteBuffer(FBO0_W * FBO0_H);
GLStateManager.glReadPixels(0, 0, FBO0_W, FBO0_H, GL11.GL_STENCIL_INDEX, GL11.GL_UNSIGNED_BYTE, stencil);
assertEquals(0x22, stencil.get(0) & 0xFF, "fbo0 stencil bottom row");
assertEquals(0x11, stencil.get((FBO0_H - 1) * FBO0_W) & 0xFF, "fbo0 stencil top row");
}
}
@@ -30,6 +30,7 @@ class GlsmSdlSplashTargetTest {
final OffscreenTarget target = createSplashTarget(backend);
Reflect.set(backend, "splashTarget", target);
try {
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
TestThreads.run("SplashTarget-Seed-Thread", () -> {
Reflect.invoke(backend, "enterContext", new Class<?>[]{boolean.class}, true);
GlsmSdlHeadlessRig.solidQuad(0f, 1f, 0f);
@@ -0,0 +1,183 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
import com.gtnewhorizons.angelica.glsm.texture.TextureStaging;
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.TransferThread;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlOutOfFrameExtension.class)
class GlsmSdlTextureStagingTest {
private static final int STAGING_SIZE = 64;
private static final int BIG_TEX = 2048;
private static final int BIG_W = 1024;
private static final int BIG_H = 1100;
private static final int SMALL = 16;
private static final int COLOR_A = 0xFF10E020;
private static final int COLOR_B = 0xFF3050C0;
private static TransferThread installed;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
final Object backend = BackendManager.RENDER_BACKEND;
if (Reflect.get(backend, "transferThread") == null) {
final ResourceManager rm = Reflect.get(backend, "resourceManager");
installed = new TransferThread(SDLGPUGate.device(), rm);
Reflect.set(backend, "transferThread", installed);
rm.setTransferThread(installed);
}
}
@AfterAll
static void removeTransferThread() throws InterruptedException {
if (installed == null) return;
GlsmSdlHeadlessRig.endFrame();
installed.shutdown();
installed.getThread().join();
final Object backend = BackendManager.RENDER_BACKEND;
Reflect.set(backend, "transferThread", null);
final ResourceManager rm = Reflect.get(backend, "resourceManager");
rm.setTransferThread(null);
installed = null;
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void stagedBandsRoundTripPerLevel(boolean inFrame) {
if (inFrame) GlsmSdlHeadlessRig.beginFrame();
final int tex = GlsmSdlHeadlessRig.createSolidMipTexture(STAGING_SIZE, 1, 0);
for (int level = 0; level <= 1; level++) {
final int size = STAGING_SIZE >> level;
final int half = size / 2;
stageBand(tex, level, 0, size, half);
stageBand(tex, level, half, size, half);
}
for (int level = 0; level <= 1; level++) {
final int size = STAGING_SIZE >> level;
final int[] got = GlsmSdlHeadlessRig.readTextureLevel(tex, level);
assertEquals(size * size, got.length, "level " + level + " size");
int mismatches = 0;
int first = -1;
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
if (got[y * size + x] != stagedColor(level, x, y)) {
if (first < 0) first = y * size + x;
mismatches++;
}
}
}
if (mismatches != 0) {
final int fx = first % size;
final int fy = first / size;
fail("level " + level + ": " + mismatches + " texels differ; first (" + fx + "," + fy + ") expected " + GlsmSdlHeadlessRig.describe(stagedColor(level, fx, fy)) + " got " + GlsmSdlHeadlessRig.describe(got[first]));
}
}
GLStateManager.glDeleteTextures(tex);
}
@ParameterizedTest
@CsvSource({"false,false", "true,true", "true,false"})
void directUploadLandsAfterEarlierBatchedUpload(boolean inFrame, boolean fboAttached) {
if (inFrame) GlsmSdlHeadlessRig.beginFrame();
final int tex = GlsmSdlHeadlessRig.newNearestTexture();
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, BIG_TEX, BIG_TEX, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
int fbo = 0;
if (fboAttached) {
fbo = GLStateManager.glGenFramebuffers();
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, tex, 0);
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
}
final ByteBuffer small = solidRgba(SMALL, SMALL, COLOR_A);
final ByteBuffer big = solidRgba(BIG_W, BIG_H, COLOR_B);
final ByteBuffer out = MemoryUtil.memAlloc(BIG_TEX * BIG_TEX * 4);
try {
assertTrue(big.remaining() > ResourceManager.BATCH_SEGMENT_CAPACITY, "second upload must take the direct path");
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SMALL, SMALL, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, small);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, BIG_W, BIG_H, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, big);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, out);
final int[] got = GlsmSdlHeadlessRig.toArgb(out, BIG_TEX * BIG_TEX);
int mismatches = 0;
for (int y = 0; y < BIG_H; y++) {
for (int x = 0; x < BIG_W; x++) {
if (got[y * BIG_TEX + x] != COLOR_B) mismatches++;
}
}
final int corner = got[0];
assertEquals(0, mismatches, () -> "texels not holding the later upload; (0,0) is " + GlsmSdlHeadlessRig.describe(corner));
} finally {
MemoryUtil.memFree(small);
MemoryUtil.memFree(big);
MemoryUtil.memFree(out);
}
if (fbo != 0) GLStateManager.glDeleteFramebuffers(fbo);
GLStateManager.glDeleteTextures(tex);
}
private static void stageBand(int tex, int level, int y0, int w, int h) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
final TextureStaging staging = GLStateManager.beginTextureStaging(level, 0, y0, w, h);
assertNotNull(staging, "beginTextureStaging level " + level + " y " + y0);
final ByteBuffer buf = staging.buffer();
assertEquals(w * h * 4, buf.remaining(), "staging capacity");
final boolean bgra = staging.bgra();
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
final int c = stagedColor(level, x, y0 + y);
final int i = (y * w + x) * 4;
final byte r = (byte) (c >> 16);
final byte b = (byte) c;
buf.put(i, bgra ? b : r);
buf.put(i + 1, (byte) (c >> 8));
buf.put(i + 2, bgra ? r : b);
buf.put(i + 3, (byte) (c >>> 24));
}
}
assertTrue(GLStateManager.commitTextureStaging(staging), "commitTextureStaging level " + level + " y " + y0);
}
private static int stagedColor(int level, int x, int y) {
return 0x80000000 | ((level * 0x55 + y) & 0x7F) << 24 | (x * 3 & 0xFF) << 16 | (y * 5 & 0xFF) << 8 | ((x ^ y) + level * 0x40) & 0xFF;
}
private static ByteBuffer solidRgba(int w, int h, int argb) {
final ByteBuffer buf = MemoryUtil.memAlloc(w * h * 4);
for (int i = 0; i < w * h; i++) {
buf.put(i * 4, (byte) (argb >> 16));
buf.put(i * 4 + 1, (byte) (argb >> 8));
buf.put(i * 4 + 2, (byte) argb);
buf.put(i * 4 + 3, (byte) (argb >>> 24));
}
return buf;
}
}
@@ -74,16 +74,16 @@ class ClearConsumptionTest {
@Test
void depthAndStencilAreConsumedIndependently() {
final ResourceManager rm = rm();
final ContextState st = new ContextState();
FBOClearTracker.recordPendingDepthClear(st, DEPTH, 1f);
FBOClearTracker.recordPendingStencilClear(st, DEPTH, 0);
consumeClears(rm(), st, fbo(SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT), DUMMY, 0, true, true, true);
consumeClears(rm, st, fbo(SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT), DUMMY, 0, true, true, true);
assertFalse(st.pendingDepthTextures.contains(DEPTH));
assertFalse(st.pendingStencilTextures.contains(DEPTH));
assertTrue(st.clearedTexturesThisFrame.contains(DEPTH));
assertTrue(st.clearedStencilTexturesThisFrame.contains(DEPTH));
assertTrue(rm.isTextureContentDefined(DEPTH), "the clear pass defines the depth-stencil contents");
}
@Test
@@ -96,17 +96,17 @@ class ClearConsumptionTest {
assertFalse(st.pendingDepthTextures.contains(DEPTH));
assertTrue(st.pendingStencilTextures.contains(DEPTH), "the format has no stencil aspect to clear");
assertFalse(st.clearedStencilTexturesThisFrame.contains(DEPTH));
}
@Test
void anUnclearedDepthTargetIsStillConsumed() {
final ResourceManager rm = rm();
final ContextState st = new ContextState();
FBOClearTracker.recordPendingDepthClear(st, DEPTH, 1f);
consumeClears(rm(), st, fbo(SDL_GPU_TEXTUREFORMAT_D24_UNORM), DUMMY, 0, false, false, false);
consumeClears(rm, st, fbo(SDL_GPU_TEXTUREFORMAT_D24_UNORM), DUMMY, 0, false, false, false);
assertFalse(st.pendingDepthTextures.contains(DEPTH));
assertFalse(st.clearedTexturesThisFrame.contains(DEPTH), "no clear was baked, so nothing was cleared");
assertFalse(rm.isTextureContentDefined(DEPTH), "no clear was baked, so nothing was defined");
}
}
@@ -0,0 +1,182 @@
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
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.resource.TextureOps;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import org.lwjgl.sdl.SDL_GPUDepthStencilTargetInfo;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_LOADOP_LOAD;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_STOREOP_STORE;
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET;
import static org.lwjgl.system.MemoryStack.stackPush;
@Tag("glsm-sdl")
@Timeout(120)
class DepthStencilReadbackTest {
private static final int W = 24;
private static final int H = 16;
private static final int RX = 4;
private static final int RY = 2;
private static final int RW = 8;
private static final int RH = 5;
private static SdlTestRig rig;
private static FrameManager frameManager;
private static ShaderManager sm;
private static PipelineStore store;
private static FBOClearTracker clearTracker;
private static TextureOps textureOps;
private static AttachmentClear attachmentClear;
private static DepthStencilReadback readback;
private static ContextState st;
private static long depthTexture;
private static int depthFormat;
@BeforeAll
static void setUp() throws Exception {
rig = SdlTestRig.acquireRealDevice();
frameManager = rig.frameManager;
rig.resourceManager.cachePreferredDepthFormats();
sm = new ShaderManager(rig.device);
store = new PipelineStore(rig.device);
clearTracker = new FBOClearTracker(frameManager, rig.resourceManager, sm);
textureOps = new TextureOps(rig.device, frameManager, rig.resourceManager, clearTracker);
attachmentClear = new AttachmentClear(frameManager, store, sm);
readback = new DepthStencilReadback(frameManager, rig.resourceManager, store, sm, clearTracker);
st = new ContextState();
rig.resourceManager.createTexture(9801, GL11.GL_TEXTURE_2D, GL30.GL_DEPTH24_STENCIL8, W, H, 1, 1);
depthTexture = rig.resourceManager.ensureTextureUsage(9801, SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET);
assertNotEquals(0L, depthTexture, "depth+stencil texture creation failed");
depthFormat = rig.resourceManager.getTextureMeta(9801).sdlFormat();
assertTrue(PixelOps.isDepthStencilFormat(depthFormat), "test needs a packed depth+stencil format");
}
@AfterAll
static void tearDown() {
if (frameManager != null && frameManager.isFrameActive()) frameManager.endFrame();
if (readback != null) {
readback.release(st);
readback.shutdown();
}
if (store != null) store.shutdown();
SdlTestRig.releaseRealDevice();
}
private static void clearRegion(float depth, int stencil) {
final FboState fbo = new FboState();
fbo.drawBuffers = new int[0];
fbo.cachedColorFormats = new int[0];
fbo.depthTexture = depthTexture;
fbo.depthFormat = depthFormat;
fbo.width = W;
fbo.height = H;
st.scissorEnabled = true;
st.scissorX = RX;
st.scissorY = RY;
st.scissorW = RW;
st.scissorH = RH;
st.depthClearValue = depth;
st.stencilClearValue = stencil;
st.pipeline.stencilWriteMask = 0xFF;
try (MemoryStack stack = stackPush()) {
final SDL_GPUDepthStencilTargetInfo ds = SDL_GPUDepthStencilTargetInfo.calloc(stack);
final long addr = ds.address();
MemoryAccess.putAddress(addr + SDL_GPUDepthStencilTargetInfo.TEXTURE, depthTexture);
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, SDL_GPU_LOADOP_LOAD);
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STENCIL_LOAD_OP, SDL_GPU_LOADOP_LOAD);
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STENCIL_STORE_OP, SDL_GPU_STOREOP_STORE);
frameManager.beginRenderPass(null, ds);
assertTrue(attachmentClear.clearInPass(st, frameManager.frame(), attachmentClear.forFbo(fbo), false, true, true), "scissored clear failed");
frameManager.endRenderPassIfActive();
}
st.scissorEnabled = false;
}
private static ByteBuffer download(long scratch, int bytesPerTexel) {
frameManager.submitMidFrame();
final ByteBuffer out = MemoryUtil.memAlloc(W * H * bytesPerTexel);
textureOps.readbackTexture(scratch, 0, 0, W, H, 0, out);
out.rewind();
return out;
}
private static boolean inRegion(int x, int y) {
return x >= RX && x < RX + RW && y >= RY && y < RY + RH;
}
@Test
void readsPendingClearThenScissoredRegion() {
frameManager.beginFrame();
FBOClearTracker.recordPendingDepthClear(st, depthTexture, 0.25f);
FBOClearTracker.recordPendingStencilClear(st, depthTexture, 0xA5);
final long depthScratch = readback.renderDepth(st, depthTexture, depthFormat, W, H);
assertNotEquals(0L, depthScratch, "depth readback pass failed");
final ByteBuffer d0 = download(depthScratch, 4);
try {
for (int i = 0; i < W * H; i++) assertEquals(0.25f, d0.getFloat(i * 4), 1e-6f, "pending depth clear at texel " + i);
} finally {
MemoryUtil.memFree(d0);
}
clearRegion(0.75f, 0x3C);
final long stencilScratch = readback.renderStencil(st, depthTexture, depthFormat, W, H);
assertNotEquals(0L, stencilScratch, "stencil readback pass failed");
final ByteBuffer s = download(stencilScratch, 1);
try {
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
assertEquals(inRegion(x, y) ? 0x3C : 0xA5, s.get(y * W + x) & 0xFF, "stencil at x=" + x + " y=" + y);
}
}
} finally {
MemoryUtil.memFree(s);
}
assertEquals(depthScratch, readback.renderDepth(st, depthTexture, depthFormat, W, H), "same-size readback must reuse the scratch");
final ByteBuffer d1 = download(depthScratch, 4);
try {
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
assertEquals(inRegion(x, y) ? 0.75f : 0.25f, d1.getFloat((y * W + x) * 4), 1e-6f, "depth at x=" + x + " y=" + y);
}
}
} finally {
MemoryUtil.memFree(d1);
}
final long stencilAgain = readback.renderStencil(st, depthTexture, depthFormat, W, H);
final ByteBuffer s2 = download(stencilAgain, 1);
try {
assertEquals(0x3C, s2.get(RY * W + RX) & 0xFF, "stencil must survive the readback passes");
assertEquals(0xA5, s2.get(0) & 0xFF, "stencil must survive the readback passes");
} finally {
MemoryUtil.memFree(s2);
}
frameManager.endFrame();
}
}
@@ -55,68 +55,24 @@ class BgraStorageChannelTest {
assertArrayEquals(new byte[]{3, 2, 1, 4}, toArray(p, 4));
}
@Test
void readback_rgbaStorage_rgbaRequest_passesThrough() {
final ByteBuffer p = pixel(0x11, 0x22, 0x33, 0x44);
PixelOps.postProcessReadback(p, 1, 1, GL11.GL_RGBA, RGBA, false);
assertArrayEquals(new byte[]{0x11, 0x22, 0x33, 0x44}, toArray(p, 4));
}
@Test
void readback_rgbaStorage_bgraRequest_swaps() {
final ByteBuffer p = pixel(0x11, 0x22, 0x33, 0x44);
PixelOps.postProcessReadback(p, 1, 1, GL12.GL_BGRA, RGBA, false);
assertArrayEquals(new byte[]{0x33, 0x22, 0x11, 0x44}, toArray(p, 4));
}
@Test
void readback_bgraStorage_bgraRequest_passesThrough() {
final ByteBuffer p = pixel(0x11, 0x22, 0x33, 0x44);
PixelOps.postProcessReadback(p, 1, 1, GL12.GL_BGRA, BGRA, false);
assertArrayEquals(new byte[]{0x11, 0x22, 0x33, 0x44}, toArray(p, 4));
}
@Test
void readback_bgraStorage_rgbaRequest_swaps() {
final ByteBuffer p = pixel(0x11, 0x22, 0x33, 0x44);
PixelOps.postProcessReadback(p, 1, 1, GL11.GL_RGBA, BGRA, false);
assertArrayEquals(new byte[]{0x33, 0x22, 0x11, 0x44}, toArray(p, 4));
}
@Test
void readback_jmAtlasWaterRoundTrip() {
final ByteBuffer p = pixel(223, 164, 64, 255); // BGRA storage of (R=64,G=164,B=223,A=255)
PixelOps.postProcessReadback(p, 1, 1, GL12.GL_BGRA, BGRA, false);
assertArrayEquals(new byte[]{(byte) 223, (byte) 164, (byte) 64, (byte) 255}, toArray(p, 4));
}
@Test
void readback_flipRowsAppliesBeforeChannelSwap() {
final ByteBuffer p = ByteBuffer.allocateDirect(8).order(ByteOrder.nativeOrder());
p.put(new byte[]{(byte) 0xAA, 0x01, 0x02, 0x03, (byte) 0xBB, 0x10, 0x20, 0x30}).rewind();
PixelOps.postProcessReadback(p, 1, 2, GL12.GL_BGRA, RGBA, true);
assertArrayEquals(new byte[]{0x20, 0x10, (byte) 0xBB, 0x30, 0x02, 0x01, (byte) 0xAA, 0x03}, toArray(p, 8));
}
@Test
void upload_rgbaSrc_rgbaStorage_passesThrough() {
final ByteBuffer src = pixel(0x11, 0x22, 0x33, 0x44);
final ByteBuffer out = PixelOps.prepareUploadBuffer(GL11.GL_RGBA, RGBA, src, 1, 1);
final ByteBuffer out = PixelOps.prepareUploadBuffer(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, RGBA, src, 1, 1);
assertSame(src, out, "no transform expected");
}
@Test
void upload_bgraSrc_bgraStorage_passesThrough() {
final ByteBuffer src = pixel(0x11, 0x22, 0x33, 0x44);
final ByteBuffer out = PixelOps.prepareUploadBuffer(GL12.GL_BGRA, BGRA, src, 1, 1);
final ByteBuffer out = PixelOps.prepareUploadBuffer(GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, BGRA, src, 1, 1);
assertSame(src, out);
}
@Test
void upload_rgbaSrc_bgraStorage_swaps() {
final ByteBuffer src = pixel(0x11, 0x22, 0x33, 0x44);
final ByteBuffer out = PixelOps.prepareUploadBuffer(GL11.GL_RGBA, BGRA, src, 1, 1);
final ByteBuffer out = PixelOps.prepareUploadBuffer(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, BGRA, src, 1, 1);
try {
assertNotSame(src, out);
assertArrayEquals(new byte[]{0x33, 0x22, 0x11, 0x44}, toArray(out, 4));
@@ -128,7 +84,7 @@ class BgraStorageChannelTest {
@Test
void upload_bgraSrc_rgbaStorage_swaps() {
final ByteBuffer src = pixel(0x11, 0x22, 0x33, 0x44);
final ByteBuffer out = PixelOps.prepareUploadBuffer(GL12.GL_BGRA, RGBA, src, 1, 1);
final ByteBuffer out = PixelOps.prepareUploadBuffer(GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, RGBA, src, 1, 1);
try {
assertNotSame(src, out);
assertArrayEquals(new byte[]{0x33, 0x22, 0x11, 0x44}, toArray(out, 4));
@@ -141,7 +97,7 @@ class BgraStorageChannelTest {
void upload_luminanceSrc_rgbaStorage_expandsOnly() {
final ByteBuffer src = ByteBuffer.allocateDirect(1).order(ByteOrder.nativeOrder());
src.put((byte) 0x77).rewind();
final ByteBuffer out = PixelOps.prepareUploadBuffer(GL11.GL_LUMINANCE, RGBA, src, 1, 1);
final ByteBuffer out = PixelOps.prepareUploadBuffer(GL11.GL_LUMINANCE, GL11.GL_UNSIGNED_BYTE, RGBA, src, 1, 1);
try {
assertNotSame(src, out);
assertArrayEquals(new byte[]{0x77, 0x77, 0x77, (byte) 0xFF}, toArray(out, 4));
@@ -154,7 +110,7 @@ class BgraStorageChannelTest {
void upload_luminanceSrc_bgraStorage_expandsAndSwaps() {
final ByteBuffer src = ByteBuffer.allocateDirect(1).order(ByteOrder.nativeOrder());
src.put((byte) 0x77).rewind();
final ByteBuffer out = PixelOps.prepareUploadBuffer(GL11.GL_LUMINANCE, BGRA, src, 1, 1);
final ByteBuffer out = PixelOps.prepareUploadBuffer(GL11.GL_LUMINANCE, GL11.GL_UNSIGNED_BYTE, BGRA, src, 1, 1);
try {
assertNotSame(src, out);
assertArrayEquals(new byte[]{0x77, 0x77, 0x77, (byte) 0xFF}, toArray(out, 4));
@@ -167,7 +123,7 @@ class BgraStorageChannelTest {
void upload_luminanceAlphaSrc_bgraStorage_expandsAndSwaps() {
final ByteBuffer src = ByteBuffer.allocateDirect(2).order(ByteOrder.nativeOrder());
src.put((byte) 0x42).put((byte) 0x88).rewind();
final ByteBuffer out = PixelOps.prepareUploadBuffer(GL11.GL_LUMINANCE_ALPHA, BGRA, src, 1, 1);
final ByteBuffer out = PixelOps.prepareUploadBuffer(GL11.GL_LUMINANCE_ALPHA, GL11.GL_UNSIGNED_BYTE, BGRA, src, 1, 1);
try {
assertNotSame(src, out);
assertArrayEquals(new byte[]{0x42, 0x42, 0x42, (byte) 0x88}, toArray(out, 4));
@@ -29,32 +29,34 @@ class FullCoverageClearDiscardTest {
final ContextState st = new ContextState();
FBOClearTracker.recordPendingColorClear(st, HANDLE, 1f, 0f, 0f, 1f);
assertTrue(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, meta()));
assertTrue(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, meta(), true));
assertFalse(st.pendingColorTextures.contains(HANDLE), "pending clear must be dropped, not left queued");
assertFalse(st.pendingColorValues.containsKey(HANDLE));
assertTrue(st.clearedTexturesThisFrame.contains(HANDLE), "the overwrite counts as the clear");
}
@Test
void fullTargetColorBlitDefinesTheContents() {
void copyWithNoPendingClearDefinesTheDestination() {
final ContextState st = new ContextState();
FBOClearTracker.recordPendingColorClear(st, HANDLE, 1f, 0f, 0f, 1f);
final SdlTestRig rig = SdlTestRig.create();
final FBOClearTracker tracker = new FBOClearTracker(rig.frameManager, rig.resourceManager, null);
assertTrue(tracker.discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, meta()));
tracker.resolveDestinationForWrite(st, HANDLE, meta(), 0, 0, 0, 0, W, H, true);
assertTrue(rig.resourceManager.isTextureContentDefined(HANDLE), "the overwrite defines the contents, so a later frame must not clear it back");
assertTrue(rig.resourceManager.isTextureContentDefined(HANDLE), "a copy is a write, so the next pass must load it, not clear it");
}
@Test
void fullTargetDepthBlitDropsThePendingClear() {
void depthStencilClearsDropOnlyForAnAllAspectOverwrite() {
final ContextState st = new ContextState();
FBOClearTracker.recordPendingDepthClear(st, HANDLE, 1f);
FBOClearTracker.recordPendingStencilClear(st, HANDLE, 0);
final FBOClearTracker tracker = tracker();
assertTrue(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, meta()));
assertFalse(st.pendingDepthTextures.contains(HANDLE));
assertTrue(st.clearedTexturesThisFrame.contains(HANDLE));
assertFalse(tracker.discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, meta(), false));
assertTrue(st.pendingDepthTextures.contains(HANDLE) && st.pendingStencilTextures.contains(HANDLE), "a single-aspect overwrite must materialize, not drop, the other aspect's clear");
assertTrue(tracker.discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, meta(), true));
assertFalse(st.pendingDepthTextures.contains(HANDLE) || st.pendingStencilTextures.contains(HANDLE));
assertFalse(st.pendingDepthValues.containsKey(HANDLE) || st.pendingStencilValues.containsKey(HANDLE));
}
@Test
@@ -69,7 +71,7 @@ class FullCoverageClearDiscardTest {
for (int[] c : partial) {
final ContextState st = new ContextState();
FBOClearTracker.recordPendingColorClear(st, HANDLE, 1f, 0f, 0f, 1f);
assertFalse(tracker().discardPendingClearIfFullyCovered(st, HANDLE, c[0], c[1], c[2], c[3], c[4], meta()),
assertFalse(tracker().discardPendingClearIfFullyCovered(st, HANDLE, c[0], c[1], c[2], c[3], c[4], meta(), true),
"x=" + c[0] + " y=" + c[1] + " level=" + c[2] + " w=" + c[3] + " h=" + c[4]);
assertTrue(st.pendingColorTextures.contains(HANDLE), "the clear must survive a partial overwrite");
}
@@ -80,7 +82,7 @@ class FullCoverageClearDiscardTest {
final ContextState st = new ContextState();
FBOClearTracker.recordPendingColorClear(st, HANDLE, 1f, 0f, 0f, 1f);
final ResourceManager.TextureMeta volume = new ResourceManager.TextureMeta(0, 0, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, W, H, 4, 1, 0);
assertFalse(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, volume),
assertFalse(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, volume, true),
"one slice of a volume is not full coverage");
assertTrue(st.pendingColorTextures.contains(HANDLE));
}
@@ -88,15 +90,14 @@ class FullCoverageClearDiscardTest {
@Test
void noPendingClearIsNotAnOverwriteClaim() {
final ContextState st = new ContextState();
assertFalse(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, meta()));
assertFalse(st.clearedTexturesThisFrame.contains(HANDLE));
assertFalse(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, meta(), true));
}
@Test
void missingMetaFallsBackToMaterializing() {
final ContextState st = new ContextState();
FBOClearTracker.recordPendingColorClear(st, HANDLE, 1f, 0f, 0f, 1f);
assertFalse(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, null));
assertFalse(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, null, true));
assertTrue(st.pendingColorTextures.contains(HANDLE));
}
@@ -107,7 +108,7 @@ class FullCoverageClearDiscardTest {
FBOClearTracker.snapshotFlushGenerations(st);
final int before = st.pendingMutationGen;
assertTrue(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, meta()));
assertTrue(tracker().discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, meta(), true));
assertEquals(before + 1, st.pendingMutationGen);
}
}
@@ -0,0 +1,587 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.lwjgl.sdl.SDLGPU.*;
class PixelPackTest {
private static final int SENTINEL = 0xEE;
@BeforeEach
void littleEndianHost() {
assumeTrue(ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN);
}
private static byte[] b(int... v) {
final byte[] out = new byte[v.length];
for (int i = 0; i < v.length; i++) out[i] = (byte) v[i];
return out;
}
private static byte[] le16(int... v) {
final byte[] out = new byte[v.length * 2];
for (int i = 0; i < v.length; i++) {
out[i * 2] = (byte) v[i];
out[i * 2 + 1] = (byte) (v[i] >>> 8);
}
return out;
}
private static byte[] le32(int... v) {
final byte[] out = new byte[v.length * 4];
for (int i = 0; i < v.length; i++) {
out[i * 4] = (byte) v[i];
out[i * 4 + 1] = (byte) (v[i] >>> 8);
out[i * 4 + 2] = (byte) (v[i] >>> 16);
out[i * 4 + 3] = (byte) (v[i] >>> 24);
}
return out;
}
private static byte[] f32(float... v) {
final int[] bits = new int[v.length];
for (int i = 0; i < v.length; i++) bits[i] = Float.floatToRawIntBits(v[i]);
return le32(bits);
}
private static ByteBuffer direct(byte[] content, int size) {
final ByteBuffer buf = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
for (int i = 0; i < size; i++) buf.put(i, (byte) SENTINEL);
for (int i = 0; i < content.length; i++) buf.put(i, content[i]);
return buf;
}
private static byte[] read(ByteBuffer buf, int len) {
final byte[] out = new byte[len];
for (int i = 0; i < len; i++) out[i] = buf.get(i);
return out;
}
private static PackState ps(int alignment) {
final PackState ps = new PackState();
ps.alignment = alignment;
return ps;
}
private static byte[] pack1(int sdlFormat, byte[] texel, boolean readPixels, int format, int type) {
final ByteBuffer src = direct(texel, texel.length);
final int outSize = PixelOps.glPixelSize(format, type);
final ByteBuffer dst = direct(new byte[0], outSize + 8);
PixelPack.packColor(MemoryUtil.memAddress(src), sdlFormat, storageBase(sdlFormat), texel.length, 1, 1, false, readPixels, format, type, ps(1), MemoryUtil.memAddress(dst));
for (int i = outSize; i < outSize + 8; i++) assertEquals((byte) SENTINEL, dst.get(i), "wrote past pixel");
return read(dst, outSize);
}
private static int storageBase(int sdlFormat) {
return switch (sdlFormat) {
case SDL_GPU_TEXTUREFORMAT_R8_UNORM, SDL_GPU_TEXTUREFORMAT_R16_UNORM, SDL_GPU_TEXTUREFORMAT_R16_FLOAT,
SDL_GPU_TEXTUREFORMAT_R32_FLOAT, SDL_GPU_TEXTUREFORMAT_R8_SNORM, SDL_GPU_TEXTUREFORMAT_R16_SNORM,
SDL_GPU_TEXTUREFORMAT_R8_UINT, SDL_GPU_TEXTUREFORMAT_R8_INT, SDL_GPU_TEXTUREFORMAT_R16_UINT,
SDL_GPU_TEXTUREFORMAT_R16_INT, SDL_GPU_TEXTUREFORMAT_R32_UINT, SDL_GPU_TEXTUREFORMAT_R32_INT -> GL11.GL_RED;
case SDL_GPU_TEXTUREFORMAT_R8G8_UNORM, SDL_GPU_TEXTUREFORMAT_R16G16_UNORM, SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT,
SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT, SDL_GPU_TEXTUREFORMAT_R8G8_SNORM, SDL_GPU_TEXTUREFORMAT_R16G16_SNORM,
SDL_GPU_TEXTUREFORMAT_R8G8_UINT, SDL_GPU_TEXTUREFORMAT_R8G8_INT, SDL_GPU_TEXTUREFORMAT_R16G16_UINT,
SDL_GPU_TEXTUREFORMAT_R16G16_INT, SDL_GPU_TEXTUREFORMAT_R32G32_UINT, SDL_GPU_TEXTUREFORMAT_R32G32_INT -> GL30.GL_RG;
case SDL_GPU_TEXTUREFORMAT_A8_UNORM -> GL11.GL_ALPHA;
case SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT, SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM -> GL11.GL_RGB;
default -> GL11.GL_RGBA;
};
}
private static byte[] packBase(int sdlFormat, int base, byte[] texel, boolean readPixels, int format, int type) {
final ByteBuffer src = direct(texel, texel.length);
final int outSize = PixelOps.glPixelSize(format, type);
final ByteBuffer dst = direct(new byte[0], outSize);
PixelPack.packColor(MemoryUtil.memAddress(src), sdlFormat, base, texel.length, 1, 1, false, readPixels, format, type, ps(1), MemoryUtil.memAddress(dst));
return read(dst, outSize);
}
@Test
void rgbBaseOverRgbaStorageReadsAlphaOne() {
final int rgba8 = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
final byte[] px = b(0x11, 0x22, 0x33, 0x80);
assertArrayEquals(b(0x11, 0x22, 0x33, 0xFF), packBase(rgba8, GL11.GL_RGB, px, true, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x11, 0x22, 0x33, 0xFF), packBase(rgba8, GL11.GL_RGB, px, false, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x33, 0x22, 0x11, 0xFF), packBase(rgba8, GL11.GL_RGB, px, true, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV));
assertArrayEquals(b(0x11, 0x22, 0x33), packBase(rgba8, GL11.GL_RGB, px, true, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0xFF), packBase(rgba8, GL11.GL_RGB, px, true, GL11.GL_ALPHA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(f32(1.0f), packBase(SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT, GL11.GL_RGB, le16(0, 0, 0, 0x3800), true, GL11.GL_ALPHA, GL11.GL_FLOAT));
assertArrayEquals(b(1, 2, 3, 1), packBase(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT, GL11.GL_RGB, b(1, 2, 3, 9), true, GL30.GL_RGBA_INTEGER, GL11.GL_UNSIGNED_BYTE));
}
@Test
void luminanceIntensityAlphaBasesRebase() {
final int rgba8 = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
final byte[] px = b(0x40, 0x50, 0x60, 0x70);
assertArrayEquals(b(0x40, 0x00, 0x00, 0xFF), packBase(rgba8, GL11.GL_LUMINANCE, px, true, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x40, 0x00, 0x00, 0xFF), packBase(rgba8, GL11.GL_INTENSITY, px, false, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x40, 0x00, 0x00, 0x70), packBase(rgba8, GL11.GL_LUMINANCE_ALPHA, px, true, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x00, 0x00, 0x00, 0x70), packBase(rgba8, GL11.GL_ALPHA, px, true, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x40), packBase(rgba8, GL11.GL_LUMINANCE, px, true, GL11.GL_LUMINANCE, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x40, 0x70), packBase(rgba8, GL11.GL_LUMINANCE_ALPHA, px, true, GL11.GL_LUMINANCE_ALPHA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x00), packBase(rgba8, GL11.GL_ALPHA, px, true, GL11.GL_LUMINANCE, GL11.GL_UNSIGNED_BYTE));
}
@Test
void luminanceSumUsesRebasedChannels() {
assertArrayEquals(b(0x80), packBase(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, GL30.GL_RG, b(0x40, 0x40, 0x40, 0x70), true, GL11.GL_LUMINANCE, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0xC0), packBase(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, GL11.GL_RGB, b(0x40, 0x40, 0x40, 0x70), true, GL11.GL_LUMINANCE, GL11.GL_UNSIGNED_BYTE));
}
@Test
void glBaseFormatFollowsMesa() {
assertEquals(GL11.GL_RGB, PixelPack.glBaseFormat(GL11.GL_RGB8));
assertEquals(GL11.GL_RGB, PixelPack.glBaseFormat(GL30.GL_R11F_G11F_B10F));
assertEquals(GL11.GL_RGB, PixelPack.glBaseFormat(GL30.GL_RGB16UI));
assertEquals(GL11.GL_RGBA, PixelPack.glBaseFormat(GL11.GL_RGBA8));
assertEquals(GL11.GL_RGBA, PixelPack.glBaseFormat(GL11.GL_RGB10_A2));
assertEquals(GL11.GL_RGBA, PixelPack.glBaseFormat(GL21.GL_SRGB8_ALPHA8));
assertEquals(GL11.GL_RGBA, PixelPack.glBaseFormat(GL30.GL_RGBA16F));
assertEquals(GL11.GL_RED, PixelPack.glBaseFormat(GL30.GL_R16F));
assertEquals(GL11.GL_RED, PixelPack.glBaseFormat(GL30.GL_R8I));
assertEquals(GL30.GL_RG, PixelPack.glBaseFormat(GL30.GL_RG32UI));
assertEquals(GL11.GL_ALPHA, PixelPack.glBaseFormat(GL11.GL_ALPHA8));
assertEquals(GL11.GL_LUMINANCE, PixelPack.glBaseFormat(1));
assertEquals(GL11.GL_LUMINANCE_ALPHA, PixelPack.glBaseFormat(GL11.GL_LUMINANCE8_ALPHA8));
assertEquals(GL11.GL_INTENSITY, PixelPack.glBaseFormat(GL11.GL_INTENSITY16));
assertEquals(GL11.GL_DEPTH_COMPONENT, PixelPack.glBaseFormat(GL30.GL_DEPTH_COMPONENT32F));
assertEquals(GL30.GL_DEPTH_STENCIL, PixelPack.glBaseFormat(GL30.GL_DEPTH24_STENCIL8));
assertEquals(GL11.GL_STENCIL_INDEX, PixelPack.glBaseFormat(GL30.GL_STENCIL_INDEX8));
assertEquals(-1, PixelPack.glBaseFormat(0x1234));
assertThrows(IllegalArgumentException.class, () -> packBase(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, -1, b(0, 0, 0, 0), true, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertThrows(IllegalArgumentException.class, () -> packBase(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, GL11.GL_DEPTH_COMPONENT, b(0, 0, 0, 0), true, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
}
private static byte[] readPix(int sdlFormat, byte[] texel, int format, int type) {
return pack1(sdlFormat, texel, true, format, type);
}
private static byte[] getTex(int sdlFormat, byte[] texel, int format, int type) {
return pack1(sdlFormat, texel, false, format, type);
}
@Test
void decodeRgba8() {
assertArrayEquals(b(0x11, 0x22, 0x33, 0x44), readPix(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, b(0x11, 0x22, 0x33, 0x44), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(le16(0x1111, 0x2222, 0x3333, 0x4444), readPix(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, b(0x11, 0x22, 0x33, 0x44), GL11.GL_RGBA, GL11.GL_UNSIGNED_SHORT));
}
@Test
void decodeBgra8() {
assertArrayEquals(b(0x11, 0x22, 0x33, 0x44), readPix(SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, b(0x33, 0x22, 0x11, 0x44), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x33, 0x22, 0x11, 0x44), readPix(SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, b(0x33, 0x22, 0x11, 0x44), GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x33, 0x22, 0x11, 0x44), readPix(SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, b(0x33, 0x22, 0x11, 0x44), GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV));
assertArrayEquals(le16(0x1111, 0x2222, 0x3333), readPix(SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, b(0x33, 0x22, 0x11, 0x44), GL11.GL_RGB, GL11.GL_UNSIGNED_SHORT));
}
@Test
void decodeSrgbReturnsStoredValues() {
assertArrayEquals(b(0x80, 0x40, 0x20, 0x10), readPix(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB, b(0x80, 0x40, 0x20, 0x10), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x80, 0x40, 0x20, 0x10), readPix(SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB, b(0x20, 0x40, 0x80, 0x10), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
}
@Test
void decodeR8AndRg8RebaseAlphaToOne() {
assertArrayEquals(b(0xFF, 0x00, 0x00, 0xFF), readPix(SDL_GPU_TEXTUREFORMAT_R8_UNORM, b(0xFF), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x40, 0x80, 0x00, 0xFF), readPix(SDL_GPU_TEXTUREFORMAT_R8G8_UNORM, b(0x40, 0x80), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x40, 0x80), readPix(SDL_GPU_TEXTUREFORMAT_R8G8_UNORM, b(0x40, 0x80), GL30.GL_RG, GL11.GL_UNSIGNED_BYTE));
}
@Test
void decodeUnorm16() {
assertArrayEquals(b(0x12), readPix(SDL_GPU_TEXTUREFORMAT_R16_UNORM, le16(0x1234), GL11.GL_RED, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(f32(0.0f, 1.0f), readPix(SDL_GPU_TEXTUREFORMAT_R16G16_UNORM, le16(0x0000, 0xFFFF), GL30.GL_RG, GL11.GL_FLOAT));
assertArrayEquals(b(18, 128, 255, 0), readPix(SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM, le16(0x1234, 0x8000, 0xFFFF, 0x0000), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
}
@Test
void decodeA8RebasesColorToZero() {
assertArrayEquals(b(0, 0, 0, 0x7F), readPix(SDL_GPU_TEXTUREFORMAT_A8_UNORM, b(0x7F), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x7F), readPix(SDL_GPU_TEXTUREFORMAT_A8_UNORM, b(0x7F), GL11.GL_ALPHA, GL11.GL_UNSIGNED_BYTE));
}
@Test
void decodeHalfFloat() {
assertArrayEquals(f32(1.0f, 0.0f, 0.0f, 1.0f), readPix(SDL_GPU_TEXTUREFORMAT_R16_FLOAT, le16(0x3C00), GL11.GL_RGBA, GL11.GL_FLOAT));
assertArrayEquals(f32(0.5f, -2.0f), readPix(SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT, le16(0x3800, 0xC000), GL30.GL_RG, GL11.GL_FLOAT));
assertArrayEquals(f32(0.25f, 0.5f, 1.0f, 2.0f), readPix(SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT, le16(0x3400, 0x3800, 0x3C00, 0x4000), GL11.GL_RGBA, GL11.GL_FLOAT));
assertArrayEquals(le16(0x3400, 0x3800, 0x3C00, 0x4000), readPix(SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT, le16(0x3400, 0x3800, 0x3C00, 0x4000), GL11.GL_RGBA, GL30.GL_HALF_FLOAT));
}
@Test
void decodeFloat32() {
assertArrayEquals(b(191), readPix(SDL_GPU_TEXTUREFORMAT_R32_FLOAT, f32(0.75f), GL11.GL_RED, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(f32(0.75f), readPix(SDL_GPU_TEXTUREFORMAT_R32_FLOAT, f32(0.75f), GL11.GL_RED, GL11.GL_FLOAT));
assertArrayEquals(f32(1.5f, -0.5f), readPix(SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT, f32(1.5f, -0.5f), GL30.GL_RG, GL11.GL_FLOAT));
assertArrayEquals(le16(0x3E00, 0xB800), readPix(SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT, f32(1.5f, -0.5f), GL30.GL_RG, GL30.GL_HALF_FLOAT));
assertArrayEquals(f32(-1.0f, 0.0f, 0.5f, 3.0f), readPix(SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, f32(-1.0f, 0.0f, 0.5f, 3.0f), GL11.GL_RGBA, GL11.GL_FLOAT));
}
@Test
void decodeR11G11B10() {
assertArrayEquals(f32(1.0f, 0.5f, 2.0f, 1.0f), readPix(SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT, le32(0x801C03C0), GL11.GL_RGBA, GL11.GL_FLOAT));
assertArrayEquals(le32(0x801C03C0), readPix(SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT, le32(0x801C03C0), GL11.GL_RGB, GL30.GL_UNSIGNED_INT_10F_11F_11F_REV));
}
@Test
void decodeR10G10B10A2() {
assertArrayEquals(b(255, 128, 0, 255), readPix(SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM, le32(0xC00803FF), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(le32(0xC00803FF), readPix(SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM, le32(0xC00803FF), GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_2_10_10_10_REV));
}
@Test
void decodePacked16() {
assertArrayEquals(b(255, 130, 8, 255), readPix(SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM, le16(0xFC01), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(le16(0xFC01), readPix(SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM, le16(0xFC01), GL11.GL_RGB, GL12.GL_UNSIGNED_SHORT_5_6_5));
assertArrayEquals(b(132, 0, 255, 255), readPix(SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM, le16(0xC01F), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(255, 136, 0, 68), readPix(SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM, le16(0x4F80), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
}
@Test
void decodeSnorm() {
assertArrayEquals(f32(-1.0f), getTex(SDL_GPU_TEXTUREFORMAT_R8_SNORM, b(0x81), GL11.GL_RED, GL11.GL_FLOAT));
assertArrayEquals(f32(0.0f), readPix(SDL_GPU_TEXTUREFORMAT_R8_SNORM, b(0x81), GL11.GL_RED, GL11.GL_FLOAT));
assertArrayEquals(b(255), getTex(SDL_GPU_TEXTUREFORMAT_R8_SNORM, b(0x7F), GL11.GL_RED, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(f32(1.0f, -1.0f), getTex(SDL_GPU_TEXTUREFORMAT_R8G8_SNORM, b(0x7F, 0x80), GL30.GL_RG, GL11.GL_FLOAT));
assertArrayEquals(b(0x7F, 0x00, 0x81, 0x7F), getTex(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM, b(0x7F, 0x00, 0x81, 0x7F), GL11.GL_RGBA, GL11.GL_BYTE));
assertArrayEquals(f32(1.0f), getTex(SDL_GPU_TEXTUREFORMAT_R16_SNORM, le16(0x7FFF), GL11.GL_RED, GL11.GL_FLOAT));
assertArrayEquals(f32(-1.0f, 0.0f), getTex(SDL_GPU_TEXTUREFORMAT_R16G16_SNORM, le16(0x8000, 0x0000), GL30.GL_RG, GL11.GL_FLOAT));
assertArrayEquals(b(127, 0x81, 64, 0), getTex(SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM, le16(0x7FFF, 0x8001, 0x4000, 0x0000), GL11.GL_RGBA, GL11.GL_BYTE));
}
@Test
void decodeInteger8() {
assertArrayEquals(b(200, 0, 0, 1), readPix(SDL_GPU_TEXTUREFORMAT_R8_UINT, b(200), GL30.GL_RGBA_INTEGER, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0), readPix(SDL_GPU_TEXTUREFORMAT_R8_INT, b(0xFB), GL30.GL_RED_INTEGER, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(le32(-5), readPix(SDL_GPU_TEXTUREFORMAT_R8_INT, b(0xFB), GL30.GL_RED_INTEGER, GL11.GL_INT));
assertArrayEquals(le16(9, 250), readPix(SDL_GPU_TEXTUREFORMAT_R8G8_UINT, b(9, 250), GL30.GL_RG_INTEGER, GL11.GL_UNSIGNED_SHORT));
assertArrayEquals(b(0x80, 0x7F), readPix(SDL_GPU_TEXTUREFORMAT_R8G8_INT, b(0x80, 0x7F), GL30.GL_RG_INTEGER, GL11.GL_BYTE));
assertArrayEquals(b(3, 2, 1, 4), readPix(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT, b(1, 2, 3, 4), GL30.GL_BGRA_INTEGER, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(le16(0xFFFF, 2, 3, 4), readPix(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT, b(0xFF, 2, 3, 4), GL30.GL_RGBA_INTEGER, GL11.GL_SHORT));
}
@Test
void decodeInteger16() {
assertArrayEquals(le32(65535), readPix(SDL_GPU_TEXTUREFORMAT_R16_UINT, le16(0xFFFF), GL30.GL_RED_INTEGER, GL11.GL_UNSIGNED_INT));
assertArrayEquals(le16(0x8000), readPix(SDL_GPU_TEXTUREFORMAT_R16_INT, le16(0x8000), GL30.GL_RED_INTEGER, GL11.GL_SHORT));
assertArrayEquals(b(255, 1), readPix(SDL_GPU_TEXTUREFORMAT_R16G16_UINT, le16(0xFFFF, 1), GL30.GL_RG_INTEGER, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(le16(0x7FFF, 1), readPix(SDL_GPU_TEXTUREFORMAT_R16G16_UINT, le16(0xFFFF, 1), GL30.GL_RG_INTEGER, GL11.GL_SHORT));
assertArrayEquals(b(0x80, 0x7F), readPix(SDL_GPU_TEXTUREFORMAT_R16G16_INT, le16(0x8000, 0x7FFF), GL30.GL_RG_INTEGER, GL11.GL_BYTE));
assertArrayEquals(le32(0xC03FFFFF), readPix(SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT, le16(1023, 2000, 3, 5), GL30.GL_RGBA_INTEGER, GL12.GL_UNSIGNED_INT_2_10_10_10_REV));
assertArrayEquals(le32(0, 2, 3, 4), readPix(SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT, le16(0xFFFF, 2, 3, 4), GL30.GL_RGBA_INTEGER, GL11.GL_UNSIGNED_INT));
}
@Test
void decodeInteger32() {
assertArrayEquals(le16(0xFFFF), readPix(SDL_GPU_TEXTUREFORMAT_R32_UINT, le32(70000), GL30.GL_RED_INTEGER, GL11.GL_UNSIGNED_SHORT));
assertArrayEquals(le32(-70000), readPix(SDL_GPU_TEXTUREFORMAT_R32_INT, le32(-70000), GL30.GL_RED_INTEGER, GL11.GL_INT));
assertArrayEquals(le32(0x7FFFFFFF, 1), readPix(SDL_GPU_TEXTUREFORMAT_R32G32_UINT, le32(0xFFFFFFFF, 1), GL30.GL_RG_INTEGER, GL11.GL_INT));
assertArrayEquals(le32(0, 7), readPix(SDL_GPU_TEXTUREFORMAT_R32G32_INT, le32(-7, 7), GL30.GL_RG_INTEGER, GL11.GL_UNSIGNED_INT));
assertArrayEquals(le32(0x7FFFFFFF, 5, 0, 7), readPix(SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT, le32(0xFFFFFFFF, 5, 0, 7), GL30.GL_RGBA_INTEGER, GL11.GL_INT));
assertArrayEquals(le32(0, 2, 3, 4), readPix(SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT, le32(-1, 2, 3, 4), GL30.GL_RGBA_INTEGER, GL11.GL_UNSIGNED_INT));
assertArrayEquals(le32(-1, 2, 3), readPix(SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT, le32(-1, 2, 3, 4), GL30.GL_RGB_INTEGER, GL11.GL_INT));
}
@Test
void encodeUnormToIntegerTypes() {
final byte[] px = b(0xFF, 0x80, 0x00, 0x40);
final int rgba8 = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
assertArrayEquals(b(127, 64, 0, 32), readPix(rgba8, px, GL11.GL_RGBA, GL11.GL_BYTE));
assertArrayEquals(le16(0xFFFF, 0x8080, 0x0000, 0x4040), readPix(rgba8, px, GL11.GL_RGBA, GL11.GL_UNSIGNED_SHORT));
assertArrayEquals(le16(0x7FFF, 0x4040, 0x0000, 0x2020), readPix(rgba8, px, GL11.GL_RGBA, GL11.GL_SHORT));
assertArrayEquals(le32(0xFFFFFFFF, 0x80808080, 0x00000000, 0x40404040), readPix(rgba8, px, GL11.GL_RGBA, GL11.GL_UNSIGNED_INT));
assertArrayEquals(le32(0x7FFFFFFF, 0x40404040, 0x00000000, 0x20202020), readPix(rgba8, px, GL11.GL_RGBA, GL11.GL_INT));
assertArrayEquals(f32(1.0f, 0.0f, 1.0f, 0.0f), readPix(rgba8, b(0xFF, 0x00, 0xFF, 0x00), GL11.GL_RGBA, GL11.GL_FLOAT));
assertArrayEquals(le16(0x3C00, 0x0000, 0x3C00, 0x0000), readPix(rgba8, b(0xFF, 0x00, 0xFF, 0x00), GL11.GL_RGBA, GL30.GL_HALF_FLOAT));
assertArrayEquals(b(0x00, 0x80, 0xFF), readPix(rgba8, px, GL12.GL_BGR, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x80), readPix(rgba8, px, GL11.GL_GREEN, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(le16(0x0000), readPix(rgba8, px, GL11.GL_BLUE, GL11.GL_UNSIGNED_SHORT));
}
@Test
void encodePackedTypes() {
final byte[] px = b(0xFF, 0x80, 0x00, 0x40);
final int rgba8 = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
assertArrayEquals(b(0xF0), readPix(rgba8, px, GL11.GL_RGB, GL12.GL_UNSIGNED_BYTE_3_3_2));
assertArrayEquals(b(0x27), readPix(rgba8, px, GL11.GL_RGB, GL12.GL_UNSIGNED_BYTE_2_3_3_REV));
assertArrayEquals(le16(0xFC00), readPix(rgba8, px, GL11.GL_RGB, GL12.GL_UNSIGNED_SHORT_5_6_5));
assertArrayEquals(le16(0x041F), readPix(rgba8, px, GL11.GL_RGB, GL12.GL_UNSIGNED_SHORT_5_6_5_REV));
assertArrayEquals(le16(0xF804), readPix(rgba8, px, GL11.GL_RGBA, GL12.GL_UNSIGNED_SHORT_4_4_4_4));
assertArrayEquals(le16(0x408F), readPix(rgba8, px, GL11.GL_RGBA, GL12.GL_UNSIGNED_SHORT_4_4_4_4_REV));
assertArrayEquals(le16(0xFC00), readPix(rgba8, px, GL11.GL_RGBA, GL12.GL_UNSIGNED_SHORT_5_5_5_1));
assertArrayEquals(le16(0x021F), readPix(rgba8, px, GL11.GL_RGBA, GL12.GL_UNSIGNED_SHORT_1_5_5_5_REV));
assertArrayEquals(le32(0xFF800040), readPix(rgba8, px, GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_8_8_8_8));
assertArrayEquals(le32(0x40FF8000), readPix(rgba8, px, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV));
assertArrayEquals(le32(0xFFE02001), readPix(rgba8, px, GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_10_10_10_2));
assertArrayEquals(le32(0x40080BFF), readPix(rgba8, px, GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_2_10_10_10_REV));
}
@Test
void encodeUnormUpConversionRoundsLikeSpec() {
final int rgba8 = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
assertArrayEquals(le32(0x666664CD), readPix(rgba8, b(51, 102, 153, 85), GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_2_10_10_10_REV));
assertArrayEquals(le32(0x59964736), readPix(rgba8, b(205, 100, 102, 102), GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_2_10_10_10_REV));
assertArrayEquals(le16(0x0102, 0xFEFE), getTex(SDL_GPU_TEXTUREFORMAT_R8G8_SNORM, b(1, 0xFF), GL30.GL_RG, GL11.GL_SHORT));
}
@Test
void encodePackedFloatTypes() {
final byte[] half = le16(0x3C00, 0x3800, 0x4000, 0x3C00);
assertArrayEquals(le32(0x801C03C0), readPix(SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT, half, GL11.GL_RGB, GL30.GL_UNSIGNED_INT_10F_11F_11F_REV));
assertArrayEquals(le32(0x81010100), getTex(SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT, le16(0x3C00, 0x3800, 0x3400, 0x3C00), GL11.GL_RGB, GL30.GL_UNSIGNED_INT_5_9_9_9_REV));
}
@Test
void encodeHalfRoundsToNearestEven() {
final byte[] src = f32(1.00048828125f, 1.00146484375f, Math.scalb(1.0f, -24), -2.0f);
assertArrayEquals(le16(0x3C00, 0x3C02, 0x0001, 0xC000), readPix(SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, src, GL11.GL_RGBA, GL30.GL_HALF_FLOAT));
assertArrayEquals(le16(0x7BFF, 0x7C00), readPix(SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT, f32(65504.0f, 65520.0f), GL30.GL_RG, GL30.GL_HALF_FLOAT));
}
@Test
void rebaseAlphaForRgbSources() {
assertArrayEquals(f32(1.0f, 0.5f, 2.0f, 1.0f), readPix(SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT, le32(0x801C03C0), GL11.GL_RGBA, GL11.GL_FLOAT));
assertArrayEquals(le16(0xFFFF), readPix(SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM, le16(0xFC01), GL11.GL_ALPHA, GL11.GL_UNSIGNED_SHORT));
}
@Test
void luminanceSumsForReadPixelsButNotGetTexImage() {
final int rgba8 = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
assertArrayEquals(b(192), readPix(rgba8, b(0x40, 0x40, 0x40, 0x80), GL11.GL_LUMINANCE, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(192, 128), readPix(rgba8, b(0x40, 0x40, 0x40, 0x80), GL11.GL_LUMINANCE_ALPHA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x40), getTex(rgba8, b(0x40, 0x40, 0x40, 0x80), GL11.GL_LUMINANCE, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x40, 0x80), getTex(rgba8, b(0x40, 0x40, 0x40, 0x80), GL11.GL_LUMINANCE_ALPHA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0x40), readPix(SDL_GPU_TEXTUREFORMAT_R8_UNORM, b(0x40), GL11.GL_LUMINANCE, GL11.GL_UNSIGNED_BYTE));
}
@Test
void luminanceSumClampsForFixedPointOnly() {
assertArrayEquals(b(255), readPix(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, b(0xFF, 0xFF, 0, 0), GL11.GL_LUMINANCE, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(f32(1.0f), readPix(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, b(0xFF, 0xFF, 0, 0), GL11.GL_LUMINANCE, GL11.GL_FLOAT));
assertArrayEquals(f32(1.5f), readPix(SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, f32(0.5f, 0.75f, 0.25f, 1.0f), GL11.GL_LUMINANCE, GL11.GL_FLOAT));
}
@Test
void clampVersusNoClamp() {
final byte[] half = le16(0x3400, 0x3800, 0x3C00, 0x4000);
assertArrayEquals(b(64, 128, 255, 255), readPix(SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT, half, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertArrayEquals(b(0), readPix(SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT, le16(0xC000, 0x3C00), GL11.GL_RED, GL11.GL_UNSIGNED_BYTE));
final byte[] f = f32(-1.0f, 0.0f, 0.5f, 3.0f);
assertArrayEquals(le16(0, 0, 16384, 32767), readPix(SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, f, GL11.GL_RGBA, GL11.GL_SHORT));
assertArrayEquals(le16(0x8001, 0, 16384, 32767), getTex(SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, f, GL11.GL_RGBA, GL11.GL_SHORT));
assertArrayEquals(f, getTex(SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, f, GL11.GL_RGBA, GL11.GL_FLOAT));
}
private static byte[] packR8(byte[] src, int w, int h, int format, int type, PackState ps, boolean flip, int dstSize) {
final ByteBuffer s = direct(src, src.length);
final ByteBuffer d = direct(new byte[0], dstSize);
PixelPack.packColor(MemoryUtil.memAddress(s), SDL_GPU_TEXTUREFORMAT_R8_UNORM, GL11.GL_RED, w, w, h, flip, true, format, type, ps, MemoryUtil.memAddress(d));
return read(d, dstSize);
}
@Test
void packAlignment() {
final byte[] src = b(1, 2, 3, 4, 5, 6);
assertArrayEquals(b(1, 2, 3, 4, 5, 6, SENTINEL), packR8(src, 3, 2, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, ps(1), false, 7));
assertEquals(6L, PixelPack.imageSize(GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 3, 2, ps(1)));
assertArrayEquals(b(1, 2, 3, SENTINEL, 4, 5, 6, SENTINEL), packR8(src, 3, 2, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, ps(4), false, 8));
assertEquals(7L, PixelPack.imageSize(GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 3, 2, ps(4)));
assertArrayEquals(b(1, 2, 3, SENTINEL, SENTINEL, SENTINEL, SENTINEL, SENTINEL, 4, 5, 6, SENTINEL), packR8(src, 3, 2, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, ps(8), false, 12));
assertEquals(11L, PixelPack.imageSize(GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 3, 2, ps(8)));
assertEquals(12, PixelPack.rowStride(GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, 3, ps(4)));
assertEquals(24, PixelPack.rowStride(GL30.GL_RG, GL11.GL_FLOAT, 3, ps(8)));
assertEquals(24, PixelPack.rowStride(GL30.GL_DEPTH_STENCIL, GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV, 3, ps(4)));
}
@Test
void packRowLengthAndSkips() {
final PackState rl = ps(1);
rl.rowLength = 5;
assertArrayEquals(b(1, 2, SENTINEL, SENTINEL, SENTINEL, 3, 4, SENTINEL), packR8(b(1, 2, 3, 4), 2, 2, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, rl, false, 8));
assertEquals(7L, PixelPack.imageSize(GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 2, 2, rl));
final PackState skip = ps(1);
skip.rowLength = 4;
skip.skipRows = 2;
skip.skipPixels = 1;
final byte[] out = packR8(b(7, 8), 2, 1, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, skip, false, 12);
final byte[] expected = b(SENTINEL, SENTINEL, SENTINEL, SENTINEL, SENTINEL, SENTINEL, SENTINEL, SENTINEL, SENTINEL, 7, 8, SENTINEL);
assertArrayEquals(expected, out);
assertEquals(11L, PixelPack.imageSize(GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, 2, 1, skip));
final PackState rgbSkip = ps(4);
rgbSkip.skipPixels = 1;
assertArrayEquals(b(SENTINEL, SENTINEL, SENTINEL, 9, 0, 0, SENTINEL), packR8(b(9), 1, 1, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, rgbSkip, false, 7));
}
@Test
void packFlipRows() {
assertArrayEquals(b(3, 4, 1, 2), packR8(b(1, 2, 3, 4), 2, 2, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, ps(1), true, 4));
assertArrayEquals(b(3, 0, 4, 0, 1, 0, 2, 0), packR8(b(1, 2, 3, 4), 2, 2, GL30.GL_RG, GL11.GL_UNSIGNED_BYTE, ps(1), true, 8));
}
@Test
void packFlipRowsWithChannelSwap() {
final ByteBuffer s = direct(b(0xAA, 0x01, 0x02, 0x03, 0xBB, 0x10, 0x20, 0x30), 8);
final ByteBuffer d = direct(new byte[0], 8);
PixelPack.packColor(MemoryUtil.memAddress(s), SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, GL11.GL_RGBA, 4, 1, 2, true, true, GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, ps(4), MemoryUtil.memAddress(d));
assertArrayEquals(b(0x20, 0x10, 0xBB, 0x30, 0x02, 0x01, 0xAA, 0x03), read(d, 8));
assertArrayEquals(b(223, 164, 64, 255), readPix(SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, b(223, 164, 64, 255), GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE));
}
@Test
void packSwapBytes() {
final PackState swap = ps(1);
swap.swapBytes = true;
final ByteBuffer s16 = direct(le16(0x1234, 0xABCD), 4);
final ByteBuffer d16 = direct(new byte[0], 4);
PixelPack.packColor(MemoryUtil.memAddress(s16), SDL_GPU_TEXTUREFORMAT_R16G16_UNORM, GL30.GL_RG, 4, 1, 1, false, true, GL30.GL_RG, GL11.GL_UNSIGNED_SHORT, swap, MemoryUtil.memAddress(d16));
assertArrayEquals(b(0x12, 0x34, 0xAB, 0xCD), read(d16, 4));
final ByteBuffer s32 = direct(f32(1.0f), 4);
final ByteBuffer d32 = direct(new byte[0], 4);
PixelPack.packColor(MemoryUtil.memAddress(s32), SDL_GPU_TEXTUREFORMAT_R32_FLOAT, GL11.GL_RED, 4, 1, 1, false, true, GL11.GL_RED, GL11.GL_FLOAT, swap, MemoryUtil.memAddress(d32));
assertArrayEquals(b(0x3F, 0x80, 0x00, 0x00), read(d32, 4));
final ByteBuffer s8888 = direct(b(0xFF, 0x80, 0x00, 0x40), 4);
final ByteBuffer d8888 = direct(new byte[0], 4);
PixelPack.packColor(MemoryUtil.memAddress(s8888), SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, GL11.GL_RGBA, 4, 1, 1, false, true, GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_8_8_8_8, swap, MemoryUtil.memAddress(d8888));
assertArrayEquals(b(0xFF, 0x80, 0x00, 0x40), read(d8888, 4));
final ByteBuffer s565 = direct(b(0xFF, 0x80, 0x00, 0x40), 4);
final ByteBuffer d565 = direct(new byte[0], 2);
PixelPack.packColor(MemoryUtil.memAddress(s565), SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, GL11.GL_RGBA, 4, 1, 1, false, true, GL11.GL_RGB, GL12.GL_UNSIGNED_SHORT_5_6_5, swap, MemoryUtil.memAddress(d565));
assertArrayEquals(b(0xFC, 0x00), read(d565, 2));
final ByteBuffer sub = direct(b(0x11, 0x22, 0x33, 0x44), 4);
final ByteBuffer dub = direct(new byte[0], 4);
PixelPack.packColor(MemoryUtil.memAddress(sub), SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, GL11.GL_RGBA, 4, 1, 1, false, true, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, swap, MemoryUtil.memAddress(dub));
assertArrayEquals(b(0x11, 0x22, 0x33, 0x44), read(dub, 4));
}
private static byte[] depth(int type, int outBytes, PackState ps, boolean flip, float... z) {
final ByteBuffer s = direct(f32(z), z.length * 4);
final ByteBuffer d = direct(new byte[0], outBytes);
PixelPack.packDepth(MemoryUtil.memAddress(s), z.length * 4, z.length, 1, flip, type, ps, MemoryUtil.memAddress(d));
return read(d, outBytes);
}
@Test
void packDepthTypes() {
final float[] z = {0.0f, 0.5f, 1.0f, 0.25f};
assertArrayEquals(f32(z), depth(GL11.GL_FLOAT, 16, ps(4), false, z));
assertArrayEquals(le32(0, 0x7FFFFFFF, 0xFFFFFFFF, 0x3FFFFFFF), depth(GL11.GL_UNSIGNED_INT, 16, ps(4), false, z));
assertArrayEquals(le16(0, 0x8000, 0xFFFF, 0x4000), depth(GL11.GL_UNSIGNED_SHORT, 8, ps(4), false, z));
assertArrayEquals(b(0, 127, 255, 63), depth(GL11.GL_UNSIGNED_BYTE, 4, ps(4), false, z));
assertArrayEquals(b(0, 63, 127, 31), depth(GL11.GL_BYTE, 4, ps(4), false, z));
assertArrayEquals(le16(0, 16383, 32767, 8191), depth(GL11.GL_SHORT, 8, ps(4), false, z));
assertArrayEquals(le32(0, 1073741823, 2147483647, 536870911), depth(GL11.GL_INT, 16, ps(4), false, z));
assertArrayEquals(le16(0x0000, 0x3800, 0x3C00, 0x3400), depth(GL30.GL_HALF_FLOAT, 8, ps(4), false, z));
}
@Test
void packDepthFlipAndSwap() {
final ByteBuffer s = direct(f32(0.0f, 1.0f), 8);
final ByteBuffer d = direct(new byte[0], 4);
final PackState swap = ps(1);
swap.swapBytes = true;
PixelPack.packDepth(MemoryUtil.memAddress(s), 4, 1, 2, true, GL11.GL_UNSIGNED_SHORT, swap, MemoryUtil.memAddress(d));
assertArrayEquals(b(0xFF, 0xFF, 0x00, 0x00), read(d, 4));
}
private static byte[] stencil(int type, int outBytes) {
final ByteBuffer s = direct(b(0, 1, 0x80, 0xFF), 4);
final ByteBuffer d = direct(new byte[0], outBytes);
PixelPack.packStencil(MemoryUtil.memAddress(s), 4, 4, 1, false, type, ps(4), MemoryUtil.memAddress(d));
return read(d, outBytes);
}
@Test
void packStencilTypes() {
assertArrayEquals(b(0, 1, 0x80, 0xFF), stencil(GL11.GL_UNSIGNED_BYTE, 4));
assertArrayEquals(b(0, 1, 0x00, 0x7F), stencil(GL11.GL_BYTE, 4));
assertArrayEquals(le16(0, 1, 128, 255), stencil(GL11.GL_UNSIGNED_SHORT, 8));
assertArrayEquals(le16(0, 1, 128, 255), stencil(GL11.GL_SHORT, 8));
assertArrayEquals(le32(0, 1, 128, 255), stencil(GL11.GL_UNSIGNED_INT, 16));
assertArrayEquals(le32(0, 1, 128, 255), stencil(GL11.GL_INT, 16));
assertArrayEquals(f32(0.0f, 1.0f, 128.0f, 255.0f), stencil(GL11.GL_FLOAT, 16));
assertArrayEquals(le16(0x0000, 0x3C00, 0x5800, 0x5BF8), stencil(GL30.GL_HALF_FLOAT, 8));
}
@Test
void packStencilFlipRows() {
final ByteBuffer s = direct(b(1, 2, 3, 4), 4);
final ByteBuffer d = direct(new byte[0], 8);
PixelPack.packStencil(MemoryUtil.memAddress(s), 2, 2, 2, true, GL11.GL_UNSIGNED_BYTE, ps(4), MemoryUtil.memAddress(d));
assertArrayEquals(b(3, 4, SENTINEL, SENTINEL, 1, 2, SENTINEL, SENTINEL), read(d, 8));
}
private static byte[] depthStencil(int type, int outBytes, PackState ps) {
final ByteBuffer z = direct(f32(1.0f, 0.5f), 8);
final ByteBuffer st = direct(b(0xFF, 0x12), 2);
final ByteBuffer d = direct(new byte[0], outBytes);
PixelPack.packDepthStencil(MemoryUtil.memAddress(z), MemoryUtil.memAddress(st), 8, 2, 2, 1, false, type, ps, MemoryUtil.memAddress(d));
return read(d, outBytes);
}
@Test
void packDepthStencilTypes() {
assertArrayEquals(le32(0xFFFFFFFF, 0x7FFFFF12), depthStencil(GL30.GL_UNSIGNED_INT_24_8, 8, ps(4)));
final byte[] f = new byte[16];
System.arraycopy(f32(1.0f), 0, f, 0, 4);
System.arraycopy(le32(0xFF), 0, f, 4, 4);
System.arraycopy(f32(0.5f), 0, f, 8, 4);
System.arraycopy(le32(0x12), 0, f, 12, 4);
assertArrayEquals(f, depthStencil(GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV, 16, ps(4)));
final PackState swap = ps(4);
swap.swapBytes = true;
assertArrayEquals(b(0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0x12), depthStencil(GL30.GL_UNSIGNED_INT_24_8, 8, swap));
}
@Test
void validateRejectsBadCombinations() {
assertThrows(IllegalArgumentException.class, () -> PixelPack.validate(GL11.GL_RGBA, GL12.GL_UNSIGNED_SHORT_5_6_5));
assertThrows(IllegalArgumentException.class, () -> PixelPack.validate(GL12.GL_BGR, GL12.GL_UNSIGNED_SHORT_5_6_5));
assertThrows(IllegalArgumentException.class, () -> PixelPack.validate(GL11.GL_RGBA, GL30.GL_UNSIGNED_INT_10F_11F_11F_REV));
assertThrows(IllegalArgumentException.class, () -> PixelPack.validate(GL30.GL_RGB_INTEGER, GL11.GL_FLOAT));
assertThrows(IllegalArgumentException.class, () -> PixelPack.validate(GL30.GL_DEPTH_STENCIL, GL11.GL_UNSIGNED_INT));
assertThrows(IllegalArgumentException.class, () -> PixelPack.validate(GL11.GL_DEPTH_COMPONENT, GL30.GL_UNSIGNED_INT_24_8));
assertThrows(IllegalArgumentException.class, () -> PixelPack.validate(GL11.GL_RGBA, GL11.GL_DOUBLE));
assertThrows(IllegalArgumentException.class, () -> PixelPack.validate(GL11.GL_RGBA, GL30.GL_UNSIGNED_INT_5_9_9_9_REV));
assertDoesNotThrow(() -> PixelPack.validate(GL30.GL_RGBA_INTEGER, GL12.GL_UNSIGNED_INT_2_10_10_10_REV));
assertDoesNotThrow(() -> PixelPack.validate(GL11.GL_STENCIL_INDEX, GL11.GL_FLOAT));
assertDoesNotThrow(() -> PixelPack.validate(GL30.GL_DEPTH_STENCIL, GL30.GL_UNSIGNED_INT_24_8));
assertDoesNotThrow(() -> PixelPack.validate(GL11.GL_RGB, GL30.GL_UNSIGNED_INT_10F_11F_11F_REV));
}
@Test
void packColorRejectsIntegerMismatchAndUnknownSources() {
assertThrows(IllegalArgumentException.class, () -> readPix(SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, f32(0, 0, 0, 0), GL30.GL_RGBA_INTEGER, GL11.GL_UNSIGNED_BYTE));
assertThrows(IllegalArgumentException.class, () -> readPix(SDL_GPU_TEXTUREFORMAT_R8_UINT, b(1), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertThrows(IllegalArgumentException.class, () -> readPix(SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM, b(0, 0, 0, 0), GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE));
assertThrows(IllegalArgumentException.class, () -> readPix(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, b(0, 0, 0, 0), GL11.GL_DEPTH_COMPONENT, GL11.GL_FLOAT));
}
@Test
void formatClassifiers() {
assertEquals(true, PixelPack.isDepthFormat(GL11.GL_DEPTH_COMPONENT));
assertEquals(true, PixelPack.isStencilFormat(GL11.GL_STENCIL_INDEX));
assertEquals(true, PixelPack.isDepthStencilFormat(GL30.GL_DEPTH_STENCIL));
assertEquals(false, PixelPack.isDepthFormat(GL11.GL_RGBA));
}
}
@@ -84,6 +84,7 @@ class ResourceManagerFormatMappingTest {
@Test
void testPackedFormats() {
assertEquals(SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT, map(GL30.GL_R11F_G11F_B10F));
assertEquals(SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM, map(GL11.GL_RGB10_A2));
}
@Test
@@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class SDLGPUFboTargetCacheTest {
@@ -140,6 +141,30 @@ class SDLGPUFboTargetCacheTest {
assertTrue(fbo.targetsDirty);
}
@Test
void sameTextureAtAnotherLevelChangesStructuralHash() {
final FboState fbo = new FboState();
fbo.colorTextures[0] = 0xAAAAL;
fbo.depthTexture = 0xDDDDL;
fbo.recomputeTargets();
final long level0 = fbo.structuralLayoutHash;
fbo.colorLevels[0] = 1;
fbo.recomputeTargets();
final long colorLevel1 = fbo.structuralLayoutHash;
assertNotEquals(level0, colorLevel1);
fbo.colorLevels[0] = 0;
fbo.depthLevel = 1;
fbo.recomputeTargets();
assertNotEquals(level0, fbo.structuralLayoutHash);
assertNotEquals(colorLevel1, fbo.structuralLayoutHash);
fbo.depthLevel = 0;
fbo.recomputeTargets();
assertEquals(level0, fbo.structuralLayoutHash);
}
@Test
void detachColor_outOfBounds_noOp() {
final FboState fbo = new FboState();
@@ -56,15 +56,6 @@ class StencilClearTrackingTest {
assertTrue(FBOClearTracker.fboHasPendingClear(st, fbo), "the pass must be rebuilt to apply the stencil clear");
}
@Test
void recordingAStencilClearRetractsTheClearedThisFrameMark() {
final ContextState st = new ContextState();
st.clearedStencilTexturesThisFrame.add(DEPTH);
FBOClearTracker.recordPendingStencilClear(st, DEPTH, 0);
assertFalse(st.clearedStencilTexturesThisFrame.contains(DEPTH), "a second clear in one frame must not be swallowed");
}
@Test
void recordingBumpsMutationGen() {
final ContextState st = new ContextState();
@@ -79,7 +70,6 @@ class StencilClearTrackingTest {
void scrubDropsStencilStateWithTheTexture() {
final ContextState st = new ContextState();
FBOClearTracker.recordPendingStencilClear(st, DEPTH, 7);
st.clearedStencilTexturesThisFrame.add(DEPTH);
final SdlTestRig rig = SdlTestRig.create();
final int glId = rig.resourceManager.genTexture();
@@ -88,7 +78,6 @@ class StencilClearTrackingTest {
assertFalse(st.pendingStencilTextures.contains(DEPTH));
assertFalse(st.pendingStencilValues.containsKey(DEPTH));
assertFalse(st.clearedStencilTexturesThisFrame.contains(DEPTH));
}
@Test
@@ -0,0 +1,210 @@
package com.gtnewhorizons.angelica.sdlgpu.shader;
import com.gtnewhorizons.angelica.glsm.shader.SpirvCompiler;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.util.shaderc.Shaderc;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.function.IntBinaryOperator;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.lwjgl.sdl.SDLGPU.*;
import static org.lwjgl.system.MemoryUtil.memFree;
class LogicOpLoweringTest {
private static final int OR_REVERSE = 13;
private static final int XOR = 6;
private static long key(int op, int... locClassPairs) {
long k = op;
for (int i = 0; i < locClassPairs.length; i += 2) k = LogicOpFormats.withClass(k, locClassPairs[i], locClassPairs[i + 1]);
return k;
}
private static String lower(String source, long key) {
final String pre = SpirvCompiler.preprocess(ShaderTransformChain.run(source, GL20.GL_FRAGMENT_SHADER), Shaderc.shaderc_fragment_shader, "logicop.frag", SpirvCompiler.Options.vulkanForced460Core());
assertNotNull(pre);
return LogicOpLowering.lower(pre, key, 7);
}
private static List<String> compileAndReflectSamplers(String src) {
final SpirvCompiler.Result r = SpirvCompiler.compile(src, Shaderc.shaderc_fragment_shader, "logicop.frag", SpirvCompiler.Options.vulkanForced460Core());
assertNotNull(r.spirv(), () -> "compile failed: " + r.error() + "\n" + src);
final ByteBuffer spirv = r.spirv();
try {
ShaderManager.remapSpirvForSDLGPU(spirv, GL20.GL_FRAGMENT_SHADER);
return ShaderManager.reflectStage(spirv, false).samplerNames();
} finally {
memFree(spirv);
}
}
@Test
void singleOutWithEarlyReturn() {
final int rgba8 = LogicOpFormats.classOf(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
final String out = lower("""
#version 330 core
uniform sampler2D tex;
in vec2 uv;
out vec4 color;
void main() {
color = texture(tex, uv);
if (color.a < 0.1) return;
color.rgb *= 0.5;
}
""", key(OR_REVERSE, 0, rgba8));
assertTrue(out.contains("void angelica_logicOpMain()"), out);
assertTrue(out.contains("#define ANGELICA_LOGIC_OP 13u"), out);
assertTrue(out.contains("uniform sampler2D angelica_LogicOpDst0;"), out);
assertTrue(out.contains("color = angelica_logicOp(color, angelica_LogicOpDst0, vec4(255.0, 255.0, 255.0, 255.0));"), out);
assertEquals(List.of("tex", "angelica_LogicOpDst0"), compileAndReflectSamplers(out));
}
@Test
void explicitLocationMrtSkipsPassthroughLocation() {
final int rgba8 = LogicOpFormats.classOf(SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM);
final int r16 = LogicOpFormats.classOf(SDL_GPU_TEXTUREFORMAT_R16_UNORM);
final String out = lower("""
#version 330 core
layout(location = 2) out vec4 c;
layout(location = 0) out vec4 a;
layout(location = 1) out vec4 b;
void main() { a = vec4(1.0); b = vec4(0.5); c = vec4(0.25); }
""", key(XOR, 0, rgba8, 2, r16));
assertTrue(out.contains("a = angelica_logicOp(a, angelica_LogicOpDst0, vec4(255.0, 255.0, 255.0, 255.0));"), out);
assertTrue(out.contains("c = angelica_logicOp(c, angelica_LogicOpDst2, vec4(65535.0, 0.0, 0.0, 0.0));"), out);
assertFalse(out.contains("angelica_LogicOpDst1"), out);
assertFalse(out.contains("b = angelica_logicOp"), out);
assertEquals(List.of("angelica_LogicOpDst0", "angelica_LogicOpDst2"), compileAndReflectSamplers(out));
}
@Test
void arrayOutExpandsToConsecutiveLocations() {
final int rgba8 = LogicOpFormats.classOf(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
final String out = lower("""
#version 330 core
#define N 3
layout(location = 1) out vec4 fragData[N];
void main() { fragData[0] = vec4(1.0); fragData[1] = vec4(0.0); fragData[2] = vec4(0.5); }
""", key(OR_REVERSE, 1, rgba8, 3, rgba8));
assertTrue(out.contains("fragData[0] = angelica_logicOp(fragData[0], angelica_LogicOpDst1,"), out);
assertTrue(out.contains("fragData[2] = angelica_logicOp(fragData[2], angelica_LogicOpDst3,"), out);
assertFalse(out.contains("fragData[1] = angelica_logicOp"), out);
assertEquals(List.of("angelica_LogicOpDst1", "angelica_LogicOpDst3"), compileAndReflectSamplers(out));
}
@Test
void narrowOutputIsWidenedAndNarrowed() {
final int r8 = LogicOpFormats.classOf(SDL_GPU_TEXTUREFORMAT_R8_UNORM);
final String out = lower("""
#version 330 core
out float v;
void main() { v = 0.5; }
""", key(XOR, 0, r8));
assertTrue(out.contains("v = angelica_logicOp(vec4(v, 0.0, 0.0, 0.0), angelica_LogicOpDst0, vec4(255.0, 0.0, 0.0, 0.0)).x;"), out);
assertEquals(List.of("angelica_LogicOpDst0"), compileAndReflectSamplers(out));
}
@Test
void compatTransformedOutputWithDefaultUniforms() {
final int rgba8 = LogicOpFormats.classOf(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
final String out = lower("""
#version 330 core
layout (location = 0) out vec4 angelica_FragData0;
uniform float angelica_currentAlphaTest;
uniform sampler2D tex;
uniform vec4 tint;
in vec2 uv;
void main() {
angelica_FragData0 = texture(tex, uv) * tint;
if (angelica_FragData0.a <= angelica_currentAlphaTest) discard;
}
""", key(OR_REVERSE, 0, rgba8));
assertTrue(out.contains("angelica_FragData0 = angelica_logicOp(angelica_FragData0, angelica_LogicOpDst0,"), out);
assertEquals(List.of("tex", "angelica_LogicOpDst0"), compileAndReflectSamplers(out));
}
@Test
void ambiguousOutsThrow() {
final int rgba8 = LogicOpFormats.classOf(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
final IllegalStateException e = assertThrows(IllegalStateException.class, () -> lower("""
#version 330 core
out vec4 a;
out vec4 b;
void main() { a = vec4(1.0); b = vec4(0.0); }
""", key(XOR, 0, rgba8)));
assertTrue(e.getMessage().contains("program 7"), e.getMessage());
}
@Test
void mesaOpMatchesTruthTable() {
final IntBinaryOperator[] gl = {
(s, d) -> 0,
(s, d) -> s & d,
(s, d) -> s & ~d,
(s, d) -> s,
(s, d) -> ~s & d,
(s, d) -> d,
(s, d) -> s ^ d,
(s, d) -> s | d,
(s, d) -> ~(s | d),
(s, d) -> ~(s ^ d),
(s, d) -> ~d,
(s, d) -> s | ~d,
(s, d) -> ~s,
(s, d) -> ~s | d,
(s, d) -> ~(s & d),
(s, d) -> ~0,
};
for (int i = 0; i < 16; i++) {
int mode = 0;
for (int s = 0; s < 2; s++) {
for (int d = 0; d < 2; d++) {
mode |= (gl[i].applyAsInt(s, d) & 1) << (2 * s + d);
}
}
assertEquals(mode, LogicOpFormats.mesaOp(GL11.GL_CLEAR + i), "GL opcode 0x" + Integer.toHexString(GL11.GL_CLEAR + i));
}
assertEquals(LogicOpFormats.OP_COPY, LogicOpFormats.mesaOp(GL11.GL_COPY));
assertEquals(1, LogicOpFormats.mesaOp(GL11.GL_NOR));
assertEquals(2, LogicOpFormats.mesaOp(GL11.GL_AND_INVERTED));
assertEquals(4, LogicOpFormats.mesaOp(GL11.GL_AND_REVERSE));
assertEquals(8, LogicOpFormats.mesaOp(GL11.GL_AND));
}
@Test
void formatClasses() {
final int rgba = LogicOpFormats.classOf(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM);
final int bgra = LogicOpFormats.classOf(SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM);
assertTrue(rgba > 0);
assertEquals(rgba, bgra);
for (int c = 0; c < 4; c++) assertEquals(255.0f, LogicOpFormats.channelMax(rgba, c));
final int b5g6r5 = LogicOpFormats.classOf(SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM);
assertEquals(31.0f, LogicOpFormats.channelMax(b5g6r5, 0));
assertEquals(63.0f, LogicOpFormats.channelMax(b5g6r5, 1));
assertEquals(0.0f, LogicOpFormats.channelMax(b5g6r5, 3));
assertEquals(0, LogicOpFormats.classOf(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB));
assertEquals(0, LogicOpFormats.classOf(SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT));
assertEquals(0, LogicOpFormats.classOf(SDL_GPU_TEXTUREFORMAT_INVALID));
assertEquals(0.0f, LogicOpFormats.channelMax(0, 0));
assertThrows(UnsupportedOperationException.class, () -> LogicOpFormats.classOf(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM));
assertThrows(UnsupportedOperationException.class, () -> LogicOpFormats.classOf(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT));
}
@Test
void keyPacking() {
final long k = key(OR_REVERSE, 0, 1, 7, 11);
assertEquals(OR_REVERSE, LogicOpFormats.opOf(k));
assertEquals(1, LogicOpFormats.classAt(k, 0));
assertEquals(0, LogicOpFormats.classAt(k, 3));
assertEquals(11, LogicOpFormats.classAt(k, 7));
}
}
@@ -1,7 +1,6 @@
package com.gtnewhorizons.angelica;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import com.gtnewhorizons.angelica.proxy.CommonProxy;
import cpw.mods.fml.common.Mod;
import cpw.mods.fml.common.SidedProxy;
@@ -32,7 +31,7 @@ public class AngelicaMod {
public static final String MOD_ID = "angelica";
public static final Logger LOGGER = LogManager.getLogger("Angelica");
public static final boolean lwjglDebug = SystemProperties.LWJGL_DEBUG;
public static final boolean tracyEnabled = Tracy.ENABLED;
public static final String KEY_CATEGORY = "key.categories.angelica";
@SidedProxy(
clientSide = "com.gtnewhorizons.angelica.proxy.ClientProxy",
@@ -17,6 +17,7 @@ import com.gtnewhorizons.angelica.glsm.streaming.PersistentStreamingBuffer;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
import com.gtnewhorizons.angelica.rendering.tesr.BatchDrawDefaults;
import com.gtnewhorizons.angelica.rendering.tesr.ModelPartBatcher;
import com.gtnewhorizons.angelica.rendering.tesr.TesrBatchRenderer;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
@@ -28,6 +29,7 @@ import net.coderbot.iris.layer.GbufferPrograms;
import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
import net.coderbot.iris.pipeline.PipelineManager;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.MathHelper;
@@ -82,6 +84,7 @@ public class BatchingFontRenderer {
private final int AAMode;
private final int AAStrength;
private final int fontBrightness;
private final int alphaTestRefLocation;
private final int mvpMatrixLocation;
private final int lightmapLocation;
@@ -125,10 +128,10 @@ public class BatchingFontRenderer {
FontProviderMC.get(this.isSGA).charWidth = this.charWidth;
FontProviderMC.get(this.isSGA).locationFontTexture = this.locationFontTexture;
//noinspection deprecation
fontShaderId = FontAAShader.getProgram().getProgramId();
AAMode = GLStateManager.glGetUniformLocation(fontShaderId, "aaMode");
AAStrength = GLStateManager.glGetUniformLocation(fontShaderId, "strength");
fontBrightness = GLStateManager.glGetUniformLocation(fontShaderId, "fontBrightness");
alphaTestRefLocation = GLStateManager.glGetUniformLocation(fontShaderId, "alphaTestRef");
mvpMatrixLocation = GLStateManager.glGetUniformLocation(fontShaderId, "u_MVPMatrix");
lightmapLocation = GLStateManager.glGetUniformLocation(fontShaderId, "u_Lightmap");
@@ -143,8 +146,6 @@ public class BatchingFontRenderer {
// === Batched rendering
private static final int INITIAL_BATCH_SIZE = 2048;
private static final ResourceLocation DUMMY_RESOURCE_LOCATION = new ResourceLocation("angelica$dummy",
"this is invalid!");
// Layout in data:
// [v, v, t, t, c, c, c, c, tb, tb, tb, tb]
@@ -153,6 +154,7 @@ public class BatchingFontRenderer {
/** {@code OpenGlHelper.lightmapTexUnit} */
private static final int LIGHTMAP_TEX_UNIT = 1;
private static final int FULL_BRIGHT_LIGHT = GLSMConfig.packBrightness(240.0f, 240.0f);
private static int rawCapacity = INITIAL_BATCH_SIZE * VERTEX_SIZE;
private static ByteBuffer vertexData = memAlloc(rawCapacity);
private static long vertexDataAddress = memAddress0(vertexData);
@@ -188,6 +190,7 @@ public class BatchingFontRenderer {
private float lightmapV = 0.0f;
private boolean lightmapActive = false;
private int lightmapTextureId = 0;
private int lightmapPackedLight = -1;
private final Vector3f lightingFactor = new Vector3f(1.0f, 1.0f, 1.0f);
private boolean lightingFactorActive = false;
@@ -214,7 +217,8 @@ public class BatchingFontRenderer {
private void captureLightmapState() {
final int unitBinding = GLStateManager.getTextures().getTextureUnitBindings(LIGHTMAP_TEX_UNIT).getBinding();
final boolean active = isWorldSpaceText() && unitBinding != 0;
final boolean active = isWorldSpaceText() && unitBinding != 0
&& GLStateManager.getTextures().getTextureUnitStates(LIGHTMAP_TEX_UNIT).isEnabled();
float u = 0.0f;
float v = 0.0f;
@@ -226,8 +230,9 @@ public class BatchingFontRenderer {
v = scratchLightmapUv.y;
texture = unitBinding;
}
final int packedLight = active ? GLSMConfig.packedLastBrightness() : FULL_BRIGHT_LIGHT;
if (active == lightmapActive && u == lightmapU && v == lightmapV && texture == lightmapTextureId) {
if (active == lightmapActive && u == lightmapU && v == lightmapV && texture == lightmapTextureId && packedLight == lightmapPackedLight) {
return;
}
sealBatchSegment();
@@ -235,6 +240,7 @@ public class BatchingFontRenderer {
lightmapU = u;
lightmapV = v;
lightmapTextureId = texture;
lightmapPackedLight = packedLight;
}
private static final class TextSegment {
@@ -251,6 +257,7 @@ public class BatchingFontRenderer {
float normalX, normalY, normalZ;
int blockEntityId;
boolean depthTest;
boolean depthMask;
}
// 16-bit EBO index range
@@ -261,6 +268,7 @@ public class BatchingFontRenderer {
private int deferredCmdWatermark;
private static final ObjectArrayList<TextSegment> deferredSegments = ObjectArrayList.wrap(new TextSegment[16], 0);
private static final BatchDrawDefaults DEFERRED_DRAW_DEFAULTS = new BatchDrawDefaults();
private static final ObjectArrayList<TextSegment> deferredSegmentPool = ObjectArrayList.wrap(new TextSegment[16], 0);
private final Matrix4f batchProj = new Matrix4f();
@@ -395,11 +403,23 @@ public class BatchingFontRenderer {
private static final int LAYER_BACKGROUND = -1;
private static final int LAYER_DEFAULT = 0;
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture, boolean isUnicode) {
/**
* FML's splash font renderer draws before the texture manager exists and binds its own
* font texture, so let it bind and read the id back.
*/
private int textureFor(FontProvider fontProvider, char chr) {
if (fontProvider instanceof FontProviderCustom || Minecraft.getMinecraft().getTextureManager() != null) {
return fontProvider.getTexture(chr);
}
((FontRendererAccessor) underlying).angelica$bindTexture(locationFontTexture);
return GLStateManager.getBoundTextureForServerState();
}
private void pushDrawCmd(int startIdx, int idxCount, int texture, boolean isUnicode) {
pushDrawCmd(startIdx, idxCount, texture, isUnicode, LAYER_DEFAULT);
}
private void pushDrawCmd(int startIdx, int idxCount, ResourceLocation texture, boolean isUnicode, int layer) {
private void pushDrawCmd(int startIdx, int idxCount, int texture, boolean isUnicode, int layer) {
arenaOwner = this;
// Never coalesce into a command below the watermark - it belongs to a sealed segment.
if (batchCommands.size() > batchSealedEnd) {
@@ -426,7 +446,7 @@ public class BatchingFontRenderer {
public int startVtx;
public int idxCount;
public boolean isUnicode;
public ResourceLocation texture;
public int texture;
/**
* Sorted ahead of the texture, so lower layers draw first. Sorting by texture
* means submission order is not kept, so anything belonging under the glyphs
@@ -434,11 +454,11 @@ public class BatchingFontRenderer {
*/
public int layer;
public void reset(int startVtx, int vtxCount, ResourceLocation texture, boolean isUnicode) {
public void reset(int startVtx, int vtxCount, int texture, boolean isUnicode) {
reset(startVtx, vtxCount, texture, isUnicode, LAYER_DEFAULT);
}
public void reset(int startVtx, int vtxCount, ResourceLocation texture, boolean isUnicode, int layer) {
public void reset(int startVtx, int vtxCount, int texture, boolean isUnicode, int layer) {
this.layer = layer;
this.startVtx = startVtx;
this.idxCount = vtxCount;
@@ -451,8 +471,7 @@ public class BatchingFontRenderer {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (FontDrawCmd) obj;
return this.startVtx == that.startVtx && this.idxCount == that.idxCount && Objects.equals(this.texture,
that.texture);
return this.startVtx == that.startVtx && this.idxCount == that.idxCount && this.texture == that.texture;
}
@Override
@@ -476,10 +495,8 @@ public class BatchingFontRenderer {
public static final Comparator<FontDrawCmd> DRAW_ORDER_COMPARATOR =
Comparator.comparingInt((FontDrawCmd fdc) -> fdc.layer)
.thenComparing(fdc -> fdc.texture,
Comparator.nullsLast(Comparator.comparing(ResourceLocation::getResourceDomain)
.thenComparing(ResourceLocation::getResourcePath)))
.thenComparing(fdc -> fdc.startVtx);
.thenComparingInt(fdc -> fdc.texture == 0 ? Integer.MAX_VALUE : fdc.texture)
.thenComparingInt(fdc -> fdc.startVtx);
}
/**
@@ -532,13 +549,14 @@ public class BatchingFontRenderer {
segment.lightmapU = lightmapU;
segment.lightmapV = lightmapV;
segment.lightmapTexture = lightmapTextureId;
segment.packedLight = GLSMConfig.packedLastBrightness();
segment.packedLight = lightmapPackedLight;
final Vector3f normal = ShaderManager.getCurrentNormal();
segment.normalX = normal.x;
segment.normalY = normal.y;
segment.normalZ = normal.z;
segment.blockEntityId = CapturedRenderingState.INSTANCE.getCurrentRenderedBlockEntity();
segment.depthTest = GLStateManager.getDepthTest().isEnabled();
segment.depthMask = GLStateManager.isEffectiveDepthMaskEnabled();
batchSegments.add(segment);
batchSealedEnd = end;
}
@@ -592,7 +610,7 @@ public class BatchingFontRenderer {
deferredIdxPos = 0;
truncateBatchToWatermark();
arenaOwner = null;
flushLastTexture = null;
flushLastTexture = 0;
}
private void deferBatch() {
@@ -617,21 +635,26 @@ public class BatchingFontRenderer {
public static void flushDeferredText() {
if (deferredSegments.isEmpty()) return;
if (deferredSegments.getFirst().owner.shouldDrawThroughPipeline()) {
flushDeferredThroughPipeline();
return;
}
final boolean locked = GLStateManager.acquireDrawLock();
DEFERRED_DRAW_DEFAULTS.apply();
try {
GLStateManager.beginForeignDraw();
if (deferredSegments.getFirst().owner.shouldDrawThroughPipeline()) {
flushDeferredThroughPipeline();
return;
}
final boolean locked = GLStateManager.acquireDrawLock();
try {
flushDeferredTextInner();
GLStateManager.beginForeignDraw();
try {
flushDeferredTextInner();
} finally {
GLStateManager.endForeignDraw();
}
} finally {
GLStateManager.endForeignDraw();
if (locked) GLStateManager.releaseDrawLock();
}
} finally {
if (locked) GLStateManager.releaseDrawLock();
DEFERRED_DRAW_DEFAULTS.restore();
}
}
@@ -641,11 +664,11 @@ public class BatchingFontRenderer {
final int d = GLStateManager.pushState(StateSet.FONT_PIPELINE);
try {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
GLStateManager.enableAlphaTest();
GLStateManager.enableBlend();
GLStateManager.tryBlendFuncSeparate(first.blendSrcRGB, first.blendDstRGB, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glDepthMask(false);
final Boolean prevTranslucency = GbufferPrograms.beginTranslucencyDeclaration(Boolean.TRUE);
GbufferPrograms.setBlockEntityDefaults();
@@ -654,6 +677,7 @@ public class BatchingFontRenderer {
GLStateManager.glPushMatrix();
try {
boolean curDepthTest = GLStateManager.getDepthTest().isEnabled();
boolean curDepthMask = GLStateManager.isEffectiveDepthMaskEnabled();
for (final TextSegment segment : deferredSegments) {
final BatchingFontRenderer owner = segment.owner;
if (segment.cmdStart == segment.cmdEnd) continue;
@@ -665,6 +689,10 @@ public class BatchingFontRenderer {
if (segment.depthTest) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest();
curDepthTest = segment.depthTest;
}
if (segment.depthMask != curDepthMask) {
GLStateManager.glDepthMask(segment.depthMask);
curDepthMask = segment.depthMask;
}
final FontDrawCmd[] cmdsData = owner.batchCommands.elements();
Arrays.sort(cmdsData, segment.cmdStart, segment.cmdEnd, FontDrawCmd.DRAW_ORDER_COMPARATOR);
@@ -688,13 +716,14 @@ public class BatchingFontRenderer {
final int d = GLStateManager.pushState(StateSet.FONT);
try {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
deferredSegments.get(0).owner.setupFontDrawState();
GLStateManager.glDepthMask(false);
flushLastTexture = DUMMY_RESOURCE_LOCATION;
flushLastTexture = -1;
resetFlushLightmap();
try (MemoryStack stack = stackPush()) {
final FloatBuffer mvpBuf = stack.mallocFloat(16);
boolean curDepthTest = GLStateManager.getDepthTest().isEnabled();
boolean curDepthMask = GLStateManager.isEffectiveDepthMaskEnabled();
for (final TextSegment segment : deferredSegments) {
mvpBuf.clear();
segment.mvp.get(mvpBuf);
@@ -705,6 +734,10 @@ public class BatchingFontRenderer {
if (segment.depthTest) GLStateManager.enableDepthTest(); else GLStateManager.disableDepthTest();
curDepthTest = segment.depthTest;
}
if (segment.depthMask != curDepthMask) {
GLStateManager.glDepthMask(segment.depthMask);
curDepthMask = segment.depthMask;
}
drawCommands(segment.owner.batchCommands.elements(), segment.cmdStart, segment.cmdEnd, segment.owner);
}
}
@@ -755,6 +788,7 @@ public class BatchingFontRenderer {
private static final Matrix4f scratchModelView = new Matrix4f();
private int fontAAModeLast = -1;
private int fontAAStrengthLast = -1;
private float fontBrightnessLast = -1;
private void flushBatch() {
final boolean locked = GLStateManager.acquireDrawLock();
@@ -813,7 +847,7 @@ public class BatchingFontRenderer {
final int d = GLStateManager.pushState(StateSet.FONT);
try {
setupFontDrawState();
flushLastTexture = DUMMY_RESOURCE_LOCATION;
flushLastTexture = -1;
resetFlushLightmap();
try (MemoryStack stack = stackPush()) {
final FloatBuffer mvpBuf = stack.mallocFloat(16);
@@ -860,6 +894,10 @@ public class BatchingFontRenderer {
fontAAStrengthLast = FontConfig.fontAAStrength;
GLStateManager.glUniform1f(AAStrength, FontConfig.fontAAStrength / 120.f);
}
if (FontConfig.fontBrightness != fontBrightnessLast) {
fontBrightnessLast = FontConfig.fontBrightness;
GLStateManager.glUniform1f(fontBrightness, 0.625f * FontConfig.fontBrightness + 0.1875f);
}
GLStateManager.glUniform1f(alphaTestRefLocation, GLStateManager.getAlphaState().getReference());
if (lightmapSamplerProgram != fontShaderId) {
GLStateManager.glUniform1i(lightmapSamplerLocation, LIGHTMAP_TEX_UNIT);
@@ -899,7 +937,7 @@ public class BatchingFontRenderer {
}
}
private static ResourceLocation flushLastTexture;
private static int flushLastTexture;
private static float flushLastLightmapU;
private static float flushLastLightmapV;
@@ -936,14 +974,14 @@ public class BatchingFontRenderer {
private static void drawCommands(FontDrawCmd[] cmdsData, int from, int to, BatchingFontRenderer owner) {
for (int i = from; i < to; i++) {
final FontDrawCmd cmd = cmdsData[i];
if (!Objects.equals(flushLastTexture, cmd.texture)) {
if (flushLastTexture == null) {
if (flushLastTexture != cmd.texture) {
if (flushLastTexture == 0) {
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
} else if (cmd.texture == null) {
} else if (cmd.texture == 0) {
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
}
if (cmd.texture != null) {
((FontRendererAccessor) owner.underlying).angelica$bindTexture(cmd.texture);
if (cmd.texture != 0) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, cmd.texture);
}
flushLastTexture = cmd.texture;
}
@@ -997,7 +1035,7 @@ public class BatchingFontRenderer {
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(prevBlockEntityId);
final Vector3f normal = ShaderManager.getCurrentNormal();
emitRangeThroughPipeline(this, cmdsData, batchSealedEnd, cmdCount,
GLSMConfig.packedLastBrightness(), normal.x, normal.y, normal.z);
lightmapPackedLight, normal.x, normal.y, normal.z);
}
} finally {
GLStateManager.glPopMatrix();
@@ -1014,21 +1052,21 @@ public class BatchingFontRenderer {
private static void emitRangeThroughPipeline(BatchingFontRenderer owner, FontDrawCmd[] cmdsData, int from, int to,
int packedLight, float normalX, float normalY, float normalZ) {
final Tessellator tessellator = Tessellator.instance;
ResourceLocation lastTexture = DUMMY_RESOURCE_LOCATION;
int lastTexture = -1;
boolean drawing = false;
try {
for (int i = from; i < to; i++) {
final FontDrawCmd cmd = cmdsData[i];
if (!Objects.equals(lastTexture, cmd.texture)) {
if (lastTexture != cmd.texture) {
if (drawing) {
tessellator.draw();
drawing = false;
}
if (cmd.texture == null) {
if (cmd.texture == 0) {
GLStateManager.disableTexture();
} else {
GLStateManager.enableTexture();
((FontRendererAccessor) owner.underlying).angelica$bindTexture(cmd.texture);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, cmd.texture);
}
lastTexture = cmd.texture;
}
@@ -1091,6 +1129,7 @@ public class BatchingFontRenderer {
lightmapU = 0.0f;
lightmapV = 0.0f;
lightmapTextureId = 0;
lightmapPackedLight = -1;
}
// === Actual text mesh generation
@@ -1242,7 +1281,7 @@ public class BatchingFontRenderer {
if (curUnderline && underlineStartX != underlineEndX) {
final int ulIdx = idxWriterIndex;
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
pushDrawCmd(ulIdx, 6, null, false);
pushDrawCmd(ulIdx, 6, 0, false);
underlineStartX = underlineEndX;
}
if (curStrikethrough && strikethroughStartX != strikethroughEndX) {
@@ -1253,7 +1292,7 @@ public class BatchingFontRenderer {
strikethroughEndX - strikethroughStartX,
glyphScaleY,
curColor);
pushDrawCmd(ulIdx, 6, null, false);
pushDrawCmd(ulIdx, 6, 0, false);
strikethroughStartX = strikethroughEndX;
}
@@ -1427,11 +1466,11 @@ public class BatchingFontRenderer {
final float glyphW = fontProvider.getGlyphW(chr) * glyphScaleX;
final float uSz = fontProvider.getUSize(chr);
final float vSz = fontProvider.getVSize(chr);
final float itOff = curItalic ? 1.0F : 0.0F; // italic offset
final float itOff = curItalic ? glyphScaleX : 0.0F; // italic offset
final float shadowOffset = fontProvider.getShadowOffset();
final int shadowCopies = FontConfig.shadowCopies;
final int boldCopies = FontConfig.boldCopies;
final ResourceLocation texture = fontProvider.getTexture(chr);
final int texture = textureFor(fontProvider, chr);
// Wave: Y offset via sine wave
float renderY = heightNorth;
@@ -1462,7 +1501,7 @@ public class BatchingFontRenderer {
if (glyphBackground != 0) {
final int bgIdx = idxWriterIndex;
pushUntexRect(renderX, renderY, glyphW, heightSouth, glyphBackground);
pushDrawCmd(bgIdx, 6, null, false, LAYER_BACKGROUND);
pushDrawCmd(bgIdx, 6, 0, false, LAYER_BACKGROUND);
}
// After the background, so this command covers only what is counted below.
@@ -1516,13 +1555,13 @@ public class BatchingFontRenderer {
if (perGlyphColor && curUnderline && underlineStartX != underlineEndX) {
final int ulIdx = idxWriterIndex;
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, glyphColor);
pushDrawCmd(ulIdx, 6, null, false);
pushDrawCmd(ulIdx, 6, 0, false);
underlineStartX = underlineEndX;
}
if (perGlyphColor && curStrikethrough && strikethroughStartX != strikethroughEndX) {
final int stIdx = idxWriterIndex;
pushUntexRect(strikethroughStartX, strikethroughY, strikethroughEndX - strikethroughStartX, glyphScaleY, glyphColor);
pushDrawCmd(stIdx, 6, null, false);
pushDrawCmd(stIdx, 6, 0, false);
strikethroughStartX = strikethroughEndX;
}
}
@@ -1530,7 +1569,7 @@ public class BatchingFontRenderer {
if (curUnderline && underlineStartX != underlineEndX) {
final int ulIdx = idxWriterIndex;
pushUntexRect(underlineStartX, underlineY, underlineEndX - underlineStartX, glyphScaleY, curColor);
pushDrawCmd(ulIdx, 6, null, false);
pushDrawCmd(ulIdx, 6, 0, false);
}
if (curStrikethrough && strikethroughStartX != strikethroughEndX) {
final int ulIdx = idxWriterIndex;
@@ -1540,7 +1579,7 @@ public class BatchingFontRenderer {
strikethroughEndX - strikethroughStartX,
glyphScaleY,
curColor);
pushDrawCmd(ulIdx, 6, null, false);
pushDrawCmd(ulIdx, 6, 0, false);
}
} finally {
@@ -1,7 +1,5 @@
package com.gtnewhorizons.angelica.client.font;
import net.minecraft.util.ResourceLocation;
public interface FontProvider {
/** Tries before {@link #getRandomReplacement} gives up and leaves the character alone. */
@@ -20,6 +18,6 @@ public interface FontProvider {
float getUSize(char chr);
float getVSize(char chr);
float getShadowOffset();
ResourceLocation getTexture(char chr);
int getTexture(char chr);
float getYScaleMultiplier();
}
@@ -1,34 +1,36 @@
package com.gtnewhorizons.angelica.client.font;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import com.gtnewhorizons.angelica.config.FontConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import jss.util.RandomXoshiro256StarStar;
import lombok.Setter;
import lombok.Value;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.ITextureObject;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.util.ResourceLocation;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import javax.imageio.ImageIO;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import java.awt.image.ColorModel;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
import java.nio.IntBuffer;
import java.util.Objects;
import static com.gtnewhorizons.angelica.client.font.FontStrategist.getFontName;
public final class FontProviderCustom implements FontProvider {
public static final Logger LOGGER = LogManager.getLogger("Angelica");
public static final String FONT_DIR = "fonts/custom/";
static final int ATLAS_SIZE = 128;
static final int ATLAS_COUNT = 512;
private final byte id; // 0 - primary font, 1 - fallback font
@@ -52,7 +54,7 @@ public final class FontProviderCustom implements FontProvider {
}
int fontPos = -1;
for (int i = 0; i < availableFonts.length; i++) {
if (Objects.equals(myFontName, availableFonts[i].getFontName())) {
if (Objects.equals(myFontName, getFontName(availableFonts[i]))) {
fontPos = i;
break;
}
@@ -75,45 +77,15 @@ public final class FontProviderCustom implements FontProvider {
this.currentFontQuality = FontConfig.customFontQuality;
this.font = FontStrategist.getAvailableFonts()[fontID].deriveFont(this.currentFontQuality);
File[] files = new File(getFontDir()).listFiles();
if (files != null) {
for (File f : files) {
if (!Files.isSymbolicLink(f.toPath())) {
f.delete();
}
}
}
TextureManager tm = Minecraft.getMinecraft().getTextureManager();
Map mapTextureObjects = tm.mapTextureObjects;
for (int i = 0; i < ATLAS_COUNT; i++) {
ResourceLocation key = new ResourceLocation(getAtlasResourceName(i));
if (mapTextureObjects.containsKey(key)) {
ITextureObject obj = (ITextureObject) mapTextureObjects.get(key);
TextureUtil.deleteTexture(obj.getGlTextureId());
mapTextureObjects.remove(key);
for (FontAtlas atlas : fontAtlases) {
if (atlas != null && atlas.texture != 0) {
GLStateManager.glDeleteTextures(atlas.texture);
}
}
this.fontAtlases = new FontAtlas[ATLAS_COUNT];
}
private String getFontDir() {
return FONT_DIR + "f" + this.id + "/";
}
private String getAtlasFilename(int atlasId) {
return "f" + this.id + "p" + atlasId;
}
String getAtlasResourceName(int atlasId) {
return "minecraft:angelica_c" + getAtlasFilename(atlasId);
}
String getAtlasFullPath(int atlasId) {
return getFontDir() + getAtlasFilename(atlasId) + ".png";
}
@Value
private class GlyphData {
float uStart;
@@ -125,9 +97,8 @@ public final class FontProviderCustom implements FontProvider {
}
private class FontAtlas {
GlyphData[] glyphData = new GlyphData[ATLAS_SIZE];
private ResourceLocation texture;
private int texture;
private final int id;
FontAtlas(int id) {
@@ -214,13 +185,46 @@ public final class FontProviderCustom implements FontProvider {
tileX++;
}
g2d.dispose();
try {
Files.createDirectories(Paths.get(getFontDir()));
ImageIO.write(image, "png", new File(getAtlasFullPath(this.id)));
} catch (IOException e) {
e.printStackTrace();
int id = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, id);
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.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
final int width = image.getWidth();
final int height = image.getHeight();
final IntBuffer pixelBuffer = memAllocInt(width * height);
long ptr = memAddress0(pixelBuffer);
if (image.getRaster().getDataBuffer() instanceof DataBufferInt dataBufferInt) {
final int[] pixelValues = dataBufferInt.getData();
for (int i : pixelValues) {
memPutInt(ptr, i);
ptr += 4;
}
pixelBuffer.limit(pixelValues.length);
} else {
Raster raster = image.getRaster();
ColorModel colorModel = image.getColorModel();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Object pixel = raster.getDataElements(x, y, null);
memPutInt(ptr, colorModel.getRGB(pixel));
ptr += 4;
}
}
pixelBuffer.limit(image.getHeight() * image.getWidth());
}
this.texture = new ResourceLocation(getAtlasResourceName(this.id));
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA, image.getWidth(), image.getHeight(), 0, GL12.GL_BGRA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixelBuffer);
memFree(pixelBuffer);
this.texture = id;
}
}
@@ -303,7 +307,7 @@ public final class FontProviderCustom implements FontProvider {
}
@Override
public ResourceLocation getTexture(char chr) {
public int getTexture(char chr) {
return getAtlas(chr).texture;
}
@@ -3,6 +3,10 @@ package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.FontConfig;
import it.unimi.dsi.fastutil.chars.Char2ShortOpenHashMap;
import jss.util.RandomXoshiro256StarStar;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.ITextureObject;
import net.minecraft.client.renderer.texture.SimpleTexture;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.ResourceLocation;
public final class FontProviderMC implements FontProvider {
@@ -109,8 +113,15 @@ public final class FontProviderMC implements FontProvider {
}
@Override
public ResourceLocation getTexture(char chr) {
return locationFontTexture;
public int getTexture(char chr) {
final TextureManager tm = Minecraft.getMinecraft().getTextureManager();
ITextureObject tex = tm.getTexture(locationFontTexture);
if (tex == null) {
tex = new SimpleTexture(locationFontTexture);
tm.loadTexture(locationFontTexture, tex);
tex = tm.getTexture(locationFontTexture);
}
return tex.getGlTextureId();
}
@Override
@@ -3,6 +3,9 @@ package com.gtnewhorizons.angelica.client.font;
import com.gtnewhorizons.angelica.config.FontConfig;
import jss.util.RandomXoshiro256StarStar;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.ITextureObject;
import net.minecraft.client.renderer.texture.SimpleTexture;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.resources.IReloadableResourceManager;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.IResourceManagerReloadListener;
@@ -79,12 +82,12 @@ public final class FontProviderUnicode implements FontProvider, IResourceManager
@Override
public float getUStart(char chr) {
final float startColumnF = (float)((this.glyphWidth[chr] >>> 4) & 15);
return ((float) (chr % 16 * 16) + startColumnF + 0.21f) / 256.0f;
return ((float) (chr % 16 * 16) + startColumnF + 0.01f) / 256.0f;
}
@Override
public float getVStart(char chr) {
return ((float) ((chr & 255) / 16 * 16) + 0.21f) / 256.0f;
return ((float) ((chr & 255) / 16 * 16) + 0.01f) / 256.0f;
}
@Override
@@ -113,12 +116,12 @@ public final class FontProviderUnicode implements FontProvider, IResourceManager
final float startColumnF = (float) startColumn;
final float endColumnF = (float) (endColumn + 1);
final float chrWidth = endColumnF - startColumnF - 0.02F;
return (chrWidth - 0.42f) / 256.0f;
return (chrWidth - 0.02f) / 256.0f;
}
@Override
public float getVSize(char chr) {
return (16.0f - 0.42f) / 256.0f;
return (16.0f - 0.02f) / 256.0f;
}
@Override
@@ -127,9 +130,17 @@ public final class FontProviderUnicode implements FontProvider, IResourceManager
}
@Override
public ResourceLocation getTexture(char chr) {
public int getTexture(char chr) {
final int uniPage = chr / 256;
return getUnicodePageLocation(uniPage);
ResourceLocation rl = getUnicodePageLocation(uniPage);
final TextureManager tm = Minecraft.getMinecraft().getTextureManager();
ITextureObject tex = tm.getTexture(rl);
if (tex == null) {
tex = new SimpleTexture(rl);
tm.loadTexture(rl, tex);
tex = tm.getTexture(rl);
}
return tex.getGlTextureId();
}
@Override
Loaded 100 of 311 files, more files were not shown because too many files have changed in this diff. Show more