mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-08 12:35:01 +00:00
SDL followup fixes (#2001)
* Deploy fixes * Narrow transformer exclusions for Legends mods, and mixin priority for CustomNPC * Journeymap 6 stencil/depth fix * OpenLTW swizzle workaround
This commit is contained in:
1 parent
166116d692
commit
8c31eec525
38 files changed
+1771
-107
No files matched your search
@@ -89,4 +89,4 @@ jobs:
|
||||
env:
|
||||
MAVEN_USER: ${{ secrets.MAVEN_USER }}
|
||||
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||
run: ./gradlew :tracy-client:publish
|
||||
run: ./gradlew -PpublishTracy=true :tracy-client:publish
|
||||
@@ -3,7 +3,7 @@ name: Release tagged build
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: [ '*' ]
|
||||
tags: [ '*', '!tracy-v*' ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
+6
-2
@@ -1,3 +1,4 @@
|
||||
import com.modrinth.minotaur.TaskModrinthUpload
|
||||
import net.darkhax.curseforgegradle.TaskPublishCurseForge
|
||||
import xyz.wagyourtail.jvmdg.gradle.flags.DowngradeFlags
|
||||
import xyz.wagyourtail.jvmdg.gradle.task.DowngradeJar
|
||||
@@ -76,7 +77,7 @@ val isMacOs = org.gradle.internal.os.OperatingSystem.current().isMacOsX
|
||||
val isMacOsArm64 = isMacOs && System.getProperty("os.arch") == "aarch64"
|
||||
|
||||
tasks.withType<JavaExec>().configureEach {
|
||||
if (name.startsWith("runClient")) {
|
||||
if (name.startsWith("runClient") && name != "runClient") {
|
||||
if (isMacOs) {
|
||||
// SDL3 / Cocoa / Metal must initialize on the JVM main thread.
|
||||
jvmArgs("-XstartOnFirstThread")
|
||||
@@ -228,7 +229,6 @@ val glCoreTest by tasks.registering(Test::class) {
|
||||
}
|
||||
|
||||
tasks.test { finalizedBy(glCoreTest) }
|
||||
tasks.check { dependsOn(glCoreTest) }
|
||||
|
||||
tasks.shadowJar {
|
||||
dependsOn(embedOnly)
|
||||
@@ -257,3 +257,7 @@ tasks.shadowJar {
|
||||
tasks.withType<TaskPublishCurseForge>().configureEach {
|
||||
uploadArtifacts.forEach { it.addGameVersion("Client") }
|
||||
}
|
||||
|
||||
tasks.withType<TaskModrinthUpload>().configureEach {
|
||||
setDependsOn(dependsOn.filterNot { it == "build" } + "assemble")
|
||||
}
|
||||
@@ -20,6 +20,7 @@ public final class SystemProperties {
|
||||
public static final boolean USE_SDL_GPU = Boolean.getBoolean(KEY_USE_SDL_GPU);
|
||||
public static final String GL_PROFILE = System.getProperty("angelica.glProfile", "");
|
||||
public static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
|
||||
public static final boolean DISABLE_LTW_WORKAROUND = Boolean.getBoolean("angelica.disableLtwWorkaround");
|
||||
|
||||
// Redirector
|
||||
public static final UnmappedGLMode UNMAPPED_GL = parseEnum("angelica.unmappedGL", UnmappedGLMode.WARN, UnmappedGLMode.WARN, UnmappedGLMode.class);
|
||||
|
||||
@@ -552,6 +552,9 @@ public class GLStateManager {
|
||||
// GL_STENCIL_BITS was removed in core profile; query via default FBO attachment
|
||||
final int stencilBits = RENDER_BACKEND.getFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_STENCIL, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE);
|
||||
glCtx.stencilBitMask = stencilBits >= 32 ? 0xFFFFFFFF : (1 << stencilBits) - 1;
|
||||
if (glCtx.stencilBitMask == 0) {
|
||||
LOGGER.warn("Default framebuffer reports 0 stencil bits on backend {}; all stencil masks clamp to 0", RENDER_BACKEND.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
// Initialize stencil masks from computed bit mask
|
||||
glCtx.stencilState.setValueMaskFront(glCtx.stencilBitMask);
|
||||
@@ -1965,6 +1968,7 @@ public class GLStateManager {
|
||||
suspendPixelUnpackBuffer();
|
||||
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
restorePixelUnpackBuffer();
|
||||
if (mode == RecordMode.NONE && RenderSystem.isLTW()) LTWWorkaround.onTexImage2D(target, level, format, pixels != null);
|
||||
maybeGenerateMipmap(target, level);
|
||||
}
|
||||
|
||||
@@ -1983,6 +1987,7 @@ public class GLStateManager {
|
||||
suspendPixelUnpackBuffer();
|
||||
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
restorePixelUnpackBuffer();
|
||||
if (mode == RecordMode.NONE && RenderSystem.isLTW()) LTWWorkaround.onTexImage2D(target, level, format, pixels != null);
|
||||
maybeGenerateMipmap(target, level);
|
||||
}
|
||||
|
||||
@@ -2001,6 +2006,7 @@ public class GLStateManager {
|
||||
suspendPixelUnpackBuffer();
|
||||
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
restorePixelUnpackBuffer();
|
||||
if (mode == RecordMode.NONE && RenderSystem.isLTW()) LTWWorkaround.onTexImage2D(target, level, format, pixels != null);
|
||||
maybeGenerateMipmap(target, level);
|
||||
}
|
||||
|
||||
@@ -2019,6 +2025,7 @@ public class GLStateManager {
|
||||
suspendPixelUnpackBuffer();
|
||||
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
|
||||
restorePixelUnpackBuffer();
|
||||
if (mode == RecordMode.NONE && RenderSystem.isLTW()) LTWWorkaround.onTexImage2D(target, level, format, pixels != null);
|
||||
maybeGenerateMipmap(target, level);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
public final class LTWWorkaround {
|
||||
|
||||
private LTWWorkaround() {}
|
||||
|
||||
private static ByteBuffer oneTexel;
|
||||
|
||||
public static boolean isLtwVersionString(String glVersion) {
|
||||
return glVersion != null && glVersion.contains("LTW");
|
||||
}
|
||||
|
||||
public static void onTexImage2D(int target, int level, int format, boolean hasPixels) {
|
||||
if (target != GL11.GL_TEXTURE_2D || level != 0 || format != GL12.GL_BGRA || hasPixels) return;
|
||||
if (oneTexel == null) {
|
||||
oneTexel = BufferUtils.createByteBuffer(4);
|
||||
}
|
||||
oneTexel.clear();
|
||||
GLStateManager.forcePixelUnpackState(PixelUnpackState.DEFAULT);
|
||||
GLStateManager.suspendPixelUnpackBuffer();
|
||||
RENDER_BACKEND.texSubImage2D(target, level, 0, 0, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, oneTexel);
|
||||
GLStateManager.restorePixelUnpackBuffer();
|
||||
GLStateManager.restorePixelUnpackState(PixelUnpackState.DEFAULT);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
|
||||
import com.gtnewhorizons.angelica.glsm.dsa.DSAAccess;
|
||||
@@ -59,6 +60,9 @@ public class RenderSystem {
|
||||
private static volatile boolean glesDetected;
|
||||
private static volatile boolean hasClipCullDistance;
|
||||
|
||||
private static volatile boolean isLTW;
|
||||
private static volatile boolean ltwDetected;
|
||||
|
||||
// Sampler object state tracking (null if unsupported)
|
||||
private static int[] samplers;
|
||||
|
||||
@@ -403,6 +407,26 @@ public class RenderSystem {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isLTW() {
|
||||
if (!ltwDetected) detectLTW();
|
||||
return isLTW;
|
||||
}
|
||||
|
||||
private static synchronized void detectLTW() {
|
||||
if (ltwDetected) return;
|
||||
if (SystemProperties.DISABLE_LTW_WORKAROUND) {
|
||||
ltwDetected = true;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final String v = RENDER_BACKEND.getString(GL11.GL_VERSION);
|
||||
if (v == null) return;
|
||||
isLTW = LTWWorkaround.isLtwVersionString(v);
|
||||
ltwDetected = true;
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean supportsSSBO() {
|
||||
return supportsSSBO;
|
||||
}
|
||||
|
||||
+9
-2
@@ -21,11 +21,11 @@ public final class EcosystemNarrowRules {
|
||||
"Reika.DragonAPI.ASM.StructureLootHooks"
|
||||
)),
|
||||
|
||||
new NarrowRule("Xaeros_common", "xaero.common.core", List.of(
|
||||
new NarrowRule("Xaeros", "xaero.common.core", List.of(
|
||||
"xaero.common.core.transformer"
|
||||
)),
|
||||
|
||||
new NarrowRule("Xaeros_map", "xaero.map.core", List.of(
|
||||
new NarrowRule("Xaeros", "xaero.map.core", List.of(
|
||||
"xaero.map.core.transformer"
|
||||
)),
|
||||
|
||||
@@ -78,6 +78,13 @@ public final class EcosystemNarrowRules {
|
||||
"alfheim.common.core.asm.hook.fixes.CorporeaInputFix",
|
||||
"alfheim.common.core.asm.hook.fixes.GodAttributesHooks",
|
||||
"alfheim.common.core.asm.hook.fixes.RecipeAncientWillsFix"
|
||||
)),
|
||||
|
||||
new NarrowRule("LegendsMod", "com.tihyo.legends.management.asm", List.of(
|
||||
"com.tihyo.legends.management.asm.LegendsLoadingPlugin",
|
||||
"com.tihyo.legends.management.asm.AbstractTransformer",
|
||||
"com.tihyo.legends.management.asm.ASMHelper",
|
||||
"com.tihyo.legends.management.asm.Transformer"
|
||||
))
|
||||
);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public final class TransformerNarrower {
|
||||
|
||||
/**
|
||||
* A rule that replaces a broad transformer exclusion with narrower ones.
|
||||
* @param name Human-readable name for logging (e.g. "DragonAPI")
|
||||
* @param name Blackboard/config key suffix. (e.g. "DragonAPI").
|
||||
* @param broad The broad exclusion to remove (e.g. "Reika.DragonAPI.ASM")
|
||||
* @param narrow Narrower exclusions to add in its place
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,18 @@ class GLVersionParseTest {
|
||||
assertEquals(-1, RenderSystem.parseMajorMinor("OpenGL ES 3.2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectsLtwWrapper() {
|
||||
assertTrue(LTWWorkaround.isLtwVersionString("3.3 OpenLTW (Built on: May 21 2026/12:38:14)"));
|
||||
assertFalse(LTWWorkaround.isLtwVersionString(null));
|
||||
assertFalse(LTWWorkaround.isLtwVersionString(""));
|
||||
assertFalse(LTWWorkaround.isLtwVersionString("4.6.0 NVIDIA 551.61"));
|
||||
assertFalse(LTWWorkaround.isLtwVersionString("4.6 (Core Profile) Mesa 24.0.3"));
|
||||
assertFalse(LTWWorkaround.isLtwVersionString("4.1 Metal - 88"));
|
||||
assertFalse(LTWWorkaround.isLtwVersionString("OpenGL ES 3.2 ANGLE (Vulkan)"));
|
||||
assertFalse(LTWWorkaround.isLtwVersionString("2.1 gl4es wrapper"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isContextValid() {
|
||||
assertTrue(RenderSystem.isContextValid(4, 4, 46, true));
|
||||
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package com.gtnewhorizons.angelica.glsm.loading;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class TransformerNarrowerTest {
|
||||
|
||||
private static final String LEGENDS_BROAD = "com.tihyo.legends.management.asm";
|
||||
private static final String LEGENDS_HOOK = LEGENDS_BROAD + ".ASMClientMethods";
|
||||
|
||||
private static Set<String> exceptionsWith(String... entries) {
|
||||
return new HashSet<>(Set.of(entries));
|
||||
}
|
||||
|
||||
private static boolean isExcluded(Set<String> exceptions, String className) {
|
||||
return exceptions.stream().anyMatch(className::startsWith);
|
||||
}
|
||||
|
||||
private static int narrow(Set<String> exceptions, Map<String, Object> blackboard) {
|
||||
return TransformerNarrower.narrow(exceptions, blackboard, "angelica", "Angelica", EcosystemNarrowRules.ALL);
|
||||
}
|
||||
|
||||
@Test
|
||||
void legendsHooksBecomeTransformableButTransformersDoNot() {
|
||||
final Set<String> exceptions = exceptionsWith(LEGENDS_BROAD);
|
||||
assertEquals(1, narrow(exceptions, new HashMap<>()));
|
||||
|
||||
assertFalse(isExcluded(exceptions, LEGENDS_HOOK));
|
||||
assertFalse(isExcluded(exceptions, LEGENDS_BROAD + ".ASMMethods"));
|
||||
|
||||
assertTrue(isExcluded(exceptions, LEGENDS_BROAD + ".LegendsLoadingPlugin"));
|
||||
assertTrue(isExcluded(exceptions, LEGENDS_BROAD + ".AbstractTransformer"));
|
||||
assertTrue(isExcluded(exceptions, LEGENDS_BROAD + ".ASMHelper"));
|
||||
assertTrue(isExcluded(exceptions, LEGENDS_BROAD + ".TransformerRenderPlayer"));
|
||||
assertTrue(isExcluded(exceptions, LEGENDS_BROAD + ".TransformerEntityRenderer"));
|
||||
assertTrue(isExcluded(exceptions, LEGENDS_BROAD + ".TransformerMethodProcess"));
|
||||
assertTrue(isExcluded(exceptions, LEGENDS_BROAD + ".TransformerGuiInventory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void disabledRuleLeavesBroadExclusionIntact() {
|
||||
final Set<String> exceptions = exceptionsWith(LEGENDS_BROAD);
|
||||
final Map<String, Object> blackboard = new HashMap<>();
|
||||
blackboard.put("angelica.narrow.LegendsMod", Boolean.FALSE);
|
||||
|
||||
assertEquals(0, narrow(exceptions, blackboard));
|
||||
assertTrue(exceptions.contains(LEGENDS_BROAD));
|
||||
assertTrue(isExcluded(exceptions, LEGENDS_HOOK));
|
||||
}
|
||||
|
||||
@Test
|
||||
void sharedXaerosKeyGovernsBothExclusions() {
|
||||
final Set<String> enabled = exceptionsWith("xaero.common.core", "xaero.map.core");
|
||||
assertEquals(2, narrow(enabled, new HashMap<>()));
|
||||
assertFalse(isExcluded(enabled, "xaero.common.gui.GuiMap"));
|
||||
assertFalse(isExcluded(enabled, "xaero.map.gui.GuiMap"));
|
||||
assertTrue(isExcluded(enabled, "xaero.common.core.transformer.Foo"));
|
||||
assertTrue(isExcluded(enabled, "xaero.map.core.transformer.Foo"));
|
||||
|
||||
final Set<String> disabled = exceptionsWith("xaero.common.core", "xaero.map.core");
|
||||
final Map<String, Object> blackboard = new HashMap<>();
|
||||
blackboard.put("angelica.narrow.Xaeros", Boolean.FALSE);
|
||||
assertEquals(0, narrow(disabled, blackboard));
|
||||
assertTrue(disabled.contains("xaero.common.core"));
|
||||
assertTrue(disabled.contains("xaero.map.core"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void narrowingIsIdempotent() {
|
||||
final Set<String> exceptions = exceptionsWith(LEGENDS_BROAD);
|
||||
assertEquals(1, narrow(exceptions, new HashMap<>()));
|
||||
|
||||
final Set<String> afterFirst = new HashSet<>(exceptions);
|
||||
assertEquals(0, narrow(exceptions, new HashMap<>()));
|
||||
assertEquals(afterFirst, exceptions);
|
||||
}
|
||||
|
||||
@Test
|
||||
void absentBroadExclusionIsNoOp() {
|
||||
final Set<String> exceptions = exceptionsWith("some.unrelated.package");
|
||||
assertEquals(0, narrow(exceptions, new HashMap<>()));
|
||||
assertEquals(Set.of("some.unrelated.package"), exceptions);
|
||||
}
|
||||
}
|
||||
@@ -549,9 +549,13 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
ResourceManager.setSingleThreadedReads(GLStateManager.isSplashComplete());
|
||||
st.clearedTexturesThisFrame.clear();
|
||||
st.clearedStencilTexturesThisFrame.clear();
|
||||
st.pendingColorTextures.clear();
|
||||
st.pendingDepthTextures.clear();
|
||||
st.pendingStencilTextures.clear();
|
||||
st.pendingSwapchainClear = false;
|
||||
st.pendingSwapchainDepthClear = false;
|
||||
st.pendingSwapchainStencilClear = false;
|
||||
st.uniformBlocks[0].flushedThisFrame = false;
|
||||
st.uniformBlocks[1].flushedThisFrame = false;
|
||||
st.fanIndexBufferOffset = 0;
|
||||
@@ -869,14 +873,15 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
@Override public void clearDepth(double depth) { s().depthClearValue = (float) depth; }
|
||||
@Override public void clearStencil(int s) {}
|
||||
@Override public void clearStencil(int s) { s().stencilClearValue = s & 0xFF; }
|
||||
|
||||
@Override public void clear(int mask) {
|
||||
final ContextState cs = s();
|
||||
if (!frameManager.isFrameActive()) return;
|
||||
final boolean wantColor = (mask & GL11.GL_COLOR_BUFFER_BIT) != 0;
|
||||
final boolean wantDepth = (mask & GL11.GL_DEPTH_BUFFER_BIT) != 0;
|
||||
if (!wantColor && !wantDepth) return;
|
||||
final boolean wantStencil = (mask & GL11.GL_STENCIL_BUFFER_BIT) != 0;
|
||||
if (!wantColor && !wantDepth && !wantStencil) return;
|
||||
|
||||
final ContextState st = cs;
|
||||
|
||||
@@ -889,7 +894,15 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
st.pendingSwapchainB = st.clearB;
|
||||
st.pendingSwapchainA = st.clearA;
|
||||
}
|
||||
// swapchain has no depth attachment in this backend; ignore wantDepth
|
||||
final int swapDepthFormat = resourceManager.getSwapchainDepthStencilFormat();
|
||||
if (wantDepth && swapDepthFormat != 0) {
|
||||
st.pendingSwapchainDepthClear = true;
|
||||
st.pendingSwapchainDepth = st.depthClearValue;
|
||||
}
|
||||
if (wantStencil && PixelOps.isDepthStencilFormat(swapDepthFormat)) {
|
||||
st.pendingSwapchainStencilClear = true;
|
||||
st.pendingSwapchainStencil = st.stencilClearValue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -914,6 +927,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
FBOClearTracker.recordPendingDepthClear(st, fbo.depthTexture, cs.depthClearValue);
|
||||
if (passActive && fbo.depthTexture == activeDepth) affectsActivePass = true;
|
||||
}
|
||||
if (wantStencil && fbo.depthTexture != 0 && PixelOps.isDepthStencilFormat(fbo.depthFormat)) {
|
||||
FBOClearTracker.recordPendingStencilClear(st, fbo.depthTexture, cs.stencilClearValue);
|
||||
if (passActive && fbo.depthTexture == activeDepth) affectsActivePass = true;
|
||||
}
|
||||
|
||||
if (affectsActivePass) {
|
||||
frameManager.endRenderPassIfActive();
|
||||
@@ -1459,14 +1476,12 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (framebuffer == 0) {
|
||||
cs.pipeline.setColorTargetFormats(cachedSwapchainFormatArray);
|
||||
cs.pipeline.setDrawBuffers(null);
|
||||
cs.pipeline.hasDepthTarget = false;
|
||||
cs.pipeline.depthTargetFormat = 0;
|
||||
cs.pipeline.setDepthTargetFormat(resourceManager.getSwapchainDepthStencilFormat());
|
||||
} else {
|
||||
final FboState fbo = resourceManager.getFbo(framebuffer);
|
||||
if (fbo != null && fbo.getColorTexture() != 0) {
|
||||
fboClearTracker.updatePipelineCacheColorFormats(cs, fbo);
|
||||
cs.pipeline.hasDepthTarget = fbo.depthTexture != 0;
|
||||
cs.pipeline.depthTargetFormat = fbo.depthFormat;
|
||||
cs.pipeline.setDepthTargetFormat(fbo.depthTexture != 0 ? fbo.depthFormat : 0);
|
||||
}
|
||||
}
|
||||
cs.pipeline.markOutputDirty();
|
||||
@@ -1503,14 +1518,23 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
fbo.cachedFormatsDirty = true;
|
||||
fboClearTracker.updatePipelineCacheColorFormats(cs, fbo);
|
||||
cs.pipeline.markOutputDirty();
|
||||
} else if (attachment == GL30.GL_DEPTH_ATTACHMENT || attachment == GL30.GL_DEPTH_STENCIL_ATTACHMENT) {
|
||||
} else if (attachment == GL30.GL_DEPTH_ATTACHMENT || attachment == GL30.GL_DEPTH_STENCIL_ATTACHMENT || attachment == GL30.GL_STENCIL_ATTACHMENT) {
|
||||
final boolean stencilOnly = attachment == GL30.GL_STENCIL_ATTACHMENT;
|
||||
if (texture == 0) {
|
||||
if (stencilOnly && fbo.depthGlId != 0) return;
|
||||
fbo.detachDepth();
|
||||
cs.pipeline.hasDepthTarget = false;
|
||||
cs.pipeline.depthTargetFormat = 0;
|
||||
cs.pipeline.markOutputDirty();
|
||||
return;
|
||||
}
|
||||
if (stencilOnly && fbo.depthGlId != 0 && fbo.depthGlId != texture) {
|
||||
if (!warnedSplitStencilAttachment) {
|
||||
warnedSplitStencilAttachment = true;
|
||||
LOG.warn("Separate stencil attachment (tex {}) on an FBO whose depth attachment is tex {}; SDL has no separate stencil aspect, keeping depth", texture, fbo.depthGlId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
final long handle = resourceManager.ensureTextureUsage(texture, SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET);
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
|
||||
fbo.depthTexture = handle;
|
||||
@@ -1674,22 +1698,67 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
@Override public int getFramebufferAttachmentParameteri(int target, int attachment, int pname) {
|
||||
final FboState fbo = resourceManager.getFbo(s().boundFboId);
|
||||
final ContextState cs = s();
|
||||
final int fboId = target == GL30.GL_READ_FRAMEBUFFER ? cs.boundReadFboId : cs.boundFboId;
|
||||
if (fboId == 0) {
|
||||
final int colorFormat = cachedSwapchainFormatArray != null ? cachedSwapchainFormatArray[0] : 0;
|
||||
return defaultFramebufferAttachmentParameteri(attachment, pname, colorFormat, resourceManager.getSwapchainDepthStencilFormat());
|
||||
}
|
||||
return fboAttachmentParameteri(resourceManager.getFbo(fboId), attachment, pname);
|
||||
}
|
||||
|
||||
static int fboAttachmentParameteri(FboState fbo, int attachment, int pname) {
|
||||
if (fbo == null) return 0;
|
||||
final int colorIdx = attachment - GL30.GL_COLOR_ATTACHMENT0;
|
||||
final boolean isColor = (colorIdx >= 0 && colorIdx < ContextState.MAX_COLOR_ATTACHMENTS);
|
||||
final boolean isDepth = (attachment == GL30.GL_DEPTH_ATTACHMENT || attachment == GL30.GL_DEPTH_STENCIL_ATTACHMENT);
|
||||
final boolean isDepth = (attachment == GL30.GL_DEPTH_ATTACHMENT || attachment == GL30.GL_DEPTH_STENCIL_ATTACHMENT || attachment == GL30.GL_STENCIL_ATTACHMENT || attachment == GL11.GL_DEPTH || attachment == GL11.GL_STENCIL);
|
||||
final boolean colorPresent = isColor && fbo.colorGlIds[colorIdx] != 0;
|
||||
final boolean depthPresent = isDepth && fbo.depthGlId != 0;
|
||||
|
||||
if (pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) {
|
||||
return isColor ? fbo.colorGlIds[colorIdx] : isDepth ? fbo.depthGlId : 0;
|
||||
return colorPresent ? fbo.colorGlIds[colorIdx] : depthPresent ? fbo.depthGlId : 0;
|
||||
}
|
||||
if (pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE) {
|
||||
if (isColor && fbo.colorGlIds[colorIdx] != 0) return GL11.GL_TEXTURE;
|
||||
if (isDepth && fbo.depthGlId != 0) return GL11.GL_TEXTURE;
|
||||
return GL11.GL_NONE;
|
||||
return (colorPresent || depthPresent) ? GL11.GL_TEXTURE : GL11.GL_NONE;
|
||||
}
|
||||
if (colorPresent) return colorAttachmentSize(fbo.colorFormats[colorIdx], pname);
|
||||
if (depthPresent) return depthAttachmentSize(fbo.depthFormat, pname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int defaultFramebufferAttachmentParameteri(int attachment, int pname, int colorFormat, int depthFormat) {
|
||||
final boolean isDepth = attachment == GL11.GL_DEPTH || attachment == GL11.GL_STENCIL || attachment == GL30.GL_DEPTH_ATTACHMENT || attachment == GL30.GL_STENCIL_ATTACHMENT || attachment == GL30.GL_DEPTH_STENCIL_ATTACHMENT;
|
||||
if (isDepth) {
|
||||
if (depthFormat == 0) {
|
||||
return pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE ? GL11.GL_NONE : 0;
|
||||
}
|
||||
if (pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE) return GL30.GL_FRAMEBUFFER_DEFAULT;
|
||||
if (pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) return 0;
|
||||
return depthAttachmentSize(depthFormat, pname);
|
||||
}
|
||||
if (pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE) return GL30.GL_FRAMEBUFFER_DEFAULT;
|
||||
if (pname == GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) return 0;
|
||||
return colorAttachmentSize(colorFormat, pname);
|
||||
}
|
||||
|
||||
private static int colorAttachmentSize(int sdlFormat, int pname) {
|
||||
return switch (pname) {
|
||||
case GL30.GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE -> PixelOps.colorChannelBits(sdlFormat, 0);
|
||||
case GL30.GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE -> PixelOps.colorChannelBits(sdlFormat, 1);
|
||||
case GL30.GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE -> PixelOps.colorChannelBits(sdlFormat, 2);
|
||||
case GL30.GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE -> PixelOps.colorChannelBits(sdlFormat, 3);
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static int depthAttachmentSize(int sdlFormat, int pname) {
|
||||
return switch (pname) {
|
||||
case GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE -> PixelOps.depthBits(sdlFormat);
|
||||
case GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE -> PixelOps.stencilBits(sdlFormat);
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
@Override public int createShader(int type) { return shaderManager.createShader(type); }
|
||||
@Override public void deleteShader(int shader) { shaderManager.deleteShader(shader); }
|
||||
@Override public void shaderSource(int shader, CharSequence source) { shaderManager.shaderSource(shader, source); }
|
||||
@@ -3199,6 +3268,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
case GL11.GL_DRAW_BUFFER -> drawBufferEnum(cs.boundFboId);
|
||||
case GL11.GL_READ_BUFFER -> readBufferEnum(cs.boundReadFboId);
|
||||
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);
|
||||
default -> {
|
||||
final int limit = deviceLimit(pname);
|
||||
yield limit >= 0 ? limit : unknownGetInteger(pname);
|
||||
@@ -3646,6 +3717,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
private boolean warnedMSAARenderbuffer = false;
|
||||
private boolean warnedSplitStencilAttachment = false;
|
||||
|
||||
@Override
|
||||
public void renderbufferStorageMultisample(int target, int samples, int internalformat, int width, int height) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.sdlgpu.shader.UniformStaging;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.Long2FloatOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
@@ -92,6 +93,7 @@ public final class ContextState {
|
||||
public float blendColorR, blendColorG, blendColorB, blendColorA;
|
||||
public int stencilRef;
|
||||
public float depthClearValue = 1.0f;
|
||||
public int stencilClearValue;
|
||||
public boolean primitiveRestartEnabled;
|
||||
public int primitiveRestartSentinel;
|
||||
|
||||
@@ -292,13 +294,20 @@ public final class ContextState {
|
||||
public int boundReadFboId;
|
||||
|
||||
public final LongOpenHashSet clearedTexturesThisFrame = new LongOpenHashSet();
|
||||
public final LongOpenHashSet clearedStencilTexturesThisFrame = new LongOpenHashSet();
|
||||
|
||||
public final LongOpenHashSet pendingColorTextures = new LongOpenHashSet();
|
||||
public final Long2ObjectOpenHashMap<float[]> pendingColorValues = new Long2ObjectOpenHashMap<>();
|
||||
public final LongOpenHashSet pendingDepthTextures = new LongOpenHashSet();
|
||||
public final Long2FloatOpenHashMap pendingDepthValues = new Long2FloatOpenHashMap();
|
||||
public final LongOpenHashSet pendingStencilTextures = new LongOpenHashSet();
|
||||
public final Long2IntOpenHashMap pendingStencilValues = new Long2IntOpenHashMap();
|
||||
public boolean pendingSwapchainClear;
|
||||
public float pendingSwapchainR, pendingSwapchainG, pendingSwapchainB, pendingSwapchainA;
|
||||
public boolean pendingSwapchainDepthClear;
|
||||
public boolean pendingSwapchainStencilClear;
|
||||
public float pendingSwapchainDepth;
|
||||
public int pendingSwapchainStencil;
|
||||
|
||||
public final LongArrayList samplerFlushColorHandles = new LongArrayList();
|
||||
public final IntArrayList samplerFlushColorGlIds = new IntArrayList();
|
||||
|
||||
@@ -57,6 +57,7 @@ public final class FrameManager {
|
||||
public long activeLayoutHash;
|
||||
public long frameNumber;
|
||||
public boolean clearedThisFrame;
|
||||
public boolean depthClearedThisFrame;
|
||||
public boolean swapchainUsedThisFrame;
|
||||
public boolean swapchainAcquiredThisFrame;
|
||||
public boolean swapchainUnavailable;
|
||||
@@ -233,6 +234,7 @@ public final class FrameManager {
|
||||
f.currentDepthTarget = 0;
|
||||
f.frameNumber++;
|
||||
f.clearedThisFrame = false;
|
||||
f.depthClearedThisFrame = false;
|
||||
f.swapchainUsedThisFrame = false;
|
||||
f.skipPresent = false;
|
||||
}
|
||||
@@ -242,14 +244,34 @@ public final class FrameManager {
|
||||
f.emptyFramesThisFrame++;
|
||||
}
|
||||
|
||||
public void ensureSwapchainRenderPass(float clearR, float clearG, float clearB, float clearA, boolean clear) {
|
||||
ensureSwapchainRenderPass(frame(), clearR, clearG, clearB, clearA, clear);
|
||||
static boolean swapchainClearNeedsPassBreak(ContextState st, FrameState f) {
|
||||
return (st.pendingSwapchainClear || st.pendingSwapchainDepthClear || st.pendingSwapchainStencilClear) && f.renderPass != 0 && f.currentColorTarget == f.swapchainTexture;
|
||||
}
|
||||
|
||||
public void ensureSwapchainRenderPass(FrameState f, float clearR, float clearG, float clearB, float clearA, boolean clear) {
|
||||
if (!ensureSwapchainAcquired(f)) return; // present-skip; don't bind tex=0
|
||||
public boolean ensureSwapchainRenderPass(FrameState f, ContextState st) {
|
||||
final boolean clearColor = st.pendingSwapchainClear;
|
||||
if (swapchainClearNeedsPassBreak(st, f)) {
|
||||
endRenderPassIfActive(f);
|
||||
}
|
||||
final boolean applied = ensureSwapchainRenderPass(f,
|
||||
clearColor ? st.pendingSwapchainR : st.clearR,
|
||||
clearColor ? st.pendingSwapchainG : st.clearG,
|
||||
clearColor ? st.pendingSwapchainB : st.clearB,
|
||||
clearColor ? st.pendingSwapchainA : st.clearA,
|
||||
clearColor,
|
||||
st.pendingSwapchainDepthClear, st.pendingSwapchainDepthClear ? st.pendingSwapchainDepth : st.depthClearValue,
|
||||
st.pendingSwapchainStencilClear, st.pendingSwapchainStencilClear ? st.pendingSwapchainStencil : st.stencilClearValue);
|
||||
if (!applied) return false;
|
||||
st.pendingSwapchainClear = false;
|
||||
st.pendingSwapchainDepthClear = false;
|
||||
st.pendingSwapchainStencilClear = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean ensureSwapchainRenderPass(FrameState f, float clearR, float clearG, float clearB, float clearA, boolean clear, boolean clearDepth, float depthValue, boolean clearStencil, int stencilValue) {
|
||||
if (!ensureSwapchainAcquired(f)) return false; // present-skip; don't bind tex=0
|
||||
if (f.renderPass != 0 && f.currentColorTarget == f.swapchainTexture) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
endActiveEncoders(f);
|
||||
@@ -258,6 +280,12 @@ public final class FrameManager {
|
||||
clear = true;
|
||||
}
|
||||
|
||||
final long depthTexture = resourceManager != null ? resourceManager.getOrCreateSwapchainDepthStencil(f.swapchainWidth, f.swapchainHeight) : 0L;
|
||||
if (depthTexture != 0 && !f.depthClearedThisFrame) {
|
||||
clearDepth = true;
|
||||
clearStencil = true;
|
||||
}
|
||||
|
||||
try (var stack = stackPush()) {
|
||||
final SDL_GPUColorTargetInfo.Buffer colorTargets = SDL_GPUColorTargetInfo.calloc(1, stack);
|
||||
final long ctAddr = colorTargets.get(0).address();
|
||||
@@ -274,19 +302,34 @@ public final class FrameManager {
|
||||
f.clearedThisFrame = true;
|
||||
}
|
||||
|
||||
SDL_GPUDepthStencilTargetInfo depthTarget = null;
|
||||
if (depthTexture != 0) {
|
||||
depthTarget = SDL_GPUDepthStencilTargetInfo.calloc(stack);
|
||||
final long dtAddr = depthTarget.address();
|
||||
MemoryAccess.putAddress(dtAddr + SDL_GPUDepthStencilTargetInfo.TEXTURE, depthTexture);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, clearDepth ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
MemoryAccess.putFloat(dtAddr + SDL_GPUDepthStencilTargetInfo.CLEAR_DEPTH, depthValue);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STENCIL_LOAD_OP, clearStencil ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STENCIL_STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
MemoryAccess.putByte(dtAddr + SDL_GPUDepthStencilTargetInfo.CLEAR_STENCIL, (byte) stencilValue);
|
||||
if (clearDepth && clearStencil) f.depthClearedThisFrame = true;
|
||||
}
|
||||
|
||||
if (preRenderPassHook != null) preRenderPassHook.run();
|
||||
assertNoEncoderActive(f, "SDL_BeginGPURenderPass(swapchain)");
|
||||
f.renderPass = SDL_BeginGPURenderPass(f.commandBuffer, colorTargets, null);
|
||||
f.renderPass = SDL_BeginGPURenderPass(f.commandBuffer, colorTargets, depthTarget);
|
||||
if (f.renderPass == 0) {
|
||||
throw new RuntimeException("Failed to begin render pass: " + SDLError.SDL_GetError());
|
||||
}
|
||||
f.renderPassGeneration++;
|
||||
f.swapchainPassesThisFrame++;
|
||||
f.currentColorTarget = f.swapchainTexture;
|
||||
f.currentDepthTarget = 0;
|
||||
f.currentDepthTarget = depthTexture;
|
||||
f.activeLayoutHash = SWAPCHAIN_LAYOUT_HASH;
|
||||
f.swapchainUsedThisFrame = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+78
-35
@@ -20,6 +20,7 @@ import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PersistentBufferSync;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PersistentMapping;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.sampler.SamplerBinder;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.sampler.StorageBufferBinder;
|
||||
@@ -108,11 +109,8 @@ public final class PipelineApplier {
|
||||
}
|
||||
|
||||
if (st.boundFboId == 0) {
|
||||
if (st.pendingSwapchainClear) {
|
||||
frameManager.ensureSwapchainRenderPass(f, st.pendingSwapchainR, st.pendingSwapchainG, st.pendingSwapchainB, st.pendingSwapchainA, true);
|
||||
st.pendingSwapchainClear = false;
|
||||
} else {
|
||||
frameManager.ensureSwapchainRenderPass(f, st.clearR, st.clearG, st.clearB, st.clearA, false);
|
||||
if (frameManager.ensureSwapchainRenderPass(f, st)) {
|
||||
st.pipeline.setDepthTargetFormat(resourceManager.getSwapchainDepthStencilFormat());
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -145,47 +143,50 @@ public final class PipelineApplier {
|
||||
tex = dummyTex;
|
||||
}
|
||||
final boolean pendingClear = tex != dummyTex && st.pendingColorTextures.contains(tex);
|
||||
final boolean firstUse = !pendingClear && tex != dummyTex && !st.clearedTexturesThisFrame.contains(tex);
|
||||
final boolean defined = tex != dummyTex && resourceManager.isTextureContentDefined(tex);
|
||||
final boolean firstUse = !pendingClear && tex != dummyTex && !defined;
|
||||
if (pendingClear || firstUse) proposedClearOps |= 1 << i;
|
||||
layoutHash = Hashing.fmix64(layoutHash, tex);
|
||||
layoutHash = Hashing.fmix64(layoutHash, db);
|
||||
if (pendingClear || firstUse) {
|
||||
final float[] c = pendingClear ? st.pendingColorValues.get(tex) : null;
|
||||
layoutHash = foldClearColor(layoutHash, c != null ? c[0] : st.clearR, c != null ? c[1] : st.clearG, c != null ? c[2] : st.clearB, c != null ? c[3] : st.clearA);
|
||||
}
|
||||
}
|
||||
final boolean proposedDepthClear;
|
||||
final boolean proposedStencilClear;
|
||||
final boolean depthHasStencil = fbo.depthTexture != 0 && PixelOps.isDepthStencilFormat(fbo.depthFormat);
|
||||
if (fbo.depthTexture != 0) {
|
||||
final boolean pendingClear = st.pendingDepthTextures.contains(fbo.depthTexture);
|
||||
final boolean firstUse = !pendingClear && !st.clearedTexturesThisFrame.contains(fbo.depthTexture);
|
||||
proposedDepthClear = pendingClear || firstUse;
|
||||
final boolean pendingStencil = depthHasStencil && st.pendingStencilTextures.contains(fbo.depthTexture);
|
||||
if (depthHasStencil) {
|
||||
final boolean firstStencilUse = !pendingStencil && !st.clearedStencilTexturesThisFrame.contains(fbo.depthTexture);
|
||||
proposedStencilClear = pendingStencil || firstStencilUse;
|
||||
} else {
|
||||
proposedStencilClear = false;
|
||||
}
|
||||
layoutHash = Hashing.fmix64(layoutHash, fbo.depthTexture);
|
||||
if (proposedDepthClear) {
|
||||
layoutHash = foldClearDepth(layoutHash, pendingClear ? st.pendingDepthValues.get(fbo.depthTexture) : st.depthClearValue);
|
||||
}
|
||||
if (proposedStencilClear) {
|
||||
layoutHash = foldClearStencil(layoutHash, pendingStencil ? st.pendingStencilValues.get(fbo.depthTexture) : st.stencilClearValue);
|
||||
}
|
||||
} else {
|
||||
proposedDepthClear = false;
|
||||
proposedStencilClear = false;
|
||||
}
|
||||
layoutHash = Hashing.fmix64(layoutHash, Float.floatToRawIntBits(st.clearR));
|
||||
layoutHash = Hashing.fmix64(layoutHash, Float.floatToRawIntBits(st.clearG));
|
||||
layoutHash = Hashing.fmix64(layoutHash, Float.floatToRawIntBits(st.clearB));
|
||||
layoutHash = Hashing.fmix64(layoutHash, Float.floatToRawIntBits(st.clearA));
|
||||
layoutHash = Hashing.fmix64(layoutHash, Float.floatToRawIntBits(st.depthClearValue));
|
||||
|
||||
frameManager.endRenderPassIfActive(f);
|
||||
|
||||
final boolean reuse = fbo.cachedTargetsValid && fbo.cachedTargetsLayoutHash == layoutHash && fbo.cachedTargetsCount == totalTargets && fbo.cachedClearOpFlags == proposedClearOps && fbo.cachedDepthClearLast == proposedDepthClear;
|
||||
final boolean reuse = fbo.cachedTargetsValid && fbo.cachedTargetsLayoutHash == layoutHash && fbo.cachedTargetsCount == totalTargets && fbo.cachedClearOpFlags == proposedClearOps && fbo.cachedDepthClearLast == proposedDepthClear && fbo.cachedStencilClearLast == proposedStencilClear;
|
||||
|
||||
SDL_GPUColorTargetInfo.Buffer colorTargets = null;
|
||||
SDL_GPUDepthStencilTargetInfo depthTarget = null;
|
||||
|
||||
if (reuse) {
|
||||
for (int i = 0; i < totalTargets; i++) {
|
||||
final int db = fbo.drawBuffers[i];
|
||||
final long tex = (db >= 0 && db < ContextState.MAX_COLOR_ATTACHMENTS)
|
||||
? fbo.colorTextures[db] : 0L;
|
||||
if (tex != 0) {
|
||||
st.pendingColorTextures.remove(tex);
|
||||
if ((proposedClearOps & (1 << i)) != 0) st.clearedTexturesThisFrame.add(tex);
|
||||
}
|
||||
}
|
||||
if (fbo.depthTexture != 0) {
|
||||
st.pendingDepthTextures.remove(fbo.depthTexture);
|
||||
if (proposedDepthClear) st.clearedTexturesThisFrame.add(fbo.depthTexture);
|
||||
}
|
||||
if (totalTargets > 0) colorTargets = fbo.cachedColorTargets.position(0).limit(totalTargets);
|
||||
if (fbo.depthTexture != 0) depthTarget = fbo.cachedDepthTarget;
|
||||
} else {
|
||||
@@ -199,9 +200,8 @@ public final class PipelineApplier {
|
||||
} else {
|
||||
tex = dummyTex;
|
||||
}
|
||||
final boolean pendingClear = tex != dummyTex && st.pendingColorTextures.remove(tex);
|
||||
final boolean firstUse = !pendingClear && tex != dummyTex && !st.clearedTexturesThisFrame.contains(tex);
|
||||
final boolean clearThis = pendingClear || firstUse;
|
||||
final boolean pendingClear = tex != dummyTex && st.pendingColorTextures.contains(tex);
|
||||
final boolean clearThis = (proposedClearOps & (1 << i)) != 0;
|
||||
final long ctAddr = colorTargets.address() + (long) i * SDL_GPUColorTargetInfo.SIZEOF;
|
||||
MemoryAccess.putAddress(ctAddr + SDL_GPUColorTargetInfo.TEXTURE, tex);
|
||||
MemoryAccess.putInt(ctAddr + SDL_GPUColorTargetInfo.LOAD_OP, clearThis ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
|
||||
@@ -220,36 +220,79 @@ public final class PipelineApplier {
|
||||
MemoryAccess.putFloat(ccAddr + SDL_FColor.B, st.clearB);
|
||||
MemoryAccess.putFloat(ccAddr + SDL_FColor.A, st.clearA);
|
||||
}
|
||||
if (tex != dummyTex) st.clearedTexturesThisFrame.add(tex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fbo.depthTexture != 0) {
|
||||
final boolean pendingClear = st.pendingDepthTextures.remove(fbo.depthTexture);
|
||||
final boolean firstUse = !pendingClear && !st.clearedTexturesThisFrame.contains(fbo.depthTexture);
|
||||
final boolean clearDepth = pendingClear || firstUse;
|
||||
final boolean pendingClear = st.pendingDepthTextures.contains(fbo.depthTexture);
|
||||
depthTarget = fbo.cachedDepthTarget;
|
||||
final long dtAddr = depthTarget.address();
|
||||
MemoryAccess.putAddress(dtAddr + SDL_GPUDepthStencilTargetInfo.TEXTURE, fbo.depthTexture);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, clearDepth ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, proposedDepthClear ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
if (clearDepth) {
|
||||
if (proposedDepthClear) {
|
||||
final float dv = pendingClear ? st.pendingDepthValues.get(fbo.depthTexture) : st.depthClearValue;
|
||||
MemoryAccess.putFloat(dtAddr + SDL_GPUDepthStencilTargetInfo.CLEAR_DEPTH, dv);
|
||||
st.clearedTexturesThisFrame.add(fbo.depthTexture);
|
||||
}
|
||||
if (depthHasStencil) {
|
||||
final boolean pendingStencil = st.pendingStencilTextures.contains(fbo.depthTexture);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STENCIL_LOAD_OP, proposedStencilClear ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STENCIL_STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
if (proposedStencilClear) {
|
||||
final int sv = pendingStencil ? st.pendingStencilValues.get(fbo.depthTexture) : st.stencilClearValue;
|
||||
MemoryAccess.putByte(dtAddr + SDL_GPUDepthStencilTargetInfo.CLEAR_STENCIL, (byte) sv);
|
||||
}
|
||||
} else {
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STENCIL_LOAD_OP, SDL_GPU_LOADOP_LOAD);
|
||||
MemoryAccess.putInt(dtAddr + SDL_GPUDepthStencilTargetInfo.STENCIL_STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
}
|
||||
}
|
||||
fbo.cachedTargetsLayoutHash = layoutHash;
|
||||
fbo.cachedTargetsCount = totalTargets;
|
||||
fbo.cachedClearOpFlags = proposedClearOps;
|
||||
fbo.cachedDepthClearLast = proposedDepthClear;
|
||||
fbo.cachedStencilClearLast = proposedStencilClear;
|
||||
fbo.cachedTargetsValid = true;
|
||||
}
|
||||
|
||||
consumeClears(resourceManager, st, fbo, dummyTex, proposedClearOps, proposedDepthClear, proposedStencilClear, depthHasStencil);
|
||||
|
||||
frameManager.beginRenderPass(colorTargets, depthTarget);
|
||||
frameManager.setActiveLayoutHash(fbo.structuralLayoutHash);
|
||||
}
|
||||
|
||||
static void consumeClears(ResourceManager rm, ContextState st, FboState fbo, long dummyTex, int clearOps, boolean depthClear, boolean stencilClear, boolean depthHasStencil) {
|
||||
for (int i = 0, n = fbo.drawBuffers.length; i < n; i++) {
|
||||
final int db = fbo.drawBuffers[i];
|
||||
final long tex = (db >= 0 && db < ContextState.MAX_COLOR_ATTACHMENTS) ? fbo.colorTextures[db] : 0L;
|
||||
if (tex == 0 || tex == dummyTex) continue;
|
||||
st.pendingColorTextures.remove(tex);
|
||||
if ((clearOps & (1 << i)) != 0) rm.markTextureContentDefined(tex);
|
||||
}
|
||||
if (fbo.depthTexture == 0) return;
|
||||
st.pendingDepthTextures.remove(fbo.depthTexture);
|
||||
if (depthClear) st.clearedTexturesThisFrame.add(fbo.depthTexture);
|
||||
if (depthHasStencil) {
|
||||
st.pendingStencilTextures.remove(fbo.depthTexture);
|
||||
if (stencilClear) st.clearedStencilTexturesThisFrame.add(fbo.depthTexture);
|
||||
}
|
||||
}
|
||||
|
||||
static long foldClearColor(long h, float r, float g, float b, float a) {
|
||||
h = Hashing.fmix64(h, Float.floatToRawIntBits(r));
|
||||
h = Hashing.fmix64(h, Float.floatToRawIntBits(g));
|
||||
h = Hashing.fmix64(h, Float.floatToRawIntBits(b));
|
||||
return Hashing.fmix64(h, Float.floatToRawIntBits(a));
|
||||
}
|
||||
|
||||
static long foldClearDepth(long h, float depth) {
|
||||
return Hashing.fmix64(h, Float.floatToRawIntBits(depth));
|
||||
}
|
||||
|
||||
static long foldClearStencil(long h, int stencil) {
|
||||
return Hashing.fmix64(h, stencil);
|
||||
}
|
||||
|
||||
private static final int RING_BLOCK_BYTES = ContextState.MAX_VERTEX_ATTRIBS * 16;
|
||||
private static final int RING_CHUNK_BYTES = ResourceManager.ATTRIB_RING_CHUNK_BLOCKS * RING_BLOCK_BYTES;
|
||||
|
||||
|
||||
+21
-1
@@ -86,6 +86,17 @@ public final class PipelineCache {
|
||||
public int stencilCompareMask = 0xFF;
|
||||
public int stencilWriteMask = 0xFF;
|
||||
|
||||
private static final LongOpenHashSet loggedStencilMaskLost = new LongOpenHashSet();
|
||||
|
||||
private void warnOnceOnStencilMaskLost() {
|
||||
if (!stencilTestEnabled || effectiveStencilTestEnabled()) return;
|
||||
if (stencilFrontCompareOp == SDL_GPU_COMPAREOP_ALWAYS && stencilBackCompareOp == SDL_GPU_COMPAREOP_ALWAYS && stencilFrontPassOp == SDL_GPU_STENCILOP_KEEP && stencilBackPassOp == SDL_GPU_STENCILOP_KEEP)
|
||||
return;
|
||||
if (!loggedStencilMaskLost.add(Hashing.packHiLo(programId, debugFboId))) return;
|
||||
LOG.warn("Stencil mask dropped: bound target has no stencil aspect. fbo={} depthFormat={} program={} frontCmp={} frontPass={} writeMask=0x{}",
|
||||
debugFboId, textureFormatName(depthTargetFormat), programId, stencilFrontCompareOp, stencilFrontPassOp, Integer.toHexString(stencilWriteMask));
|
||||
}
|
||||
|
||||
public boolean cullEnabled = false;
|
||||
public int cullFaceMode = SDL_GPU_CULLMODE_BACK;
|
||||
public int frontFace = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE;
|
||||
@@ -141,6 +152,14 @@ public final class PipelineCache {
|
||||
this.drawBuffers = drawBuffers;
|
||||
}
|
||||
|
||||
public boolean setDepthTargetFormat(int sdlFormat) {
|
||||
if (depthTargetFormat == sdlFormat && hasDepthTarget == (sdlFormat != 0)) return false;
|
||||
hasDepthTarget = sdlFormat != 0;
|
||||
depthTargetFormat = sdlFormat;
|
||||
markOutputDirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
private int attachmentForSlot(int slot) {
|
||||
final int[] db = drawBuffers;
|
||||
if (db == null || slot < 0 || slot >= db.length) return 0;
|
||||
@@ -340,7 +359,7 @@ public final class PipelineCache {
|
||||
private void warnOnceOnUnconsumedFragOutput(int numColorTargets) {
|
||||
if (maxFragOutputLocation < numColorTargets) return;
|
||||
if (!loggedUnconsumedFragOutput.add(Hashing.packHiLo(programId, numColorTargets))) return;
|
||||
LOG.warn("[PipelineCache] program {} declares fragment output location {} but framebuffer {} ({} colour attachment(s)) has {} draw buffer(s) {}; writes past location {} are discarded", programId, maxFragOutputLocation, debugFboId, debugFboAttachments, numColorTargets, Arrays.toString(drawBuffers), numColorTargets - 1);
|
||||
LOG.warn("[PipelineCache] program {} declares fragment output location {} but framebuffer {} ({} color attachment(s)) has {} draw buffer(s) {}; writes past location {} are discarded", programId, maxFragOutputLocation, debugFboId, debugFboAttachments, numColorTargets, Arrays.toString(drawBuffers), numColorTargets - 1);
|
||||
}
|
||||
|
||||
private long createPipeline(PipelineStore store, ContextState cs) {
|
||||
@@ -360,6 +379,7 @@ public final class PipelineCache {
|
||||
try (var stack = stackPush()) {
|
||||
final int numColorTargets = colorTargetFormats().length;
|
||||
warnOnceOnUnconsumedFragOutput(numColorTargets);
|
||||
warnOnceOnStencilMaskLost();
|
||||
final SDL_GPUColorTargetDescription.Buffer colorDesc = SDL_GPUColorTargetDescription.calloc(numColorTargets, stack);
|
||||
for (int i = 0; i < numColorTargets; i++) {
|
||||
final var desc = colorDesc.get(i);
|
||||
|
||||
+55
-30
@@ -49,8 +49,19 @@ public final class FBOClearTracker {
|
||||
st.clearedTexturesThisFrame.remove(tex);
|
||||
}
|
||||
|
||||
public static void recordPendingStencilClear(ContextState st, long tex, int value) {
|
||||
st.pendingStencilValues.put(tex, value);
|
||||
if (st.pendingStencilTextures.add(tex)) {
|
||||
st.pendingMutationGen++;
|
||||
}
|
||||
st.clearedStencilTexturesThisFrame.remove(tex);
|
||||
}
|
||||
|
||||
public static boolean fboHasPendingClear(ContextState st, FboState fbo) {
|
||||
if (fbo.depthTexture != 0 && st.pendingDepthTextures.contains(fbo.depthTexture)) return true;
|
||||
if (fbo.depthTexture != 0
|
||||
&& (st.pendingDepthTextures.contains(fbo.depthTexture) || st.pendingStencilTextures.contains(fbo.depthTexture))) {
|
||||
return true;
|
||||
}
|
||||
if (st.pendingColorTextures.isEmpty()) return false;
|
||||
for (int db : fbo.drawBuffers) {
|
||||
if (db < 0 || db >= ContextState.MAX_COLOR_ATTACHMENTS) continue;
|
||||
@@ -67,7 +78,7 @@ public final class FBOClearTracker {
|
||||
}
|
||||
|
||||
public void flushPendingClearsForBoundSamplers(ContextState st) {
|
||||
if (st.pendingColorTextures.isEmpty() && st.pendingDepthTextures.isEmpty()) return;
|
||||
if (st.pendingColorTextures.isEmpty() && st.pendingDepthTextures.isEmpty() && st.pendingStencilTextures.isEmpty()) return;
|
||||
|
||||
if (st.lastFlushedSamplerBindGen == st.samplerBindGen && st.lastFlushedProgram == st.boundProgram && st.lastFlushedPendingMutationGen == st.pendingMutationGen) {
|
||||
return;
|
||||
@@ -115,7 +126,7 @@ public final class FBOClearTracker {
|
||||
if (st.pendingColorTextures.remove(handle)) {
|
||||
colorHandles.add(handle);
|
||||
colorGlIds.add(glTexId);
|
||||
} else if (st.pendingDepthTextures.remove(handle)) {
|
||||
} else if (st.pendingDepthTextures.contains(handle) || st.pendingStencilTextures.contains(handle)) {
|
||||
depthHandles.add(handle);
|
||||
depthGlIds.add(glTexId);
|
||||
}
|
||||
@@ -169,6 +180,7 @@ public final class FBOClearTracker {
|
||||
MemoryAccess.putFloat(ccAddr + SDL_FColor.B, color[2]);
|
||||
MemoryAccess.putFloat(ccAddr + SDL_FColor.A, color[3]);
|
||||
st.clearedTexturesThisFrame.add(handle);
|
||||
resourceManager.markTextureContentDefined(handle);
|
||||
consumed[idx] = true;
|
||||
}
|
||||
frameManager.noteClearPass();
|
||||
@@ -180,19 +192,38 @@ public final class FBOClearTracker {
|
||||
final int dn = depthHandles.size();
|
||||
for (int i = 0; i < dn; i++) {
|
||||
final long handle = depthHandles.getLong(i);
|
||||
final float val = st.pendingDepthValues.get(handle);
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
final SDL_GPUDepthStencilTargetInfo dt = SDL_GPUDepthStencilTargetInfo.calloc(stack);
|
||||
final long addr = dt.address();
|
||||
MemoryAccess.putAddress(addr + SDL_GPUDepthStencilTargetInfo.TEXTURE, handle);
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, SDL_GPU_LOADOP_CLEAR);
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
MemoryAccess.putFloat(addr + SDL_GPUDepthStencilTargetInfo.CLEAR_DEPTH, val);
|
||||
frameManager.noteClearPass();
|
||||
frameManager.beginRenderPass(null, dt);
|
||||
frameManager.endRenderPassIfActive();
|
||||
st.clearedTexturesThisFrame.add(handle);
|
||||
emitDepthStencilClearPass(st, handle, st.pendingDepthTextures.remove(handle), st.pendingStencilTextures.remove(handle), false);
|
||||
}
|
||||
}
|
||||
|
||||
private void emitDepthStencilClearPass(ContextState st, long handle, boolean clearDepth, boolean clearStencil, boolean materialized) {
|
||||
if (!clearDepth && !clearStencil) return;
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
final SDL_GPUDepthStencilTargetInfo dt = SDL_GPUDepthStencilTargetInfo.calloc(stack);
|
||||
final long addr = dt.address();
|
||||
MemoryAccess.putAddress(addr + SDL_GPUDepthStencilTargetInfo.TEXTURE, handle);
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, clearDepth ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_LOAD);
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
if (clearDepth) {
|
||||
MemoryAccess.putFloat(addr + SDL_GPUDepthStencilTargetInfo.CLEAR_DEPTH, st.pendingDepthValues.get(handle));
|
||||
}
|
||||
if (clearStencil) {
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STENCIL_LOAD_OP, SDL_GPU_LOADOP_CLEAR);
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STENCIL_STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
MemoryAccess.putByte(addr + SDL_GPUDepthStencilTargetInfo.CLEAR_STENCIL, (byte) st.pendingStencilValues.get(handle));
|
||||
}
|
||||
frameManager.noteClearPass();
|
||||
if (materialized) frameManager.noteMaterializedClearPass();
|
||||
frameManager.beginRenderPass(null, dt);
|
||||
frameManager.endRenderPassIfActive();
|
||||
}
|
||||
if (clearDepth) {
|
||||
st.pendingDepthValues.remove(handle);
|
||||
st.clearedTexturesThisFrame.add(handle);
|
||||
}
|
||||
if (clearStencil) {
|
||||
st.pendingStencilValues.remove(handle);
|
||||
st.clearedStencilTexturesThisFrame.add(handle);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,28 +237,18 @@ public final class FBOClearTracker {
|
||||
if (depth) st.pendingDepthValues.remove(handle);
|
||||
else st.pendingColorValues.remove(handle);
|
||||
st.clearedTexturesThisFrame.add(handle);
|
||||
resourceManager.markTextureContentDefined(handle);
|
||||
st.pendingMutationGen++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void materializePendingClearForTexture(ContextState st, long handle) {
|
||||
if (handle == 0) return;
|
||||
if (st.pendingDepthTextures.remove(handle)) {
|
||||
final float val = st.pendingDepthValues.remove(handle);
|
||||
final boolean depthPending = st.pendingDepthTextures.remove(handle);
|
||||
final boolean stencilPending = st.pendingStencilTextures.remove(handle);
|
||||
if (depthPending || stencilPending) {
|
||||
frameManager.endRenderPassIfActive();
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
final SDL_GPUDepthStencilTargetInfo dt = SDL_GPUDepthStencilTargetInfo.calloc(stack);
|
||||
final long addr = dt.address();
|
||||
MemoryAccess.putAddress(addr + SDL_GPUDepthStencilTargetInfo.TEXTURE, handle);
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.LOAD_OP, SDL_GPU_LOADOP_CLEAR);
|
||||
MemoryAccess.putInt(addr + SDL_GPUDepthStencilTargetInfo.STORE_OP, SDL_GPU_STOREOP_STORE);
|
||||
MemoryAccess.putFloat(addr + SDL_GPUDepthStencilTargetInfo.CLEAR_DEPTH, val);
|
||||
frameManager.noteClearPass();
|
||||
frameManager.noteMaterializedClearPass();
|
||||
frameManager.beginRenderPass(null, dt);
|
||||
frameManager.endRenderPassIfActive();
|
||||
st.clearedTexturesThisFrame.add(handle);
|
||||
}
|
||||
emitDepthStencilClearPass(st, handle, depthPending, stencilPending, true);
|
||||
st.pendingMutationGen++;
|
||||
return;
|
||||
}
|
||||
@@ -250,6 +271,7 @@ public final class FBOClearTracker {
|
||||
frameManager.beginRenderPass(targets, null);
|
||||
frameManager.endRenderPassIfActive();
|
||||
st.clearedTexturesThisFrame.add(handle);
|
||||
resourceManager.markTextureContentDefined(handle);
|
||||
}
|
||||
st.pendingMutationGen++;
|
||||
}
|
||||
@@ -262,7 +284,10 @@ public final class FBOClearTracker {
|
||||
st.pendingColorValues.remove(handle);
|
||||
st.pendingDepthTextures.remove(handle);
|
||||
st.pendingDepthValues.remove(handle);
|
||||
st.pendingStencilTextures.remove(handle);
|
||||
st.pendingStencilValues.remove(handle);
|
||||
st.clearedTexturesThisFrame.remove(handle);
|
||||
st.clearedStencilTexturesThisFrame.remove(handle);
|
||||
}
|
||||
|
||||
public boolean fbosHaveSameAttachments(ContextState st, int a, int b) {
|
||||
|
||||
@@ -35,6 +35,7 @@ public final class FboState {
|
||||
public int cachedTargetsCount = -1;
|
||||
public int cachedClearOpFlags;
|
||||
public boolean cachedDepthClearLast;
|
||||
public boolean cachedStencilClearLast;
|
||||
public boolean cachedTargetsValid;
|
||||
|
||||
public long getColorTexture() { return colorTextures[0]; }
|
||||
|
||||
@@ -34,6 +34,85 @@ public final class PixelOps {
|
||||
return sdlFormat == SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT || sdlFormat == SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT;
|
||||
}
|
||||
|
||||
public static int depthBits(int sdlFormat) {
|
||||
return switch (sdlFormat) {
|
||||
case SDL_GPU_TEXTUREFORMAT_D16_UNORM -> 16;
|
||||
case SDL_GPU_TEXTUREFORMAT_D24_UNORM, SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT -> 24;
|
||||
case SDL_GPU_TEXTUREFORMAT_D32_FLOAT, SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT -> 32;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
public static int stencilBits(int sdlFormat) {
|
||||
return isDepthStencilFormat(sdlFormat) ? 8 : 0;
|
||||
}
|
||||
|
||||
public static int colorChannelBits(int sdlFormat, int channel) {
|
||||
if (channel < 0 || channel > 3) return 0;
|
||||
return switch (sdlFormat) {
|
||||
case SDL_GPU_TEXTUREFORMAT_A8_UNORM -> channel == 3 ? 8 : 0;
|
||||
case SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM -> channel == 3 ? 2 : 10;
|
||||
case SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT -> switch (channel) {
|
||||
case 0, 1 -> 11;
|
||||
case 2 -> 10;
|
||||
default -> 0;
|
||||
};
|
||||
case SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM -> switch (channel) {
|
||||
case 0, 2 -> 5;
|
||||
case 1 -> 6;
|
||||
default -> 0;
|
||||
};
|
||||
case SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM -> channel == 3 ? 1 : 5;
|
||||
case SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM -> 4;
|
||||
default -> channel < sdlFormatColorComponents(sdlFormat) ? uniformChannelBits(sdlFormat) : 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static int uniformChannelBits(int sdlFormat) {
|
||||
return switch (sdlFormat) {
|
||||
case SDL_GPU_TEXTUREFORMAT_R8_UNORM, SDL_GPU_TEXTUREFORMAT_R8_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R8_UINT, SDL_GPU_TEXTUREFORMAT_R8_INT,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8_UNORM, SDL_GPU_TEXTUREFORMAT_R8G8_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8_UINT, SDL_GPU_TEXTUREFORMAT_R8G8_INT,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT, SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB -> 8;
|
||||
case SDL_GPU_TEXTUREFORMAT_R16_UNORM, SDL_GPU_TEXTUREFORMAT_R16_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R16_FLOAT, SDL_GPU_TEXTUREFORMAT_R16_UINT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16_INT, SDL_GPU_TEXTUREFORMAT_R16G16_UNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16_SNORM, SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16_UINT, SDL_GPU_TEXTUREFORMAT_R16G16_INT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT -> 16;
|
||||
case SDL_GPU_TEXTUREFORMAT_R32_FLOAT, SDL_GPU_TEXTUREFORMAT_R32_UINT,
|
||||
SDL_GPU_TEXTUREFORMAT_R32_INT, SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT,
|
||||
SDL_GPU_TEXTUREFORMAT_R32G32_UINT, SDL_GPU_TEXTUREFORMAT_R32G32_INT,
|
||||
SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT,
|
||||
SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT -> 32;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
private static int sdlFormatColorComponents(int sdlFormat) {
|
||||
return switch (sdlFormat) {
|
||||
case SDL_GPU_TEXTUREFORMAT_R8_UNORM, SDL_GPU_TEXTUREFORMAT_R8_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R8_UINT, SDL_GPU_TEXTUREFORMAT_R8_INT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16_UNORM, SDL_GPU_TEXTUREFORMAT_R16_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R16_FLOAT, SDL_GPU_TEXTUREFORMAT_R16_UINT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16_INT, SDL_GPU_TEXTUREFORMAT_R32_FLOAT,
|
||||
SDL_GPU_TEXTUREFORMAT_R32_UINT, SDL_GPU_TEXTUREFORMAT_R32_INT -> 1;
|
||||
case SDL_GPU_TEXTUREFORMAT_R8G8_UNORM, SDL_GPU_TEXTUREFORMAT_R8G8_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8_UINT, SDL_GPU_TEXTUREFORMAT_R8G8_INT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16_UNORM, SDL_GPU_TEXTUREFORMAT_R16G16_SNORM,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT, SDL_GPU_TEXTUREFORMAT_R16G16_UINT,
|
||||
SDL_GPU_TEXTUREFORMAT_R16G16_INT, SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT,
|
||||
SDL_GPU_TEXTUREFORMAT_R32G32_UINT, SDL_GPU_TEXTUREFORMAT_R32G32_INT -> 2;
|
||||
default -> 4;
|
||||
};
|
||||
}
|
||||
|
||||
public static int glPixelSize(int format, int type) {
|
||||
final int packed = GLTypes.packedTexelBytes(type);
|
||||
if (packed != 0) return packed;
|
||||
|
||||
+125
-2
@@ -127,6 +127,10 @@ public final class ResourceManager {
|
||||
|
||||
private int preferredD24 = SDL_GPU_TEXTUREFORMAT_D24_UNORM;
|
||||
private int preferredD24S8 = SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT;
|
||||
private volatile int swapchainDepthStencilFormat = SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT;
|
||||
private long swapchainDepthStencil;
|
||||
private int swapchainDepthStencilWidth;
|
||||
private int swapchainDepthStencilHeight;
|
||||
|
||||
private static final Supplier<ContextState.VAOState> VAO_FACTORY = ContextState.VAOState::new;
|
||||
private static final Supplier<TextureSamplerState> TEX_SAMPLER_FACTORY = TextureSamplerState::new;
|
||||
@@ -154,6 +158,21 @@ public final class ResourceManager {
|
||||
preferredD24S8 = SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT;
|
||||
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;
|
||||
} else {
|
||||
swapchainDepthStencilFormat = 0;
|
||||
LOG.warn("No depth+stencil format usable as a render target; default framebuffer will have no depth or stencil");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getSwapchainDepthStencilFormat() {
|
||||
return swapchainDepthStencilFormat;
|
||||
}
|
||||
|
||||
public int genTexture() {
|
||||
@@ -292,7 +311,7 @@ public final class ResourceManager {
|
||||
} finally {
|
||||
wLock.unlock();
|
||||
}
|
||||
releaseTextureHandle(handle);
|
||||
releaseTextureHandleAfterPendingUploads(handle);
|
||||
}
|
||||
|
||||
public void releaseTextureHandleForRealloc(int glId) {
|
||||
@@ -304,9 +323,24 @@ public final class ResourceManager {
|
||||
} finally {
|
||||
wLock.unlock();
|
||||
}
|
||||
releaseTextureHandleAfterPendingUploads(handle);
|
||||
}
|
||||
|
||||
private void releaseTextureHandleAfterPendingUploads(long handle) {
|
||||
if (handle == 0) return;
|
||||
if (shouldDeferTextureRelease()) {
|
||||
releaseTextureDeferred(handle);
|
||||
return;
|
||||
}
|
||||
releaseTextureHandle(handle);
|
||||
}
|
||||
|
||||
boolean shouldDeferTextureRelease() {
|
||||
final TransferThread tt = transferThread;
|
||||
if (tt != null && tt.getSubmittedSeq() < lastTextureUploadSeq) return true;
|
||||
return hasBatchedUploads();
|
||||
}
|
||||
|
||||
public void trackBufferHandle(long handle) {
|
||||
if (handle == 0) return;
|
||||
wLock.lock();
|
||||
@@ -319,6 +353,40 @@ public final class ResourceManager {
|
||||
try { liveTextureHandles.add(handle); } finally { wLock.unlock(); }
|
||||
}
|
||||
|
||||
private final LongOpenHashSet definedContentTextures = new LongOpenHashSet();
|
||||
|
||||
public boolean isTextureContentDefined(long handle) {
|
||||
if (handle == 0) return false;
|
||||
if (fastRead()) return definedContentTextures.contains(handle);
|
||||
rLock.lock();
|
||||
try { return definedContentTextures.contains(handle); } finally { rLock.unlock(); }
|
||||
}
|
||||
|
||||
public void markTextureContentDefined(long handle) {
|
||||
if (handle == 0) return;
|
||||
wLock.lock();
|
||||
try { definedContentTextures.add(handle); } finally { wLock.unlock(); }
|
||||
}
|
||||
|
||||
void markTextureContentsDefined(LongArrayList handles) {
|
||||
if (handles.isEmpty()) return;
|
||||
wLock.lock();
|
||||
try {
|
||||
for (int i = 0, n = handles.size(); i < n; i++) {
|
||||
final long handle = handles.getLong(i);
|
||||
if (handle != 0) definedContentTextures.add(handle);
|
||||
}
|
||||
} finally {
|
||||
wLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void markTextureContentUndefined(long handle) {
|
||||
if (handle == 0) return;
|
||||
wLock.lock();
|
||||
try { definedContentTextures.remove(handle); } finally { wLock.unlock(); }
|
||||
}
|
||||
|
||||
public void releaseBufferHandle(long handle) {
|
||||
if (handle == 0) return;
|
||||
final boolean live;
|
||||
@@ -338,6 +406,8 @@ public final class ResourceManager {
|
||||
|
||||
public void releaseTextureHandle(long handle) {
|
||||
if (handle == 0) return;
|
||||
dropBatchedUploadsTargeting(handle);
|
||||
markTextureContentUndefined(handle);
|
||||
final boolean live;
|
||||
wLock.lock();
|
||||
try {
|
||||
@@ -620,6 +690,7 @@ public final class ResourceManager {
|
||||
|
||||
private final LongArrayList deferredBufferReleases = new LongArrayList();
|
||||
private final LongArrayList deferredTextureReleases = new LongArrayList();
|
||||
private volatile long lastTextureUploadSeq;
|
||||
|
||||
public void deleteBuffer(int glId) {
|
||||
final ByteBuffer droppedPboStaging;
|
||||
@@ -1560,12 +1631,16 @@ public final class ResourceManager {
|
||||
f.batchMapped = null;
|
||||
}
|
||||
|
||||
markTextureContentsDefined(f.pendingTexHandles);
|
||||
|
||||
try (var stack = stackPush()) {
|
||||
final SDL_GPUTextureTransferInfo src = SDL_GPUTextureTransferInfo.calloc(stack);
|
||||
final SDL_GPUTextureRegion dst = SDL_GPUTextureRegion.calloc(stack);
|
||||
for (int i = 0; i < n; i++) {
|
||||
final long texture = f.pendingTexHandles.getLong(i);
|
||||
if (texture == 0) continue;
|
||||
src.transfer_buffer(f.batchXfer).offset(f.pendingTexOffsets.getInt(i));
|
||||
dst.texture(f.pendingTexHandles.getLong(i)).mip_level(f.pendingTexLevels.getInt(i))
|
||||
dst.texture(texture).mip_level(f.pendingTexLevels.getInt(i))
|
||||
.x(f.pendingTexX.getInt(i)).y(f.pendingTexY.getInt(i)).z(0)
|
||||
.w(f.pendingTexW.getInt(i)).h(f.pendingTexH.getInt(i)).d(1);
|
||||
SDL_UploadToGPUTexture(copyPass, src, dst, false);
|
||||
@@ -1586,6 +1661,14 @@ public final class ResourceManager {
|
||||
frameManager.recordUploadCommands(totalBytes, n);
|
||||
}
|
||||
|
||||
void dropBatchedUploadsTargeting(long handle) {
|
||||
final LongArrayList handles = frameManager.frame().pendingTexHandles;
|
||||
if (handles.isEmpty()) return;
|
||||
for (int i = 0, n = handles.size(); i < n; i++) {
|
||||
if (handles.getLong(i) == handle) handles.set(i, 0L);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasBatchedUploads() {
|
||||
return !frameManager.frame().pendingTexHandles.isEmpty();
|
||||
}
|
||||
@@ -1616,6 +1699,7 @@ public final class ResourceManager {
|
||||
.w(w).h(h).d(d);
|
||||
SDL_UploadToGPUTexture(copyPass, src, dst, false);
|
||||
}
|
||||
markTextureContentDefined(gpuTexture);
|
||||
|
||||
frameManager.recordUploadCommands(size, 1);
|
||||
returnTransferBuffer(xfer, size);
|
||||
@@ -1649,6 +1733,7 @@ public final class ResourceManager {
|
||||
|
||||
SDL_UploadToGPUTexture(copyPass, src, dst, false);
|
||||
}
|
||||
markTextureContentDefined(gpuTexture);
|
||||
|
||||
frameManager.recordUploadCommands(size, 1);
|
||||
returnTransferBuffer(xfer, size);
|
||||
@@ -1670,6 +1755,8 @@ public final class ResourceManager {
|
||||
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
|
||||
final long seq = TransferThread.nextSeq();
|
||||
st.frameHighestEnqueuedSeq = seq;
|
||||
lastTextureUploadSeq = seq;
|
||||
markTextureContentDefined(texHandle);
|
||||
tt.enqueue(TransferThread.TextureRegionUpload.acquire(xfer, texHandle, x, y, w, h, level, size, seq));
|
||||
tt.wake();
|
||||
return true;
|
||||
@@ -1999,6 +2086,34 @@ public final class ResourceManager {
|
||||
return dummyColorTarget;
|
||||
}
|
||||
|
||||
public long getOrCreateSwapchainDepthStencil(int width, int height) {
|
||||
if (swapchainDepthStencilFormat == 0 || width <= 0 || height <= 0) return 0;
|
||||
if (swapchainDepthStencil != 0 && swapchainDepthStencilWidth == width && swapchainDepthStencilHeight == height) {
|
||||
return swapchainDepthStencil;
|
||||
}
|
||||
if (swapchainDepthStencil != 0) {
|
||||
releaseTextureDeferred(swapchainDepthStencil);
|
||||
swapchainDepthStencil = 0;
|
||||
}
|
||||
try (var stack = stackPush()) {
|
||||
final SDL_GPUTextureCreateInfo ci = SDL_GPUTextureCreateInfo.calloc(stack)
|
||||
.type(SDL_GPU_TEXTURETYPE_2D)
|
||||
.format(swapchainDepthStencilFormat)
|
||||
.usage(SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET)
|
||||
.width(width).height(height).layer_count_or_depth(1).num_levels(1);
|
||||
swapchainDepthStencil = SDL_CreateGPUTexture(device.getDevice(), ci);
|
||||
}
|
||||
if (swapchainDepthStencil == 0) {
|
||||
swapchainDepthStencilFormat = 0;
|
||||
LOG.warn("Failed to create the default framebuffer depth+stencil target ({}x{}); it will have neither", width, height);
|
||||
return 0;
|
||||
}
|
||||
trackTextureHandle(swapchainDepthStencil);
|
||||
swapchainDepthStencilWidth = width;
|
||||
swapchainDepthStencilHeight = height;
|
||||
return swapchainDepthStencil;
|
||||
}
|
||||
|
||||
private static final int DUMMY_VBO_SIZE = 16 * 16;
|
||||
public static final int ATTRIB_RING_BLOCKS = 2048 * Device.FRAMES_IN_FLIGHT;
|
||||
public static final int ATTRIB_RING_CHUNK_BLOCKS = 64;
|
||||
@@ -2114,6 +2229,8 @@ public final class ResourceManager {
|
||||
case GL14.GL_DEPTH_COMPONENT32, GL30.GL_DEPTH_COMPONENT32F -> SDL_GPU_TEXTUREFORMAT_D32_FLOAT;
|
||||
case GL30.GL_DEPTH24_STENCIL8 -> preferredD24S8;
|
||||
case GL30.GL_DEPTH32F_STENCIL8 -> SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT;
|
||||
case GL11.GL_STENCIL_INDEX, GL30.GL_STENCIL_INDEX1, GL30.GL_STENCIL_INDEX4,
|
||||
GL30.GL_STENCIL_INDEX8, GL30.GL_STENCIL_INDEX16 -> preferredD24S8;
|
||||
case GL30.GL_R8UI -> SDL_GPU_TEXTUREFORMAT_R8_UINT;
|
||||
case GL30.GL_R16UI -> SDL_GPU_TEXTUREFORMAT_R16_UINT;
|
||||
case GL30.GL_R32UI -> SDL_GPU_TEXTUREFORMAT_R32_UINT;
|
||||
@@ -2233,6 +2350,12 @@ public final class ResourceManager {
|
||||
releaseTextureHandle(dummyColorTarget);
|
||||
dummyColorTarget = 0;
|
||||
}
|
||||
if (swapchainDepthStencil != 0) {
|
||||
releaseTextureHandle(swapchainDepthStencil);
|
||||
swapchainDepthStencil = 0;
|
||||
swapchainDepthStencilWidth = 0;
|
||||
swapchainDepthStencilHeight = 0;
|
||||
}
|
||||
if (fallbackStorageTexture3D != 0) {
|
||||
releaseTextureHandle(fallbackStorageTexture3D);
|
||||
fallbackStorageTexture3D = 0;
|
||||
|
||||
+45
-9
@@ -34,6 +34,7 @@ public final class TransferThread {
|
||||
private static final Tracy.ZoneId Z_SDL_UPLOAD = Tracy.zoneId("sdlUpload", Tracy.COLOR_WORKER);
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU-Transfer");
|
||||
private boolean warnedNullTextureUpload;
|
||||
|
||||
private static final AtomicLong SEQ = new AtomicLong();
|
||||
|
||||
@@ -214,6 +215,19 @@ public final class TransferThread {
|
||||
return flushRequestedSeq > submittedSeq || openBytes >= FLUSH_COALESCE_BYTES || openCount >= FLUSH_COALESCE_COMMANDS;
|
||||
}
|
||||
|
||||
static boolean shouldPublishRetired(boolean hasOpenCB, long openHighestSeq, long submittedSeq) {
|
||||
return !hasOpenCB && openHighestSeq > submittedSeq;
|
||||
}
|
||||
|
||||
private void publishSubmittedSeq() {
|
||||
if (openHighestSeq <= submittedSeq) return;
|
||||
synchronized (submittedLock) {
|
||||
submittedSeq = openHighestSeq;
|
||||
submittedLock.notifyAll();
|
||||
}
|
||||
drainDuePendingFrees();
|
||||
}
|
||||
|
||||
private void run() {
|
||||
LOG.info("Transfer thread started");
|
||||
try {
|
||||
@@ -230,6 +244,9 @@ public final class TransferThread {
|
||||
|
||||
if (shouldFlush(openCB != 0, flushRequestedSeq, submittedSeq, openBytes, openCount)) {
|
||||
flushOpenCB();
|
||||
} else if (shouldPublishRetired(openCB != 0, openHighestSeq, submittedSeq)) {
|
||||
publishSubmittedSeq();
|
||||
openHighestSeq = 0;
|
||||
}
|
||||
|
||||
if (Tracy.ENABLED) pendingActiveNanos += System.nanoTime() - workStart;
|
||||
@@ -315,10 +332,18 @@ public final class TransferThread {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void retireSeq(long seq) {
|
||||
openHighestSeq = Math.max(openHighestSeq, seq);
|
||||
}
|
||||
|
||||
private void recordBufferUpload(long transferBuffer, long size, long dstGpuBuffer, long dstOffset, long seq, boolean cycle) {
|
||||
if (transferBuffer == 0) return;
|
||||
if (transferBuffer == 0) {
|
||||
retireSeq(seq);
|
||||
return;
|
||||
}
|
||||
if (!ensureOpenCB()) {
|
||||
resourceManager.returnTransferBufferThreadSafe(transferBuffer, size);
|
||||
retireSeq(seq);
|
||||
return;
|
||||
}
|
||||
srcLoc.transfer_buffer(transferBuffer).offset(0);
|
||||
@@ -333,7 +358,10 @@ public final class TransferThread {
|
||||
}
|
||||
|
||||
private void recordGpuCopy(long srcHandle, long dstHandle, long readOffset, long writeOffset, long size, long seq) {
|
||||
if (!ensureOpenCB()) return;
|
||||
if (!ensureOpenCB()) {
|
||||
retireSeq(seq);
|
||||
return;
|
||||
}
|
||||
srcBufLoc.buffer(srcHandle).offset((int) readOffset);
|
||||
dstBufLoc.buffer(dstHandle).offset((int) writeOffset);
|
||||
SDL_CopyGPUBufferToBuffer(openCopyPass, srcBufLoc, dstBufLoc, (int) size, false);
|
||||
@@ -344,9 +372,22 @@ public final class TransferThread {
|
||||
}
|
||||
|
||||
private void recordTextureUpload(long transferBuffer, long texHandle, int x, int y, int w, int h, int level, long size, long seq) {
|
||||
if (transferBuffer == 0) return;
|
||||
if (transferBuffer == 0) {
|
||||
retireSeq(seq);
|
||||
return;
|
||||
}
|
||||
if (texHandle == 0) {
|
||||
if (!warnedNullTextureUpload) {
|
||||
warnedNullTextureUpload = true;
|
||||
LOG.warn("Deferred texture upload with no destination texture; dropped (level={} {}x{} at {},{})", level, w, h, x, y);
|
||||
}
|
||||
resourceManager.returnTransferBufferThreadSafe(transferBuffer, size);
|
||||
retireSeq(seq);
|
||||
return;
|
||||
}
|
||||
if (!ensureOpenCB()) {
|
||||
resourceManager.returnTransferBufferThreadSafe(transferBuffer, size);
|
||||
retireSeq(seq);
|
||||
return;
|
||||
}
|
||||
texXferInfo.transfer_buffer(transferBuffer).offset(0);
|
||||
@@ -368,12 +409,7 @@ public final class TransferThread {
|
||||
device.reportGpuFailure("submit transfer command buffer");
|
||||
}
|
||||
|
||||
synchronized (submittedLock) {
|
||||
submittedSeq = openHighestSeq;
|
||||
submittedLock.notifyAll();
|
||||
}
|
||||
|
||||
drainDuePendingFrees();
|
||||
publishSubmittedSeq();
|
||||
|
||||
for (int i = 0, n = pendingReturnHandles.size(); i < n; i++) {
|
||||
resourceManager.returnTransferBufferThreadSafe(pendingReturnHandles.getLong(i), pendingReturnSizes.getLong(i));
|
||||
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend.defaultFramebufferAttachmentParameteri;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend.fboAttachmentParameteri;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_D24_UNORM;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
|
||||
class FramebufferAttachmentQueryTest {
|
||||
|
||||
private static final int COLOR = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
|
||||
private static int defaultFb(int attachment, int pname, int depthFormat) {
|
||||
return defaultFramebufferAttachmentParameteri(attachment, pname, COLOR, depthFormat);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultFramebufferReportsEightStencilBits() {
|
||||
assertEquals(8, defaultFb(GL11.GL_STENCIL, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT));
|
||||
assertEquals(8, defaultFb(GL11.GL_STENCIL, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultFramebufferReportsDepthAndColorSizes() {
|
||||
assertEquals(24, defaultFb(GL11.GL_DEPTH, GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT));
|
||||
assertEquals(32, defaultFb(GL11.GL_DEPTH, GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT));
|
||||
assertEquals(8, defaultFb(GL11.GL_BACK_LEFT, GL30.GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE, SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT));
|
||||
assertEquals(8, defaultFb(GL11.GL_BACK_LEFT, GL30.GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE, SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultFramebufferWithoutDepthReportsNone() {
|
||||
assertEquals(0, defaultFb(GL11.GL_STENCIL, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, 0));
|
||||
assertEquals(0, defaultFb(GL11.GL_DEPTH, GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, 0));
|
||||
assertEquals(GL11.GL_NONE, defaultFb(GL11.GL_DEPTH, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, 0));
|
||||
assertEquals(GL30.GL_FRAMEBUFFER_DEFAULT, defaultFb(GL11.GL_BACK_LEFT, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void packedDepthStencilAttachmentReportsBothAspects() {
|
||||
final FboState fbo = new FboState();
|
||||
fbo.depthGlId = 42;
|
||||
fbo.depthTexture = 0xDDDDL;
|
||||
fbo.depthFormat = SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT;
|
||||
|
||||
for (int attachment : new int[]{ GL30.GL_DEPTH_ATTACHMENT, GL30.GL_STENCIL_ATTACHMENT, GL30.GL_DEPTH_STENCIL_ATTACHMENT, GL11.GL_STENCIL }) {
|
||||
assertEquals(8, fboAttachmentParameteri(fbo, attachment, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE), "attachment " + attachment);
|
||||
}
|
||||
assertEquals(24, fboAttachmentParameteri(fbo, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE));
|
||||
assertEquals(42, fboAttachmentParameteri(fbo, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME));
|
||||
assertEquals(GL11.GL_TEXTURE, fboAttachmentParameteri(fbo, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void depthOnlyAttachmentReportsNoStencil() {
|
||||
final FboState fbo = new FboState();
|
||||
fbo.depthGlId = 7;
|
||||
fbo.depthTexture = 0xDDDDL;
|
||||
fbo.depthFormat = SDL_GPU_TEXTUREFORMAT_D24_UNORM;
|
||||
|
||||
assertEquals(0, fboAttachmentParameteri(fbo, GL11.GL_STENCIL, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE));
|
||||
assertEquals(24, fboAttachmentParameteri(fbo, GL11.GL_DEPTH, GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingAttachmentReportsNone() {
|
||||
final FboState fbo = new FboState();
|
||||
assertEquals(GL11.GL_NONE, fboAttachmentParameteri(fbo, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE));
|
||||
assertEquals(0, fboAttachmentParameteri(fbo, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE));
|
||||
assertEquals(0, fboAttachmentParameteri(null, GL30.GL_DEPTH_ATTACHMENT, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void colorAttachmentReportsChannelSizes() {
|
||||
final FboState fbo = new FboState();
|
||||
fbo.colorGlIds[1] = 11;
|
||||
fbo.colorTextures[1] = 0xCCCCL;
|
||||
fbo.colorFormats[1] = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
|
||||
assertEquals(8, fboAttachmentParameteri(fbo, GL30.GL_COLOR_ATTACHMENT0 + 1, GL30.GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE));
|
||||
assertEquals(11, fboAttachmentParameteri(fbo, GL30.GL_COLOR_ATTACHMENT0 + 1, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME));
|
||||
assertEquals(GL11.GL_NONE, fboAttachmentParameteri(fbo, GL30.GL_COLOR_ATTACHMENT0, GL30.GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE));
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.frame;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.swapchainClearNeedsPassBreak;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class SwapchainClearPassBreakTest {
|
||||
|
||||
private static final long SWAPCHAIN = 0x5A1CL;
|
||||
|
||||
private static FrameManager.FrameState activeSwapchainPass() {
|
||||
final FrameManager.FrameState f = new FrameManager.FrameState();
|
||||
f.swapchainTexture = SWAPCHAIN;
|
||||
f.renderPass = 0xBEEFL;
|
||||
f.currentColorTarget = SWAPCHAIN;
|
||||
return f;
|
||||
}
|
||||
|
||||
@Test
|
||||
void aPendingColorClearBreaksTheActivePass() {
|
||||
final ContextState st = new ContextState();
|
||||
st.pendingSwapchainClear = true;
|
||||
assertTrue(swapchainClearNeedsPassBreak(st, activeSwapchainPass()), "otherwise the clear is deferred to the next pass and wipes what this one drew");
|
||||
}
|
||||
|
||||
@Test
|
||||
void aPendingDepthOrStencilClearBreaksTheActivePass() {
|
||||
final ContextState depth = new ContextState();
|
||||
depth.pendingSwapchainDepthClear = true;
|
||||
assertTrue(swapchainClearNeedsPassBreak(depth, activeSwapchainPass()));
|
||||
|
||||
final ContextState stencil = new ContextState();
|
||||
stencil.pendingSwapchainStencilClear = true;
|
||||
assertTrue(swapchainClearNeedsPassBreak(stencil, activeSwapchainPass()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nothingPendingLeavesThePassAlone() {
|
||||
assertFalse(swapchainClearNeedsPassBreak(new ContextState(), activeSwapchainPass()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aClearWithNoActivePassNeedsNoBreak() {
|
||||
final ContextState st = new ContextState();
|
||||
st.pendingSwapchainClear = true;
|
||||
|
||||
final FrameManager.FrameState f = activeSwapchainPass();
|
||||
f.renderPass = 0;
|
||||
assertFalse(swapchainClearNeedsPassBreak(st, f));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aPassBoundToAnFboIsNotTheSwapchainPass() {
|
||||
final ContextState st = new ContextState();
|
||||
st.pendingSwapchainClear = true;
|
||||
|
||||
final FrameManager.FrameState f = activeSwapchainPass();
|
||||
f.currentColorTarget = 0xF80L;
|
||||
assertFalse(swapchainClearNeedsPassBreak(st, f));
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineApplier.consumeClears;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_D24_UNORM;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT;
|
||||
|
||||
class ClearConsumptionTest {
|
||||
|
||||
private static final long COLOR = 0xC010L;
|
||||
private static final long DEPTH = 0xD00DL;
|
||||
private static final long DUMMY = 0xD04DL;
|
||||
|
||||
private static FboState fbo(int depthFormat) {
|
||||
final FboState fbo = new FboState();
|
||||
fbo.colorTextures[0] = COLOR;
|
||||
fbo.colorGlIds[0] = 3;
|
||||
if (depthFormat != 0) {
|
||||
fbo.depthTexture = DEPTH;
|
||||
fbo.depthGlId = 5;
|
||||
fbo.depthFormat = depthFormat;
|
||||
}
|
||||
fbo.recomputeTargets();
|
||||
return fbo;
|
||||
}
|
||||
|
||||
private static ResourceManager rm() {
|
||||
return SdlTestRig.resourceManager();
|
||||
}
|
||||
|
||||
@Test
|
||||
void aClearedColorTargetBecomesContentDefined() {
|
||||
final ResourceManager rm = rm();
|
||||
final ContextState st = new ContextState();
|
||||
final FboState fbo = fbo(0);
|
||||
FBOClearTracker.recordPendingColorClear(st, COLOR, 0f, 0f, 0f, 1f);
|
||||
|
||||
consumeClears(rm, st, fbo, DUMMY, 0b1, false, false, false);
|
||||
|
||||
assertFalse(st.pendingColorTextures.contains(COLOR), "the pass applied the clear, so it is no longer pending");
|
||||
assertTrue(rm.isTextureContentDefined(COLOR), "a later frame must not clear it again");
|
||||
}
|
||||
|
||||
@Test
|
||||
void anUnclearedColorTargetIsNotMarkedDefined() {
|
||||
final ResourceManager rm = rm();
|
||||
final ContextState st = new ContextState();
|
||||
|
||||
consumeClears(rm, st, fbo(0), DUMMY, 0, false, false, false);
|
||||
|
||||
assertFalse(rm.isTextureContentDefined(COLOR));
|
||||
}
|
||||
|
||||
@Test
|
||||
void theDummyTargetIsNeverTracked() {
|
||||
final ResourceManager rm = rm();
|
||||
final ContextState st = new ContextState();
|
||||
final FboState fbo = fbo(0);
|
||||
fbo.colorTextures[0] = DUMMY;
|
||||
fbo.recomputeTargets();
|
||||
|
||||
consumeClears(rm, st, fbo, DUMMY, 0b1, false, false, false);
|
||||
|
||||
assertFalse(rm.isTextureContentDefined(DUMMY), "the shared stand-in target has no per-FBO content");
|
||||
}
|
||||
|
||||
@Test
|
||||
void depthAndStencilAreConsumedIndependently() {
|
||||
final ContextState st = new ContextState();
|
||||
FBOClearTracker.recordPendingDepthClear(st, DEPTH, 1f);
|
||||
FBOClearTracker.recordPendingStencilClear(st, DEPTH, 0);
|
||||
|
||||
consumeClears(rm(), st, fbo(SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT), DUMMY, 0, true, true, true);
|
||||
|
||||
assertFalse(st.pendingDepthTextures.contains(DEPTH));
|
||||
assertFalse(st.pendingStencilTextures.contains(DEPTH));
|
||||
assertTrue(st.clearedTexturesThisFrame.contains(DEPTH));
|
||||
assertTrue(st.clearedStencilTexturesThisFrame.contains(DEPTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aDepthOnlyTargetLeavesStencilStateAlone() {
|
||||
final ContextState st = new ContextState();
|
||||
FBOClearTracker.recordPendingDepthClear(st, DEPTH, 1f);
|
||||
FBOClearTracker.recordPendingStencilClear(st, DEPTH, 0);
|
||||
|
||||
consumeClears(rm(), st, fbo(SDL_GPU_TEXTUREFORMAT_D24_UNORM), DUMMY, 0, true, false, false);
|
||||
|
||||
assertFalse(st.pendingDepthTextures.contains(DEPTH));
|
||||
assertTrue(st.pendingStencilTextures.contains(DEPTH), "the format has no stencil aspect to clear");
|
||||
assertFalse(st.clearedStencilTexturesThisFrame.contains(DEPTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void anUnclearedDepthTargetIsStillConsumed() {
|
||||
final ContextState st = new ContextState();
|
||||
FBOClearTracker.recordPendingDepthClear(st, DEPTH, 1f);
|
||||
|
||||
consumeClears(rm(), st, fbo(SDL_GPU_TEXTUREFORMAT_D24_UNORM), DUMMY, 0, false, false, false);
|
||||
|
||||
assertFalse(st.pendingDepthTextures.contains(DEPTH));
|
||||
assertFalse(st.clearedTexturesThisFrame.contains(DEPTH), "no clear was baked, so nothing was cleared");
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineApplier.foldClearColor;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineApplier.foldClearDepth;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineApplier.foldClearStencil;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
class ClearValueLayoutHashTest {
|
||||
|
||||
private static final long SEED = 0xA5A5A5A5A5A5A5A5L;
|
||||
|
||||
@Test
|
||||
void depthZeroAndDepthOneDoNotCollide() {
|
||||
assertNotEquals(foldClearDepth(SEED, 0.0f), foldClearDepth(SEED, 1.0f), "the minimap mask clear must not reuse the world's depth-1 clear target");
|
||||
}
|
||||
|
||||
@Test
|
||||
void depthFoldIsStableForTheSameValue() {
|
||||
assertEquals(foldClearDepth(SEED, 0.0f), foldClearDepth(SEED, 0.0f));
|
||||
}
|
||||
|
||||
@Test
|
||||
void negativeZeroIsDistinguishedFromZero() {
|
||||
assertNotEquals(foldClearDepth(SEED, 0.0f), foldClearDepth(SEED, -0.0f), "raw bits, not numeric equality, so the cached value always matches what was baked");
|
||||
}
|
||||
|
||||
@Test
|
||||
void everyColorChannelParticipates() {
|
||||
final long base = foldClearColor(SEED, 0f, 0f, 0f, 1f);
|
||||
assertNotEquals(base, foldClearColor(SEED, 1f, 0f, 0f, 1f), "red");
|
||||
assertNotEquals(base, foldClearColor(SEED, 0f, 1f, 0f, 1f), "green");
|
||||
assertNotEquals(base, foldClearColor(SEED, 0f, 0f, 1f, 1f), "blue");
|
||||
assertNotEquals(base, foldClearColor(SEED, 0f, 0f, 0f, 0f), "alpha");
|
||||
}
|
||||
|
||||
@Test
|
||||
void stencilValuesDoNotCollide() {
|
||||
assertNotEquals(foldClearStencil(SEED, 0), foldClearStencil(SEED, 1));
|
||||
assertEquals(foldClearStencil(SEED, 0xFF), foldClearStencil(SEED, 0xFF));
|
||||
}
|
||||
}
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
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.resource.PixelOps;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.sdl.SDLError;
|
||||
import org.lwjgl.sdl.SDL_GPUColorTargetInfo;
|
||||
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.assertTrue;
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
|
||||
class StencilMaskGpuTest {
|
||||
|
||||
private static SdlTestRig rig;
|
||||
private static ShaderManager sm;
|
||||
|
||||
private static final int SIZE = 64;
|
||||
private static final int MASK = 32;
|
||||
|
||||
private static PipelineStore store;
|
||||
private static long colorTexture;
|
||||
private static int colorFormat;
|
||||
private static long depthTexture;
|
||||
private static int depthFormat;
|
||||
private static long maskPipeline;
|
||||
private static long fillPipeline;
|
||||
|
||||
private static final String VERTEX = String.join("\n",
|
||||
"#version 460 core",
|
||||
"void main() {",
|
||||
" vec2 p = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2);",
|
||||
" gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);",
|
||||
"}",
|
||||
"");
|
||||
|
||||
private static final String MASK_FRAGMENT = String.join("\n",
|
||||
"#version 460 core",
|
||||
"layout(location = 0) out vec4 fragColor;",
|
||||
"void main() {",
|
||||
" if (gl_FragCoord.x >= " + MASK + ".0 || gl_FragCoord.y >= " + MASK + ".0) discard;",
|
||||
" fragColor = vec4(0.0, 0.0, 1.0, 1.0);",
|
||||
"}",
|
||||
"");
|
||||
|
||||
private static final String FILL_FRAGMENT = String.join("\n",
|
||||
"#version 460 core",
|
||||
"layout(location = 0) out vec4 fragColor;",
|
||||
"void main() {",
|
||||
" fragColor = vec4(1.0, 0.0, 0.0, 1.0);",
|
||||
"}",
|
||||
"");
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() throws Exception {
|
||||
rig = SdlTestRig.acquireRealDevice();
|
||||
rig.resourceManager.cachePreferredDepthFormats();
|
||||
sm = new ShaderManager(rig.device);
|
||||
VAOManager.init(0);
|
||||
store = new PipelineStore(rig.device);
|
||||
|
||||
colorTexture = rig.resourceManager.createTexture(9500, GL11.GL_TEXTURE_2D, GL11.GL_RGBA8, SIZE, SIZE, 1, 1);
|
||||
assertTrue(colorTexture != 0, "color target creation failed");
|
||||
colorFormat = rig.resourceManager.getTextureMeta(9500).sdlFormat();
|
||||
PipelineCache.setSwapchainFormats(new int[]{ colorFormat });
|
||||
|
||||
rig.resourceManager.createTexture(9501, GL11.GL_TEXTURE_2D, GL30.GL_DEPTH24_STENCIL8, SIZE, SIZE, 1, 1);
|
||||
depthTexture = rig.resourceManager.ensureTextureUsage(9501, SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET);
|
||||
assertTrue(depthTexture != 0, "depth+stencil target creation failed");
|
||||
depthFormat = rig.resourceManager.getTextureMeta(9501).sdlFormat();
|
||||
assertEquals(8, PixelOps.stencilBits(depthFormat), "test needs a packed depth+stencil format");
|
||||
|
||||
maskPipeline = createPipeline(MASK_FRAGMENT, true);
|
||||
fillPipeline = createPipeline(FILL_FRAGMENT, false);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() {
|
||||
SdlTestRig.releaseRealDevice();
|
||||
}
|
||||
|
||||
private static long createPipeline(String fragmentSource, boolean writesMask) {
|
||||
final int vs = sm.createShader(GL20.GL_VERTEX_SHADER);
|
||||
sm.shaderSource(vs, VERTEX);
|
||||
sm.compileShader(vs);
|
||||
final int fs = sm.createShader(GL20.GL_FRAGMENT_SHADER);
|
||||
sm.shaderSource(fs, fragmentSource);
|
||||
sm.compileShader(fs);
|
||||
final int prog = sm.createProgram();
|
||||
sm.attachShader(prog, vs);
|
||||
sm.attachShader(prog, fs);
|
||||
sm.linkProgram(prog);
|
||||
final ShaderManager.ProgramObject po = sm.getProgram(prog);
|
||||
assertTrue(po != null && po.linked, "link failed: " + (po == null ? "no program" : po.infoLog));
|
||||
|
||||
po.sdlVertexShader = sm.createSDLShader(po.vertexSpirv, SDL_GPU_SHADERSTAGE_VERTEX, po.vertexResources.numSamplers(), po.vertexResources.numUBOs(), po.vertexResources.numStorageBuffers(), po.vertexResources.numStorageTextures());
|
||||
po.sdlFragmentShader = sm.createSDLShader(po.fragmentSpirv, SDL_GPU_SHADERSTAGE_FRAGMENT, po.fragmentResources.numSamplers(), po.fragmentResources.numUBOs(), po.fragmentResources.numStorageBuffers(), po.fragmentResources.numStorageTextures());
|
||||
assertTrue(po.sdlVertexShader != 0 && po.sdlFragmentShader != 0, "SDL shader creation failed");
|
||||
|
||||
final ContextState cs = new ContextState();
|
||||
final PipelineCache cache = cs.pipeline;
|
||||
cache.vertexShader = po.sdlVertexShader;
|
||||
cache.fragmentShader = po.sdlFragmentShader;
|
||||
cache.programId = prog;
|
||||
cache.maxFragOutputLocation = po.maxFragOutputLocation;
|
||||
cache.shaderInputMask = po.vertexInputMask;
|
||||
cache.shaderInputVecSize = po.vertexInputVecSize;
|
||||
cache.shaderInputBaseType = po.vertexInputBaseType;
|
||||
cache.shaderInputName = po.vertexInputName;
|
||||
cache.setColorTargetFormats(new int[]{ colorFormat });
|
||||
cache.setDrawBuffers(new int[]{ 0 });
|
||||
cache.hasDepthTarget = true;
|
||||
cache.depthTargetFormat = depthFormat;
|
||||
cache.depthTestEnabled = false;
|
||||
cache.depthWriteEnabled = false;
|
||||
cache.stencilTestEnabled = true;
|
||||
cache.stencilCompareMask = 0xFF;
|
||||
cache.stencilWriteMask = writesMask ? 0xFF : 0x00;
|
||||
final int compareOp = writesMask ? SDL_GPU_COMPAREOP_ALWAYS : SDL_GPU_COMPAREOP_EQUAL;
|
||||
final int passOp = writesMask ? SDL_GPU_STENCILOP_REPLACE : SDL_GPU_STENCILOP_KEEP;
|
||||
cache.stencilFrontCompareOp = compareOp;
|
||||
cache.stencilBackCompareOp = compareOp;
|
||||
cache.stencilFrontPassOp = passOp;
|
||||
cache.stencilBackPassOp = passOp;
|
||||
cache.markShaderDirty();
|
||||
cache.markOutputDirty();
|
||||
|
||||
assertTrue(cache.effectiveStencilTestEnabled(), "stencil must survive the depth-format gate");
|
||||
final long pipeline = cache.getOrCreatePipeline(store, cs);
|
||||
assertTrue(pipeline != 0, "pipeline creation failed: " + SDLError.SDL_GetError());
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
private static void render(int secondPassStencilLoad) {
|
||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||
final long cb = SDL_AcquireGPUCommandBuffer(rig.device.getDevice());
|
||||
assertTrue(cb != 0, "no command buffer");
|
||||
|
||||
final SDL_GPUColorTargetInfo.Buffer color = SDL_GPUColorTargetInfo.calloc(1, stack);
|
||||
color.get(0).texture(colorTexture).load_op(SDL_GPU_LOADOP_CLEAR).store_op(SDL_GPU_STOREOP_STORE);
|
||||
final SDL_GPUDepthStencilTargetInfo ds = SDL_GPUDepthStencilTargetInfo.calloc(stack)
|
||||
.texture(depthTexture)
|
||||
.load_op(SDL_GPU_LOADOP_CLEAR).store_op(SDL_GPU_STOREOP_STORE).clear_depth(1.0f)
|
||||
.stencil_load_op(SDL_GPU_LOADOP_CLEAR).stencil_store_op(SDL_GPU_STOREOP_STORE).clear_stencil((byte) 0);
|
||||
|
||||
long pass = SDL_BeginGPURenderPass(cb, color, ds);
|
||||
assertTrue(pass != 0, "no render pass");
|
||||
SDL_SetGPUStencilReference(pass, (byte) 1);
|
||||
SDL_BindGPUGraphicsPipeline(pass, maskPipeline);
|
||||
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
|
||||
if (secondPassStencilLoad < 0) {
|
||||
SDL_BindGPUGraphicsPipeline(pass, fillPipeline);
|
||||
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
|
||||
}
|
||||
SDL_EndGPURenderPass(pass);
|
||||
|
||||
if (secondPassStencilLoad >= 0) {
|
||||
color.get(0).load_op(SDL_GPU_LOADOP_LOAD);
|
||||
ds.load_op(SDL_GPU_LOADOP_LOAD).stencil_load_op(secondPassStencilLoad).clear_stencil((byte) 0);
|
||||
pass = SDL_BeginGPURenderPass(cb, color, ds);
|
||||
assertTrue(pass != 0, "no second render pass");
|
||||
SDL_SetGPUStencilReference(pass, (byte) 1);
|
||||
SDL_BindGPUGraphicsPipeline(pass, fillPipeline);
|
||||
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(pass);
|
||||
}
|
||||
|
||||
assertTrue(SDL_SubmitGPUCommandBuffer(cb), "draw submit failed");
|
||||
SDL_WaitForGPUIdle(rig.device.getDevice());
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] readRedPixels() {
|
||||
final ByteBuffer pixels = MemoryUtil.memAlloc(SIZE * SIZE * 4);
|
||||
try {
|
||||
final long cb = SDL_AcquireGPUCommandBuffer(rig.device.getDevice());
|
||||
assertTrue(cb != 0, "no readback command buffer");
|
||||
rig.resourceManager.downloadFromTexture(cb, colorTexture, 0, 0, SIZE, SIZE, 0, pixels);
|
||||
int count = 0, maxX = -1, minX = SIZE;
|
||||
for (int y = 0; y < SIZE; y++) {
|
||||
for (int x = 0; x < SIZE; x++) {
|
||||
final int i = (y * SIZE + x) * 4;
|
||||
final boolean red = (pixels.get(i) & 0xFF) > 200 && (pixels.get(i + 1) & 0xFF) < 64;
|
||||
if (red) {
|
||||
count++;
|
||||
if (x > maxX) maxX = x;
|
||||
if (x < minX) minX = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
return new int[]{ count, minX, maxX };
|
||||
} finally {
|
||||
MemoryUtil.memFree(pixels);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void fillIsClippedToTheStencilMask() {
|
||||
render(-1);
|
||||
final int[] r = readRedPixels();
|
||||
assertEquals(MASK * MASK, r[0], "fill must cover exactly the masked region, not the whole target");
|
||||
assertEquals(0, r[1], "masked region starts at x=0");
|
||||
assertEquals(MASK - 1, r[2], "masked region must not bleed past the mask");
|
||||
}
|
||||
|
||||
@Test
|
||||
void maskSurvivesAPassBoundaryWhenStencilIsLoaded() {
|
||||
render(SDL_GPU_LOADOP_LOAD);
|
||||
final int[] r = readRedPixels();
|
||||
assertEquals(MASK * MASK, r[0], "a stored stencil buffer must still clip the next pass");
|
||||
assertEquals(MASK - 1, r[2]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void clearingStencilWipesTheMask() {
|
||||
render(SDL_GPU_LOADOP_CLEAR);
|
||||
assertEquals(0, readRedPixels()[0], "a cleared stencil buffer must clip everything");
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ColorContentLifetimeTest {
|
||||
|
||||
private static final long TEX = 0xC010125L;
|
||||
|
||||
@Test
|
||||
void freshTextureHasUndefinedContents() {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
assertFalse(rm.isTextureContentDefined(TEX), "never written, so the first pass must clear");
|
||||
}
|
||||
|
||||
@Test
|
||||
void definedContentsSurviveAcrossFrames() {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
rm.markTextureContentDefined(TEX);
|
||||
assertTrue(rm.isTextureContentDefined(TEX));
|
||||
|
||||
assertTrue(rm.isTextureContentDefined(TEX), "contents outlive the frame that produced them");
|
||||
}
|
||||
|
||||
@Test
|
||||
void releaseResetsContentsBecauseSdlCanRecycleThePointer() {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
rm.markTextureContentDefined(TEX);
|
||||
rm.markTextureContentUndefined(TEX);
|
||||
assertFalse(rm.isTextureContentDefined(TEX), "a recycled handle must not inherit the previous texture's defined flag");
|
||||
}
|
||||
|
||||
@Test
|
||||
void theZeroHandleIsNeverDefined() {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
rm.markTextureContentDefined(0L);
|
||||
assertFalse(rm.isTextureContentDefined(0L));
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
|
||||
class DepthStencilBitsTest {
|
||||
|
||||
@Test
|
||||
void depthBitsPerFormat() {
|
||||
assertEquals(16, PixelOps.depthBits(SDL_GPU_TEXTUREFORMAT_D16_UNORM));
|
||||
assertEquals(24, PixelOps.depthBits(SDL_GPU_TEXTUREFORMAT_D24_UNORM));
|
||||
assertEquals(24, PixelOps.depthBits(SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT));
|
||||
assertEquals(32, PixelOps.depthBits(SDL_GPU_TEXTUREFORMAT_D32_FLOAT));
|
||||
assertEquals(32, PixelOps.depthBits(SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT));
|
||||
assertEquals(0, PixelOps.depthBits(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM));
|
||||
}
|
||||
|
||||
@Test
|
||||
void onlyPackedFormatsCarryStencil() {
|
||||
assertEquals(8, PixelOps.stencilBits(SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT));
|
||||
assertEquals(8, PixelOps.stencilBits(SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT));
|
||||
assertEquals(0, PixelOps.stencilBits(SDL_GPU_TEXTUREFORMAT_D24_UNORM));
|
||||
assertEquals(0, PixelOps.stencilBits(SDL_GPU_TEXTUREFORMAT_D32_FLOAT));
|
||||
assertEquals(0, PixelOps.stencilBits(SDL_GPU_TEXTUREFORMAT_D16_UNORM));
|
||||
assertEquals(0, PixelOps.stencilBits(SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stencilBitsAgreesWithIsDepthStencilFormat() {
|
||||
final int[] formats = {
|
||||
SDL_GPU_TEXTUREFORMAT_D16_UNORM, SDL_GPU_TEXTUREFORMAT_D24_UNORM, SDL_GPU_TEXTUREFORMAT_D32_FLOAT,
|
||||
SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT, SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT,
|
||||
SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
|
||||
};
|
||||
for (int f : formats) {
|
||||
assertEquals(PixelOps.isDepthStencilFormat(f), PixelOps.stencilBits(f) != 0, "format " + f);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void colorChannelBitsForCommonFormats() {
|
||||
for (int fmt : new int[]{ SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM }) {
|
||||
for (int c = 0; c < 4; c++) assertEquals(8, PixelOps.colorChannelBits(fmt, c), "channel " + c);
|
||||
}
|
||||
assertEquals(16, PixelOps.colorChannelBits(SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT, 3));
|
||||
assertEquals(8, PixelOps.colorChannelBits(SDL_GPU_TEXTUREFORMAT_R8_UNORM, 0));
|
||||
assertEquals(0, PixelOps.colorChannelBits(SDL_GPU_TEXTUREFORMAT_R8_UNORM, 1));
|
||||
assertEquals(8, PixelOps.colorChannelBits(SDL_GPU_TEXTUREFORMAT_R8G8_UNORM, 1));
|
||||
assertEquals(0, PixelOps.colorChannelBits(SDL_GPU_TEXTUREFORMAT_R8G8_UNORM, 2));
|
||||
assertEquals(10, PixelOps.colorChannelBits(SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM, 0));
|
||||
assertEquals(2, PixelOps.colorChannelBits(SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM, 3));
|
||||
assertEquals(0, PixelOps.colorChannelBits(SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void depthFormatsAreNotColorFormats() {
|
||||
assertTrue(PixelOps.isDepthFormat(SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT));
|
||||
assertFalse(PixelOps.isDepthFormat(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM));
|
||||
}
|
||||
}
|
||||
+12
@@ -35,6 +35,18 @@ class FullCoverageClearDiscardTest {
|
||||
assertTrue(st.clearedTexturesThisFrame.contains(HANDLE), "the overwrite counts as the clear");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fullTargetColorBlitDefinesTheContents() {
|
||||
final ContextState st = new ContextState();
|
||||
FBOClearTracker.recordPendingColorClear(st, HANDLE, 1f, 0f, 0f, 1f);
|
||||
|
||||
final SdlTestRig rig = SdlTestRig.create();
|
||||
final FBOClearTracker tracker = new FBOClearTracker(rig.frameManager, rig.resourceManager, null);
|
||||
assertTrue(tracker.discardPendingClearIfFullyCovered(st, HANDLE, 0, 0, 0, W, H, meta()));
|
||||
|
||||
assertTrue(rig.resourceManager.isTextureContentDefined(HANDLE), "the overwrite defines the contents, so a later frame must not clear it back");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fullTargetDepthBlitDropsThePendingClear() {
|
||||
final ContextState st = new ContextState();
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps.colorChannelBits;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_A8_UNORM;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_R8_UNORM;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
|
||||
class PixelFormatBitsTest {
|
||||
|
||||
private static int[] rgba(int sdlFormat) {
|
||||
return new int[]{
|
||||
colorChannelBits(sdlFormat, 0),
|
||||
colorChannelBits(sdlFormat, 1),
|
||||
colorChannelBits(sdlFormat, 2),
|
||||
colorChannelBits(sdlFormat, 3),
|
||||
};
|
||||
}
|
||||
|
||||
private static void assertRgba(int sdlFormat, int r, int g, int b, int a) {
|
||||
assertEquals(r, colorChannelBits(sdlFormat, 0), "red");
|
||||
assertEquals(g, colorChannelBits(sdlFormat, 1), "green");
|
||||
assertEquals(b, colorChannelBits(sdlFormat, 2), "blue");
|
||||
assertEquals(a, colorChannelBits(sdlFormat, 3), "alpha");
|
||||
}
|
||||
|
||||
@Test
|
||||
void uniformWidthFormats() {
|
||||
assertRgba(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, 8, 8, 8, 8);
|
||||
assertRgba(SDL_GPU_TEXTUREFORMAT_R8_UNORM, 8, 0, 0, 0);
|
||||
assertRgba(SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT, 16, 16, 0, 0);
|
||||
assertRgba(SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, 32, 32, 32, 32);
|
||||
}
|
||||
|
||||
@Test
|
||||
void packedFormatsReportTheirRealChannelWidths() {
|
||||
assertRgba(SDL_GPU_TEXTUREFORMAT_A8_UNORM, 0, 0, 0, 8);
|
||||
assertRgba(SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM, 10, 10, 10, 2);
|
||||
assertRgba(SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT, 11, 11, 10, 0);
|
||||
assertRgba(SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM, 5, 6, 5, 0);
|
||||
assertRgba(SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM, 5, 5, 5, 1);
|
||||
assertRgba(SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM, 4, 4, 4, 4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void depthAndCompressedFormatsHaveNoColorChannels() {
|
||||
assertRgba(SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT, 0, 0, 0, 0);
|
||||
assertRgba(SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void outOfRangeChannelsAreZero() {
|
||||
assertEquals(0, colorChannelBits(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, -1));
|
||||
assertEquals(0, colorChannelBits(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, 4));
|
||||
assertEquals(4, rgba(SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM).length);
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT;
|
||||
|
||||
class StencilClearTrackingTest {
|
||||
|
||||
private static final long DEPTH = 0xD00DL;
|
||||
|
||||
private static FBOClearTracker tracker() {
|
||||
final SdlTestRig rig = SdlTestRig.create();
|
||||
return new FBOClearTracker(rig.frameManager, rig.resourceManager, null);
|
||||
}
|
||||
|
||||
private static FboState packedDepthStencilFbo() {
|
||||
final FboState fbo = new FboState();
|
||||
fbo.depthTexture = DEPTH;
|
||||
fbo.depthGlId = 5;
|
||||
fbo.depthFormat = SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT;
|
||||
fbo.recomputeTargets();
|
||||
return fbo;
|
||||
}
|
||||
|
||||
@Test
|
||||
void pendingStencilClearIsTrackedSeparatelyFromDepth() {
|
||||
final ContextState st = new ContextState();
|
||||
FBOClearTracker.recordPendingStencilClear(st, DEPTH, 0);
|
||||
|
||||
assertTrue(st.pendingStencilTextures.contains(DEPTH));
|
||||
assertFalse(st.pendingDepthTextures.contains(DEPTH), "a stencil clear must not imply a depth clear");
|
||||
}
|
||||
|
||||
@Test
|
||||
void stencilClearSurvivesADepthClearOnTheSameHandle() {
|
||||
final ContextState st = new ContextState();
|
||||
FBOClearTracker.recordPendingStencilClear(st, DEPTH, 3);
|
||||
FBOClearTracker.recordPendingDepthClear(st, DEPTH, 1f);
|
||||
|
||||
assertTrue(st.pendingStencilTextures.contains(DEPTH), "depth and stencil share a texture but not a clear");
|
||||
assertEquals(3, st.pendingStencilValues.get(DEPTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aPendingStencilClearAloneBlocksTargetReuse() {
|
||||
final ContextState st = new ContextState();
|
||||
final FboState fbo = packedDepthStencilFbo();
|
||||
assertFalse(FBOClearTracker.fboHasPendingClear(st, fbo));
|
||||
|
||||
FBOClearTracker.recordPendingStencilClear(st, DEPTH, 0);
|
||||
assertTrue(FBOClearTracker.fboHasPendingClear(st, fbo), "the pass must be rebuilt to apply the stencil clear");
|
||||
}
|
||||
|
||||
@Test
|
||||
void recordingAStencilClearRetractsTheClearedThisFrameMark() {
|
||||
final ContextState st = new ContextState();
|
||||
st.clearedStencilTexturesThisFrame.add(DEPTH);
|
||||
|
||||
FBOClearTracker.recordPendingStencilClear(st, DEPTH, 0);
|
||||
assertFalse(st.clearedStencilTexturesThisFrame.contains(DEPTH), "a second clear in one frame must not be swallowed");
|
||||
}
|
||||
|
||||
@Test
|
||||
void recordingBumpsMutationGen() {
|
||||
final ContextState st = new ContextState();
|
||||
FBOClearTracker.snapshotFlushGenerations(st);
|
||||
final int before = st.pendingMutationGen;
|
||||
|
||||
FBOClearTracker.recordPendingStencilClear(st, DEPTH, 0);
|
||||
assertEquals(before + 1, st.pendingMutationGen);
|
||||
}
|
||||
|
||||
@Test
|
||||
void scrubDropsStencilStateWithTheTexture() {
|
||||
final ContextState st = new ContextState();
|
||||
FBOClearTracker.recordPendingStencilClear(st, DEPTH, 7);
|
||||
st.clearedStencilTexturesThisFrame.add(DEPTH);
|
||||
|
||||
final SdlTestRig rig = SdlTestRig.create();
|
||||
final int glId = rig.resourceManager.genTexture();
|
||||
SdlReflect.putTextureHandle(rig.resourceManager, glId, DEPTH);
|
||||
new FBOClearTracker(rig.frameManager, rig.resourceManager, null).scrubPendingClearsForTexture(st, glId);
|
||||
|
||||
assertFalse(st.pendingStencilTextures.contains(DEPTH));
|
||||
assertFalse(st.pendingStencilValues.containsKey(DEPTH));
|
||||
assertFalse(st.clearedStencilTexturesThisFrame.contains(DEPTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stencilClearValueIsMaskedToEightBits() {
|
||||
final ContextState st = new ContextState();
|
||||
FBOClearTracker.recordPendingStencilClear(st, DEPTH, 0xFF);
|
||||
assertEquals(0xFF, st.pendingStencilValues.get(DEPTH));
|
||||
assertEquals((byte) 0xFF, (byte) st.pendingStencilValues.get(DEPTH));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aStencilOnlyClearStillReachesTheSamplerFlush() {
|
||||
final ContextState st = new ContextState();
|
||||
FBOClearTracker.recordPendingStencilClear(st, DEPTH, 0);
|
||||
final int gen = st.pendingMutationGen;
|
||||
|
||||
tracker().flushPendingClearsForBoundSamplers(st);
|
||||
|
||||
assertEquals(gen, st.lastFlushedPendingMutationGen, "the early-out must not skip a texture whose only pending clear is stencil");
|
||||
}
|
||||
|
||||
@Test
|
||||
void trackerIsUnusedForFbosWithoutADepthAttachment() {
|
||||
final ContextState st = new ContextState();
|
||||
FBOClearTracker.recordPendingStencilClear(st, DEPTH, 0);
|
||||
final FboState colorOnly = new FboState();
|
||||
colorOnly.colorTextures[0] = 0xAAAAL;
|
||||
colorOnly.recomputeTargets();
|
||||
assertFalse(FBOClearTracker.fboHasPendingClear(st, colorOnly));
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class TextureReleaseUploadOrderTest {
|
||||
|
||||
private static final long HANDLE = 0xBEEFL;
|
||||
|
||||
private static List<Long> deferredReleases(ResourceManager rm) {
|
||||
return Reflect.get(rm, "deferredTextureReleases");
|
||||
}
|
||||
|
||||
private static void setUploadSeqs(ResourceManager rm, long lastUpload) {
|
||||
Reflect.setDeclared(ResourceManager.class, rm, "lastTextureUploadSeq", lastUpload);
|
||||
}
|
||||
|
||||
@Test
|
||||
void releaseIsImmediateWhenNoTransferThreadIsAttached() {
|
||||
final ResourceManager rm = SdlTestRig.resourceManager();
|
||||
setUploadSeqs(rm, 50L);
|
||||
assertFalse(rm.shouldDeferTextureRelease(), "with no transfer thread there is nothing to outlive");
|
||||
}
|
||||
|
||||
@Test
|
||||
void releaseIsDeferredWhileTextureUploadsAreStillQueued() {
|
||||
final SdlTestRig rig = SdlTestRig.create();
|
||||
final ResourceManager rm = rig.resourceManager;
|
||||
final TransferThread tt = new TransferThread(rig.device, rm);
|
||||
rm.setTransferThread(tt);
|
||||
setUploadSeqs(rm, 50L);
|
||||
Reflect.setDeclared(TransferThread.class, tt, "submittedSeq", 10L);
|
||||
|
||||
rm.trackTextureHandle(HANDLE);
|
||||
final int glId = rm.genTexture();
|
||||
SdlReflect.putTextureHandle(rm, glId, HANDLE);
|
||||
|
||||
rm.deleteTexture(glId);
|
||||
assertEquals(List.of(HANDLE), deferredReleases(rm), "the free must wait for the queued upload");
|
||||
}
|
||||
|
||||
@Test
|
||||
void releaseIsImmediateOnceTheTransferThreadHasCaughtUp() {
|
||||
final SdlTestRig rig = SdlTestRig.create();
|
||||
final ResourceManager rm = rig.resourceManager;
|
||||
final TransferThread tt = new TransferThread(rig.device, rm);
|
||||
rm.setTransferThread(tt);
|
||||
setUploadSeqs(rm, 50L);
|
||||
|
||||
Reflect.setDeclared(TransferThread.class, tt, "submittedSeq", 49L);
|
||||
assertTrue(rm.shouldDeferTextureRelease(), "one upload still unsubmitted is enough to defer");
|
||||
|
||||
Reflect.setDeclared(TransferThread.class, tt, "submittedSeq", 50L);
|
||||
assertFalse(rm.shouldDeferTextureRelease(), "nothing is in flight, so no need to defer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void aQueuedInlineBatchAlsoDefersTheRelease() {
|
||||
final SdlTestRig rig = SdlTestRig.create();
|
||||
final ResourceManager rm = rig.resourceManager;
|
||||
rig.frameManager.setResourceManager(rm);
|
||||
assertFalse(rm.shouldDeferTextureRelease(), "nothing queued yet");
|
||||
|
||||
rig.frameManager.frame().pendingTexHandles.add(HANDLE);
|
||||
assertTrue(rm.hasBatchedUploads());
|
||||
assertTrue(rm.shouldDeferTextureRelease(), "a queued batch entry must outlive the release");
|
||||
|
||||
rig.frameManager.frame().pendingTexHandles.clear();
|
||||
assertFalse(rm.shouldDeferTextureRelease());
|
||||
}
|
||||
|
||||
@Test
|
||||
void releasingATextureBlanksItsQueuedBatchEntries() {
|
||||
final SdlTestRig rig = SdlTestRig.create();
|
||||
final ResourceManager rm = rig.resourceManager;
|
||||
rig.frameManager.setResourceManager(rm);
|
||||
final var handles = rig.frameManager.frame().pendingTexHandles;
|
||||
handles.add(HANDLE);
|
||||
handles.add(0xFEEDL);
|
||||
handles.add(HANDLE);
|
||||
|
||||
rm.dropBatchedUploadsTargeting(HANDLE);
|
||||
|
||||
assertEquals(0L, handles.getLong(0), "every entry for the dying texture is blanked");
|
||||
assertEquals(0xFEEDL, handles.getLong(1), "other destinations are untouched");
|
||||
assertEquals(0L, handles.getLong(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void reallocReleaseObeysTheSameRule() {
|
||||
final SdlTestRig rig = SdlTestRig.create();
|
||||
final ResourceManager rm = rig.resourceManager;
|
||||
final TransferThread tt = new TransferThread(rig.device, rm);
|
||||
rm.setTransferThread(tt);
|
||||
setUploadSeqs(rm, 7L);
|
||||
Reflect.setDeclared(TransferThread.class, tt, "submittedSeq", 0L);
|
||||
|
||||
rm.trackTextureHandle(HANDLE);
|
||||
final int glId = rm.genTexture();
|
||||
SdlReflect.putTextureHandle(rm, glId, HANDLE);
|
||||
|
||||
rm.releaseTextureHandleForRealloc(glId);
|
||||
assertEquals(List.of(HANDLE), deferredReleases(rm), "glTexImage2D re-specification frees the old handle on the same path as a delete");
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.resource.TransferThread.shouldPublishRetired;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class TransferRetireSeqTest {
|
||||
|
||||
private static TransferThread thread() {
|
||||
final SdlTestRig rig = SdlTestRig.create();
|
||||
return new TransferThread(rig.device, rig.resourceManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
void anOpenCommandBufferIsNeverPublishedEarly() {assertFalse(shouldPublishRetired(true, 50L, 10L),
|
||||
"those copies are recorded but not submitted; a waiter must not be released");
|
||||
}
|
||||
|
||||
@Test
|
||||
void retiredSeqsArePublishedWhenNothingIsOpen() {
|
||||
assertTrue(shouldPublishRetired(false, 50L, 10L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void anAlreadyPublishedSeqIsNotRepublished() {
|
||||
assertFalse(shouldPublishRetired(false, 10L, 10L));
|
||||
assertFalse(shouldPublishRetired(false, 0L, 10L));
|
||||
}
|
||||
|
||||
@Test
|
||||
void aDroppedTextureUploadStillRetiresItsSeq() {
|
||||
final TransferThread tt = thread();
|
||||
Reflect.invoke(tt, "recordTextureUpload",
|
||||
new Class<?>[]{ long.class, long.class, int.class, int.class, int.class, int.class, int.class, long.class, long.class },
|
||||
0L, 0xBEEFL, 0, 0, 16, 16, 0, 1024L, 42L);
|
||||
|
||||
assertEquals(42L, (long) Reflect.get(tt, "openHighestSeq"), "a dropped upload must still advance the watermark or awaitSubmittedUpTo blocks forever");
|
||||
tt.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
void aDroppedBufferUploadStillRetiresItsSeq() {
|
||||
final TransferThread tt = thread();
|
||||
Reflect.invoke(tt, "recordBufferUpload", new Class<?>[]{ long.class, long.class, long.class, long.class, long.class, boolean.class }, 0L, 256L, 0xF00L, 0L, 7L, false);
|
||||
|
||||
assertEquals(7L, (long) Reflect.get(tt, "openHighestSeq"));
|
||||
tt.shutdown();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.LTWWorkaround;
|
||||
|
||||
/**
|
||||
* Checks for known problematic driver configurations and applies workarounds. Adapted from Embeddium/Sodium's LateDriverScanner.
|
||||
@@ -28,6 +29,14 @@ public class DriverCompatabilityCheck {
|
||||
LOGGER.info("OpenGL Renderer: {}", renderer);
|
||||
LOGGER.info("OpenGL Version: {}", version);
|
||||
|
||||
if (LTWWorkaround.isLtwVersionString(version)) {
|
||||
if (SystemProperties.DISABLE_LTW_WORKAROUND) {
|
||||
LOGGER.info("OpenLTW workaround disabled via -Dangelica.disableLtwWorkaround=true");
|
||||
} else {
|
||||
LOGGER.info("OpenLTW wrapper detected; enabling BGRA texture swizzle workaround");
|
||||
}
|
||||
}
|
||||
|
||||
if (vendor != null && vendor.contains("NVIDIA")) {
|
||||
if (SystemProperties.DISABLE_NVIDIA_WORKAROUND) {
|
||||
LOGGER.info("NVIDIA workaround disabled via -Dangelica.disableNvidiaWorkaround=true");
|
||||
|
||||
@@ -385,6 +385,11 @@ public class AngelicaConfig {
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public boolean narrowFoamFix;
|
||||
|
||||
@Config.Comment("Narrow Legends Mod transformer exclusions to allow GL redirection")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
public boolean narrowLegendsMod;
|
||||
}
|
||||
|
||||
@Config.Comment("Renders chunks before neighbors are ready. Improves loading at render distance edges, useful for low render distance servers.")
|
||||
|
||||
@@ -201,6 +201,7 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
|
||||
narrowTransformerConfig("Ears", AngelicaConfig.transformerCompat.narrowEars);
|
||||
narrowTransformerConfig("FiskHeroes", AngelicaConfig.transformerCompat.narrowFiskHeroes);
|
||||
narrowTransformerConfig("FoamFix", AngelicaConfig.transformerCompat.narrowFoamFix);
|
||||
narrowTransformerConfig("LegendsMod", AngelicaConfig.transformerCompat.narrowLegendsMod);
|
||||
|
||||
tweaks.add("com.gtnewhorizons.angelica.loading.fml.tweakers.IncompatibleModsDisablerTweaker");
|
||||
if (AngelicaConfig.enableHudCaching) {
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(ModelRenderer.class)
|
||||
@Mixin(value = ModelRenderer.class, priority = 1500)
|
||||
public abstract class MixinModelRenderer {
|
||||
|
||||
@Redirect(method = {"render", "renderWithRotation"}, at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glCallList(I)V", remap = false))
|
||||
|
||||
@@ -138,7 +138,7 @@ publishing {
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
if (System.getenv("MAVEN_USER") != null) {
|
||||
if (hasProperty("publishTracy") && System.getenv("MAVEN_USER") != null) {
|
||||
maven {
|
||||
name = "GTNHMaven"
|
||||
url = uri(rootProject.findProperty("mavenPublishUrl")?.toString() ?: "https://nexus.gtnewhorizons.com/repository/releases/")
|
||||
|
||||
Reference in new issue
Block a user