mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-08 12:35:01 +00:00
Fix scissor clears wiping the whole screen on SDL-GPU (#2138)
This commit is contained in:
1 parent
b76284048a
commit
44c930f4ab
17 files changed
+740
-171
No files matched your search
@@ -302,7 +302,7 @@ public class Feature {
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_SCISSOR_BIT, ImmutableSet.of(
|
||||
GLStateManager.getScissorTest()
|
||||
// Scissor box
|
||||
, GLStateManager.getScissorState()
|
||||
));
|
||||
attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of(
|
||||
GLStateManager.getStencilTest()
|
||||
|
||||
@@ -48,6 +48,10 @@ public class FeedbackManager {
|
||||
return renderMode == GL11.GL_FEEDBACK;
|
||||
}
|
||||
|
||||
public static int getRenderMode() {
|
||||
return renderMode;
|
||||
}
|
||||
|
||||
public static void glFeedbackBuffer(int type, FloatBuffer buffer) {
|
||||
if (renderMode == GL11.GL_FEEDBACK) {
|
||||
GLStateManager.LOGGER.error("glFeedbackBuffer called while already in feedback mode");
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.MaterialStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.MatrixModeStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.ScissorStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
|
||||
@@ -41,6 +42,7 @@ import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
@@ -125,6 +127,7 @@ public class GLContextState {
|
||||
public final BlendStateStack blendState = member(new BlendStateStack(StackIdAllocator.nextId()));
|
||||
public final BooleanStateStack blendMode = track(new BooleanStateStack(GL11.GL_BLEND));
|
||||
public final BooleanStateStack scissorTest = track(new BooleanStateStack(GL11.GL_SCISSOR_TEST));
|
||||
public final BooleanStateStack rasterizerDiscard = track(new BooleanStateStack(GL30.GL_RASTERIZER_DISCARD));
|
||||
public final DepthStateStack depthState = member(new DepthStateStack(StackIdAllocator.nextId()));
|
||||
public final BooleanStateStack depthTest = track(new BooleanStateStack(GL11.GL_DEPTH_TEST));
|
||||
public final FogStateStack fogState = member(new FogStateStack(StackIdAllocator.nextId()));
|
||||
@@ -200,6 +203,7 @@ public class GLContextState {
|
||||
public final MaterialStateStack frontMaterial = member(new MaterialStateStack(GL11.GL_FRONT, StackIdAllocator.nextId()));
|
||||
public final MaterialStateStack backMaterial = member(new MaterialStateStack(GL11.GL_BACK, StackIdAllocator.nextId()));
|
||||
public final ViewPortStateStack viewportState = member(new ViewPortStateStack(StackIdAllocator.nextId()));
|
||||
public final ScissorStateStack scissorState = member(new ScissorStateStack(StackIdAllocator.nextId()));
|
||||
public int activeProgram = 0;
|
||||
public final IntegerStateStack programStack = member(new IntegerStateStack(0, StackIdAllocator.nextId()));
|
||||
public int listBase = 0;
|
||||
|
||||
@@ -49,6 +49,7 @@ import com.gtnewhorizons.angelica.glsm.stacks.MaterialStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.MatrixModeStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.ScissorStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StackIdAllocator;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.TextureBindingStack;
|
||||
@@ -585,6 +586,7 @@ public class GLStateManager {
|
||||
// Initialize viewport state from display dimensions.
|
||||
// After Display.create(), viewport is (0, 0, width, height)
|
||||
mod(ctx().viewportState).setViewPort(0, 0, displayWidth, displayHeight);
|
||||
mod(ctx().scissorState).setScissor(0, 0, displayWidth, displayHeight);
|
||||
}
|
||||
|
||||
final String glVendor = RENDER_BACKEND.getString(GL11.GL_VENDOR);
|
||||
@@ -762,6 +764,7 @@ public class GLStateManager {
|
||||
case GL13.GL_SAMPLE_ALPHA_TO_COVERAGE -> glCtx.sampleAlphaToCoverageState.enable();
|
||||
case GL13.GL_SAMPLE_ALPHA_TO_ONE -> glCtx.sampleAlphaToOneState.enable();
|
||||
case GL13.GL_SAMPLE_COVERAGE -> glCtx.sampleCoverageState.enable();
|
||||
case GL30.GL_RASTERIZER_DISCARD -> glCtx.rasterizerDiscard.enable();
|
||||
case GL11.GL_SCISSOR_TEST -> enableScissorTest();
|
||||
case GL11.GL_STENCIL_TEST -> glCtx.stencilTest.enable();
|
||||
case GL11.GL_TEXTURE_1D -> glCtx.textures.getTexture1DStates(glCtx.activeTextureUnit.getValue()).enable();
|
||||
@@ -844,6 +847,7 @@ public class GLStateManager {
|
||||
case GL13.GL_SAMPLE_ALPHA_TO_COVERAGE -> glCtx.sampleAlphaToCoverageState.disable();
|
||||
case GL13.GL_SAMPLE_ALPHA_TO_ONE -> glCtx.sampleAlphaToOneState.disable();
|
||||
case GL13.GL_SAMPLE_COVERAGE -> glCtx.sampleCoverageState.disable();
|
||||
case GL30.GL_RASTERIZER_DISCARD -> glCtx.rasterizerDiscard.disable();
|
||||
case GL11.GL_SCISSOR_TEST -> disableScissorTest();
|
||||
case GL11.GL_STENCIL_TEST -> glCtx.stencilTest.disable();
|
||||
case GL11.GL_TEXTURE_1D -> glCtx.textures.getTexture1DStates(glCtx.activeTextureUnit.getValue()).disable();
|
||||
@@ -920,6 +924,7 @@ public class GLStateManager {
|
||||
case GL13.GL_SAMPLE_ALPHA_TO_COVERAGE -> glCtx.sampleAlphaToCoverageState.isEnabled();
|
||||
case GL13.GL_SAMPLE_ALPHA_TO_ONE -> glCtx.sampleAlphaToOneState.isEnabled();
|
||||
case GL13.GL_SAMPLE_COVERAGE -> glCtx.sampleCoverageState.isEnabled();
|
||||
case GL30.GL_RASTERIZER_DISCARD -> glCtx.rasterizerDiscard.isEnabled();
|
||||
case GL11.GL_SCISSOR_TEST -> glCtx.scissorTest.isEnabled();
|
||||
case GL11.GL_STENCIL_TEST -> glCtx.stencilTest.isEnabled();
|
||||
case GL11.GL_TEXTURE_1D -> glCtx.textures.getTexture1DStates(glCtx.activeTextureUnit.getValue()).isEnabled();
|
||||
@@ -997,6 +1002,7 @@ public class GLStateManager {
|
||||
case GL13.GL_SAMPLE_ALPHA_TO_COVERAGE -> glCtx.sampleAlphaToCoverageState.isEnabled();
|
||||
case GL13.GL_SAMPLE_ALPHA_TO_ONE -> glCtx.sampleAlphaToOneState.isEnabled();
|
||||
case GL13.GL_SAMPLE_COVERAGE -> glCtx.sampleCoverageState.isEnabled();
|
||||
case GL30.GL_RASTERIZER_DISCARD -> glCtx.rasterizerDiscard.isEnabled();
|
||||
case GL11.GL_SCISSOR_TEST -> glCtx.scissorTest.isEnabled();
|
||||
case GL11.GL_STENCIL_TEST -> glCtx.stencilTest.isEnabled();
|
||||
case GL11.GL_TEXTURE_1D -> glCtx.textures.getTexture1DStates(glCtx.activeTextureUnit.getValue()).isEnabled();
|
||||
@@ -1125,6 +1131,7 @@ public class GLStateManager {
|
||||
|
||||
switch (pname) {
|
||||
case GL11.GL_VIEWPORT -> ctx().viewportState.get(params);
|
||||
case GL11.GL_SCISSOR_BOX -> ctx().scissorState.get(params);
|
||||
case GL11.GL_POLYGON_MODE -> {
|
||||
final PolygonState polygon = ctx().polygonState;
|
||||
final int pos = params.position();
|
||||
@@ -1950,6 +1957,7 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
depth = Math.min(1.0, Math.max(0.0, depth));
|
||||
mod(glCtx.depthState);
|
||||
if (isCachingEnabled()) glCtx.depthState.setClearValue(depth);
|
||||
RENDER_BACKEND.clearDepth(depth);
|
||||
@@ -3954,6 +3962,7 @@ public class GLStateManager {
|
||||
|
||||
RENDER_BACKEND.viewport(glCtx.viewportState.x, glCtx.viewportState.y, glCtx.viewportState.width, glCtx.viewportState.height);
|
||||
RENDER_BACKEND.depthRange(glCtx.viewportState.depthRangeNear, glCtx.viewportState.depthRangeFar);
|
||||
RENDER_BACKEND.scissor(glCtx.scissorState.x, glCtx.scissorState.y, glCtx.scissorState.width, glCtx.scissorState.height);
|
||||
RENDER_BACKEND.lineWidth(Math.clamp(glCtx.lineState.getWidth(), lineWidthMin, lineWidthMax));
|
||||
RENDER_BACKEND.pointSize(glCtx.pointState.getSize());
|
||||
|
||||
@@ -4064,6 +4073,11 @@ public class GLStateManager {
|
||||
RENDER_BACKEND.depthRange(glCtx.viewportState.depthRangeNear, glCtx.viewportState.depthRangeFar);
|
||||
attribBackendCalls += 2;
|
||||
}
|
||||
if ((restore & StateSet.R_SCISSOR) != 0 && (changed & StateSet.R_SCISSOR) != 0) {
|
||||
attribValueRestores++;
|
||||
RENDER_BACKEND.scissor(glCtx.scissorState.x, glCtx.scissorState.y, glCtx.scissorState.width, glCtx.scissorState.height);
|
||||
attribBackendCalls++;
|
||||
}
|
||||
if ((restore & StateSet.R_LINE) != 0 && (changed & StateSet.R_LINE) != 0) {
|
||||
attribValueRestores++;
|
||||
RENDER_BACKEND.lineWidth(Math.clamp(glCtx.lineState.getWidth(), lineWidthMin, lineWidthMax));
|
||||
@@ -4152,6 +4166,9 @@ public class GLStateManager {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (ctx().rasterizerDiscard.isEnabled() || FeedbackManager.getRenderMode() != GL11.GL_RENDER) {
|
||||
return;
|
||||
}
|
||||
if (firstClearPending && Thread.currentThread() == MainThread && getDrawFramebuffer() == 0) {
|
||||
firstClearPending = false;
|
||||
final long start = System.nanoTime();
|
||||
@@ -5945,6 +5962,9 @@ public class GLStateManager {
|
||||
}
|
||||
}
|
||||
RENDER_BACKEND.scissor(x, y, width, height);
|
||||
if (isCachingEnabled()) {
|
||||
mod(ctx().scissorState).setScissor(x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
public static void glStencilFunc(int func, int ref, int mask) {
|
||||
@@ -6860,6 +6880,13 @@ public class GLStateManager {
|
||||
ctx().viewportState.get(params);
|
||||
}
|
||||
}
|
||||
case GL11.GL_SCISSOR_BOX -> {
|
||||
if (params.length < 4) {
|
||||
RENDER_BACKEND.getInteger(pname, params);
|
||||
} else {
|
||||
ctx().scissorState.get(params);
|
||||
}
|
||||
}
|
||||
case GL11.GL_POLYGON_MODE -> {
|
||||
final PolygonState polygon = ctx().polygonState;
|
||||
params[0] = polygon.getFrontMode();
|
||||
@@ -8187,6 +8214,10 @@ public class GLStateManager {
|
||||
return ctx().viewportState;
|
||||
}
|
||||
|
||||
public static ScissorStateStack getScissorState() {
|
||||
return ctx().scissorState;
|
||||
}
|
||||
|
||||
public static int getActiveProgram() {
|
||||
return ctx().activeProgram;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ public final class StateSet {
|
||||
public static final int R_TEXTURE = 1 << 12;
|
||||
public static final int R_ACTIVE_UNIT = 1 << 13;
|
||||
public static final int R_PROGRAM = 1 << 14;
|
||||
public static final int R_SCISSOR = 1 << 15;
|
||||
|
||||
public static final int B_FRAG_COLORBUF = 1;
|
||||
public static final int B_FRAG_TEXTURE = 1 << 1;
|
||||
@@ -205,6 +206,9 @@ public final class StateSet {
|
||||
if ((mask & GL11.GL_VIEWPORT_BIT) != 0) {
|
||||
restore |= R_VIEWPORT;
|
||||
}
|
||||
if ((mask & GL11.GL_SCISSOR_BIT) != 0) {
|
||||
restore |= R_SCISSOR;
|
||||
}
|
||||
if ((mask & GL11.GL_LINE_BIT) != 0) {
|
||||
restore |= R_LINE;
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ public final class CowDispatch {
|
||||
public static final byte LINE = 20;
|
||||
public static final byte TEXTURE_BINDING = 21;
|
||||
public static final byte TEX_ENV = 22;
|
||||
public static final byte SCISSOR = 23;
|
||||
|
||||
public static byte kindOf(CowStateStack<?> s) {
|
||||
final Class<?> c = s.getClass();
|
||||
@@ -66,6 +67,7 @@ public final class CowDispatch {
|
||||
if (c == LineStateStack.class) return LINE;
|
||||
if (c == TextureBindingStack.class) return TEXTURE_BINDING;
|
||||
if (c == TexEnvState.class) return TEX_ENV;
|
||||
if (c == ScissorStateStack.class) return SCISSOR;
|
||||
throw new IllegalStateException("Unknown CowStateStack class: " + c.getName());
|
||||
}
|
||||
|
||||
@@ -94,6 +96,7 @@ public final class CowDispatch {
|
||||
case LINE -> ((LineStateStack) s).topSlotChanged();
|
||||
case TEXTURE_BINDING -> ((TextureBindingStack) s).topSlotChanged();
|
||||
case TEX_ENV -> ((TexEnvState) s).topSlotChanged();
|
||||
case SCISSOR -> ((ScissorStateStack) s).topSlotChanged();
|
||||
default -> throw new IllegalStateException("Unknown kind: " + kind);
|
||||
};
|
||||
}
|
||||
@@ -123,6 +126,7 @@ public final class CowDispatch {
|
||||
case LINE -> ((LineStateStack) s).restoreSlot(slot);
|
||||
case TEXTURE_BINDING -> ((TextureBindingStack) s).restoreSlot(slot);
|
||||
case TEX_ENV -> ((TexEnvState) s).restoreSlot(slot);
|
||||
case SCISSOR -> ((ScissorStateStack) s).restoreSlot(slot);
|
||||
default -> throw new IllegalStateException("Unknown kind: " + kind);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.gtnewhorizons.angelica.glsm.stacks;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ScissorState;
|
||||
|
||||
public final class ScissorStateStack extends ScissorState implements CowStateStack<ScissorStateStack> {
|
||||
|
||||
protected final ScissorState[] stack;
|
||||
private final CowDepths cow = new CowDepths(StateSet.R_SCISSOR);
|
||||
|
||||
public ScissorStateStack(int id) {
|
||||
cow.id = id;
|
||||
stack = new ScissorState[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
|
||||
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) {
|
||||
stack[i] = new ScissorState();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CowDepths cowDepths() {
|
||||
return cow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void captureSlot(int s) {
|
||||
stack[s].set(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreSlot(int s) {
|
||||
set(stack[s]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean topSlotChanged() {
|
||||
return !cow.isEmpty() && !sameAs(stack[cow.top()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stackId() {
|
||||
return cow.id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.gtnewhorizons.angelica.glsm.states;
|
||||
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
public class ScissorState implements ISettableState<ScissorState> {
|
||||
public int x;
|
||||
public int y;
|
||||
public int width;
|
||||
public int height;
|
||||
|
||||
public void setScissor(int x, int y, int width, int height) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public void get(IntBuffer params) {
|
||||
params.put(0, x);
|
||||
params.put(1, y);
|
||||
params.put(2, width);
|
||||
params.put(3, height);
|
||||
}
|
||||
|
||||
public void get(int[] params) {
|
||||
params[0] = x;
|
||||
params[1] = y;
|
||||
params[2] = width;
|
||||
params[3] = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScissorState set(ScissorState state) {
|
||||
this.x = state.x;
|
||||
this.y = state.y;
|
||||
this.width = state.width;
|
||||
this.height = state.height;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sameAs(Object state) {
|
||||
if (this == state) return true;
|
||||
if (!(state instanceof ScissorState scissorState)) return false;
|
||||
return x == scissorState.x && y == scissorState.y && width == scissorState.width && height == scissorState.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScissorState copy() {
|
||||
return new ScissorState().set(this);
|
||||
}
|
||||
|
||||
}
|
||||
+62
@@ -3,8 +3,10 @@ package com.gtnewhorizons.angelica.glsm;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyIsEnabled;
|
||||
import static com.gtnewhorizons.angelica.util.GLSMUtil.verifyState;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@GLCoreTest
|
||||
@@ -170,6 +172,66 @@ public class GLSM_PopAttribMaskFidelity_GLTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissorBoxFollowsScissorBitOnly() {
|
||||
final int[] boxA = { 10, 20, 100, 200 };
|
||||
final int[] boxB = { 30, 40, 50, 60 };
|
||||
try {
|
||||
GLStateManager.glScissor(boxA[0], boxA[1], boxA[2], boxA[3]);
|
||||
GLStateManager.disableScissorTest();
|
||||
verifyState(GL11.GL_SCISSOR_BOX, boxA, "Scissor box - Initial");
|
||||
verifyIsEnabled(GL11.GL_SCISSOR_TEST, false, "Scissor - Initial");
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_SCISSOR_BIT);
|
||||
GLStateManager.enableScissorTest();
|
||||
GLStateManager.glScissor(boxB[0], boxB[1], boxB[2], boxB[3]);
|
||||
GLStateManager.glPopAttrib();
|
||||
|
||||
verifyState(GL11.GL_SCISSOR_BOX, boxA, "Scissor box - GL_SCISSOR_BIT member, must restore");
|
||||
verifyIsEnabled(GL11.GL_SCISSOR_TEST, false, "Scissor - GL_SCISSOR_BIT member, must restore");
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
|
||||
GLStateManager.enableScissorTest();
|
||||
GLStateManager.glScissor(boxB[0], boxB[1], boxB[2], boxB[3]);
|
||||
GLStateManager.glPopAttrib();
|
||||
|
||||
verifyState(GL11.GL_SCISSOR_BOX, boxB, "Scissor box - Not a GL_ENABLE_BIT member, must stay changed");
|
||||
verifyIsEnabled(GL11.GL_SCISSOR_TEST, false, "Scissor - GL_ENABLE_BIT member, must restore");
|
||||
} finally {
|
||||
GLStateManager.disableScissorTest();
|
||||
GLStateManager.glScissor(0, 0, 800, 600);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void rasterizerDiscardIsNotAnEnableBitMember() {
|
||||
try {
|
||||
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, false, "RasterizerDiscard - Initial");
|
||||
|
||||
GLStateManager.glEnable(GL30.GL_RASTERIZER_DISCARD);
|
||||
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, true, "RasterizerDiscard - After enable");
|
||||
|
||||
GLStateManager.glPushAttrib(GL11.GL_ENABLE_BIT);
|
||||
GLStateManager.glDisable(GL30.GL_RASTERIZER_DISCARD);
|
||||
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, false, "RasterizerDiscard - After disable inside GL_ENABLE_BIT");
|
||||
GLStateManager.glPopAttrib();
|
||||
|
||||
verifyIsEnabled(GL30.GL_RASTERIZER_DISCARD, false, "RasterizerDiscard - Not a GL_ENABLE_BIT member, must stay disabled");
|
||||
} finally {
|
||||
GLStateManager.glDisable(GL30.GL_RASTERIZER_DISCARD);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearDepthIsClamped() {
|
||||
try {
|
||||
GLStateManager.glClearDepth(2.0);
|
||||
verifyState(GL11.GL_DEPTH_CLEAR_VALUE, 1.0f, "Depth clear value - glClearDepth(2.0) must clamp to 1.0");
|
||||
} finally {
|
||||
GLStateManager.glClearDepth(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void colorBufferBitPopDoesNotRestoreClipPlaneButMaskStaysFaithful() {
|
||||
try {
|
||||
|
||||
@@ -45,7 +45,7 @@ public class GLSM_ValueStackCow_GLTest {
|
||||
"glAlphaFunc", "glBlendFunc", "tryBlendFuncSeparate", "glBlendEquation", "glBlendEquationSeparate",
|
||||
"glBlendColor", "glColorMask", "glClearColor", "glDrawBuffer", "glLogicOp",
|
||||
"glDepthFunc", "glDepthMask", "glClearDepth", "glStencilFunc", "glStencilOp",
|
||||
"glStencilMask", "glClearStencil", "glViewport", "glDepthRange", "glLineWidth",
|
||||
"glStencilMask", "glClearStencil", "glViewport", "glScissor", "glDepthRange", "glLineWidth",
|
||||
"glLineStipple", "glPointSize", "glPolygonMode", "glCullFace", "glFrontFace",
|
||||
"glPolygonOffset", "fogColor", "glFogf", "glFogi", "glLightf",
|
||||
"glLighti", "glLight", "glLightModelf", "glLightModeli", "glLightModel",
|
||||
@@ -74,6 +74,7 @@ public class GLSM_ValueStackCow_GLTest {
|
||||
() -> GLStateManager.glStencilMask(0x0F),
|
||||
() -> GLStateManager.glClearStencil(9),
|
||||
() -> GLStateManager.glViewport(1, 2, 3, 4),
|
||||
() -> GLStateManager.glScissor(1, 2, 3, 4),
|
||||
() -> GLStateManager.glDepthRange(0.2, 0.8),
|
||||
() -> GLStateManager.glLineWidth(3.0f),
|
||||
() -> GLStateManager.glLineStipple(3, (short) 0x00FF),
|
||||
|
||||
@@ -162,6 +162,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
private boolean droppedSsboWriteWarned;
|
||||
private boolean depthClearSuppressedWarned;
|
||||
private boolean stencilClearSuppressedWarned;
|
||||
private boolean partialClearFallbackWarned;
|
||||
private final DeferredCopyOp deferredCopyOp = new DeferredCopyOp();
|
||||
|
||||
private TransferThread transferThread;
|
||||
@@ -814,6 +815,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
st.scissorDirty = true;
|
||||
}
|
||||
case GL31.GL_PRIMITIVE_RESTART -> cs.primitiveRestartEnabled = on;
|
||||
case GL30.GL_RASTERIZER_DISCARD -> cs.rasterizerDiscard = on;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,28 +956,78 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public void clearStencil(int s) { s().stencilClearValue = s & 0xFF; }
|
||||
|
||||
@Override public void clear(int mask) {
|
||||
final ContextState cs = s();
|
||||
final ContextState st = s();
|
||||
if (!frameManager.isFrameActive()) return;
|
||||
final boolean wantColor = (mask & GL11.GL_COLOR_BUFFER_BIT) != 0;
|
||||
boolean wantColor = (mask & GL11.GL_COLOR_BUFFER_BIT) != 0;
|
||||
boolean wantDepth = (mask & GL11.GL_DEPTH_BUFFER_BIT) != 0;
|
||||
boolean wantStencil = (mask & GL11.GL_STENCIL_BUFFER_BIT) != 0;
|
||||
if (wantDepth && !cs.pipeline.depthWriteEnabled) {
|
||||
if (wantDepth && !st.pipeline.depthWriteEnabled) {
|
||||
wantDepth = false;
|
||||
if (!depthClearSuppressedWarned) {
|
||||
depthClearSuppressedWarned = true;
|
||||
LOG.warn("clear: GL_DEPTH_BUFFER_BIT requested with glDepthMask(false); clear suppressed per GL semantics");
|
||||
}
|
||||
}
|
||||
if (wantStencil && cs.pipeline.stencilWriteMask == 0) {
|
||||
if (wantStencil && st.pipeline.stencilWriteMask == 0) {
|
||||
wantStencil = false;
|
||||
if (!stencilClearSuppressedWarned) {
|
||||
stencilClearSuppressedWarned = true;
|
||||
LOG.warn("clear: GL_STENCIL_BUFFER_BIT requested with a zero glStencilMask; clear suppressed per GL semantics");
|
||||
}
|
||||
}
|
||||
if (wantColor && (st.pipeline.colorWriteMask & 0xF) == 0) {
|
||||
wantColor = false;
|
||||
}
|
||||
if (!wantColor && !wantDepth && !wantStencil) return;
|
||||
|
||||
final ContextState st = cs;
|
||||
final FboState fbo;
|
||||
if (st.boundFboId == 0) {
|
||||
fbo = null;
|
||||
} else {
|
||||
fbo = resourceManager.getFbo(st.boundFboId);
|
||||
if (fbo == null) return;
|
||||
if (fbo.targetsDirty) fbo.recomputeTargets();
|
||||
if (fbo.cachedFormatsDirty && fbo.hasAnyColor) fboClearTracker.updatePipelineCacheColorFormats(st, fbo);
|
||||
}
|
||||
|
||||
final AttachmentClear.Target target = fbo != null ? attachmentClear.forFbo(fbo) : attachmentClear.forFbo0(cachedSwapchainFormatArray, frameManager.getFbo0Width(), frameManager.getFbo0Height());
|
||||
|
||||
int skipSlots = 0;
|
||||
boolean colorPartial = false;
|
||||
if (wantColor) {
|
||||
final int colorWriteMask = st.pipeline.colorWriteMask;
|
||||
final int[] formats = target.colorFormats;
|
||||
final int slotCount = formats != null ? formats.length : 0;
|
||||
boolean anyLive = false;
|
||||
for (int i = 0; i < slotCount; i++) {
|
||||
final boolean present;
|
||||
if (fbo != null) {
|
||||
final int db = target.drawBuffers[i];
|
||||
present = db >= 0 && db < ContextState.MAX_COLOR_ATTACHMENTS && fbo.colorTextures[db] != 0;
|
||||
} else {
|
||||
present = i == 0;
|
||||
}
|
||||
if (!present) continue;
|
||||
final int fm = PixelOps.colorComponentMask(formats[i]);
|
||||
final int m = colorWriteMask & fm;
|
||||
if (m == 0) {
|
||||
skipSlots |= 1 << i;
|
||||
} else {
|
||||
anyLive = true;
|
||||
if (m != fm) colorPartial = true;
|
||||
}
|
||||
}
|
||||
wantColor = anyLive;
|
||||
}
|
||||
|
||||
if (target.width > 0 && target.height > 0) {
|
||||
final int coverage = attachmentClear.coverage(st, target);
|
||||
if (coverage == AttachmentClear.EMPTY) return;
|
||||
final boolean partial = coverage == AttachmentClear.PARTIAL || colorPartial || (wantStencil && (st.pipeline.stencilWriteMask & 0xFF) != 0xFF);
|
||||
if (partial && !(wantColor && attachmentClear.hasIntegerColorTarget(target)) && clearPartial(st, fbo, target, wantColor, wantDepth, wantStencil)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// FBO0 path: just record pending state; ensureFbo0RenderPass consumes it lazily.
|
||||
if (st.boundFboId == 0) {
|
||||
@@ -998,15 +1050,9 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
return;
|
||||
}
|
||||
|
||||
final FboState fbo = resourceManager.getFbo(st.boundFboId);
|
||||
if (fbo == null) return;
|
||||
|
||||
if (!wantColor && (wantDepth || wantStencil)) {
|
||||
final FrameState f = frameManager.frame();
|
||||
if (attachmentClear.eligible(st, f, fbo, wantDepth, wantStencil)
|
||||
&& attachmentClear.clearInPass(st, f, fbo, wantDepth, wantStencil)) {
|
||||
return;
|
||||
}
|
||||
final FrameState f = frameManager.frame();
|
||||
if (!wantColor && !SystemProperties.SDL_DISABLE_IN_PASS_CLEAR && f.renderPass != 0 && f.activeLayoutHash == fbo.structuralLayoutHash && !FBOClearTracker.fboHasPendingClear(st, fbo) && clearPartial(st, fbo, target, false, wantDepth, wantStencil)) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean affectsActivePass = false;
|
||||
@@ -1015,7 +1061,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final long activeDepth = passActive ? frameManager.getCurrentDepthTarget() : 0;
|
||||
|
||||
if (wantColor) {
|
||||
for (int db : fbo.drawBuffers) {
|
||||
final int[] drawBuffers = fbo.drawBuffers;
|
||||
for (int i = 0; i < drawBuffers.length; i++) {
|
||||
if ((skipSlots & (1 << i)) != 0) continue;
|
||||
final int db = drawBuffers[i];
|
||||
if (db < 0 || db >= ContextState.MAX_COLOR_ATTACHMENTS) continue;
|
||||
final long tex = fbo.colorTextures[db];
|
||||
if (tex == 0) continue;
|
||||
@@ -1024,11 +1073,11 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
}
|
||||
if (wantDepth && fbo.depthTexture != 0) {
|
||||
FBOClearTracker.recordPendingDepthClear(st, fbo.depthTexture, cs.depthClearValue);
|
||||
FBOClearTracker.recordPendingDepthClear(st, fbo.depthTexture, st.depthClearValue);
|
||||
if (passActive && fbo.depthTexture == activeDepth) affectsActivePass = true;
|
||||
}
|
||||
if (wantStencil && fbo.depthTexture != 0 && PixelOps.isDepthStencilFormat(fbo.depthFormat)) {
|
||||
FBOClearTracker.recordPendingStencilClear(st, fbo.depthTexture, cs.stencilClearValue);
|
||||
FBOClearTracker.recordPendingStencilClear(st, fbo.depthTexture, st.stencilClearValue);
|
||||
if (passActive && fbo.depthTexture == activeDepth) affectsActivePass = true;
|
||||
}
|
||||
|
||||
@@ -1037,6 +1086,33 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean clearPartial(ContextState st, FboState fbo, AttachmentClear.Target t, boolean color, boolean depth, boolean stencil) {
|
||||
final FrameState f = frameManager.frame();
|
||||
pipelineApplier.ensureRenderPass(st, f);
|
||||
final long expectedLayout = fbo == null ? FrameManager.FBO0_LAYOUT_HASH : fbo.structuralLayoutHash;
|
||||
if (f.renderPass == 0 || f.activeLayoutHash != expectedLayout) {
|
||||
return warnPartialClearFallback();
|
||||
}
|
||||
if (fbo == null) {
|
||||
t.depthTexture = f.currentDepthTarget;
|
||||
t.depthFormat = t.depthTexture != 0 ? resourceManager.getSwapchainDepthStencilFormat() : 0;
|
||||
}
|
||||
color &= t.colorFormats != null && (fbo == null || fbo.hasAnyColor);
|
||||
depth &= t.depthTexture != 0;
|
||||
stencil &= PixelOps.isDepthStencilFormat(t.depthFormat);
|
||||
if (!color && !depth && !stencil) return true;
|
||||
if (attachmentClear.clearInPass(st, f, t, color, depth, stencil)) return true;
|
||||
return warnPartialClearFallback();
|
||||
}
|
||||
|
||||
private boolean warnPartialClearFallback() {
|
||||
if (!partialClearFallbackWarned) {
|
||||
partialClearFallbackWarned = true;
|
||||
LOG.error("clear: in-pass partial clear unavailable; falling back to the legacy clear path");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override public void lineWidth(float width) { /* no-op */ }
|
||||
@Override public void pointSize(float size) { /* no-op */ }
|
||||
@Override public void logicOp(int opcode) { /* no-op */ }
|
||||
@@ -1051,9 +1127,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
@Override public void drawArrays(int mode, int first, int count) {
|
||||
if (count <= 0) return;
|
||||
final ContextState st = s();
|
||||
if (st.rasterizerDiscard) return;
|
||||
final FrameState f = frameManager.frame();
|
||||
if (!f.frameActive) { f.droppedDrawsThisFrame++; return; }
|
||||
final ContextState st = s();
|
||||
if (mode == GL11.GL_TRIANGLE_FAN && count >= 3) {
|
||||
if (!drawDispatch.drawTriangleFanAsTriangleList(st, first, count)) { f.droppedDrawsThisFrame++; return; }
|
||||
if (SystemProperties.FFP_TRACE) ffpTrace.trace(st, "arraysFan", mode, count, 0, first, 0);
|
||||
@@ -1096,6 +1173,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
private long prepareIndexedDrawBind(ContextState st, int mode, int type, String opName) {
|
||||
if (st.rasterizerDiscard) return 0;
|
||||
if (!drawDispatch.prepareIndexedDraw(st, mode, type)) return 0;
|
||||
final int ebo = st.currentVao.elementBuffer;
|
||||
final long eboHandle = resourceManager.getBufferHandle(ebo);
|
||||
@@ -1868,6 +1946,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
srcWidth = meta != null ? meta.width() : fbo.width;
|
||||
srcHeight = meta != null ? meta.height() : fbo.height;
|
||||
srcY = y;
|
||||
if (frameManager.isFrameActive()) fboClearTracker.materializePendingClearForTexture(cs, texHandle);
|
||||
}
|
||||
if (texHandle == 0 || srcWidth <= 0 || srcHeight <= 0) return;
|
||||
|
||||
@@ -3747,6 +3826,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
case GL11.GL_SCISSOR_TEST -> cs.scissorEnabled;
|
||||
case GL11.GL_STENCIL_TEST -> cs.pipeline.stencilTestEnabled;
|
||||
case GL11.GL_DEPTH_WRITEMASK -> cs.pipeline.depthWriteEnabled;
|
||||
case GL30.GL_RASTERIZER_DISCARD -> cs.rasterizerDiscard;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
@@ -4024,8 +4104,9 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override
|
||||
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
|
||||
if (count <= 0 || primcount <= 0) return;
|
||||
if (!frameManager.isFrameActive()) return;
|
||||
final ContextState st = s();
|
||||
if (st.rasterizerDiscard) return;
|
||||
if (!frameManager.isFrameActive()) return;
|
||||
drawDispatch.setPrimitiveTypeForDraw(st, FormatMap.mapPrimitiveType(mode));
|
||||
pipelineApplier.ensureRenderPass(st);
|
||||
if (!frameManager.isRenderPassActive()) return;
|
||||
|
||||
@@ -113,6 +113,7 @@ public final class ContextState {
|
||||
public float viewportDepthFar = 1.0f;
|
||||
public int scissorX, scissorY, scissorW, scissorH;
|
||||
public boolean scissorEnabled;
|
||||
public boolean rasterizerDiscard;
|
||||
public int activeTextureUnit;
|
||||
public int boundProgram;
|
||||
public int autoPushedProgram;
|
||||
|
||||
+173
-73
@@ -1,10 +1,9 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
@@ -23,7 +22,10 @@ import org.lwjgl.sdl.SDL_GPURasterizerState;
|
||||
import org.lwjgl.sdl.SDL_GPUVertexInputState;
|
||||
import org.lwjgl.sdl.SDL_GPUViewport;
|
||||
import org.lwjgl.sdl.SDL_Rect;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.taumc.glsl.Transformer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.LongSupplier;
|
||||
|
||||
@@ -38,15 +40,33 @@ public final class AttachmentClear {
|
||||
private static final String FRAGMENT_SOURCE = "angelica:sdlgpu/clear_attachment.fsh";
|
||||
private static final long KEY_SEED = 0x5B1D2A7C4E9F3061L;
|
||||
private static final int CACHE_SLOTS = 4;
|
||||
private static final int MAX_COLOR_TARGETS = ContextState.MAX_COLOR_ATTACHMENTS;
|
||||
|
||||
public static final int EMPTY = 0;
|
||||
public static final int PARTIAL = 1;
|
||||
public static final int FULL = 2;
|
||||
|
||||
public static final class Target {
|
||||
public int[] colorFormats;
|
||||
public int[] drawBuffers;
|
||||
public int depthFormat;
|
||||
public long depthTexture;
|
||||
public int width;
|
||||
public int height;
|
||||
public boolean flipY;
|
||||
}
|
||||
|
||||
private final FrameManager frameManager;
|
||||
private final PipelineStore pipelineStore;
|
||||
private final ShaderManager shaderManager;
|
||||
|
||||
private int program;
|
||||
private final Target fboTarget = new Target();
|
||||
private final Target fbo0Target = new Target();
|
||||
|
||||
private int vertexShaderId;
|
||||
private long sdlVertexShader;
|
||||
private long sdlFragmentShader;
|
||||
private boolean failed;
|
||||
private final long[] sdlFragmentShaders = new long[MAX_COLOR_TARGETS + 1];
|
||||
private final boolean[] fragmentFailed = new boolean[MAX_COLOR_TARGETS + 1];
|
||||
|
||||
private final long[] cacheKeys = new long[CACHE_SLOTS];
|
||||
private final long[] cachePipelines = new long[CACHE_SLOTS];
|
||||
@@ -55,12 +75,12 @@ public final class AttachmentClear {
|
||||
private final int[] scissorRect = new int[4];
|
||||
|
||||
private final Builder builder = new Builder();
|
||||
private int[] buildColorFormats;
|
||||
private int buildColorCount;
|
||||
private int buildDepthFormat;
|
||||
private Target buildTarget;
|
||||
private int buildColorTargets;
|
||||
private boolean buildDepthFlag;
|
||||
private boolean buildStencilFlag;
|
||||
private int buildWriteMask;
|
||||
private int buildStencilMask;
|
||||
private int buildColorMask;
|
||||
|
||||
public AttachmentClear(FrameManager frameManager, PipelineStore pipelineStore, ShaderManager shaderManager) {
|
||||
this.frameManager = frameManager;
|
||||
@@ -72,40 +92,69 @@ public final class AttachmentClear {
|
||||
@Override public long getAsLong() { return createPipeline(); }
|
||||
}
|
||||
|
||||
public boolean eligible(ContextState st, FrameState f, FboState fbo, boolean wantDepth, boolean wantStencil) {
|
||||
if (SystemProperties.SDL_DISABLE_IN_PASS_CLEAR) return false;
|
||||
if (st.boundFboId == 0) return false;
|
||||
if (f.renderPass == 0) return false;
|
||||
if (fbo.depthTexture == 0) return false;
|
||||
if (f.currentDepthTarget != fbo.depthTexture) return false;
|
||||
if (f.activeLayoutHash != fbo.structuralLayoutHash || f.currentColorTarget != fbo.primaryTarget) return false;
|
||||
if (FBOClearTracker.fboHasPendingClear(st, fbo)) return false;
|
||||
if (wantStencil && !PixelOps.isDepthStencilFormat(fbo.depthFormat)) return false;
|
||||
if (fbo.width <= 0 || fbo.height <= 0) return false;
|
||||
return computeScissor(st, fbo);
|
||||
public Target forFbo(FboState fbo) {
|
||||
final Target t = fboTarget;
|
||||
t.colorFormats = fbo.cachedColorFormats;
|
||||
t.drawBuffers = fbo.drawBuffers;
|
||||
t.depthFormat = fbo.depthTexture != 0 ? fbo.depthFormat : 0;
|
||||
t.depthTexture = fbo.depthTexture;
|
||||
t.width = fbo.width;
|
||||
t.height = fbo.height;
|
||||
t.flipY = false;
|
||||
return t;
|
||||
}
|
||||
|
||||
private boolean computeScissor(ContextState st, FboState fbo) {
|
||||
public Target forFbo0(int[] swapchainFormats, int w, int h) {
|
||||
final Target t = fbo0Target;
|
||||
t.colorFormats = swapchainFormats;
|
||||
t.drawBuffers = null;
|
||||
t.depthFormat = 0;
|
||||
t.depthTexture = 0;
|
||||
t.width = w;
|
||||
t.height = h;
|
||||
t.flipY = true;
|
||||
return t;
|
||||
}
|
||||
|
||||
static boolean enabled(Target t, int i) {
|
||||
return t.drawBuffers == null || t.drawBuffers[i] >= 0;
|
||||
}
|
||||
|
||||
public boolean hasIntegerColorTarget(Target t) {
|
||||
final int[] formats = t.colorFormats;
|
||||
if (formats == null) return false;
|
||||
for (int i = 0; i < formats.length; i++) {
|
||||
if (enabled(t, i) && PixelOps.isSdlFormatInteger(formats[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int coverage(ContextState st, Target t) {
|
||||
final int sx, sy, sw, sh;
|
||||
if (st.scissorEnabled) {
|
||||
sx = st.scissorX;
|
||||
sy = st.scissorY;
|
||||
sy = t.flipY ? t.height - st.scissorY - st.scissorH : st.scissorY;
|
||||
sw = st.scissorW;
|
||||
sh = st.scissorH;
|
||||
} else {
|
||||
sx = 0;
|
||||
sy = 0;
|
||||
sw = fbo.width;
|
||||
sh = fbo.height;
|
||||
sw = t.width;
|
||||
sh = t.height;
|
||||
}
|
||||
return ScissorClamp.clamp(sx, sy, sw, sh, fbo.width, fbo.height, scissorRect);
|
||||
if (!ScissorClamp.clamp(sx, sy, sw, sh, t.width, t.height, scissorRect)) return EMPTY;
|
||||
if (scissorRect[ScissorClamp.X] == 0 && scissorRect[ScissorClamp.Y] == 0
|
||||
&& scissorRect[ScissorClamp.W] == t.width && scissorRect[ScissorClamp.H] == t.height) return FULL;
|
||||
return PARTIAL;
|
||||
}
|
||||
|
||||
public boolean clearInPass(ContextState st, FrameState f, FboState fbo, boolean depth, boolean stencil) {
|
||||
final int writeMask = stencil ? (st.pipeline.stencilWriteMask & 0xFF) : 0;
|
||||
final long pipeline = getOrCreatePipeline(fbo, depth, stencil, writeMask);
|
||||
public boolean clearInPass(ContextState st, FrameState f, Target t, boolean color, boolean depth, boolean stencil) {
|
||||
final int colorTargets = color ? t.colorFormats.length : 0;
|
||||
final int stencilMask = stencil ? (st.pipeline.stencilWriteMask & 0xFF) : 0;
|
||||
final int colorMask = color ? st.pipeline.colorWriteMask : 0;
|
||||
final long pipeline = getOrCreatePipeline(t, colorTargets, depth, stencil, stencilMask, colorMask);
|
||||
if (pipeline == 0) return false;
|
||||
if (!computeScissor(st, fbo)) return false;
|
||||
if (coverage(st, t) == EMPTY) return false;
|
||||
|
||||
final long rp = f.renderPass;
|
||||
SDL_BindGPUGraphicsPipeline(rp, pipeline);
|
||||
@@ -114,8 +163,8 @@ public final class AttachmentClear {
|
||||
final long vpAddr = st.cachedViewport.address();
|
||||
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.X, 0f);
|
||||
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.Y, 0f);
|
||||
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.W, fbo.width);
|
||||
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.H, fbo.height);
|
||||
MemoryAccess.putFloat(vpAddr + SDL_GPUViewport.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);
|
||||
@@ -131,6 +180,18 @@ public final class AttachmentClear {
|
||||
SDL_SetGPUStencilReference(rp, (byte) st.stencilClearValue);
|
||||
}
|
||||
|
||||
if (colorTargets > 0) {
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final ByteBuffer clearColor = stack.malloc(16);
|
||||
clearColor.putFloat(0, st.clearR);
|
||||
clearColor.putFloat(4, st.clearG);
|
||||
clearColor.putFloat(8, st.clearB);
|
||||
clearColor.putFloat(12, st.clearA);
|
||||
SDL_PushGPUFragmentUniformData(frameManager.getCommandBuffer(f), 0, clearColor);
|
||||
}
|
||||
st.lastPushedProgramFs = -1;
|
||||
}
|
||||
|
||||
SDL_DrawGPUPrimitives(rp, 3, 1, 0, 0);
|
||||
|
||||
st.lastBoundPipeline = 0;
|
||||
@@ -139,34 +200,40 @@ public final class AttachmentClear {
|
||||
st.lastAppliedStencilRef = Integer.MIN_VALUE;
|
||||
st.lastAppliedVboBindCb = 0;
|
||||
|
||||
if (depth) st.clearedTexturesThisFrame.add(fbo.depthTexture);
|
||||
if (stencil) st.clearedStencilTexturesThisFrame.add(fbo.depthTexture);
|
||||
if (t.depthTexture != 0) {
|
||||
if (depth) st.clearedTexturesThisFrame.add(t.depthTexture);
|
||||
if (stencil) st.clearedStencilTexturesThisFrame.add(t.depthTexture);
|
||||
}
|
||||
|
||||
frameManager.noteInPassClear();
|
||||
return true;
|
||||
}
|
||||
|
||||
long getOrCreatePipeline(FboState fbo, boolean depthFlag, boolean stencilFlag, int writeMask) {
|
||||
if (!ensureShaders()) return 0;
|
||||
long getOrCreatePipeline(Target t, int colorTargets, boolean depth, boolean stencil, int stencilMask, int colorMask) {
|
||||
if (!ensureShaders(colorTargets)) return 0;
|
||||
|
||||
final int[] formats = fbo.cachedColorFormats;
|
||||
final int[] formats = t.colorFormats;
|
||||
final int count = formats != null ? formats.length : 0;
|
||||
long key = KEY_SEED;
|
||||
key = Hashing.fmix64(key, count);
|
||||
for (int i = 0; i < count; i++) key = Hashing.fmix64(key, formats[i]);
|
||||
key = Hashing.fmix64(key, fbo.depthFormat);
|
||||
key = Hashing.fmix64(key, (depthFlag ? 1 : 0) | ((stencilFlag ? 1 : 0) << 1) | (writeMask << 8));
|
||||
for (int i = 0; i < count; i++) {
|
||||
key = Hashing.fmix64(key, formats[i]);
|
||||
key = Hashing.fmix64(key, enabled(t, i) ? 1 : 0);
|
||||
}
|
||||
key = Hashing.fmix64(key, t.depthFormat);
|
||||
key = Hashing.fmix64(key, colorTargets);
|
||||
key = Hashing.fmix64(key, (depth ? 1 : 0) | ((stencil ? 1 : 0) << 1) | (stencilMask << 8) | (colorMask << 16));
|
||||
|
||||
for (int i = 0; i < CACHE_SLOTS; i++) {
|
||||
if (cacheKeys[i] == key && cachePipelines[i] != 0) return cachePipelines[i];
|
||||
}
|
||||
|
||||
buildColorFormats = formats;
|
||||
buildColorCount = count;
|
||||
buildDepthFormat = fbo.depthFormat;
|
||||
buildDepthFlag = depthFlag;
|
||||
buildStencilFlag = stencilFlag;
|
||||
buildWriteMask = writeMask;
|
||||
buildTarget = t;
|
||||
buildColorTargets = colorTargets;
|
||||
buildDepthFlag = depth;
|
||||
buildStencilFlag = stencil;
|
||||
buildStencilMask = stencilMask;
|
||||
buildColorMask = colorMask;
|
||||
final long pipeline = pipelineStore.getOrBuild(key, builder);
|
||||
if (pipeline != 0) {
|
||||
cacheKeys[cacheNext] = key;
|
||||
@@ -178,24 +245,27 @@ public final class AttachmentClear {
|
||||
|
||||
private long createPipeline() {
|
||||
try (var stack = stackPush()) {
|
||||
final int numColorTargets = buildColorCount;
|
||||
final Target t = buildTarget;
|
||||
final int numColorTargets = t.colorFormats != null ? t.colorFormats.length : 0;
|
||||
final SDL_GPUColorTargetDescription.Buffer colorDesc = SDL_GPUColorTargetDescription.calloc(numColorTargets, stack);
|
||||
for (int i = 0; i < numColorTargets; i++) {
|
||||
final var desc = colorDesc.get(i);
|
||||
desc.format(buildColorFormats[i]);
|
||||
desc.format(t.colorFormats[i]);
|
||||
final int mask = (buildColorTargets > 0 && enabled(t, i)) ? buildColorMask : 0;
|
||||
desc.blend_state()
|
||||
.enable_blend(false)
|
||||
.color_write_mask((byte) 0)
|
||||
.color_write_mask((byte) mask)
|
||||
.enable_color_write_mask(true);
|
||||
}
|
||||
|
||||
final boolean hasDepthTarget = t.depthFormat != 0;
|
||||
final SDL_GPUDepthStencilState depthStencil = SDL_GPUDepthStencilState.calloc(stack)
|
||||
.enable_depth_test(buildDepthFlag)
|
||||
.enable_depth_write(buildDepthFlag)
|
||||
.compare_op(SDL_GPU_COMPAREOP_ALWAYS)
|
||||
.enable_stencil_test(buildStencilFlag)
|
||||
.compare_mask((byte) 0xFF)
|
||||
.write_mask((byte) buildWriteMask);
|
||||
.write_mask((byte) buildStencilMask);
|
||||
if (buildStencilFlag) {
|
||||
depthStencil.front_stencil_state()
|
||||
.fail_op(SDL_GPU_STENCILOP_REPLACE)
|
||||
@@ -220,14 +290,14 @@ public final class AttachmentClear {
|
||||
final SDL_GPUGraphicsPipelineTargetInfo targetInfo = SDL_GPUGraphicsPipelineTargetInfo.calloc(stack)
|
||||
.num_color_targets(numColorTargets)
|
||||
.color_target_descriptions(colorDesc)
|
||||
.depth_stencil_format(buildDepthFormat)
|
||||
.has_depth_stencil_target(true);
|
||||
.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(sdlFragmentShader)
|
||||
.fragment_shader(sdlFragmentShaders[buildColorTargets])
|
||||
.primitive_type(SDL_GPU_PRIMITIVETYPE_TRIANGLELIST)
|
||||
.rasterizer_state(rasterizer)
|
||||
.multisample_state(multisample)
|
||||
@@ -244,48 +314,78 @@ public final class AttachmentClear {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ensureShaders() {
|
||||
if (program != 0) return true;
|
||||
if (failed) return false;
|
||||
failed = true;
|
||||
private boolean ensureShaders(int colorTargets) {
|
||||
if (colorTargets < 0 || colorTargets > MAX_COLOR_TARGETS) return false;
|
||||
if (sdlFragmentShaders[colorTargets] != 0) return true;
|
||||
if (fragmentFailed[colorTargets]) return false;
|
||||
fragmentFailed[colorTargets] = true;
|
||||
|
||||
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 vs = shaderManager.createShader(GL20.GL_VERTEX_SHADER);
|
||||
shaderManager.shaderSource(vs, ShaderLoader.getShaderSource(VERTEX_SOURCE));
|
||||
shaderManager.compileShader(vs);
|
||||
final int fs = shaderManager.createShader(GL20.GL_FRAGMENT_SHADER);
|
||||
shaderManager.shaderSource(fs, ShaderLoader.getShaderSource(FRAGMENT_SOURCE));
|
||||
shaderManager.shaderSource(fs, fragmentSource);
|
||||
shaderManager.compileShader(fs);
|
||||
|
||||
final int prog = shaderManager.createProgram();
|
||||
shaderManager.attachShader(prog, vs);
|
||||
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 failed to link: {}", po == null ? "no program object" : po.infoLog);
|
||||
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;
|
||||
}
|
||||
|
||||
po.sdlVertexShader = shaderManager.createSDLShader(po.vertexSpirv, SDL_GPU_SHADERSTAGE_VERTEX, po.vertexResources.numSamplers(), po.vertexResources.numUBOs(), po.vertexResources.numStorageBuffers(), po.vertexResources.numStorageTextures());
|
||||
po.sdlFragmentShader = shaderManager.createSDLShader(po.fragmentSpirv, SDL_GPU_SHADERSTAGE_FRAGMENT, po.fragmentResources.numSamplers(), po.fragmentResources.numUBOs(), po.fragmentResources.numStorageBuffers(), po.fragmentResources.numStorageTextures());
|
||||
if (po.sdlVertexShader == 0 || po.sdlFragmentShader == 0) {
|
||||
LOG.error("AttachmentClear: clear_attachment SDL shader creation failed");
|
||||
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;
|
||||
}
|
||||
|
||||
program = prog;
|
||||
sdlVertexShader = po.sdlVertexShader;
|
||||
sdlFragmentShader = po.sdlFragmentShader;
|
||||
failed = false;
|
||||
sdlFragmentShaders[colorTargets] = fragmentShader;
|
||||
fragmentFailed[colorTargets] = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String withColorOutputs(String source, int count) {
|
||||
final GlslTransformUtils.QuietParse parsed = GlslTransformUtils.parseBothQuiet(source);
|
||||
final Transformer transformer = new Transformer(parsed.full());
|
||||
transformer.injectVariable("uniform vec4 clearColor;");
|
||||
for (int i = 0; i < count; i++) {
|
||||
transformer.injectVariable("layout(location = " + i + ") out vec4 angelica_ClearColor" + i + ";");
|
||||
transformer.appendMain("angelica_ClearColor" + i + " = clearColor;");
|
||||
}
|
||||
final String header = GlslTransformUtils.getFormattedShader(parsed.pre(), "").trim();
|
||||
final StringBuilder out = new StringBuilder();
|
||||
transformer.mutateTree(tree -> out.append(GlslTransformUtils.getFormattedShader(tree, header)));
|
||||
return out.toString();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
program = 0;
|
||||
vertexShaderId = 0;
|
||||
sdlVertexShader = 0;
|
||||
sdlFragmentShader = 0;
|
||||
failed = false;
|
||||
Arrays.fill(sdlFragmentShaders, 0L);
|
||||
Arrays.fill(fragmentFailed, false);
|
||||
Arrays.fill(cacheKeys, 0L);
|
||||
Arrays.fill(cachePipelines, 0L);
|
||||
cacheNext = 0;
|
||||
|
||||
@@ -74,6 +74,14 @@ public final class PixelOps {
|
||||
};
|
||||
}
|
||||
|
||||
public static int colorComponentMask(int sdlFormat) {
|
||||
int mask = 0;
|
||||
for (int c = 0; c < 4; c++) {
|
||||
if (colorChannelBits(sdlFormat, c) > 0) mask |= 1 << c;
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
private static int uniformChannelBits(int sdlFormat) {
|
||||
return switch (sdlFormat) {
|
||||
case SDL_GPU_TEXTUREFORMAT_R8_UNORM, SDL_GPU_TEXTUREFORMAT_R8_SNORM,
|
||||
@@ -174,6 +182,16 @@ public final class PixelOps {
|
||||
};
|
||||
}
|
||||
|
||||
public static boolean isSdlFormatInteger(int sdlFormat) {
|
||||
if (isSdlFormatUnsignedInteger(sdlFormat)) return true;
|
||||
return switch (sdlFormat) {
|
||||
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 -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
public static ByteBuffer expandToRGBA(int format, ByteBuffer src, int width, int height) {
|
||||
if (src == null) return null;
|
||||
final int pixelCount = width * height;
|
||||
|
||||
+212
@@ -10,9 +10,14 @@ import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
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.lang.reflect.Field;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -67,6 +72,12 @@ class GlsmSdlAttachmentClearTest {
|
||||
return r > 200 && g < 64;
|
||||
}
|
||||
|
||||
private static boolean isGreen(int argb) {
|
||||
final int r = (argb >> 16) & 0xFF;
|
||||
final int g = (argb >> 8) & 0xFF;
|
||||
return g > 200 && r < 64;
|
||||
}
|
||||
|
||||
private static boolean depthEquals(float depthValue) {
|
||||
GLStateManager.glEnable(GL11.GL_DEPTH_TEST);
|
||||
GLStateManager.glDepthFunc(GL11.GL_EQUAL);
|
||||
@@ -358,4 +369,205 @@ class GlsmSdlAttachmentClearTest {
|
||||
assertTrue(Math.abs(g - 102) <= 2, "green must be untouched: " + g);
|
||||
assertTrue(Math.abs(b - 153) <= 2, "blue must be untouched: " + b);
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissoredColorAndDepthClearStaysInTheRect() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClearDepth(0.9);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.0f, 0.0f, 1.0f);
|
||||
|
||||
final FrameManager.FrameState f = frame();
|
||||
final long passBefore = f.renderPass;
|
||||
assertTrue(passBefore != 0, "expected an active render pass before the scissored clear");
|
||||
final int inPassBefore = f.inPassClearsThisFrame;
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(0, 0, SIZE / 2, SIZE);
|
||||
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClearDepth(0.0);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
assertEquals(passBefore, f.renderPass, "the scissored clear must not break the active render pass");
|
||||
assertEquals(inPassBefore + 1, f.inPassClearsThisFrame, "the scissored clear must take the in-pass path");
|
||||
|
||||
final int probeSize = 4;
|
||||
final int leftProbeX = SIZE / 2 - probeSize * 2;
|
||||
final int rightProbeX = SIZE / 2 + probeSize;
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_DEPTH_TEST);
|
||||
GLStateManager.glDepthFunc(GL11.GL_EQUAL);
|
||||
GLStateManager.glDepthMask(false);
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(leftProbeX, 0, probeSize, SIZE);
|
||||
drawFullQuadAtZ(0.0f * 2.0f - 1.0f, 0.0f, 1.0f, 0.0f);
|
||||
GLStateManager.glScissor(rightProbeX, 0, probeSize, SIZE);
|
||||
drawFullQuadAtZ(0.9f * 2.0f - 1.0f, 0.0f, 1.0f, 0.0f);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glDisable(GL11.GL_DEPTH_TEST);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 8, SIZE / 2)), "inside the scissor rect must be the clear color");
|
||||
assertFalse(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE - SIZE / 8, SIZE / 2)), "outside the scissor rect must keep the prior blue quad");
|
||||
assertTrue(isGreen(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, leftProbeX + probeSize / 2, SIZE / 2)), "inside the scissor rect the depth must have been cleared to 0");
|
||||
assertTrue(isGreen(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, rightProbeX + probeSize / 2, SIZE / 2)), "outside the scissor rect the prior depth must be untouched");
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissoredClearAfterPendingFullClear() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(0, 0, SIZE / 2, SIZE);
|
||||
GLStateManager.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
GLStateManager.glScissor(0, 0, 0, SIZE);
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
assertTrue(isGreen(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 4, SIZE / 2)), "the scissored clear must have painted the left half green");
|
||||
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2 + SIZE / 4, SIZE / 2)), "the empty-scissor clear must not have touched the right half");
|
||||
}
|
||||
|
||||
@Test
|
||||
void colorMaskIsHonoredByClear() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.solidQuad(0.2f, 0.4f, 0.6f);
|
||||
|
||||
GLStateManager.glColorMask(true, false, false, true);
|
||||
GLStateManager.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
final int argb = GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2);
|
||||
final int r = (argb >> 16) & 0xFF;
|
||||
final int g = (argb >> 8) & 0xFF;
|
||||
final int b = argb & 0xFF;
|
||||
assertEquals(255, r, "red must be written: colorMask allowed it");
|
||||
assertTrue(Math.abs(g - 102) <= 2, "green must be untouched by the masked clear: " + g);
|
||||
assertTrue(Math.abs(b - 153) <= 2, "blue must be untouched by the masked clear: " + b);
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissoredClearWritesEveryDrawBuffer() {
|
||||
final int texA = createColorTexture();
|
||||
final int texB = createColorTexture();
|
||||
|
||||
final int mrtFbo = GLStateManager.glGenFramebuffers();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, mrtFbo);
|
||||
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, texA, 0);
|
||||
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT1, GL11.GL_TEXTURE_2D, texB, 0);
|
||||
GLStateManager.glViewport(0, 0, SIZE, SIZE);
|
||||
GLStateManager.glDrawBuffers(IntBuffer.wrap(new int[]{ GL30.GL_COLOR_ATTACHMENT0, GL30.GL_COLOR_ATTACHMENT1 }));
|
||||
|
||||
GLStateManager.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(0, 0, SIZE / 2, SIZE);
|
||||
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
final int readFbo = GLStateManager.glGenFramebuffers();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, readFbo);
|
||||
|
||||
assertLeftHalfRed(texA, "texture A");
|
||||
assertLeftHalfRed(texB, "texture B");
|
||||
|
||||
GLStateManager.glDeleteFramebuffers(readFbo);
|
||||
GLStateManager.glDeleteFramebuffers(mrtFbo);
|
||||
GLStateManager.glDeleteTextures(texA);
|
||||
GLStateManager.glDeleteTextures(texB);
|
||||
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
}
|
||||
|
||||
private static int createColorTexture() {
|
||||
return createColorTexture(GL11.GL_RGBA8, GL11.GL_RGBA);
|
||||
}
|
||||
|
||||
private static int createColorTexture(int internalFormat, int format) {
|
||||
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, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
return tex;
|
||||
}
|
||||
|
||||
private static void assertLeftHalfRed(int texture, String label) {
|
||||
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, texture, 0);
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 4, SIZE / 2)), label + " left half must be cleared red");
|
||||
assertFalse(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2 + SIZE / 4, SIZE / 2)), label + " right half must keep the base clear");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rasterizerDiscardSkipsClearsAndDraws() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
|
||||
GLStateManager.glEnable(GL30.GL_RASTERIZER_DISCARD);
|
||||
GLStateManager.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glDisable(GL30.GL_RASTERIZER_DISCARD);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2)), "rasterizer discard must skip both the clear and the draw");
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearIsIgnoredOutsideRenderMode() {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GlsmSdlHeadlessRig.solidQuad(1.0f, 0.0f, 0.0f);
|
||||
|
||||
final FloatBuffer feedbackBuffer = BufferUtils.createFloatBuffer(16);
|
||||
GLStateManager.glFeedbackBuffer(GL11.GL_3D, feedbackBuffer);
|
||||
GLStateManager.glRenderMode(GL11.GL_FEEDBACK);
|
||||
GLStateManager.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glRenderMode(GL11.GL_RENDER);
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
assertTrue(isRed(GlsmSdlHeadlessRig.pixelAt(pixels, SIZE, SIZE / 2, SIZE / 2)), "a clear issued outside GL_RENDER mode must be ignored");
|
||||
}
|
||||
|
||||
@Test
|
||||
void colorMaskCoveringEveryPresentChannelIsAFullClear() {
|
||||
final int tex = createColorTexture(GL30.GL_R8, GL11.GL_RED);
|
||||
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);
|
||||
|
||||
GLStateManager.glClearColor(0.2f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GlsmSdlHeadlessRig.solidQuad(0.4f, 0.0f, 0.0f);
|
||||
|
||||
final FrameManager.FrameState f = frame();
|
||||
final int inPassBefore = f.inPassClearsThisFrame;
|
||||
|
||||
GLStateManager.glColorMask(true, false, false, false);
|
||||
GLStateManager.glClearColor(0.6f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
|
||||
assertEquals(inPassBefore, f.inPassClearsThisFrame, "masking every present channel on a single-red-channel target must take the load-op path, not in-pass");
|
||||
|
||||
GLStateManager.glDeleteFramebuffers(fbo);
|
||||
GLStateManager.glDeleteTextures(tex);
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
}
|
||||
}
|
||||
+13
@@ -95,6 +95,19 @@ class GlsmSdlFbo0ViewportTest {
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, FBO0_H - VIEW, VIEW, VIEW), BLUE, "above the viewport");
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissoredClearFlipsYOnFbo0() {
|
||||
bindFbo0WithFullViewport();
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(0, 0, FBO0_W, VIEW);
|
||||
GLStateManager.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, 0, FBO0_W, VIEW), RED, "GL scissor y=0 is the bottom rows of the FBO0 texture");
|
||||
GlsmSdlHeadlessRig.assertUniform(GlsmSdlHeadlessRig.readTarget(0, FBO0_H - VIEW, FBO0_W, VIEW), BLUE, "the top rows must stay untouched by the flipped scissor");
|
||||
}
|
||||
|
||||
@Test
|
||||
void scissorLargerThanFbo0StillCoversTheWholeTarget() {
|
||||
bindFbo0WithFullViewport();
|
||||
|
||||
+13
-77
@@ -2,9 +2,6 @@ package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
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.shader.ShaderManager;
|
||||
@@ -15,10 +12,8 @@ import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_D32_FLOAT;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET;
|
||||
|
||||
class AttachmentClearTest {
|
||||
@@ -76,92 +71,33 @@ class AttachmentClearTest {
|
||||
return fbo;
|
||||
}
|
||||
|
||||
@Test
|
||||
void eligibleTruthTable() {
|
||||
final FboState fbo = makeFbo();
|
||||
final ContextState baseline = new ContextState();
|
||||
baseline.boundFboId = 1;
|
||||
final FrameManager.FrameState f = new FrameManager.FrameState();
|
||||
f.renderPass = 0xBEEFL;
|
||||
f.currentDepthTarget = fbo.depthTexture;
|
||||
f.currentColorTarget = fbo.primaryTarget;
|
||||
f.activeLayoutHash = fbo.structuralLayoutHash;
|
||||
assertTrue(attachmentClear.eligible(baseline, f, fbo, true, false), "baseline row must be eligible");
|
||||
|
||||
final ContextState noFbo = new ContextState();
|
||||
noFbo.boundFboId = 0;
|
||||
assertFalse(attachmentClear.eligible(noFbo, f, fbo, true, false), "boundFboId==0 must be ineligible");
|
||||
|
||||
final FrameManager.FrameState noPass = new FrameManager.FrameState();
|
||||
noPass.currentDepthTarget = fbo.depthTexture;
|
||||
noPass.currentColorTarget = fbo.primaryTarget;
|
||||
noPass.activeLayoutHash = fbo.structuralLayoutHash;
|
||||
assertFalse(attachmentClear.eligible(baseline, noPass, fbo, true, false), "renderPass==0 must be ineligible");
|
||||
|
||||
final FrameManager.FrameState wrongDepth = new FrameManager.FrameState();
|
||||
wrongDepth.renderPass = 0xBEEFL;
|
||||
wrongDepth.currentDepthTarget = fbo.depthTexture + 1;
|
||||
wrongDepth.currentColorTarget = fbo.primaryTarget;
|
||||
wrongDepth.activeLayoutHash = fbo.structuralLayoutHash;
|
||||
assertFalse(attachmentClear.eligible(baseline, wrongDepth, fbo, true, false), "depth target mismatch must be ineligible");
|
||||
|
||||
final FrameManager.FrameState wrongLayout = new FrameManager.FrameState();
|
||||
wrongLayout.renderPass = 0xBEEFL;
|
||||
wrongLayout.currentDepthTarget = fbo.depthTexture;
|
||||
wrongLayout.currentColorTarget = fbo.primaryTarget;
|
||||
wrongLayout.activeLayoutHash = fbo.structuralLayoutHash ^ 1L;
|
||||
assertFalse(attachmentClear.eligible(baseline, wrongLayout, fbo, true, false), "layout hash mismatch must be ineligible");
|
||||
|
||||
final FrameManager.FrameState wrongColor = new FrameManager.FrameState();
|
||||
wrongColor.renderPass = 0xBEEFL;
|
||||
wrongColor.currentDepthTarget = fbo.depthTexture;
|
||||
wrongColor.currentColorTarget = fbo.primaryTarget + 1;
|
||||
wrongColor.activeLayoutHash = fbo.structuralLayoutHash;
|
||||
assertFalse(attachmentClear.eligible(baseline, wrongColor, fbo, true, false), "color target mismatch must be ineligible");
|
||||
|
||||
final ContextState pending = new ContextState();
|
||||
pending.boundFboId = 1;
|
||||
FBOClearTracker.recordPendingDepthClear(pending, fbo.depthTexture, 1.0f);
|
||||
assertFalse(attachmentClear.eligible(pending, f, fbo, true, false), "a pending deferred clear must be ineligible");
|
||||
|
||||
final FboState depthOnlyFormat = makeFbo();
|
||||
depthOnlyFormat.depthFormat = SDL_GPU_TEXTUREFORMAT_D32_FLOAT;
|
||||
assertFalse(attachmentClear.eligible(baseline, f, depthOnlyFormat, false, true), "stencil clear on a non depth-stencil format must be ineligible");
|
||||
assertTrue(attachmentClear.eligible(baseline, f, depthOnlyFormat, true, false), "depth clear on a depth-only format stays eligible");
|
||||
|
||||
final FboState zeroSize = makeFbo();
|
||||
zeroSize.width = 0;
|
||||
assertFalse(attachmentClear.eligible(baseline, f, zeroSize, true, false), "zero-size target must be ineligible");
|
||||
|
||||
final ContextState scissoredEmpty = new ContextState();
|
||||
scissoredEmpty.boundFboId = 1;
|
||||
scissoredEmpty.scissorEnabled = true;
|
||||
scissoredEmpty.scissorX = SIZE + 10;
|
||||
scissoredEmpty.scissorY = 0;
|
||||
scissoredEmpty.scissorW = 4;
|
||||
scissoredEmpty.scissorH = 4;
|
||||
assertFalse(attachmentClear.eligible(scissoredEmpty, f, fbo, true, false), "a scissor clamped to empty must be ineligible");
|
||||
}
|
||||
|
||||
@Test
|
||||
void onePipelinePerLayoutModeAndMask() {
|
||||
final FboState fbo = makeFbo();
|
||||
final long p1 = attachmentClear.getOrCreatePipeline(fbo, true, false, 0);
|
||||
final long p1Again = attachmentClear.getOrCreatePipeline(fbo, true, false, 0);
|
||||
final long p1 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 0, true, false, 0, 0);
|
||||
final long p1Again = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 0, true, false, 0, 0);
|
||||
assertTrue(p1 != 0, "pipeline build must succeed");
|
||||
assertEquals(p1, p1Again, "the same (layout, mode, mask) must reuse the cached pipeline");
|
||||
|
||||
final long p2 = attachmentClear.getOrCreatePipeline(fbo, false, true, 0xFF);
|
||||
final long p2 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 0, false, true, 0xFF, 0);
|
||||
assertNotEquals(p1, p2, "a different clear mode must build a different pipeline");
|
||||
|
||||
final long p3 = attachmentClear.getOrCreatePipeline(fbo, false, true, 0x0F);
|
||||
final long p3 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 0, false, true, 0x0F, 0);
|
||||
assertNotEquals(p2, p3, "a different stencil write mask must build a different pipeline");
|
||||
|
||||
final FboState depthOnlyLayout = makeFbo();
|
||||
depthOnlyLayout.drawBuffers = new int[0];
|
||||
depthOnlyLayout.cachedColorFormats = new int[0];
|
||||
final long p4 = attachmentClear.getOrCreatePipeline(depthOnlyLayout, true, false, 0);
|
||||
final long p4 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(depthOnlyLayout), 0, true, false, 0, 0);
|
||||
assertTrue(p4 != 0, "a depth-only layout pipeline must still build");
|
||||
assertNotEquals(p1, p4, "a different color-target layout must build a different pipeline");
|
||||
|
||||
final long pc1 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 1, false, false, 0, 0xF);
|
||||
final long pc1Again = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 1, false, false, 0, 0xF);
|
||||
assertTrue(pc1 != 0, "color pipeline build must succeed");
|
||||
assertEquals(pc1, pc1Again, "the same color mask must reuse the cached pipeline");
|
||||
|
||||
final long pc2 = attachmentClear.getOrCreatePipeline(attachmentClear.forFbo(fbo), 1, false, false, 0, 0x9);
|
||||
assertNotEquals(pc1, pc2, "a different color write mask must build a different pipeline");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user