Emulate color logic ops and fix pixel readback on SDL-GPU (#2157)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-09-27 15:33:04 -07:00
1 parent 74ea5c4709
commit 7bb5bfd45c
45 files changed
+4412 -329

No files matched your search

@@ -8009,6 +8009,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.
@@ -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);
@@ -393,6 +393,7 @@ public abstract class RenderBackend {
public abstract void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels);
public abstract void readPixels(int x, int y, int width, int height, int format, int type, long pixelBufferOffset);
public abstract void getTexImage(int target, int level, int format, int type, ByteBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, IntBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, long pixelBufferOffset);
@@ -0,0 +1,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();
}
}
@@ -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");
}
}
@@ -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), 1e-6f, () -> label + " float " + idx);
}
}
public static void assertShorts(ByteBuffer buf, int offset, int[] expected, String label) {
for (int i = 0; i < expected.length; i++) {
final int idx = i;
assertEquals(expected[i], buf.getShort(offset + i * 2) & 0xFFFF, () -> label + " short " + idx);
}
}
public static void rgba8() {
final int fbo = colorFbo(GL11.GL_RGBA8, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
clearColor(51 / 255f, 102 / 255f, 153 / 255f, 204 / 255f);
clearTopHalf(1f, 0f, 0f, 1f);
final ByteBuffer rgba = read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4);
assertBytes(rgba, 0, new int[] { 51, 102, 153, 204 }, "RGBA8 as RGBA/UBYTE bottom row");
assertBytes(rgba, (SIZE - 1) * SIZE * 4, new int[] { 255, 0, 0, 255 }, "RGBA8 as RGBA/UBYTE top row");
assertBytes(read(GL12.GL_BGRA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 153, 102, 51, 204 }, "RGBA8 as BGRA/UBYTE");
final ByteBuffer rev = read(GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_8_8_8_8_REV, SIZE * SIZE * 4);
assertEquals(0xCC996633, rev.getInt(0), "RGBA8 as RGBA/8_8_8_8_REV");
final ByteBuffer f = read(GL11.GL_RGBA, GL11.GL_FLOAT, SIZE * SIZE * 16);
assertFloatsNear(f, 0, new float[] { 51 / 255f, 102 / 255f, 153 / 255f, 204 / 255f }, "RGBA8 as RGBA/FLOAT");
final ByteBuffer rgb = read(GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, SIZE * 12);
assertBytes(rgb, 0, new int[] { 51, 102, 153, 51, 102, 153 }, "RGBA8 as RGB/UBYTE");
assertBytes(rgb, 12 * (SIZE - 1), new int[] { 255, 0, 0 }, "RGBA8 as RGB/UBYTE top row at 4-aligned stride");
assertBytes(read(GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE), 0, new int[] { 51, 51, 51, 51 }, "RGBA8 as RED/UBYTE");
release(fbo);
}
public static void packState() {
final int fbo = colorFbo(GL11.GL_RGBA8, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
clearColor(51 / 255f, 102 / 255f, 153 / 255f, 204 / 255f);
clearTopHalf(1f, 0f, 0f, 1f);
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
final ByteBuffer rgb = read(GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 3);
assertBytes(rgb, (SIZE - 1) * SIZE * 3, new int[] { 255, 0, 0 }, "PACK_ALIGNMENT 1 tightly packs RGB rows");
GLStateManager.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
GLStateManager.glPixelStorei(GL11.GL_PACK_ROW_LENGTH, 6);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_PIXELS, 1);
GLStateManager.glPixelStorei(GL11.GL_PACK_SKIP_ROWS, 2);
final int rowBytes = 6 * 4;
final ByteBuffer skipped = read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, rowBytes * (SIZE + 2));
assertBytes(skipped, 0, new int[] { 0x7E, 0x7E, 0x7E, 0x7E }, "skipped rows untouched");
assertBytes(skipped, 2 * rowBytes, new int[] { 0x7E, 0x7E, 0x7E, 0x7E }, "skipped pixel untouched");
assertBytes(skipped, 2 * rowBytes + 4, new int[] { 51, 102, 153, 204 }, "first pixel after skips");
assertBytes(skipped, (2 + SIZE - 1) * rowBytes + 4, new int[] { 255, 0, 0, 255 }, "top row at ROW_LENGTH stride");
assertBytes(skipped, 2 * rowBytes + 4 + SIZE * 4, new int[] { 0x7E, 0x7E, 0x7E, 0x7E }, "row padding untouched");
resetPack();
GLStateManager.glPixelStorei(GL11.GL_PACK_SWAP_BYTES, GL11.GL_TRUE);
final ByteBuffer swapped = read(GL11.GL_RGBA, GL30.GL_HALF_FLOAT, SIZE * SIZE * 8);
assertBytes(swapped, 0, new int[] { 0x32, 0x66 }, "SWAP_BYTES on HALF_FLOAT");
resetPack();
release(fbo);
}
public static void pixelPackBuffer() {
final int fbo = colorFbo(GL11.GL_RGBA8, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE);
clearColor(51 / 255f, 102 / 255f, 153 / 255f, 204 / 255f);
final int pbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, pbo);
GLStateManager.glBufferData(GL21.GL_PIXEL_PACK_BUFFER, 16 + SIZE * SIZE * 4, GL15.GL_STREAM_READ);
GLStateManager.glReadPixels(0, 0, SIZE, SIZE, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, 16L);
final ByteBuffer mapped = GLStateManager.glMapBuffer(GL21.GL_PIXEL_PACK_BUFFER, GL15.GL_READ_ONLY);
assertTrue(mapped != null, "PBO map");
assertBytes(mapped, 16, new int[] { 51, 102, 153, 204 }, "PBO readPixels at offset 16");
GLStateManager.glUnmapBuffer(GL21.GL_PIXEL_PACK_BUFFER);
GLStateManager.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
GLStateManager.glDeleteBuffers(pbo);
release(fbo);
}
public static void rgb8() {
final int fbo = colorFbo(GL11.GL_RGB8, GL11.GL_RGB, GL11.GL_UNSIGNED_BYTE);
clearColor(51 / 255f, 102 / 255f, 153 / 255f, 0.5f);
assertBytes(read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 51, 102, 153, 255 }, "RGB8 as RGBA reads alpha 1");
release(fbo);
}
public static void rgba16f() {
final int fbo = colorFbo(GL30.GL_RGBA16F, GL11.GL_RGBA, GL11.GL_FLOAT);
clearColor(0.25f, 0.625f, 0.75f, 2.0f);
assertFloats(read(GL11.GL_RGBA, GL11.GL_FLOAT, SIZE * SIZE * 16), 0, new float[] { 0.25f, 0.625f, 0.75f, 2.0f }, "RGBA16F as RGBA/FLOAT keeps >1");
assertShorts(read(GL11.GL_RGBA, GL30.GL_HALF_FLOAT, SIZE * SIZE * 8), 0, new int[] { 0x3400, 0x3900, 0x3A00, 0x4000 }, "RGBA16F as RGBA/HALF_FLOAT");
assertBytes(read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 64, 159, 191, 255 }, "RGBA16F as RGBA/UBYTE clamps");
release(fbo);
}
public static void rgba32f() {
final int fbo = colorFbo(GL30.GL_RGBA32F, GL11.GL_RGBA, GL11.GL_FLOAT);
clearColor(0.2f, -0.5f, 3.0f, 1.0f);
assertFloats(read(GL11.GL_RGBA, GL11.GL_FLOAT, SIZE * SIZE * 16), 0, new float[] { 0.2f, -0.5f, 3.0f, 1.0f }, "RGBA32F as RGBA/FLOAT");
assertBytes(read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 51, 0, 255, 255 }, "RGBA32F as RGBA/UBYTE clamps both ends");
release(fbo);
}
public static void r8() {
final int fbo = colorFbo(GL30.GL_R8, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE);
clearColor(0.2f, 0.9f, 0.9f, 0.9f);
assertBytes(read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 51, 0, 0, 255 }, "R8 as RGBA rebases G/B=0 A=1");
release(fbo);
}
public static void rg16f() {
final int fbo = colorFbo(GL30.GL_RG16F, GL30.GL_RG, GL11.GL_FLOAT);
clearColor(0.25f, 0.625f, 0.9f, 0.9f);
assertFloats(read(GL11.GL_RGBA, GL11.GL_FLOAT, SIZE * SIZE * 16), 0, new float[] { 0.25f, 0.625f, 0f, 1f }, "RG16F as RGBA/FLOAT");
release(fbo);
}
public static void rgb10a2() {
final int texel = (1 << 30) | (614 << 20) | (409 << 10) | 205;
final ByteBuffer data = BufferUtils.createByteBuffer(SIZE * SIZE * 4).order(ByteOrder.nativeOrder());
for (int i = 0; i < SIZE * SIZE; i++) data.putInt(i * 4, texel);
final int fbo = colorFbo(GL11.GL_RGB10_A2, GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_2_10_10_10_REV, data);
final ByteBuffer raw = read(GL11.GL_RGBA, GL12.GL_UNSIGNED_INT_2_10_10_10_REV, SIZE * SIZE * 4);
assertEquals(texel, raw.getInt(0), "RGB10A2 as RGBA/2_10_10_10_REV");
assertBytes(read(GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE * 4), 0, new int[] { 51, 102, 153, 85 }, "RGB10A2 as RGBA/UBYTE");
release(fbo);
}
public static void r11g11b10f() {
final int fbo = colorFbo(GL30.GL_R11F_G11F_B10F, GL11.GL_RGB, GL11.GL_FLOAT);
clearColor(0.25f, 0.625f, 1.5f, 1f);
assertFloats(read(GL11.GL_RGBA, GL11.GL_FLOAT, SIZE * SIZE * 16), 0, new float[] { 0.25f, 0.625f, 1.5f, 1f }, "R11G11B10F as RGBA/FLOAT");
release(fbo);
}
public static void depthStencil() {
final int fbo = depthStencilFbo();
GLStateManager.glDepthMask(true);
GLStateManager.glStencilMask(0xFF);
GLStateManager.glClearDepth(0.25);
GLStateManager.glClearStencil(0xC3);
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
GLStateManager.glScissor(0, SIZE / 2, SIZE, SIZE / 2);
GLStateManager.glClearDepth(0.75);
GLStateManager.glClearStencil(0x5A);
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT | GL11.GL_STENCIL_BUFFER_BIT);
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
GLStateManager.glClearDepth(1.0);
GLStateManager.glClearStencil(0);
final ByteBuffer depth = read(GL11.GL_DEPTH_COMPONENT, GL11.GL_FLOAT, SIZE * SIZE * 4);
assertEquals(0.25f, depth.getFloat(0), 1e-6f, "depth bottom row as FLOAT");
assertEquals(0.75f, depth.getFloat((SIZE - 1) * SIZE * 4), 1e-6f, "depth top row as FLOAT");
final ByteBuffer depthUs = read(GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_SHORT, SIZE * SIZE * 2);
assertEquals(16384, depthUs.getShort(0) & 0xFFFF, 1, "depth as UNSIGNED_SHORT");
final ByteBuffer depthUi = read(GL11.GL_DEPTH_COMPONENT, GL11.GL_UNSIGNED_INT, SIZE * SIZE * 4);
assertEquals(0x40000000L >>> 8, (depthUi.getInt(0) & 0xFFFFFFFFL) >>> 8, 1, "depth as UNSIGNED_INT (top 24 bits)");
final ByteBuffer stencil = read(GL11.GL_STENCIL_INDEX, GL11.GL_UNSIGNED_BYTE, SIZE * SIZE);
assertBytes(stencil, 0, new int[] { 0xC3, 0xC3, 0xC3, 0xC3 }, "stencil bottom row");
assertBytes(stencil, (SIZE - 1) * SIZE, new int[] { 0x5A, 0x5A, 0x5A, 0x5A }, "stencil top row");
final ByteBuffer ds = read(GL30.GL_DEPTH_STENCIL, GL30.GL_UNSIGNED_INT_24_8, SIZE * SIZE * 4);
final int packed = ds.getInt(0);
assertEquals(0xC3, packed & 0xFF, "DEPTH_STENCIL stencil byte");
assertEquals(0x400000, (packed >>> 8) & 0xFFFFFF, 1, "DEPTH_STENCIL depth 24 bits");
release(fbo);
}
}
@@ -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];
}
}
@@ -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
@@ -25,6 +25,7 @@ import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
import com.gtnewhorizons.angelica.sdlgpu.frame.Presenter;
import com.gtnewhorizons.retrofuturabootstrap.MainStartOnFirstThread;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.AttachmentClear;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.DepthStencilReadback;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.DrawDispatch;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.FFPTrace;
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineApplier;
@@ -39,8 +40,10 @@ import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.FormatMap;
import com.gtnewhorizons.angelica.sdlgpu.resource.Image3DClear;
import com.gtnewhorizons.angelica.sdlgpu.resource.PersistentBufferSync;
import com.gtnewhorizons.angelica.sdlgpu.resource.PackState;
import com.gtnewhorizons.angelica.sdlgpu.resource.PersistentMapping;
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelPack;
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
import com.gtnewhorizons.angelica.sdlgpu.resource.ReadbackShadows;
import com.gtnewhorizons.angelica.sdlgpu.resource.TextureOps;
@@ -154,6 +157,7 @@ public class SDLGPURenderBackend extends RenderBackend {
});
private final PipelineApplier pipelineApplier = new PipelineApplier(frameManager, resourceManager, shaderManager, pipelineStore, fboClearTracker, persistentSync, samplerBinder, storageTextureBinder, storageBufferBinder);
private final AttachmentClear attachmentClear = new AttachmentClear(frameManager, pipelineStore, shaderManager);
private final DepthStencilReadback depthStencilReadback = new DepthStencilReadback(frameManager, resourceManager, pipelineStore, shaderManager, fboClearTracker);
private final DrawDispatch drawDispatch = new DrawDispatch(device, frameManager, resourceManager, pipelineApplier, this::enqueuePreCopied);
private final FFPTrace ffpTrace = SystemProperties.FFP_TRACE ? new FFPTrace(resourceManager) : null;
@@ -494,6 +498,14 @@ public class SDLGPURenderBackend extends RenderBackend {
if (st.cachedViewport != null) { st.cachedViewport.free(); st.cachedViewport = null; }
if (st.cachedScissor != null) { st.cachedScissor.free(); st.cachedScissor = null; }
if (st.cachedBlendColor != null) { st.cachedBlendColor.free(); st.cachedBlendColor = null; }
for (int i = 0; i < st.logicOpScratch.length; i++) {
if (st.logicOpScratch[i] != 0L) resourceManager.releaseTextureHandle(st.logicOpScratch[i]);
st.logicOpScratch[i] = 0L;
}
depthStencilReadback.release(st);
st.activeLogicOpVariant = null;
st.lastFragSamplerVariant = null;
st.appliedLogicOpKey = 0;
st.fragSamplerBindings.free();
st.vertSamplerBindings.free();
if (st.mappedStagingBuffer != null) {
@@ -505,6 +517,8 @@ public class SDLGPURenderBackend extends RenderBackend {
drawDispatch.clearWarnedState();
attachmentClear.shutdown();
depthStencilReadback.shutdown();
textureOps.shutdown();
pipelineStore.shutdown();
shaderManager.shutdown();
resourceManager.shutdown();
@@ -835,6 +849,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;
}
@@ -1136,7 +1157,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 */ }
@@ -1158,7 +1179,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);
@@ -1523,7 +1544,7 @@ public class SDLGPURenderBackend extends RenderBackend {
if (cp == 0) return;
final ByteBuffer unpacked = PixelOps.applyUnpackPixelStore(pixels, width, height * depth, format, type, st.pixelStore);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
final ByteBuffer prepped = PixelOps.prepareUploadBuffer(format, meta.sdlFormat(), unpacked, width, height * depth);
final ByteBuffer prepped = PixelOps.prepareUploadBuffer(format, type, meta.sdlFormat(), unpacked, width, height * depth);
try {
resourceManager.uploadToTexture3D(cp, prepped, texHandle, 0, 0, 0, width, height, depth, level);
} finally {
@@ -1677,11 +1698,15 @@ public class SDLGPURenderBackend extends RenderBackend {
final ContextState.PixelStoreState ps = s().pixelStore;
switch (pname) {
case GL11.GL_UNPACK_ALIGNMENT -> ps.unpackAlignment = param;
case GL11.GL_PACK_ALIGNMENT -> ps.packAlignment = param;
case GL11.GL_PACK_ALIGNMENT -> { if (param == 1 || param == 2 || param == 4 || param == 8) ps.packAlignment = param; }
case GL11.GL_PACK_ROW_LENGTH -> { if (param >= 0) ps.packRowLength = param; }
case GL11.GL_PACK_SKIP_PIXELS -> { if (param >= 0) ps.packSkipPixels = param; }
case GL11.GL_PACK_SKIP_ROWS -> { if (param >= 0) ps.packSkipRows = param; }
case GL11.GL_PACK_SWAP_BYTES -> ps.packSwapBytes = param != 0;
case GL11.GL_UNPACK_ROW_LENGTH -> ps.unpackRowLength = param;
case GL11.GL_UNPACK_SKIP_PIXELS -> ps.unpackSkipPixels = param;
case GL11.GL_UNPACK_SKIP_ROWS -> ps.unpackSkipRows = param;
default -> { /* GL_PACK_* and GL_UNPACK_SWAP_BYTES etc are ignored */ }
default -> { /* GL_UNPACK_SWAP_BYTES etc are ignored */ }
}
}
@@ -1946,123 +1971,217 @@ public class SDLGPURenderBackend extends RenderBackend {
}
private static final int READBACK_COLOR = 0;
private static final int READBACK_DEPTH = 1;
private static final int READBACK_STENCIL = 2;
private static final int READBACK_DEPTH_STENCIL = 3;
@Override public void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels) {
if (pixels == null || width <= 0 || height <= 0) return;
if (pixels == null) return;
readPixelsInto(x, y, width, height, format, type, MemoryUtil.memAddress(pixels), pixels.remaining(), 0, 0L);
}
@Override public void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
if (pixels == null) return;
readPixelsInto(x, y, width, height, format, type, MemoryUtil.memAddress(pixels), (long) pixels.remaining() << 2, 0, 0L);
}
@Override public void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels) {
if (pixels == null) return;
readPixelsInto(x, y, width, height, format, type, MemoryUtil.memAddress(pixels), (long) pixels.remaining() << 2, 0, 0L);
}
@Override public void readPixels(int x, int y, int width, int height, int format, int type, long pixelBufferOffset) {
final int pbo = s().boundPixelPackBuffer;
if (pbo == 0) return;
readPixelsInto(x, y, width, height, format, type, 0L, resourceManager.getBufferSize(pbo) - pixelBufferOffset, pbo, pixelBufferOffset);
}
@Override public void getTexImage(int target, int level, int format, int type, ByteBuffer pixels) {
if (pixels == null) return;
getTexImageInto(target, level, format, type, MemoryUtil.memAddress(pixels), pixels.remaining(), 0, 0L);
}
@Override public void getTexImage(int target, int level, int format, int type, IntBuffer pixels) {
if (pixels == null) return;
getTexImageInto(target, level, format, type, MemoryUtil.memAddress(pixels), (long) pixels.remaining() << 2, 0, 0L);
}
@Override public void getTexImage(int target, int level, int format, int type, long pixelBufferOffset) {
final int pbo = s().boundPixelPackBuffer;
if (pbo == 0) return;
getTexImageInto(target, level, format, type, 0L, resourceManager.getBufferSize(pbo) - pixelBufferOffset, pbo, pixelBufferOffset);
}
private static int readbackKind(int format) {
if (PixelPack.isDepthStencilFormat(format)) return READBACK_DEPTH_STENCIL;
if (PixelPack.isDepthFormat(format)) return READBACK_DEPTH;
if (PixelPack.isStencilFormat(format)) return READBACK_STENCIL;
return READBACK_COLOR;
}
private static PackState loadPackState(ContextState cs) {
final ContextState.PixelStoreState store = cs.pixelStore;
final PackState ps = cs.packState;
ps.alignment = store.packAlignment;
ps.rowLength = store.packRowLength;
ps.skipPixels = store.packSkipPixels;
ps.skipRows = store.packSkipRows;
ps.swapBytes = store.packSwapBytes;
return ps;
}
private static void checkReadbackCapacity(int format, int type, int width, int height, PackState ps, long capacity) {
final long needed = PixelPack.imageSize(format, type, width, height, ps);
if (capacity < needed) throw new IllegalArgumentException("readback of " + width + "x" + height + " format 0x" + Integer.toHexString(format) + " type 0x" + Integer.toHexString(type) + " needs " + needed + " bytes, destination has " + capacity);
}
private void readPixelsInto(int x, int y, int width, int height, int format, int type, long dst, long capacity, int pbo, long pboOffset) {
PixelPack.validate(format, type);
if (width <= 0 || height <= 0) return;
final ContextState cs = s();
checkReadbackCapacity(format, type, width, height, loadPackState(cs), capacity);
final int kind = readbackKind(format);
final boolean fromFbo0 = cs.boundReadFboId == 0;
final long texHandle;
final int srcSdlFormat;
long colorTex = 0L;
int colorFormat = 0;
int colorBase = GL11.GL_RGBA;
long depthTex = 0L;
int depthFormat = 0;
final int srcWidth;
final int srcHeight;
final int srcY;
if (fromFbo0) {
texHandle = frameManager.getFbo0Texture();
srcSdlFormat = frameManager.getSwapchainFormat();
srcWidth = frameManager.getFbo0Width();
srcHeight = frameManager.getFbo0Height();
srcY = srcHeight - y - height;
if (kind == READBACK_COLOR) {
colorTex = frameManager.getFbo0Texture();
colorFormat = frameManager.getSwapchainFormat();
} else {
depthFormat = resourceManager.getSwapchainDepthStencilFormat();
if (depthFormat != 0) depthTex = resourceManager.getSwapchainDepthStencil();
}
} else {
final FboState fbo = resourceManager.getFbo(cs.boundReadFboId);
if (fbo == null) return;
final int colorGlId = fbo.colorGlIds[fbo.readBufferIndex];
texHandle = resourceManager.getTextureHandle(colorGlId);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(colorGlId);
srcSdlFormat = meta != null ? meta.sdlFormat() : SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
srcWidth = meta != null ? meta.width() : fbo.width;
srcHeight = meta != null ? meta.height() : fbo.height;
srcY = y;
if (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());
srcWidth = meta != null ? meta.width() : fbo.width;
srcHeight = meta != null ? meta.height() : fbo.height;
} else {
depthTex = fbo.depthTexture;
depthFormat = fbo.depthFormat;
srcWidth = fbo.width;
srcHeight = fbo.height;
}
}
if (texHandle == 0 || srcWidth <= 0 || srcHeight <= 0) return;
if (srcWidth <= 0 || srcHeight <= 0) return;
final int srcY = fromFbo0 ? srcHeight - y - height : y;
final long clip = CopyRectClip.clipCopyRect(x, srcY, 0, 0, width, height, srcWidth, srcHeight, width, height);
if (clip == CopyRectClip.EMPTY) return;
final int sx = CopyRectClip.srcX(clip);
final int sy = CopyRectClip.srcY(clip);
final int w = CopyRectClip.width(clip);
final int h = CopyRectClip.height(clip);
final int colOffset = CopyRectClip.dstX(clip, x, 0);
final int dy = CopyRectClip.dstY(clip, srcY, 0);
final int rowOffset = fromFbo0 ? (height - h - dy) : dy;
readback(cs, true, kind, colorTex, colorFormat, colorBase, depthTex, depthFormat, 0, srcWidth, srcHeight,
CopyRectClip.srcX(clip), CopyRectClip.srcY(clip), w, h, fromFbo0,
width, CopyRectClip.dstX(clip, x, 0), fromFbo0 ? height - h - dy : dy, format, type, dst, pbo, pboOffset);
}
final int basePos = pixels.position();
final int baseLimit = pixels.limit();
final int stride = width * 4;
if (baseLimit - basePos < height * stride) return;
try {
if (w == width) {
final int start = basePos + rowOffset * stride;
pixels.position(0);
pixels.limit(start + h * stride);
pixels.position(start);
flushAndSubmitMidFrame();
textureOps.readbackTexture(texHandle, sx, sy, w, h, 0, pixels);
pixels.position(0);
pixels.limit(baseLimit);
pixels.position(start);
PixelOps.postProcessReadback(pixels, width, h, format, srcSdlFormat, fromFbo0);
} else {
final ByteBuffer staging = MemoryUtil.memAlloc(w * h * 4);
try {
flushAndSubmitMidFrame();
textureOps.readbackTexture(texHandle, sx, sy, w, h, 0, staging);
staging.rewind();
PixelOps.postProcessReadback(staging, w, h, format, srcSdlFormat, fromFbo0);
for (int row = 0; row < h; row++) {
staging.position(0);
staging.limit((row + 1) * w * 4);
staging.position(row * w * 4);
pixels.position(basePos + (rowOffset + row) * stride + colOffset * 4);
pixels.put(staging);
}
} finally {
MemoryUtil.memFree(staging);
}
}
} finally {
pixels.limit(baseLimit);
pixels.position(basePos);
}
}
@Override public void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
readPixels(x, y, width, height, format, type, MemoryUtil.memByteBuffer(pixels));
}
@Override public void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels) {
readPixels(x, y, width, height, format, type, MemoryUtil.memByteBuffer(pixels));
}
@Override public void getTexImage(int target, int level, int format, int type, ByteBuffer pixels) {
private void getTexImageInto(int target, int level, int format, int type, long dst, long capacity, int pbo, long pboOffset) {
PixelPack.validate(format, type);
final ContextState cs = s();
final int glId = boundTextureFor(cs, target);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
if (meta == null || level < 0 || level >= meta.levels()) return;
final int width = PixelOps.mipLevelSize(meta.width(), level);
final int height = PixelOps.mipLevelSize(meta.height(), level);
if (width <= 0 || height <= 0) return;
checkReadbackCapacity(format, type, width, height, loadPackState(cs), capacity);
final int kind = readbackKind(format);
final long texHandle = resourceManager.getTextureHandle(glId);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
if (meta == null) return;
flushAndSubmitMidFrame();
textureOps.readbackTexture(texHandle, 0, 0, meta.width(), meta.height(), level, pixels);
pixels.rewind();
PixelOps.postProcessReadback(pixels, meta.width(), meta.height(), format, meta.sdlFormat(), false);
final int sdlFormat = meta.sdlFormat();
if ((kind == READBACK_COLOR) == PixelOps.isDepthFormat(sdlFormat)) return;
if (kind != READBACK_COLOR && level != 0) throw new UnsupportedOperationException("glGetTexImage of depth/stencil level " + level + " is not supported on SDL-GPU");
readback(cs, false, kind, kind == READBACK_COLOR ? texHandle : 0L, sdlFormat, PixelPack.glBaseFormat(meta.glFormat()), kind == READBACK_COLOR ? 0L : texHandle, sdlFormat, level, width, height,
0, 0, width, height, false, width, 0, 0, format, type, dst, pbo, pboOffset);
}
@Override public void getTexImage(int target, int level, int format, int type, IntBuffer pixels) {
getTexImage(target, level, format, type, MemoryUtil.memByteBuffer(pixels));
}
@Override public void getTexImage(int target, int level, int format, int type, long pixelBufferOffset) {
final ContextState cs = s();
final int pbo = cs.boundPixelPackBuffer;
if (pbo == 0) return;
final int glId = boundTextureFor(cs, target);
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
if (meta == null) return;
final ByteBuffer tmp = MemoryUtil.memAlloc(meta.width() * meta.height() * 4);
try {
getTexImage(target, level, format, type, tmp);
final long handle = resourceManager.getBufferHandle(pbo);
if (handle != 0 && frameManager.getCommandBuffer() != 0) {
tmp.rewind();
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), tmp, handle, pixelBufferOffset, false);
resourceManager.markBufferContentsDefined(pbo);
private void readback(ContextState cs, boolean readPixels, int kind, long colorTex, int colorFormat, int colorBase, long depthTex, int depthFormat, int level, int srcWidth, int srcHeight,
int sx, int sy, int w, int h, boolean flipRows, int width, int colOffset, int rowOffset, int format, int type, long dst, int pbo, long pboOffset) {
final boolean wantsStencil = kind == READBACK_STENCIL || kind == READBACK_DEPTH_STENCIL;
if (kind == READBACK_COLOR ? colorTex == 0L : depthTex == 0L) return;
if (wantsStencil && !PixelOps.isDepthStencilFormat(depthFormat)) return;
final int texelBytes = switch (kind) {
case READBACK_COLOR -> PixelOps.sdlFormatTexelBytes(colorFormat);
case READBACK_STENCIL -> 1;
default -> 4;
};
final int primaryBytes = w * h * texelBytes;
final int stencilOffset = alignReadback(primaryBytes);
final int downloadEnd = kind == READBACK_DEPTH_STENCIL ? stencilOffset + alignReadback(w * h) : stencilOffset;
final PackState ps = cs.packState;
final ContextState.PixelStoreState store = cs.pixelStore;
final int pixelBytes = PixelOps.glPixelSize(format, type);
ps.alignment = store.packAlignment;
ps.rowLength = store.packRowLength > 0 ? store.packRowLength : width;
ps.skipPixels = store.packSkipPixels + colOffset;
ps.skipRows = store.packSkipRows + rowOffset;
final long stride = PixelPack.rowStride(format, type, width, ps);
final long firstByte = ps.skipRows * stride + (long) ps.skipPixels * pixelBytes;
final long endByte = (long) (ps.skipRows + h - 1) * stride + (long) (ps.skipPixels + w) * pixelBytes;
final long base = textureOps.ensureReadbackStaging(pbo != 0 ? downloadEnd + endByte : downloadEnd);
final long packDst = pbo != 0 ? base + downloadEnd : dst;
switch (kind) {
case READBACK_COLOR -> {
fboClearTracker.materializePendingClearForRead(cs, frameManager.frame(), colorTex);
flushAndSubmitMidFrame();
textureOps.readbackTextureToStaging(colorTex, sx, sy, w, h, level, 0, primaryBytes);
PixelPack.packColor(base, colorFormat, colorBase, w * texelBytes, w, h, flipRows, readPixels, format, type, ps, packDst);
}
case READBACK_DEPTH -> {
final long depth = requireReadbackScratch(depthStencilReadback.renderDepth(cs, depthTex, depthFormat, srcWidth, srcHeight), "depth");
flushAndSubmitMidFrame();
textureOps.readbackTextureToStaging(depth, sx, sy, w, h, 0, 0, primaryBytes);
PixelPack.packDepth(base, w * 4, w, h, flipRows, type, ps, packDst);
}
case READBACK_STENCIL -> {
final long stencil = requireReadbackScratch(depthStencilReadback.renderStencil(cs, depthTex, depthFormat, srcWidth, srcHeight), "stencil");
flushAndSubmitMidFrame();
textureOps.readbackTextureToStaging(stencil, sx, sy, w, h, 0, 0, primaryBytes);
PixelPack.packStencil(base, w, w, h, flipRows, type, ps, packDst);
}
default -> {
final long depth = requireReadbackScratch(depthStencilReadback.renderDepth(cs, depthTex, depthFormat, srcWidth, srcHeight), "depth");
final long stencil = requireReadbackScratch(depthStencilReadback.renderStencil(cs, depthTex, depthFormat, srcWidth, srcHeight), "stencil");
flushAndSubmitMidFrame();
textureOps.readbackTextureToStaging(depth, sx, sy, w, h, 0, 0, primaryBytes);
textureOps.readbackTextureToStaging(stencil, sx, sy, w, h, 0, stencilOffset, w * h);
PixelPack.packDepthStencil(base, base + stencilOffset, w * 4, w, w, h, flipRows, type, ps, packDst);
}
} finally {
MemoryUtil.memFree(tmp);
}
if (pbo != 0) uploadReadbackToPixelPackBuffer(pbo, pboOffset, (int) (downloadEnd + firstByte), (int) (endByte - firstByte), firstByte);
}
private static long requireReadbackScratch(long scratch, String what) {
if (scratch == 0L) throw new IllegalStateException("SDL-GPU " + what + " readback pass unavailable (no active frame, or the attachment cannot be sampled)");
return scratch;
}
private static int alignReadback(int bytes) {
return (bytes + 15) & ~15;
}
private void uploadReadbackToPixelPackBuffer(int pbo, long pboOffset, int stagingOffset, int size, long firstByte) {
final long handle = resourceManager.getBufferHandle(pbo);
if (handle == 0 || frameManager.getCommandBuffer() == 0) return;
final ByteBuffer region = textureOps.readbackStagingRegion(stagingOffset, size);
try {
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), region, handle, pboOffset + firstByte, false);
} finally {
region.clear();
}
resourceManager.markBufferContentsDefined(pbo);
flushAndSubmitMidFrame();
}
@Override public int getFramebufferAttachmentParameteri(int target, int attachment, int pname) {
@@ -2163,6 +2282,8 @@ public class SDLGPURenderBackend extends RenderBackend {
final ShaderManager.ProgramObject prog = shaderManager.getProgram(program);
if (prog == null) {
cs.pipeline.invalidateShaderHandles();
cs.appliedLogicOpKey = 0;
cs.activeLogicOpVariant = null;
return;
}
if (prog.sdlVertexShader == 0 && prog.vertexSpirv != null) {
@@ -2180,6 +2301,8 @@ public class SDLGPURenderBackend extends RenderBackend {
cs.pipeline.setVertexInputs(prog.vertexInputMask, prog.vertexInputVecSize, prog.vertexInputBaseType, prog.vertexInputName, prog.vertexInputSetId);
cs.pipeline.markInputDirty();
cs.pipeline.markShaderDirty();
cs.appliedLogicOpKey = 0;
cs.activeLogicOpVariant = null;
}
@Override public void useProgram(int program) {
final ContextState cs = s();
@@ -2195,6 +2318,8 @@ public class SDLGPURenderBackend extends RenderBackend {
}
}
st.boundProgram = program;
st.appliedLogicOpKey = 0;
st.activeLogicOpVariant = null;
if (program == 0) {
st.boundProgramObj = null;
cs.pipeline.vertexShader = 0;
@@ -3755,6 +3880,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);
@@ -3857,6 +3987,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;
};
}
@@ -4138,7 +4270,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,20 @@ public final class ContextState {
public int ssboBinds;
public float blendColorR, blendColorG, blendColorB, blendColorA;
public boolean logicOpEnabled;
public int logicOpMode = GL11.GL_COPY;
public long appliedLogicOpKey;
public LogicOpVariant activeLogicOpVariant;
public final long[] logicOpScratch = new long[MAX_COLOR_ATTACHMENTS];
public final int[] logicOpScratchFormat = new int[MAX_COLOR_ATTACHMENTS];
public final int[] logicOpScratchWidth = new int[MAX_COLOR_ATTACHMENTS];
public final int[] logicOpScratchHeight = new int[MAX_COLOR_ATTACHMENTS];
public long readbackDepthScratch;
public int readbackDepthScratchWidth;
public int readbackDepthScratchHeight;
public long readbackStencilScratch;
public int readbackStencilScratchWidth;
public int readbackStencilScratchHeight;
public int stencilRef;
public float depthClearValue = 1.0f;
public int stencilClearValue;
@@ -305,6 +327,7 @@ public final class ContextState {
public final long[] lastVertSamplerTex = new long[MAX_SAMPLERS];
public final long[] lastVertSamplerSmp = new long[MAX_SAMPLERS];
public int lastFragSamplerProgram;
public LogicOpVariant lastFragSamplerVariant;
public int lastVertSamplerProgram;
public final PointerBuffer fragStorageTexBindings = PointerBuffer.allocateDirect(MAX_IMAGE_UNITS);
@@ -416,6 +439,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();
@@ -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,11 +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;
FullscreenPass.markStateClobbered(st);
frameManager.noteInPassClear();
return true;
@@ -253,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)
@@ -274,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");
}
}
@@ -315,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;
@@ -377,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,123 @@
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.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);
TextureOps.copyTexture(cp, tex, 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 int targetWidth(FboState fbo, int loc) {
if (fbo == null) return frameManager.getFbo0Width();
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(fbo.colorGlIds[drawBuffer(fbo, loc)]);
return meta != null ? meta.width() : fbo.width;
}
private int targetHeight(FboState fbo, int loc) {
if (fbo == null) return frameManager.getFbo0Height();
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(fbo.colorGlIds[drawBuffer(fbo, loc)]);
return meta != null ? meta.height() : 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) {
@@ -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;
@@ -244,6 +244,17 @@ public final class FBOClearTracker {
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) {
if (handle == 0) return;
final boolean depthPending = st.pendingDepthTextures.remove(handle);
@@ -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;
}
}
}
}
@@ -129,6 +129,7 @@ public final class ResourceManager {
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 +160,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;
}
@@ -2135,7 +2152,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);
}
@@ -2150,6 +2167,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;
@@ -2309,6 +2347,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;
@@ -49,7 +51,7 @@ public final class TextureOps {
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);
@@ -190,7 +221,10 @@ public final class TextureOps {
}
public void copyTexture(long srcTex, int srcX, int srcY, long dstTex, int dstLevel, int dstX, int dstY, int w, int h) {
final long cp = frameManager.ensureCopyPass();
copyTexture(frameManager.ensureCopyPass(), srcTex, srcX, srcY, dstTex, dstLevel, dstX, dstY, w, h);
}
public static void copyTexture(long cp, long srcTex, 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);
@@ -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;
}
@@ -431,11 +431,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 +616,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 +786,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 +913,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; }
@@ -1745,6 +1823,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; }
@@ -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,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");
}
}
@@ -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));
@@ -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
@@ -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));
}
}
@@ -138,6 +138,12 @@ public enum Mixins implements IMixins {
.addClientMixins("angelica.gui.MixinGuiMainMenuSplash")
),
ANGELICA_TEXT_HIGHLIGHT(
new MixinBuilder("Draw text field selection without color logic op")
.setPhase(Phase.EARLY)
.addClientMixins("angelica.gui.MixinGuiTextField")
),
ANGELICA_FONT_RENDERER(new MixinBuilder()
.setPhase(Phase.EARLY)
.setApplyIf(() -> AngelicaConfig.enableFontRenderer)
@@ -0,0 +1,37 @@
package com.gtnewhorizons.angelica.mixins.hooks;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import net.minecraft.client.renderer.Tessellator;
import org.lwjgl.opengl.GL11;
public class TextHighlightHooks {
public static void draw(int x1, int y1, int x2, int y2) {
final int depth = GLStateManager.pushState(StateSet.BLEND);
GLStateManager.glDisable(GL11.GL_TEXTURE_2D);
GLStateManager.glEnable(GL11.GL_BLEND);
GLStateManager.glBlendFuncSeparate(GL11.GL_ONE_MINUS_DST_COLOR, GL11.GL_ONE_MINUS_SRC_COLOR, GL11.GL_ONE, GL11.GL_ZERO);
GLStateManager.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
quad(x1, y1, x2, y2);
GLStateManager.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
GLStateManager.glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
quad(x1, y1, x2, y2);
GLStateManager.glEnable(GL11.GL_TEXTURE_2D);
GLStateManager.popStateTo(depth);
}
private static void quad(int x1, int y1, int x2, int y2) {
final Tessellator tessellator = Tessellator.instance;
tessellator.startDrawingQuads();
tessellator.addVertex(x1, y2, 0.0D);
tessellator.addVertex(x2, y2, 0.0D);
tessellator.addVertex(x2, y1, 0.0D);
tessellator.addVertex(x1, y1, 0.0D);
tessellator.draw();
}
}
@@ -14,9 +14,8 @@ import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.I18n;
import net.minecraft.util.ChatAllowedCharacters;
import net.minecraft.util.MathHelper;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.hooks.TextHighlightHooks;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.GL11;
import java.util.HashSet;
import java.util.List;
@@ -173,13 +172,7 @@ public class SearchTextFieldComponent extends AbstractWidget {
if (x1 > this.dim.getOriginX() + this.dim.getWidth()) {
x1 = this.dim.getOriginX() + this.dim.getWidth();
}
GLStateManager.glEnable(GL11.GL_COLOR_LOGIC_OP);
GLStateManager.glLogicOp(GL11.GL_OR_REVERSE);
drawRect(x1, y1, x2, y2, -0xFFFF01);
GLStateManager.glDisable(GL11.GL_COLOR_LOGIC_OP);
TextHighlightHooks.draw(x1, y1, x2, y2);
}
private int getMaxLength() {
@@ -0,0 +1,44 @@
package com.gtnewhorizons.angelica.mixins.early.angelica.gui;
import com.gtnewhorizons.angelica.mixins.hooks.TextHighlightHooks;
import net.minecraft.client.gui.GuiTextField;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(GuiTextField.class)
public abstract class MixinGuiTextField {
@Shadow public int xPosition;
@Shadow public int width;
@Inject(method = "drawCursorVertical", at = @At("HEAD"), cancellable = true)
private void angelica$drawCursorVertical(int x1, int y1, int x2, int y2, CallbackInfo ci) {
int tmp;
if (x1 < x2) {
tmp = x1;
x1 = x2;
x2 = tmp;
}
if (y1 < y2) {
tmp = y1;
y1 = y2;
y2 = tmp;
}
if (x2 > this.xPosition + this.width) {
x2 = this.xPosition + this.width;
}
if (x1 > this.xPosition + this.width) {
x1 = this.xPosition + this.width;
}
TextHighlightHooks.draw(x1, y1, x2, y2);
ci.cancel();
}
}