mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-08 12:35:01 +00:00
Emulate color logic ops and fix pixel readback on SDL-GPU (#2157)
This commit is contained in:
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];
|
||||
}
|
||||
}
|
||||
+31
-1
@@ -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]);
|
||||
}
|
||||
}
|
||||
+9
-113
@@ -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);
|
||||
|
||||
+314
@@ -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;
|
||||
}
|
||||
}
|
||||
+123
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
-7
@@ -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));
|
||||
}
|
||||
}
|
||||
+173
@@ -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");
|
||||
}
|
||||
}
|
||||
+182
@@ -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();
|
||||
}
|
||||
}
|
||||
+7
-51
@@ -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));
|
||||
}
|
||||
}
|
||||
+1
@@ -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
|
||||
|
||||
+210
@@ -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();
|
||||
}
|
||||
}
|
||||
+2
-9
@@ -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() {
|
||||
|
||||
+44
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user