mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 01:34:48 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab3bf17696 | ||
|
|
cfc6e12e00 | ||
|
|
8537835d0c | ||
|
|
8e1809622c | ||
|
|
f81ee4b03a | ||
|
|
49db8e111d | ||
|
|
bd9a3c3c5c | ||
|
|
01ff612d1d | ||
|
|
0cae2be1f6 | ||
|
|
85d0641a73 | ||
|
|
46f8157087 | ||
|
|
d3dee841e8 | ||
|
|
3d3d0f9e18 | ||
|
|
23aa04361d |
No files matched your search
@@ -9,7 +9,42 @@ on:
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@master
|
||||
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@05e7cd7ce0ea015acda8c10c1c39fb3a20a49e60
|
||||
with:
|
||||
disable-spotless-pr: true
|
||||
java-distribution: temurin
|
||||
cache-rfg: true
|
||||
secrets: inherit
|
||||
|
||||
sdl-gpu-tests:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
concurrency:
|
||||
group: sdl-gpu-tests-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Install a software Vulkan driver
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y xvfb mesa-vulkan-drivers
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: |
|
||||
21
|
||||
25
|
||||
- uses: gradle/actions/setup-gradle@v5
|
||||
- name: Run SDL-GPU tests
|
||||
run: xvfb-run -a --server-args="-screen 0 1366x768x24" ./gradlew --build-cache --stacktrace --continue :sdl-gpu:glsmSdlTest :sdl-gpu:sdlGpuDeviceTest
|
||||
- name: Check LWJGL did not extract natives to the shared temp dir
|
||||
run: test ! -e "/tmp/lwjgl_$USER"
|
||||
- name: Attach SDL-GPU test results
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: sdl-gpu-test-results
|
||||
path: |
|
||||
sdl-gpu/build/test-results/
|
||||
sdl-gpu/build/reports/tests/
|
||||
sdl-gpu/hs_err_pid*.log
|
||||
+43
-2
@@ -89,8 +89,11 @@ tasks.register<Copy>("copyDependencies") {
|
||||
val embedOnly: Configuration by configurations
|
||||
val shadowImplementation: Configuration by configurations
|
||||
|
||||
val dependencyClasspath: FileCollection = sourceSets.main.get().runtimeClasspath - sourceSets.main.get().output
|
||||
|
||||
val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = embedOnly
|
||||
classpath = dependencyClasspath
|
||||
outputs.dir(temporaryDir)
|
||||
}
|
||||
|
||||
@@ -104,6 +107,7 @@ val stripModuleInfoFromShadow by tasks.registering(Jar::class) {
|
||||
|
||||
val downgradeShadowImplForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = files(stripModuleInfoFromShadow.map { it.archiveFile })
|
||||
classpath = dependencyClasspath
|
||||
outputs.dir(temporaryDir)
|
||||
}
|
||||
|
||||
@@ -118,6 +122,7 @@ val glsmTestFixtures: Configuration by configurations.creating {
|
||||
|
||||
val downgradeGlsmTestFixturesForTest by tasks.registering(DowngradeFiles::class) {
|
||||
inputCollection = glsmTestFixtures
|
||||
classpath = dependencyClasspath
|
||||
outputs.dir(temporaryDir)
|
||||
}
|
||||
|
||||
@@ -159,9 +164,25 @@ if (isMacOs && providers.gradleProperty("clientJvmVendor").isPresent) {
|
||||
}
|
||||
}
|
||||
|
||||
// Forge's asm-debug-all 5.0.3 cannot read the Java 25 class files ArchScan walks; this ASM has to resolve first.
|
||||
val archScanAsm: Configuration by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
archScanAsm(libs.asm.tree.test)
|
||||
archScanAsm(libs.asm.commons.test)
|
||||
}
|
||||
|
||||
sourceSets.test {
|
||||
compileClasspath = archScanAsm + compileClasspath
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform { excludeTags = setOf("gl-core") }
|
||||
configureAngelicaJava8()
|
||||
classpath = archScanAsm + classpath
|
||||
jvmArgs("-Dceleritas.lwjglService=org.taumc.celeritas.lwjgl.HeadlessTestLWJGLService")
|
||||
dependsOn(":glsm:classes", ":lwjgl3-backend:classes", ":sdl-gpu:classes")
|
||||
}
|
||||
@@ -169,6 +190,13 @@ tasks.test {
|
||||
val downgradeRootMainClasses = tasks.named<DowngradeFiles>("downgradeMainClasses")
|
||||
val downgradeRootTestClasses = tasks.named<DowngradeFiles>("downgradeTestClasses")
|
||||
|
||||
// JVMDowngrader only declares outputs for inputs that exist at configuration, so a clean tree has none.
|
||||
for (name in listOf("main", "mixin", "test")) {
|
||||
tasks.named<DowngradeFiles>("downgrade${name.replaceFirstChar { it.uppercase() }}Classes") {
|
||||
sourceSets[name].output.classesDirs.files.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
|
||||
}
|
||||
}
|
||||
|
||||
val glCoreTest by tasks.registering(Test::class) {
|
||||
description = "Runs the gl-core tests in their own JVM with a real GL context."
|
||||
group = "verification"
|
||||
@@ -185,10 +213,23 @@ val glCoreTest by tasks.registering(Test::class) {
|
||||
.plus(sourceSets["test"].runtimeClasspath.minus(rawMain).minus(rawTest))
|
||||
|
||||
configureAngelicaJava8()
|
||||
mustRunAfter(tasks.test)
|
||||
}
|
||||
|
||||
tasks.test { finalizedBy(glCoreTest) }
|
||||
val verifyTestsRan by tasks.registering
|
||||
listOf(tasks.test, glCoreTest).forEach { testTask ->
|
||||
val name = testTask.name
|
||||
val verify = tasks.register("verify${name.replaceFirstChar { it.uppercase() }}Ran") {
|
||||
val resultsDir = layout.buildDirectory.dir("test-results/$name")
|
||||
dependsOn(testTask)
|
||||
doLast {
|
||||
val dir = resultsDir.get().asFile
|
||||
val xmls = dir.listFiles { f -> f.name.startsWith("TEST-") && f.name.endsWith(".xml") } ?: emptyArray()
|
||||
check(xmls.isNotEmpty()) { ":$name produced no TEST-*.xml in $dir - test task likely went NO-SOURCE" }
|
||||
}
|
||||
}
|
||||
verifyTestsRan.configure { dependsOn(verify) }
|
||||
}
|
||||
tasks.check { dependsOn(verifyTestsRan) }
|
||||
|
||||
apply(from = "gradle/angelica-shadow-common.gradle.kts")
|
||||
apply(from = "experimental-surround/surround.root.gradle.kts")
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.file.ArchiveOperations
|
||||
import org.gradle.api.tasks.SourceSetContainer
|
||||
import org.gradle.api.tasks.Sync
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
import org.gradle.api.tasks.testing.Test
|
||||
import org.gradle.kotlin.dsl.get
|
||||
import org.gradle.kotlin.dsl.register
|
||||
import org.gradle.kotlin.dsl.the
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class UnpackLwjgl3Natives : Sync() {
|
||||
@get:Inject
|
||||
abstract val archives: ArchiveOperations
|
||||
}
|
||||
|
||||
// LWJGL extracts jar-packed natives to a shared temp dir with no cross-process safety; plain files it loads in place.
|
||||
fun Project.registerLwjgl3NativesUnpack(): TaskProvider<UnpackLwjgl3Natives> {
|
||||
val jars = the<SourceSetContainer>()["test"].runtimeClasspath.filter {
|
||||
it.name.startsWith("lwjgl") && it.name.contains("-natives-") && !it.name.startsWith("lwjgl-platform-")
|
||||
}
|
||||
return tasks.register<UnpackLwjgl3Natives>("unpackLwjgl3Natives") {
|
||||
val archives = archives
|
||||
from(jars.elements.map { files -> files.map { archives.zipTree(it) } })
|
||||
exclude("META-INF/**")
|
||||
into(layout.buildDirectory.dir("lwjgl3-natives"))
|
||||
}
|
||||
}
|
||||
|
||||
fun Test.loadLwjgl3NativesFrom(unpack: TaskProvider<UnpackLwjgl3Natives>) {
|
||||
classpath = project.files(unpack) + classpath
|
||||
}
|
||||
@@ -204,7 +204,6 @@ afterEvaluate {
|
||||
testImplementation(libs.junit.platform.reporting)
|
||||
testImplementation(libs.junit.platform.launcher)
|
||||
|
||||
testImplementation(libs.archunit.junit5)
|
||||
testImplementation(libs.retrofuturabootstrap) { isTransitive = false }
|
||||
|
||||
glsmTestFixtures(testFixtures(project(":glsm")) as ModuleDependency) { isTransitive = false }
|
||||
|
||||
@@ -48,6 +48,7 @@ repositories {
|
||||
}
|
||||
|
||||
tasks.withType<DowngradeFiles>().configureEach {
|
||||
outputs.cacheIf { true }
|
||||
downgradeTo.set(JavaVersion.VERSION_1_8)
|
||||
multiReleaseOriginal.set(false)
|
||||
multiReleaseVersions.set(emptySet())
|
||||
|
||||
@@ -183,6 +183,7 @@ dependencies {
|
||||
}
|
||||
|
||||
tasks.withType<DowngradeFiles>().configureEach {
|
||||
outputs.cacheIf { true }
|
||||
downgradeTo.set(JavaVersion.VERSION_1_8)
|
||||
multiReleaseOriginal.set(false)
|
||||
multiReleaseVersions.set(emptySet())
|
||||
@@ -302,6 +303,8 @@ val verifyTestsRan by tasks.registering {
|
||||
}
|
||||
tasks.check { dependsOn(verifyTestsRan) }
|
||||
|
||||
val unpackLwjgl3Natives = registerLwjgl3NativesUnpack()
|
||||
|
||||
val spirvTest by tasks.registering(Test::class) {
|
||||
description = "Runs SpirvShaderTranslator* tests against native shaderc + spvc on Java 21."
|
||||
group = "verification"
|
||||
@@ -330,6 +333,7 @@ val spirvTest by tasks.registering(Test::class) {
|
||||
"-Djava.library.path=${extractNatives3.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}",
|
||||
"--enable-native-access=ALL-UNNAMED"
|
||||
)
|
||||
loadLwjgl3NativesFrom(unpackLwjgl3Natives)
|
||||
|
||||
testLogging {
|
||||
showStandardStreams = true
|
||||
|
||||
@@ -1025,7 +1025,7 @@ public class DisplayListManager {
|
||||
}
|
||||
|
||||
final DisplayListCommand[] complexObjects = commandBuffer.getComplexObjects();
|
||||
compiled = new CompiledDisplayList(commandBuffer.finish(), complexObjects, compiledBuffers, indexedBatches);
|
||||
compiled = new CompiledDisplayList(commandBuffer.finish(), complexObjects, compiledBuffers, indexedBatches, GLStateManager.ctx());
|
||||
currentRecorder = null;
|
||||
} catch (Exception e) {
|
||||
GLStateManager.LOGGER.error("Encountered a fatal issue while trying to compile a display list.");
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ImageUnitArray;
|
||||
import com.gtnewhorizons.angelica.glsm.states.SamplerUnitArray;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
@@ -104,6 +105,8 @@ public class GLContextState {
|
||||
public boolean dirtyNormalAttrib;
|
||||
public boolean dirtyTexCoordAttrib;
|
||||
public boolean dirtyLightmapAttrib = true;
|
||||
public long entityIds = InstancedAttribs.DEFAULT_ENTITY_IDS;
|
||||
public boolean dirtyEntityIdsAttrib = true;
|
||||
public boolean unit23TexCoordSetDuringDraw = false;
|
||||
public int maxBoundTextureUnit = 0;
|
||||
public int maxBoundImageUnit = 0;
|
||||
@@ -236,6 +239,8 @@ public class GLContextState {
|
||||
public int boundCopyReadBuffer;
|
||||
public int boundCopyWriteBuffer;
|
||||
public final Int2IntMap boundOtherBuffers = new Int2IntOpenHashMap();
|
||||
public final int[] uniformBufferBindings = new int[GLStateManager.INDEXED_BUFFER_SLOTS];
|
||||
public final int[] storageBufferBindings = new int[GLStateManager.INDEXED_BUFFER_SLOTS];
|
||||
public PixelStoreState pixelUnpackState = PixelStoreState.DEFAULT;
|
||||
public PixelStoreState pixelPackState = PixelStoreState.DEFAULT;
|
||||
public final int[] clientAttribSavedMask = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
@@ -245,7 +250,6 @@ public class GLContextState {
|
||||
public final int[] clientAttribSavedPackBuffer = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public final VAOManager.ClientArrays[] clientAttribSavedArrays = new VAOManager.ClientArrays[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
|
||||
public int clientAttribStackPointer = 0;
|
||||
public float maxPointSize = Float.NaN;
|
||||
public int restoreChangedMask;
|
||||
public long restoreUnitChangedMask;
|
||||
public final BlendState vanillaBlendBefore = new BlendState();
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FfpExtendedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.Instancing;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
@@ -25,6 +26,7 @@ import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GlintColorHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CommandRecorder;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
|
||||
@@ -68,6 +70,7 @@ import com.gtnewhorizons.angelica.glsm.states.ImageUnitBinding;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureBinding;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.LineQuads;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorEntityIds;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
@@ -93,7 +96,6 @@ import org.lwjgl.opengl.Display;
|
||||
import org.lwjgl.opengl.Drawable;
|
||||
import org.lwjgl.opengl.EXTTextureFilterAnisotropic;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL42;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL14;
|
||||
@@ -104,6 +106,8 @@ import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL33;
|
||||
import org.lwjgl.opengl.GL42;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
import org.lwjgl.opengl.GLContext;
|
||||
import org.lwjgl.opengl.KHRDebug;
|
||||
import org.lwjgl.opengl.KHRDebugCallback;
|
||||
@@ -328,6 +332,7 @@ public class GLStateManager {
|
||||
public static final int MAX_PROJECTION_STACK_DEPTH = 4;
|
||||
public static final int MAX_TEXTURE_STACK_DEPTH = 4;
|
||||
public static final int MAX_CLIP_PLANES = 8;
|
||||
public static final int INDEXED_BUFFER_SLOTS = 64;
|
||||
public static final int MAX_TEXTURE_UNITS = RENDER_BACKEND.getInteger(GL20.GL_MAX_TEXTURE_IMAGE_UNITS);
|
||||
public static final int MAX_LEGACY_TEXTURE_UNITS = Math.min(VertexKey.MAX_UNITS, MAX_TEXTURE_UNITS);
|
||||
public static final int MAX_TEXTURE_COORDS = Math.min(VAOManager.TEX_COORD_UNITS, MAX_TEXTURE_UNITS);
|
||||
@@ -394,6 +399,26 @@ public class GLStateManager {
|
||||
}
|
||||
}
|
||||
|
||||
public static void setEntityIds(int entity, int blockEntity, int item) {
|
||||
if (!isMainThread()) return;
|
||||
final GLContextState glCtx = ctx();
|
||||
final long ids = InstancedAttribs.packEntityInfo(entity, blockEntity, item);
|
||||
if (ids == glCtx.entityIds) return;
|
||||
TessellatorEntityIds.onIdsChanged(glCtx.entityIds, ids);
|
||||
glCtx.entityIds = ids;
|
||||
glCtx.dirtyEntityIdsAttrib = true;
|
||||
}
|
||||
|
||||
public static void flushEntityIds(GLContextState glCtx) {
|
||||
if (!glCtx.dirtyEntityIdsAttrib || glCtx.activeProgram == 0) return;
|
||||
final ImmediateExtendedAttribHandler handler = GLSMHooks.immediateExtendedHandler;
|
||||
if (handler == null || !handler.wantsEntityIds()) return;
|
||||
final long ids = glCtx.entityIds;
|
||||
RENDER_BACKEND.vertexAttrib4f(ImmediateExtendedAttribHandler.LOC_ENTITY_IDS,
|
||||
InstancedAttribs.normalizedId(ids, 0), InstancedAttribs.normalizedId(ids, 16), InstancedAttribs.normalizedId(ids, 32), 0.0f);
|
||||
glCtx.dirtyEntityIdsAttrib = false;
|
||||
}
|
||||
|
||||
|
||||
public static int getMaxBoundTextureUnit() {
|
||||
return ctx().maxBoundTextureUnit;
|
||||
@@ -415,6 +440,7 @@ public class GLStateManager {
|
||||
|
||||
// This setting varies depending on driver, so it gets queried at runtime
|
||||
public static int DEFAULT_DRAW_BUFFER = GL11.GL_BACK;
|
||||
private static float maxPointSize;
|
||||
|
||||
private static volatile Thread CurrentThread = MainThread;
|
||||
@Setter @Getter private static boolean runningSplash = true;
|
||||
@@ -517,18 +543,6 @@ public class GLStateManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static float maxPointSize(GLContextState glCtx) {
|
||||
if (Float.isNaN(glCtx.maxPointSize)) {
|
||||
final FloatBuffer range = glCtx.queryScratch;
|
||||
range.clear();
|
||||
RENDER_BACKEND.getFloat(GL11.GL_POINT_SIZE_RANGE, range);
|
||||
final float smooth = range.get(1);
|
||||
RENDER_BACKEND.getFloat(GL12.GL_ALIASED_POINT_SIZE_RANGE, range);
|
||||
glCtx.maxPointSize = Math.max(smooth, range.get(1));
|
||||
}
|
||||
return glCtx.maxPointSize;
|
||||
}
|
||||
|
||||
private static <T extends CowStateStack<?>> T mod(T s) {
|
||||
s.beforeModify();
|
||||
return s;
|
||||
@@ -777,6 +791,13 @@ public class GLStateManager {
|
||||
initCallback.run();
|
||||
}
|
||||
|
||||
final FloatBuffer range = glCtx.queryScratch;
|
||||
range.clear();
|
||||
RENDER_BACKEND.getFloat(GL11.GL_POINT_SIZE_RANGE, range);
|
||||
final float smooth = range.get(1);
|
||||
RENDER_BACKEND.getFloat(GL12.GL_ALIASED_POINT_SIZE_RANGE, range);
|
||||
maxPointSize = Math.max(smooth, range.get(1));
|
||||
|
||||
// Drain any pending GL errors from initialization. In core profile, some legacy queries may generate GL_INVALID_ENUM. The splash thread inherits the
|
||||
// DrawableGL context and its error state, so stale errors here would cause SplashProgress.checkGLError() to fail.
|
||||
int err;
|
||||
@@ -1482,7 +1503,7 @@ public class GLStateManager {
|
||||
case GL11.GL_LINE_WIDTH -> glCtx.lineState.getWidth();
|
||||
case GL11.GL_POINT_SIZE -> glCtx.pointState.getSize();
|
||||
case GL14.GL_POINT_SIZE_MIN -> glCtx.pointState.getMinSize();
|
||||
case GL14.GL_POINT_SIZE_MAX -> Float.isNaN(glCtx.pointState.getMaxSize()) ? maxPointSize(glCtx) : glCtx.pointState.getMaxSize();
|
||||
case GL14.GL_POINT_SIZE_MAX -> Float.isNaN(glCtx.pointState.getMaxSize()) ? maxPointSize : glCtx.pointState.getMaxSize();
|
||||
case GL14.GL_POINT_FADE_THRESHOLD_SIZE -> glCtx.pointState.getFadeThreshold();
|
||||
default -> {
|
||||
final int desc = isCachingEnabled() ? LegacyQueries.desc(pname) : 0;
|
||||
@@ -4825,6 +4846,7 @@ public class GLStateManager {
|
||||
glCtx.dirtyNormalAttrib = true;
|
||||
glCtx.dirtyTexCoordAttrib = true;
|
||||
glCtx.dirtyLightmapAttrib = true;
|
||||
glCtx.dirtyEntityIdsAttrib = true;
|
||||
}
|
||||
|
||||
private static void applyPolygonOffset(GLContextState glCtx) {
|
||||
@@ -7267,6 +7289,10 @@ public class GLStateManager {
|
||||
e.setValue(0);
|
||||
RENDER_BACKEND.bindBuffer(e.getIntKey(), 0);
|
||||
}
|
||||
for (int i = 0; i < INDEXED_BUFFER_SLOTS; i++) {
|
||||
if (glCtx.uniformBufferBindings[i] == buffer) glCtx.uniformBufferBindings[i] = 0;
|
||||
if (glCtx.storageBufferBindings[i] == buffer) glCtx.storageBufferBindings[i] = 0;
|
||||
}
|
||||
writeMappedBuffers.remove(buffer);
|
||||
|
||||
glCtx.vaos.onDeleteBuffer(buffer);
|
||||
@@ -7881,7 +7907,9 @@ public class GLStateManager {
|
||||
public static void glBindBufferBase(int target, int index, int buffer) {
|
||||
final boolean locked = acquireDrawLock();
|
||||
try {
|
||||
trackBufferBinding(ctx(), target, buffer);
|
||||
final GLContextState glCtx = ctx();
|
||||
trackBufferBinding(glCtx, target, buffer);
|
||||
trackIndexedBufferBinding(glCtx, target, index, buffer);
|
||||
RENDER_BACKEND.bindBufferBase(target, index, buffer);
|
||||
} finally {
|
||||
if (locked) releaseDrawLock();
|
||||
@@ -7891,13 +7919,35 @@ public class GLStateManager {
|
||||
public static void glBindBufferRange(int target, int index, int buffer, long offset, long size) {
|
||||
final boolean locked = acquireDrawLock();
|
||||
try {
|
||||
trackBufferBinding(ctx(), target, buffer);
|
||||
final GLContextState glCtx = ctx();
|
||||
trackBufferBinding(glCtx, target, buffer);
|
||||
trackIndexedBufferBinding(glCtx, target, index, buffer);
|
||||
RENDER_BACKEND.bindBufferRange(target, index, buffer, offset, size);
|
||||
} finally {
|
||||
if (locked) releaseDrawLock();
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] indexedBufferBindings(GLContextState glCtx, int target, int index) {
|
||||
if (index < 0 || index >= INDEXED_BUFFER_SLOTS) return null;
|
||||
if (target == GL31.GL_UNIFORM_BUFFER) return glCtx.uniformBufferBindings;
|
||||
if (target == GL43.GL_SHADER_STORAGE_BUFFER) return glCtx.storageBufferBindings;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void trackIndexedBufferBinding(GLContextState glCtx, int target, int index, int buffer) {
|
||||
final int[] bindings = indexedBufferBindings(glCtx, target, index);
|
||||
if (bindings != null) bindings[index] = buffer;
|
||||
}
|
||||
|
||||
public static int getIndexedBufferBinding(int target, int index) {
|
||||
if (isCachingEnabled()) {
|
||||
final int[] bindings = indexedBufferBindings(ctx(), target, index);
|
||||
if (bindings != null) return bindings[index];
|
||||
}
|
||||
return RENDER_BACKEND.getIndexedBufferBinding(target, index);
|
||||
}
|
||||
|
||||
public static int glGetUniformBlockIndex(int program, CharSequence name) { return RENDER_BACKEND.getUniformBlockIndex(program, name); }
|
||||
public static void glUniformBlockBinding(int program, int blockIndex, int binding) { RENDER_BACKEND.uniformBlockBinding(program, blockIndex, binding); }
|
||||
public static void glBindFragDataLocation(int program, int colorNumber, CharSequence name) { RENDER_BACKEND.bindFragDataLocation(program, colorNumber, name); }
|
||||
@@ -8656,7 +8706,9 @@ public class GLStateManager {
|
||||
return ctx().imageUnits.get(unit);
|
||||
}
|
||||
|
||||
private static int imageBindingOrBackend(int pname, int index) {
|
||||
private static int indexedIntegerOrBackend(int pname, int index) {
|
||||
if (pname == GL31.GL_UNIFORM_BUFFER_BINDING) return getIndexedBufferBinding(GL31.GL_UNIFORM_BUFFER, index);
|
||||
if (pname == GL43.GL_SHADER_STORAGE_BUFFER_BINDING) return getIndexedBufferBinding(GL43.GL_SHADER_STORAGE_BUFFER, index);
|
||||
final ImageUnitBinding binding = !isCachingEnabled() ? null : ctx().imageUnits.get(index);
|
||||
if (binding == null) return RENDER_BACKEND.getIntegerIndexed(pname, index);
|
||||
return switch (pname) {
|
||||
@@ -8671,11 +8723,11 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static int glGetInteger(int pname, int index) {
|
||||
return imageBindingOrBackend(pname, index);
|
||||
return indexedIntegerOrBackend(pname, index);
|
||||
}
|
||||
|
||||
public static int glGetIntegeri(int pname, int index) {
|
||||
return imageBindingOrBackend(pname, index);
|
||||
return indexedIntegerOrBackend(pname, index);
|
||||
}
|
||||
|
||||
public static void glGetIntegeri(int pname, int index, IntBuffer params) {
|
||||
@@ -8683,11 +8735,11 @@ public class GLStateManager {
|
||||
}
|
||||
|
||||
public static void glGetIntegeri_v(int pname, int index, IntBuffer params) {
|
||||
params.put(params.position(), imageBindingOrBackend(pname, index));
|
||||
params.put(params.position(), indexedIntegerOrBackend(pname, index));
|
||||
}
|
||||
|
||||
public static void glGetBooleani_v(int pname, int index, ByteBuffer params) {
|
||||
params.put(params.position(), (byte) (imageBindingOrBackend(pname, index) != 0 ? GL11.GL_TRUE : GL11.GL_FALSE));
|
||||
params.put(params.position(), (byte) (indexedIntegerOrBackend(pname, index) != 0 ? GL11.GL_TRUE : GL11.GL_FALSE));
|
||||
}
|
||||
|
||||
public static void glGetBooleani(int pname, int index, ByteBuffer params) {
|
||||
|
||||
@@ -556,7 +556,7 @@ public class RenderSystem {
|
||||
}
|
||||
|
||||
public static int getIndexedBufferBinding(int target, int index) {
|
||||
return RENDER_BACKEND.getIndexedBufferBinding(target, index);
|
||||
return GLStateManager.getIndexedBufferBinding(target, index);
|
||||
}
|
||||
|
||||
public static void bindBufferBase(int target, int index, int buffer) {
|
||||
|
||||
@@ -21,7 +21,11 @@ public final class BackendOptions {
|
||||
}
|
||||
|
||||
public static String sdlGpuDriver() {
|
||||
return SystemProperties.SDL_GPU_DRIVER_OVERRIDE != null ? SystemProperties.SDL_GPU_DRIVER_OVERRIDE : configDriver;
|
||||
return sdlGpuDriver(configDriver);
|
||||
}
|
||||
|
||||
public static String sdlGpuDriver(String driver) {
|
||||
return SystemProperties.SDL_GPU_DRIVER_OVERRIDE != null ? SystemProperties.SDL_GPU_DRIVER_OVERRIDE : driver;
|
||||
}
|
||||
|
||||
public static boolean configSdlGpu() {
|
||||
|
||||
@@ -1,55 +1,96 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
|
||||
public final class BackendStartGuard {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica/BackendStartGuard");
|
||||
|
||||
private static File pending;
|
||||
static final int STRIKE_LIMIT = 3;
|
||||
|
||||
private static File marker;
|
||||
private static File verified;
|
||||
private static String key;
|
||||
private static int strikes;
|
||||
private static boolean pending;
|
||||
private static boolean tripped;
|
||||
|
||||
private BackendStartGuard() {}
|
||||
|
||||
public static boolean begin(File marker, boolean sdlGpuConfigured, boolean flagForced) {
|
||||
pending = null;
|
||||
tripped = false;
|
||||
if (!sdlGpuConfigured || flagForced) {
|
||||
public static boolean begin(File dir, boolean sdlGpuConfigured, boolean flagForced, int requirementsRevision, String requestedDriver) {
|
||||
marker = new File(dir, "sdlgpu-starting");
|
||||
verified = new File(dir, "sdlgpu-verified");
|
||||
key = requirementsRevision + "/" + requestedDriver;
|
||||
pending = tripped = false;
|
||||
if (flagForced) return false;
|
||||
if (!sdlGpuConfigured) {
|
||||
delete(marker);
|
||||
return false;
|
||||
}
|
||||
if (marker.isFile()) {
|
||||
if (key.equals(read(verified))) return false;
|
||||
final String count = read(marker);
|
||||
final Integer parsed = count == null ? null : Ints.tryParse(count.trim());
|
||||
strikes = parsed == null ? 0 : parsed;
|
||||
if (strikes >= STRIKE_LIMIT) {
|
||||
LOGGER.warn("SDL GPU failed to bring up the device {} times in a row; resetting the render backend to OpenGL", strikes);
|
||||
delete(marker);
|
||||
tripped = true;
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
final File dir = marker.getParentFile();
|
||||
if (dir != null && !dir.isDirectory() && !dir.mkdirs()) throw new IOException("cannot create " + dir);
|
||||
if (marker.createNewFile()) pending = marker;
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not write {}: {}", marker, e.toString());
|
||||
if (strikes == STRIKE_LIMIT - 1) {
|
||||
LOGGER.warn("SDL GPU failed to bring up the device {} times in a row; one more and the render backend switches to OpenGL", strikes);
|
||||
} else if (strikes > 0) {
|
||||
LOGGER.info("The last start ended while bringing up the SDL GPU device");
|
||||
}
|
||||
pending = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void finish() {
|
||||
if (pending == null) return;
|
||||
delete(pending);
|
||||
pending = null;
|
||||
public static void arm() {
|
||||
if (pending) write(marker, Integer.toString(strikes + 1));
|
||||
}
|
||||
|
||||
public static void disarm() {
|
||||
if (pending) delete(marker);
|
||||
}
|
||||
|
||||
public static void latch() {
|
||||
if (!pending) return;
|
||||
pending = false;
|
||||
write(verified, key);
|
||||
LOGGER.info("SDL GPU booted; the startup guard stays off until the requirements revision or driver changes");
|
||||
}
|
||||
|
||||
public static boolean tripped() {
|
||||
return tripped;
|
||||
}
|
||||
|
||||
private static void delete(File marker) {
|
||||
if (marker.isFile() && !marker.delete()) {
|
||||
LOGGER.warn("Could not delete {}", marker);
|
||||
private static String read(File file) {
|
||||
try {
|
||||
return file.isFile() ? new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8) : null;
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not read {}: {}", file, e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void write(File file, String content) {
|
||||
try {
|
||||
Files.createDirectories(file.toPath().getParent());
|
||||
Files.write(file.toPath(), content.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (IOException e) {
|
||||
LOGGER.warn("Could not write {}: {}", file, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void delete(File file) {
|
||||
if (file.isFile() && !file.delete()) LOGGER.warn("Could not delete {}", file);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,8 @@ public final class InstancedAttribs {
|
||||
public static final int OFFSET_LIGHTMAP = HEAD_SIZE;
|
||||
public static final int STRIDE = 72;
|
||||
|
||||
public static final long DEFAULT_ENTITY_IDS = packEntityInfo(-1, 0, -1);
|
||||
|
||||
public static void writeHead(long ptr, float[] colMajor, int off, int colorABGR, int overlayABGR, long entityInfo) {
|
||||
writeRows(ptr, colMajor, off);
|
||||
writeTail(ptr, colorABGR, overlayABGR, entityInfo);
|
||||
@@ -60,13 +62,21 @@ public final class InstancedAttribs {
|
||||
return id == 0xFFFF ? -1 : id;
|
||||
}
|
||||
|
||||
public static float normalizedId(long info, int shift) {
|
||||
return ((info >>> shift) & 0xFFFF) / 65535.0f;
|
||||
}
|
||||
|
||||
public static void pointHead(long base, int stride) {
|
||||
for (int r = 0; r < 3; r++) {
|
||||
GLStateManager.glVertexAttribPointer(LOC_ROW0 + r, 4, GL11.GL_FLOAT, false, stride, base + OFFSET_ROWS + r * 16L);
|
||||
}
|
||||
GLStateManager.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_COLOR);
|
||||
GLStateManager.glVertexAttribPointer(LOC_OVERLAY, 4, GL11.GL_UNSIGNED_BYTE, true, stride, base + OFFSET_OVERLAY);
|
||||
GLStateManager.glVertexAttribIPointer(LOC_ENTITY, 4, GL11.GL_UNSIGNED_SHORT, stride, base + OFFSET_ENTITY);
|
||||
pointEntityIds(LOC_ENTITY, stride, base + OFFSET_ENTITY);
|
||||
}
|
||||
|
||||
public static void pointEntityIds(int location, int stride, long offset) {
|
||||
GLStateManager.glVertexAttribPointer(location, 4, GL11.GL_UNSIGNED_SHORT, true, stride, offset);
|
||||
}
|
||||
|
||||
public static void pointTemplate(long base) {
|
||||
|
||||
@@ -139,6 +139,7 @@ public final class ShaderManager {
|
||||
GLSMHooks.resolvePendingProgram();
|
||||
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
|
||||
if (bh != null) bh.flushDeferredBlend();
|
||||
GLStateManager.flushEntityIds(glCtx);
|
||||
|
||||
// Handle FFP & Iris uniforms
|
||||
final int currentProgramId = glCtx.activeProgram;
|
||||
|
||||
+10
@@ -3,15 +3,18 @@ package com.gtnewhorizons.angelica.glsm.hooks;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public interface ImmediateExtendedAttribHandler {
|
||||
|
||||
int EXT_STRIDE = 12;
|
||||
int ENTITY_IDS_STRIDE = 8;
|
||||
int LOC_MC_ENTITY = 11;
|
||||
int LOC_MID_TEX = 12;
|
||||
int LOC_TANGENT = 13;
|
||||
int LOC_MIDBLOCK = 14;
|
||||
int LOC_ENTITY_IDS = 15;
|
||||
|
||||
int RAW_NORMAL_INDEX = 6;
|
||||
|
||||
@@ -19,6 +22,8 @@ public interface ImmediateExtendedAttribHandler {
|
||||
|
||||
boolean wantsTangent();
|
||||
|
||||
boolean wantsEntityIds();
|
||||
|
||||
boolean wantsExtendedCapture();
|
||||
|
||||
boolean wantsFaceNormals();
|
||||
@@ -65,4 +70,9 @@ public interface ImmediateExtendedAttribHandler {
|
||||
GLStateManager.glEnableVertexAttribArray(LOC_TANGENT);
|
||||
GLStateManager.glVertexAttribPointer(LOC_TANGENT, 4, GL11.GL_BYTE, true, stride, baseOffset + 8);
|
||||
}
|
||||
|
||||
static void setupEntityIdsPointer(long offset, int stride) {
|
||||
GLStateManager.glEnableVertexAttribArray(LOC_ENTITY_IDS);
|
||||
InstancedAttribs.pointEntityIds(LOC_ENTITY_IDS, stride, offset);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -525,8 +525,9 @@ public final class CommandBufferExecutor {
|
||||
ptr += 4;
|
||||
}
|
||||
case GLCommand.BIND_VAO -> {
|
||||
GLStateManager.glBindVertexArray(memGetInt(ptr));
|
||||
final int vao = memGetInt(ptr);
|
||||
ptr += 4;
|
||||
if (compiledDisplayList.replaysVertexArrayBind(vao)) GLStateManager.glBindVertexArray(vao);
|
||||
}
|
||||
|
||||
// === Complex object reference ===
|
||||
|
||||
+10
-2
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
|
||||
@@ -30,12 +31,13 @@ public final class CompiledDisplayList {
|
||||
* Empty display list singleton. Per OpenGL spec, an empty list is still valid.
|
||||
* This instance does nothing when rendered and is never deleted (shared singleton).
|
||||
*/
|
||||
public static final CompiledDisplayList EMPTY = new CompiledDisplayList(null, null, null, Collections.emptyList());
|
||||
public static final CompiledDisplayList EMPTY = new CompiledDisplayList(null, null, null, Collections.emptyList(), null);
|
||||
|
||||
private final ByteBuffer commandBuffer; // Off-heap command storage, must be freed
|
||||
private final DisplayListCommand[] complexObjects; // Complex commands (TexImage2D, etc.)
|
||||
private final DisplayListVBO ownedVbos; // GPU resources referenced by index
|
||||
private final List<IndexedDrawBatch> indexedBatches; // Shared VAO/VBO/EBO triples per layout group
|
||||
private GLContextState compileCtx;
|
||||
private Int2IntMap replayPrograms;
|
||||
private long programLifetimeGeneration = -1;
|
||||
|
||||
@@ -55,11 +57,16 @@ public final class CompiledDisplayList {
|
||||
return valid ? program : 0;
|
||||
}
|
||||
|
||||
public CompiledDisplayList(ByteBuffer commandBuffer, DisplayListCommand[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches) {
|
||||
public CompiledDisplayList(ByteBuffer commandBuffer, DisplayListCommand[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches, GLContextState compileCtx) {
|
||||
this.commandBuffer = commandBuffer;
|
||||
this.complexObjects = complexObjects;
|
||||
this.ownedVbos = ownedVbos;
|
||||
this.indexedBatches = indexedBatches;
|
||||
this.compileCtx = compileCtx;
|
||||
}
|
||||
|
||||
boolean replaysVertexArrayBind(int vao) {
|
||||
return vao == 0 || compileCtx == GLStateManager.ctx();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,6 +108,7 @@ public final class CompiledDisplayList {
|
||||
if (commandBuffer != null) {
|
||||
memFree(commandBuffer);
|
||||
}
|
||||
compileCtx = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-7
@@ -3,21 +3,20 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
|
||||
public final class BatchedIndexedDrawCmd implements DisplayListCommand {
|
||||
private int sharedVAO;
|
||||
private IndexedDrawBatch batch;
|
||||
private int drawMode;
|
||||
private int indexCount;
|
||||
private int indexType;
|
||||
private long indexByteOffset;
|
||||
|
||||
public void fill(int sharedVAO, int drawMode, int indexCount, int indexType, long indexByteOffset) {
|
||||
this.sharedVAO = sharedVAO;
|
||||
public void fill(IndexedDrawBatch batch, int drawMode, int indexCount, int indexType, long indexByteOffset) {
|
||||
this.batch = batch;
|
||||
this.drawMode = drawMode;
|
||||
this.indexCount = indexCount;
|
||||
this.indexType = indexType;
|
||||
this.indexByteOffset = indexByteOffset;
|
||||
}
|
||||
|
||||
public int getSharedVAO() { return sharedVAO; }
|
||||
public int getDrawMode() { return drawMode; }
|
||||
public int getIndexCount() { return indexCount; }
|
||||
public int getIndexType() { return indexType; }
|
||||
@@ -25,13 +24,13 @@ public final class BatchedIndexedDrawCmd implements DisplayListCommand {
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
if (sharedVAO == 0) return;
|
||||
GLStateManager.glBindVertexArray(sharedVAO);
|
||||
if (batch == null) return;
|
||||
batch.bind();
|
||||
GLStateManager.glDrawElements(drawMode, indexCount, indexType, indexByteOffset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BatchedIndexedDrawCmd(vao=" + sharedVAO + ", mode=" + drawMode + ", count=" + indexCount + ", type=" + indexType + ", offset=" + indexByteOffset + ")";
|
||||
return "BatchedIndexedDrawCmd(batch=" + batch + ", mode=" + drawMode + ", count=" + indexCount + ", type=" + indexType + ", offset=" + indexByteOffset + ")";
|
||||
}
|
||||
}
|
||||
+33
-7
@@ -1,34 +1,60 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
public final class IndexedDrawBatch {
|
||||
private int sharedVAO;
|
||||
private final AttribLayoutKey layout;
|
||||
private int sharedVBO;
|
||||
private int sharedEBO;
|
||||
private int vao;
|
||||
private GLContextState vaoCtx;
|
||||
private boolean deleted;
|
||||
|
||||
public IndexedDrawBatch(int sharedVAO, int sharedVBO, int sharedEBO) {
|
||||
this.sharedVAO = sharedVAO;
|
||||
public IndexedDrawBatch(AttribLayoutKey layout, int sharedVBO, int sharedEBO) {
|
||||
this.layout = layout;
|
||||
this.sharedVBO = sharedVBO;
|
||||
this.sharedEBO = sharedEBO;
|
||||
}
|
||||
|
||||
public int getSharedVAO() { return sharedVAO; }
|
||||
public int getSharedVBO() { return sharedVBO; }
|
||||
public int getSharedEBO() { return sharedEBO; }
|
||||
|
||||
public void bind() {
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
if (vaoCtx == glCtx) {
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
return;
|
||||
}
|
||||
final int prevVBO = GLStateManager.getBoundVBO();
|
||||
final int v = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(v);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, sharedVBO);
|
||||
final int stride = layout.stride();
|
||||
for (int i = 0; i < layout.locations.length; i++) {
|
||||
final int loc = layout.locations[i];
|
||||
GLStateManager.glVertexAttribPointer(loc, layout.sizes[i], layout.types[i], layout.normalized[i], stride, layout.offset(i));
|
||||
GLStateManager.glEnableVertexAttribArray(loc);
|
||||
}
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEBO);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
|
||||
vao = v;
|
||||
vaoCtx = glCtx;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (deleted) return;
|
||||
deleted = true;
|
||||
if (sharedVAO != 0) GLStateManager.glDeleteVertexArrays(sharedVAO);
|
||||
if (vaoCtx == GLStateManager.ctx()) GLStateManager.glDeleteVertexArrays(vao);
|
||||
if (sharedVBO != 0) GLStateManager.glDeleteBuffers(sharedVBO);
|
||||
if (sharedEBO != 0) GLStateManager.glDeleteBuffers(sharedEBO);
|
||||
sharedVAO = sharedVBO = sharedEBO = 0;
|
||||
vao = sharedVBO = sharedEBO = 0;
|
||||
vaoCtx = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IndexedDrawBatch(vao=" + sharedVAO + ", vbo=" + sharedVBO + ", ebo=" + sharedEBO + ")";
|
||||
return "IndexedDrawBatch(vao=" + vao + ", vbo=" + sharedVBO + ", ebo=" + sharedEBO + ")";
|
||||
}
|
||||
}
|
||||
+2
-19
@@ -35,7 +35,6 @@ public final class IndexedDrawBatchBuilder {
|
||||
final List<IndexedDrawBatch> out = new ArrayList<>();
|
||||
if (captures.isEmpty()) return out;
|
||||
|
||||
final int prevVAO = GLStateManager.getBoundVAO();
|
||||
final int prevVBO = GLStateManager.getBoundVBO();
|
||||
final int prevEBO = GLStateManager.getBoundEBO();
|
||||
|
||||
@@ -58,7 +57,6 @@ public final class IndexedDrawBatchBuilder {
|
||||
if (!success) {
|
||||
for (GroupResult r : results) r.batch.delete();
|
||||
}
|
||||
GLStateManager.glBindVertexArray(prevVAO);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
|
||||
}
|
||||
@@ -69,9 +67,8 @@ public final class IndexedDrawBatchBuilder {
|
||||
private record GroupResult(IndexedDrawBatch batch, List<IndexedDrawCapture> captures,
|
||||
int[] drawModes, int[] indexCounts, long[] indexOffsets) {
|
||||
void commit() {
|
||||
final int sharedVAO = batch.getSharedVAO();
|
||||
for (int i = 0; i < captures.size(); i++) {
|
||||
captures.get(i).placeholder.fill(sharedVAO, drawModes[i], indexCounts[i], GL11.GL_UNSIGNED_INT, indexOffsets[i]);
|
||||
captures.get(i).placeholder.fill(batch, drawModes[i], indexCounts[i], GL11.GL_UNSIGNED_INT, indexOffsets[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +84,6 @@ public final class IndexedDrawBatchBuilder {
|
||||
throw new IllegalStateException("batch exceeds 2GiB — split needed");
|
||||
}
|
||||
|
||||
int sharedVAO = 0;
|
||||
int sharedVBO = 0;
|
||||
int sharedEBO = 0;
|
||||
ByteBuffer eboData = null;
|
||||
@@ -99,12 +95,10 @@ public final class IndexedDrawBatchBuilder {
|
||||
final long[] indexOffsets = new long[n];
|
||||
|
||||
try {
|
||||
sharedVAO = GLStateManager.glGenVertexArrays();
|
||||
sharedVBO = GLStateManager.glGenBuffers();
|
||||
sharedEBO = GLStateManager.glGenBuffers();
|
||||
eboData = MemoryUtilities.memAlloc((int) totalIndexBytes);
|
||||
|
||||
GLStateManager.glBindVertexArray(sharedVAO);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, sharedVBO);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, totalVertexBytes, GL15.GL_STATIC_DRAW);
|
||||
|
||||
@@ -146,23 +140,12 @@ public final class IndexedDrawBatchBuilder {
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEBO);
|
||||
GLStateManager.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboData, GL15.GL_STATIC_DRAW);
|
||||
|
||||
final AttribLayoutKey layout = group.get(0).layoutKey;
|
||||
final int stride = layout.stride();
|
||||
for (int i = 0; i < layout.locations.length; i++) {
|
||||
final int loc = layout.locations[i];
|
||||
final int size = layout.sizes[i];
|
||||
final int type = layout.types[i];
|
||||
GLStateManager.glVertexAttribPointer(loc, size, type, layout.normalized[i], stride, layout.offset(i));
|
||||
GLStateManager.glEnableVertexAttribArray(loc);
|
||||
}
|
||||
|
||||
success = true;
|
||||
return new GroupResult(new IndexedDrawBatch(sharedVAO, sharedVBO, sharedEBO),
|
||||
return new GroupResult(new IndexedDrawBatch(group.get(0).layoutKey, sharedVBO, sharedEBO),
|
||||
group, drawModes, indexCounts, indexOffsets);
|
||||
} finally {
|
||||
if (eboData != null) MemoryUtilities.memFree(eboData);
|
||||
if (!success) {
|
||||
if (sharedVAO != 0) GLStateManager.glDeleteVertexArrays(sharedVAO);
|
||||
if (sharedVBO != 0) GLStateManager.glDeleteBuffers(sharedVBO);
|
||||
if (sharedEBO != 0) GLStateManager.glDeleteBuffers(sharedEBO);
|
||||
}
|
||||
|
||||
+25
-6
@@ -34,6 +34,17 @@ public final class GlslVulkanPreprocess {
|
||||
"const_cast", "dynamic_cast", "reinterpret_cast", "static_cast", "export",
|
||||
"and", "or", "xor", "bitand", "bitor", "compl", "and_eq", "or_eq", "xor_eq", "not_eq");
|
||||
|
||||
private static final Object2ObjectLinkedOpenHashMap<String, String> IDENTIFIER_RENAMES = identifierRenames();
|
||||
|
||||
private static Object2ObjectLinkedOpenHashMap<String, String> identifierRenames() {
|
||||
final Object2ObjectLinkedOpenHashMap<String, String> renames = new Object2ObjectLinkedOpenHashMap<>();
|
||||
renames.put("sampler", SAMPLER_RENAMED);
|
||||
renames.put("gl_VertexID", "gl_VertexIndex");
|
||||
renames.put("gl_InstanceID", "gl_InstanceIndex");
|
||||
for (String reserved : TARGET_RESERVED) renames.put(reserved, RESERVED_RENAMED_PREFIX + reserved);
|
||||
return renames;
|
||||
}
|
||||
|
||||
public static final String SAMPLERLESS_EXTENSION = "#extension GL_EXT_samplerless_texture_functions : require";
|
||||
|
||||
private static final int MAX_VS_INPUT_LOCATIONS = 16;
|
||||
@@ -191,14 +202,13 @@ public final class GlslVulkanPreprocess {
|
||||
rewriteReadOnlyImageCalls(index.callsTo("imageSize"), "textureSize", readOnlyImages, edits);
|
||||
}
|
||||
if (needsSamplerless) {
|
||||
final int versionEnd = index.directives().versionEnd();
|
||||
if (versionEnd >= 0) edits.add(new Edit(versionEnd, versionEnd - 1, "\n" + SAMPLERLESS_EXTENSION));
|
||||
addSamplerlessExtension(index, edits);
|
||||
}
|
||||
|
||||
rename(index, "sampler", SAMPLER_RENAMED, edits);
|
||||
rename(index, "gl_VertexID", "gl_VertexIndex", edits);
|
||||
rename(index, "gl_InstanceID", "gl_InstanceIndex", edits);
|
||||
for (String reserved : TARGET_RESERVED) rename(index, reserved, RESERVED_RENAMED_PREFIX + reserved, edits);
|
||||
for (var it = IDENTIFIER_RENAMES.object2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
|
||||
final var rename = it.next();
|
||||
rename(index, rename.getKey(), rename.getValue(), edits);
|
||||
}
|
||||
|
||||
if (maxExplicitLoc >= 0 && !unlocatedVsInputStarts.isEmpty()) {
|
||||
int next = maxExplicitLoc + 1;
|
||||
@@ -216,6 +226,15 @@ public final class GlslVulkanPreprocess {
|
||||
return new Metadata(bools, explicitInputs, needsSamplerless);
|
||||
}
|
||||
|
||||
public static boolean renamesIdentifier(String name) {
|
||||
return IDENTIFIER_RENAMES.containsKey(name);
|
||||
}
|
||||
|
||||
public static void addSamplerlessExtension(ShaderIndex index, List<Edit> edits) {
|
||||
final int versionEnd = index.directives().versionEnd();
|
||||
if (versionEnd >= 0) edits.add(new Edit(versionEnd, versionEnd - 1, "\n" + SAMPLERLESS_EXTENSION));
|
||||
}
|
||||
|
||||
private static void rename(ShaderIndex index, String name, String replacement, List<Edit> edits) {
|
||||
for (Token tok : index.occurrences(name)) edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), replacement));
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ import java.util.Set;
|
||||
*/
|
||||
public final class ShaderIndex {
|
||||
|
||||
public record Directives(Set<String> identifiers, boolean hasConditionalOrMacro, int versionEnd) {
|
||||
private static final Directives NONE = new Directives(Set.of(), false, -1);
|
||||
public record Directives(Set<String> identifiers, Set<String> definedMacros, boolean hasConditionalOrMacro, int versionEnd) {
|
||||
private static final Directives NONE = new Directives(Set.of(), Set.of(), false, -1);
|
||||
}
|
||||
|
||||
private final GLSLParser.Translation_unitContext root;
|
||||
@@ -83,6 +83,7 @@ public final class ShaderIndex {
|
||||
if (source.indexOf('#') < 0) return Directives.NONE;
|
||||
final GLSLLexer lexer = GlslTransformUtils.quietLexer(source);
|
||||
final Set<String> names = new HashSet<>();
|
||||
final Set<String> macros = new HashSet<>();
|
||||
boolean conditionalOrMacro = false;
|
||||
int versionEnd = -1;
|
||||
boolean inVersion = false;
|
||||
@@ -100,11 +101,17 @@ public final class ShaderIndex {
|
||||
inVersion = false;
|
||||
switch (type) {
|
||||
case GLSLLexer.PROGRAM_TEXT, GLSLLexer.MACRO_TEXT, GLSLLexer.CONSTANT_EXPRESSION -> collectIdentifiers(token.getText(), names);
|
||||
case GLSLLexer.MACRO_NAME -> macros.add(leadingIdentifier(token.getText()));
|
||||
case GLSLLexer.IF_DIRECTIVE, GLSLLexer.IFDEF_DIRECTIVE, GLSLLexer.IFNDEF_DIRECTIVE, GLSLLexer.DEFINE_DIRECTIVE -> conditionalOrMacro = true;
|
||||
default -> { }
|
||||
}
|
||||
}
|
||||
return new Directives(names, conditionalOrMacro, versionEnd);
|
||||
return new Directives(names, macros, conditionalOrMacro, versionEnd);
|
||||
}
|
||||
|
||||
private static String leadingIdentifier(String text) {
|
||||
final int args = text.indexOf('(');
|
||||
return args < 0 ? text : text.substring(0, args);
|
||||
}
|
||||
|
||||
private static void collectIdentifiers(String text, Set<String> out) {
|
||||
|
||||
@@ -5,12 +5,25 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
public final class StreamingVaos {
|
||||
private static final int FORMAT_COUNT = VertexFlags.BITSET_SIZE;
|
||||
|
||||
static final int MID_TEX_AND_TANGENT = 1;
|
||||
static final int ENTITY_IDS = 2;
|
||||
private static final int EXTENDED_LAYOUTS = MID_TEX_AND_TANGENT | ENTITY_IDS;
|
||||
|
||||
final int[] persistentVAOs = new int[FORMAT_COUNT];
|
||||
final int[] orphanVAOs = new int[FORMAT_COUNT];
|
||||
|
||||
final int[] extendedPersistentVAOs = new int[FORMAT_COUNT];
|
||||
final int[] extendedOrphanVAOs = new int[FORMAT_COUNT];
|
||||
// An extended draw always carries at least one layout, so row 0 is layout 1
|
||||
private final int[][] extendedPersistentVAOs = new int[EXTENDED_LAYOUTS][FORMAT_COUNT];
|
||||
private final int[][] extendedOrphanVAOs = new int[EXTENDED_LAYOUTS][FORMAT_COUNT];
|
||||
|
||||
final int[] lineQuadPersistentVAOs = new int[FORMAT_COUNT];
|
||||
final int[] lineQuadOrphanVAOs = new int[FORMAT_COUNT];
|
||||
|
||||
int[] extendedPersistent(int layout) {
|
||||
return extendedPersistentVAOs[layout - 1];
|
||||
}
|
||||
|
||||
int[] extendedOrphan(int layout) {
|
||||
return extendedOrphanVAOs[layout - 1];
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package com.gtnewhorizons.angelica.glsm.streaming;
|
||||
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutLong;
|
||||
|
||||
/**
|
||||
* Entity, block entity, and item ids of the vertices in the main Tessellator batch.
|
||||
*/
|
||||
public final class TessellatorEntityIds {
|
||||
|
||||
private static Tessellator owner;
|
||||
private static int runCount;
|
||||
private static int[] runStarts = new int[8];
|
||||
private static long[] runIds = new long[8];
|
||||
|
||||
private TessellatorEntityIds() {}
|
||||
|
||||
public static void onIdsChanged(long previous, long next) {
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
if (tess == null || !tess.isDrawing || tess.vertexCount == 0) return;
|
||||
final int start = tess.vertexCount;
|
||||
if (owner != tess) {
|
||||
owner = tess;
|
||||
runCount = 0;
|
||||
}
|
||||
if (runCount == 0) addRun(0, previous);
|
||||
if (runStarts[runCount - 1] == start) {
|
||||
runIds[runCount - 1] = next;
|
||||
if (runCount > 1 && runIds[runCount - 2] == next) runCount--;
|
||||
} else {
|
||||
addRun(start, next);
|
||||
}
|
||||
}
|
||||
|
||||
public static void onReset(Tessellator tess) {
|
||||
if (tess != owner) return;
|
||||
owner = null;
|
||||
runCount = 0;
|
||||
}
|
||||
|
||||
public static boolean hasIdChanges(Tessellator tess) {
|
||||
return tess == owner && runCount > 1;
|
||||
}
|
||||
|
||||
static int runsFor(Tessellator tess, int vertexCount) {
|
||||
if (tess != owner) return 0;
|
||||
int runs = runCount;
|
||||
while (runs > 0 && runStarts[runs - 1] >= vertexCount) runs--;
|
||||
return runs;
|
||||
}
|
||||
|
||||
static long firstRunIds() {
|
||||
return runIds[0];
|
||||
}
|
||||
|
||||
static void write(long dst, int stride, int vertexCount, int runs) {
|
||||
for (int run = 0; run < runs; run++) {
|
||||
final int end = run + 1 < runs ? runStarts[run + 1] : vertexCount;
|
||||
final long ids = runIds[run];
|
||||
for (int vertex = runStarts[run]; vertex < end; vertex++) {
|
||||
memPutLong(dst + (long) vertex * stride, ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void addRun(int start, long ids) {
|
||||
if (runCount == runStarts.length) {
|
||||
runStarts = Arrays.copyOf(runStarts, runCount * 2);
|
||||
runIds = Arrays.copyOf(runIds, runCount * 2);
|
||||
}
|
||||
runStarts[runCount] = start;
|
||||
runIds[runCount] = ids;
|
||||
runCount++;
|
||||
}
|
||||
}
|
||||
+117
-75
@@ -109,53 +109,7 @@ public class TessellatorStreamingDrawer {
|
||||
if (GLSMConfig.expandVertexFormats) {
|
||||
effectiveFlags = VertexFlags.COLOR_BIT | VertexFlags.TEXTURE_BIT | VertexFlags.NORMAL_BIT | VertexFlags.BRIGHTNESS_BIT;
|
||||
if (effectiveFlags != flags) {
|
||||
final int[] rawBuffer = tess.rawBuffer;
|
||||
final int defaultColor;
|
||||
if (!tess.hasColor) {
|
||||
final var c = GLStateManager.getColor();
|
||||
defaultColor = ((int)(clamp(0f, 1f, c.getAlpha()) * 255) << 24) | ((int)(clamp(0f, 1f, c.getBlue()) * 255) << 16)
|
||||
| ((int)(clamp(0f, 1f, c.getGreen()) * 255) << 8) | (int)(clamp(0f, 1f, c.getRed()) * 255);
|
||||
} else {
|
||||
defaultColor = 0;
|
||||
}
|
||||
|
||||
final int defaultBrightness;
|
||||
if (!tess.hasBrightness) {
|
||||
defaultBrightness = GLSMConfig.packedLastBrightness();
|
||||
} else {
|
||||
defaultBrightness = 0;
|
||||
}
|
||||
|
||||
final ImmediateExtendedAttribHandler normalHandler = GLSMHooks.immediateExtendedHandler;
|
||||
final boolean faceNormals = !tess.hasNormals && normalHandler != null
|
||||
&& ImmediateExtendedAttribHandler.extPrimVerts(tess.drawMode, vertexCount) == 4 && normalHandler.wantsFaceNormals();
|
||||
final boolean fillNormal = !tess.hasNormals && !faceNormals;
|
||||
|
||||
final int defaultNormal;
|
||||
if (fillNormal) {
|
||||
final var n = ShaderManager.getCurrentNormal();
|
||||
defaultNormal = ((int)(clamp(-1f, 1f, n.z) * 127) << 16) | (((int)(clamp(-1f, 1f, n.y) * 127) & 0xFF) << 8) | ((int)(clamp(-1f, 1f, n.x) * 127) & 0xFF);
|
||||
} else {
|
||||
defaultNormal = 0;
|
||||
}
|
||||
|
||||
final int defaultTexU, defaultTexV;
|
||||
if (!tess.hasTexture) {
|
||||
final var tc = ShaderManager.getCurrentTexCoord();
|
||||
defaultTexU = Float.floatToRawIntBits(tc.x);
|
||||
defaultTexV = Float.floatToRawIntBits(tc.y);
|
||||
} else {
|
||||
defaultTexU = 0; defaultTexV = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
final int base = i * 8;
|
||||
if (!tess.hasTexture) { rawBuffer[base + 3] = defaultTexU; rawBuffer[base + 4] = defaultTexV; }
|
||||
if (!tess.hasColor) { rawBuffer[base + 5] = defaultColor; }
|
||||
if (fillNormal) { rawBuffer[base + 6] = defaultNormal; }
|
||||
if (!tess.hasBrightness) { rawBuffer[base + 7] = defaultBrightness; }
|
||||
}
|
||||
if (faceNormals) normalHandler.writeFaceNormals(rawBuffer, vertexCount);
|
||||
fillMissingAttributes(tess);
|
||||
}
|
||||
} else {
|
||||
effectiveFlags = flags;
|
||||
@@ -176,20 +130,34 @@ public class TessellatorStreamingDrawer {
|
||||
final int extPrim = extHandler == null ? 0 : ImmediateExtendedAttribHandler.extPrimVerts(tess.drawMode, vertexCount);
|
||||
final boolean wantsExt = extHandler != null && extHandler.wantsExtended();
|
||||
final boolean doExt = extPrim != 0 && tess.hasTexture && wantsExt;
|
||||
if (doExt) {
|
||||
final int combinedStride = vertexSize + ImmediateExtendedAttribHandler.EXT_STRIDE;
|
||||
final int combinedBytes = vertexCount * combinedStride;
|
||||
ensureExtScratch(combinedBytes);
|
||||
interleaveBase(repackAddress, extScratchAddress, vertexCount, vertexSize, combinedStride);
|
||||
// format expansion backfills a normal into rawBuffer, so trust effectiveFlags rather than tess.hasNormals
|
||||
final int normalIndex = (effectiveFlags & VertexFlags.NORMAL_BIT) != 0 ? ImmediateExtendedAttribHandler.RAW_NORMAL_INDEX : -1;
|
||||
extHandler.build(tess.rawBuffer, vertexCount, extPrim, normalIndex, extScratchAddress + vertexSize, combinedStride);
|
||||
extScratch.position(0);
|
||||
extScratch.limit(combinedBytes);
|
||||
uploadAndDrawExtended(extScratch, effectiveFlags, format, combinedStride, tess.drawMode, vertexCount);
|
||||
shrinkExtScratchIfOversized(combinedBytes);
|
||||
} else {
|
||||
uploadAndDraw(repackBuffer, effectiveFlags, format, vertexSize, tess.drawMode, vertexCount);
|
||||
final int idRuns = TessellatorEntityIds.runsFor(tess, vertexCount);
|
||||
final boolean doIds = idRuns > 1 && extHandler != null && extHandler.wantsEntityIds() && !LineQuads.isLineMode(tess.drawMode);
|
||||
final GLContextState swappedCtx = idRuns >= 1 && !doIds ? GLStateManager.ctx() : null;
|
||||
final long restoreIds = swappedCtx != null ? swapEntityIds(swappedCtx, TessellatorEntityIds.firstRunIds()) : 0L;
|
||||
try {
|
||||
if (doExt || doIds) {
|
||||
final int layout = (doExt ? StreamingVaos.MID_TEX_AND_TANGENT : 0) | (doIds ? StreamingVaos.ENTITY_IDS : 0);
|
||||
final int combinedStride = combinedStride(format, layout);
|
||||
final int combinedBytes = vertexCount * combinedStride;
|
||||
ensureExtScratch(combinedBytes);
|
||||
interleaveBase(repackAddress, extScratchAddress, vertexCount, vertexSize, combinedStride);
|
||||
long extAddress = extScratchAddress + vertexSize;
|
||||
if (doExt) {
|
||||
// format expansion backfills a normal into rawBuffer, so trust effectiveFlags rather than tess.hasNormals
|
||||
final int normalIndex = (effectiveFlags & VertexFlags.NORMAL_BIT) != 0 ? ImmediateExtendedAttribHandler.RAW_NORMAL_INDEX : -1;
|
||||
extHandler.build(tess.rawBuffer, vertexCount, extPrim, normalIndex, extAddress, combinedStride);
|
||||
extAddress += ImmediateExtendedAttribHandler.EXT_STRIDE;
|
||||
}
|
||||
if (doIds) TessellatorEntityIds.write(extAddress, combinedStride, vertexCount, idRuns);
|
||||
extScratch.position(0);
|
||||
extScratch.limit(combinedBytes);
|
||||
uploadAndDrawExtended(extScratch, effectiveFlags, format, layout, combinedStride, tess.drawMode, vertexCount);
|
||||
shrinkExtScratchIfOversized(combinedBytes);
|
||||
} else {
|
||||
uploadAndDraw(repackBuffer, effectiveFlags, format, vertexSize, tess.drawMode, vertexCount);
|
||||
}
|
||||
} finally {
|
||||
if (swappedCtx != null) swapEntityIds(swappedCtx, restoreIds);
|
||||
}
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
@@ -206,6 +174,57 @@ public class TessellatorStreamingDrawer {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void fillMissingAttributes(Tessellator tess) {
|
||||
final int vertexCount = tess.vertexCount;
|
||||
final int[] rawBuffer = tess.rawBuffer;
|
||||
final int defaultColor;
|
||||
if (!tess.hasColor) {
|
||||
final var c = GLStateManager.getColor();
|
||||
defaultColor = ((int)(clamp(0f, 1f, c.getAlpha()) * 255) << 24) | ((int)(clamp(0f, 1f, c.getBlue()) * 255) << 16)
|
||||
| ((int)(clamp(0f, 1f, c.getGreen()) * 255) << 8) | (int)(clamp(0f, 1f, c.getRed()) * 255);
|
||||
} else {
|
||||
defaultColor = 0;
|
||||
}
|
||||
|
||||
final int defaultBrightness;
|
||||
if (!tess.hasBrightness) {
|
||||
defaultBrightness = GLSMConfig.packedLastBrightness();
|
||||
} else {
|
||||
defaultBrightness = 0;
|
||||
}
|
||||
|
||||
final ImmediateExtendedAttribHandler normalHandler = GLSMHooks.immediateExtendedHandler;
|
||||
final boolean faceNormals = !tess.hasNormals && normalHandler != null
|
||||
&& ImmediateExtendedAttribHandler.extPrimVerts(tess.drawMode, vertexCount) == 4 && normalHandler.wantsFaceNormals();
|
||||
final boolean fillNormal = !tess.hasNormals && !faceNormals;
|
||||
|
||||
final int defaultNormal;
|
||||
if (fillNormal) {
|
||||
final var n = ShaderManager.getCurrentNormal();
|
||||
defaultNormal = ((int)(clamp(-1f, 1f, n.z) * 127) << 16) | (((int)(clamp(-1f, 1f, n.y) * 127) & 0xFF) << 8) | ((int)(clamp(-1f, 1f, n.x) * 127) & 0xFF);
|
||||
} else {
|
||||
defaultNormal = 0;
|
||||
}
|
||||
|
||||
final int defaultTexU, defaultTexV;
|
||||
if (!tess.hasTexture) {
|
||||
final var tc = ShaderManager.getCurrentTexCoord();
|
||||
defaultTexU = Float.floatToRawIntBits(tc.x);
|
||||
defaultTexV = Float.floatToRawIntBits(tc.y);
|
||||
} else {
|
||||
defaultTexU = 0; defaultTexV = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < vertexCount; i++) {
|
||||
final int base = i * 8;
|
||||
if (!tess.hasTexture) { rawBuffer[base + 3] = defaultTexU; rawBuffer[base + 4] = defaultTexV; }
|
||||
if (!tess.hasColor) { rawBuffer[base + 5] = defaultColor; }
|
||||
if (fillNormal) { rawBuffer[base + 6] = defaultNormal; }
|
||||
if (!tess.hasBrightness) { rawBuffer[base + 7] = defaultBrightness; }
|
||||
}
|
||||
if (faceNormals) normalHandler.writeFaceNormals(rawBuffer, vertexCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw DirectTessellator data via streaming VBO+VAO. Used for live immediate mode emulation.
|
||||
*/
|
||||
@@ -248,7 +267,7 @@ public class TessellatorStreamingDrawer {
|
||||
final long srcBase = memAddress0(packed) + packed.position();
|
||||
final int texOffset = ImmediateExtendedAttribHandler.texOffset(format);
|
||||
final int normalOffset = ImmediateExtendedAttribHandler.normalOffset(format);
|
||||
final int combinedStride = vertexSize + ImmediateExtendedAttribHandler.EXT_STRIDE;
|
||||
final int combinedStride = combinedStride(format, StreamingVaos.MID_TEX_AND_TANGENT);
|
||||
final int combinedBytes = vertexCount * combinedStride;
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
@@ -257,7 +276,7 @@ public class TessellatorStreamingDrawer {
|
||||
extHandler.buildPacked(srcBase, vertexSize, 0, texOffset, normalOffset, vertexCount, extPrim, extScratchAddress + vertexSize, combinedStride);
|
||||
extScratch.position(0);
|
||||
extScratch.limit(combinedBytes);
|
||||
uploadAndDrawExtended(extScratch, flags, format, combinedStride, drawMode, vertexCount);
|
||||
uploadAndDrawExtended(extScratch, flags, format, StreamingVaos.MID_TEX_AND_TANGENT, combinedStride, drawMode, vertexCount);
|
||||
shrinkExtScratchIfOversized(combinedBytes);
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
@@ -443,6 +462,7 @@ public class TessellatorStreamingDrawer {
|
||||
FfpExtendedAttribs.beginInternalDraw();
|
||||
try {
|
||||
if (drawMode == GL11.GL_QUADS) {
|
||||
GLStateManager.drawCalls++;
|
||||
QuadConverter.drawQuadsAsTriangles(glCtx, firstVertex, vertexCount);
|
||||
} else {
|
||||
GLStateManager.glDrawArrays(drawMode, firstVertex, vertexCount);
|
||||
@@ -452,11 +472,11 @@ public class TessellatorStreamingDrawer {
|
||||
}
|
||||
}
|
||||
|
||||
private static void uploadAndDrawExtended(ByteBuffer combined, int flags, VertexFormat format, int combinedStride, int drawMode, int vertexCount) {
|
||||
private static void uploadAndDrawExtended(ByteBuffer combined, int flags, VertexFormat format, int layout, int combinedStride, int drawMode, int vertexCount) {
|
||||
final GLContextState glCtx = GLStateManager.ctx();
|
||||
final StreamingVaos vaos = glCtx.streamingVaos;
|
||||
ensureVAO(vaos.persistentVAOs, vaos.orphanVAOs, flags, format);
|
||||
ensureExtendedVAOs(vaos, flags, format);
|
||||
ensureExtendedVAOs(vaos, flags, format, layout);
|
||||
|
||||
int firstVertex = -1;
|
||||
|
||||
@@ -465,9 +485,9 @@ public class TessellatorStreamingDrawer {
|
||||
}
|
||||
|
||||
if (firstVertex >= 0) {
|
||||
GLStateManager.glBindVertexArray(vaos.extendedPersistentVAOs[flags]);
|
||||
GLStateManager.glBindVertexArray(vaos.extendedPersistent(layout)[flags]);
|
||||
} else {
|
||||
GLStateManager.glBindVertexArray(vaos.extendedOrphanVAOs[flags]);
|
||||
GLStateManager.glBindVertexArray(vaos.extendedOrphan(layout)[flags]);
|
||||
orphanBuffers[flags].upload(combined);
|
||||
firstVertex = 0;
|
||||
}
|
||||
@@ -476,14 +496,14 @@ public class TessellatorStreamingDrawer {
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
private static void ensureExtendedVAOs(StreamingVaos vaos, int flags, VertexFormat format) {
|
||||
final int[] extendedOrphanVAOs = vaos.extendedOrphanVAOs;
|
||||
final int[] extendedPersistentVAOs = vaos.extendedPersistentVAOs;
|
||||
private static void ensureExtendedVAOs(StreamingVaos vaos, int flags, VertexFormat format, int layout) {
|
||||
final int[] extendedOrphanVAOs = vaos.extendedOrphan(layout);
|
||||
final int[] extendedPersistentVAOs = vaos.extendedPersistent(layout);
|
||||
if (extendedOrphanVAOs[flags] == 0) {
|
||||
extendedOrphanVAOs[flags] = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(extendedOrphanVAOs[flags]);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, orphanBuffers[flags].getBufferId());
|
||||
setupExtendedBufferState(format);
|
||||
setupExtendedBufferState(format, layout);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
@@ -492,20 +512,42 @@ public class TessellatorStreamingDrawer {
|
||||
extendedPersistentVAOs[flags] = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(extendedPersistentVAOs[flags]);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, persistentBuffer.getBufferId());
|
||||
setupExtendedBufferState(format);
|
||||
setupExtendedBufferState(format, layout);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setupExtendedBufferState(VertexFormat format) {
|
||||
final int combinedStride = format.getVertexSize() + ImmediateExtendedAttribHandler.EXT_STRIDE;
|
||||
private static long swapEntityIds(GLContextState glCtx, long ids) {
|
||||
final long previous = glCtx.entityIds;
|
||||
if (previous != ids) {
|
||||
glCtx.entityIds = ids;
|
||||
glCtx.dirtyEntityIdsAttrib = true;
|
||||
}
|
||||
return previous;
|
||||
}
|
||||
|
||||
private static int combinedStride(VertexFormat format, int layout) {
|
||||
int stride = format.getVertexSize();
|
||||
if ((layout & StreamingVaos.MID_TEX_AND_TANGENT) != 0) stride += ImmediateExtendedAttribHandler.EXT_STRIDE;
|
||||
if ((layout & StreamingVaos.ENTITY_IDS) != 0) stride += ImmediateExtendedAttribHandler.ENTITY_IDS_STRIDE;
|
||||
return stride;
|
||||
}
|
||||
|
||||
private static void setupExtendedBufferState(VertexFormat format, int layout) {
|
||||
final int combinedStride = combinedStride(format, layout);
|
||||
long offset = 0;
|
||||
for (final VertexFormatElement element : format.elementsArray) {
|
||||
element.setupBufferState(offset, combinedStride);
|
||||
offset += element.getByteSize();
|
||||
}
|
||||
ImmediateExtendedAttribHandler.setupExtAttribPointers(offset, combinedStride);
|
||||
if ((layout & StreamingVaos.MID_TEX_AND_TANGENT) != 0) {
|
||||
ImmediateExtendedAttribHandler.setupExtAttribPointers(offset, combinedStride);
|
||||
offset += ImmediateExtendedAttribHandler.EXT_STRIDE;
|
||||
}
|
||||
if ((layout & StreamingVaos.ENTITY_IDS) != 0) {
|
||||
ImmediateExtendedAttribHandler.setupEntityIdsPointer(offset, combinedStride);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureExtScratch(int requiredBytes) {
|
||||
|
||||
+13
@@ -6,6 +6,7 @@ import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL21;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@@ -61,6 +62,18 @@ public class GLSM_DeletedBufferBinding_GLTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletedIndexedUniformBufferIsNotRestoredFromCache() {
|
||||
final int index = 3;
|
||||
final int ubo = makeBoundBuffer(GL31.GL_UNIFORM_BUFFER);
|
||||
GLStateManager.glBindBufferBase(GL31.GL_UNIFORM_BUFFER, index, ubo);
|
||||
assertEquals(ubo, GLStateManager.getIndexedBufferBinding(GL31.GL_UNIFORM_BUFFER, index));
|
||||
|
||||
GLStateManager.glDeleteBuffers(ubo);
|
||||
assertEquals(0, GL30.glGetInteger(GL31.GL_UNIFORM_BUFFER_BINDING, index));
|
||||
assertEquals(0, GLStateManager.getIndexedBufferBinding(GL31.GL_UNIFORM_BUFFER, index));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletedElementArrayBufferDoesNotBlockClientDrawElements() {
|
||||
final int ebo = makeBoundBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER);
|
||||
|
||||
+118
@@ -3,6 +3,8 @@ package com.gtnewhorizons.angelica.glsm;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FfpFixture;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.TestThreads;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -14,6 +16,7 @@ import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.SharedDrawable;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
@@ -29,14 +32,22 @@ import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
@GLCoreTest
|
||||
public class GLSM_SharedDrawableContext_GLTest {
|
||||
|
||||
private SharedDrawable sharedDrawable;
|
||||
private int listVbo;
|
||||
private int listEbo;
|
||||
private int indexedList;
|
||||
private int sharedContextVao;
|
||||
|
||||
@AfterEach
|
||||
void cleanup() {
|
||||
if (indexedList != 0) GLStateManager.glDeleteLists(indexedList, 1);
|
||||
if (listVbo != 0) GLStateManager.glDeleteBuffers(listVbo);
|
||||
if (listEbo != 0) GLStateManager.glDeleteBuffers(listEbo);
|
||||
if (sharedDrawable != null) {
|
||||
DrawableContexts.destroy(sharedDrawable);
|
||||
sharedDrawable = null;
|
||||
@@ -222,6 +233,113 @@ public class GLSM_SharedDrawableContext_GLTest {
|
||||
});
|
||||
}
|
||||
|
||||
private int compileIndexedListOnSharedDrawable() throws Exception {
|
||||
sharedDrawable = new SharedDrawable(Display.getDrawable());
|
||||
DrawableContexts.run(sharedDrawable, "SharedDrawableContext-IndexedList-Thread", () -> {
|
||||
final FloatBuffer vertices = BufferUtils.createFloatBuffer(9);
|
||||
vertices.put(new float[] { -0.5f, -0.5f, 0f, 0.5f, -0.5f, 0f, 0f, 0.5f, 0f });
|
||||
vertices.flip();
|
||||
final ByteBuffer indices = BufferUtils.createByteBuffer(6);
|
||||
indices.putShort((short) 0).putShort((short) 1).putShort((short) 2);
|
||||
indices.flip();
|
||||
|
||||
listVbo = GLStateManager.glGenBuffers();
|
||||
listEbo = GLStateManager.glGenBuffers();
|
||||
GLStateManager.glBindVertexArray(GLStateManager.glGenVertexArrays());
|
||||
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, listVbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, vertices, GL15.GL_STATIC_DRAW);
|
||||
GLStateManager.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 12, 0L);
|
||||
GLStateManager.glEnableVertexAttribArray(0);
|
||||
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, listEbo);
|
||||
GLStateManager.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indices, GL15.GL_STATIC_DRAW);
|
||||
|
||||
indexedList = GLStateManager.glGenLists(1);
|
||||
GLStateManager.glNewList(indexedList, GL11.GL_COMPILE);
|
||||
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
final IndexedDrawBatch batch = indexedBatch();
|
||||
batch.bind();
|
||||
sharedContextVao = Reflect.<Integer>get(batch, "vao");
|
||||
});
|
||||
return indexedList;
|
||||
}
|
||||
|
||||
private IndexedDrawBatch indexedBatch() {
|
||||
return DisplayListManager.getDisplayList(indexedList).getIndexedBatches().get(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexedListCompiledOnSharedDrawableReplaysOnDisplay() throws Exception {
|
||||
final int list = compileIndexedListOnSharedDrawable();
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
final boolean wasEnabled = ShaderManager.isEnabled();
|
||||
ShaderManager.enable();
|
||||
sm.activate();
|
||||
try {
|
||||
GLStateManager.glCallList(list);
|
||||
|
||||
indexedBatch().bind();
|
||||
assertEquals(indexedBatch().getSharedEBO(), GL11.glGetInteger(GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING));
|
||||
} finally {
|
||||
sm.deactivate();
|
||||
if (!wasEnabled) ShaderManager.disable();
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void deletingListLeavesDisplayVaoWithTheSharedContextName() throws Exception {
|
||||
final int list = compileIndexedListOnSharedDrawable();
|
||||
final IntBuffer made = BufferUtils.createIntBuffer(4096);
|
||||
while (!GL30.glIsVertexArray(sharedContextVao) && made.hasRemaining()) {
|
||||
final int v = GLStateManager.glGenVertexArrays();
|
||||
made.put(v);
|
||||
if (v == sharedContextVao) GLStateManager.glBindVertexArray(v);
|
||||
}
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
assumeTrue(GL30.glIsVertexArray(sharedContextVao));
|
||||
|
||||
GLStateManager.glDeleteLists(list, 1);
|
||||
indexedList = 0;
|
||||
|
||||
assertTrue(GL30.glIsVertexArray(sharedContextVao));
|
||||
made.flip();
|
||||
while (made.hasRemaining()) GLStateManager.glDeleteVertexArrays(made.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void recordedVaoBindReplaysOnlyOnItsCompileContext() throws Exception {
|
||||
final int bound = GLStateManager.glGenVertexArrays();
|
||||
final int local = GLStateManager.glGenVertexArrays();
|
||||
final int localList = GLStateManager.glGenLists(1);
|
||||
final int foreignList = GLStateManager.glGenLists(1);
|
||||
GLStateManager.glNewList(localList, GL11.GL_COMPILE);
|
||||
GLStateManager.glBindVertexArray(local);
|
||||
GLStateManager.glEndList();
|
||||
|
||||
sharedDrawable = new SharedDrawable(Display.getDrawable());
|
||||
DrawableContexts.run(sharedDrawable, "SharedDrawableContext-VaoBind-Thread", () -> {
|
||||
int foreign;
|
||||
do foreign = GLStateManager.glGenVertexArrays(); while (foreign == bound);
|
||||
GLStateManager.glNewList(foreignList, GL11.GL_COMPILE);
|
||||
GLStateManager.glBindVertexArray(foreign);
|
||||
GLStateManager.glEndList();
|
||||
});
|
||||
|
||||
GLStateManager.glBindVertexArray(bound);
|
||||
GLStateManager.glCallList(foreignList);
|
||||
assertEquals(bound, GLStateManager.getBoundVAO());
|
||||
GLStateManager.glCallList(localList);
|
||||
assertEquals(local, GLStateManager.getBoundVAO());
|
||||
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glDeleteVertexArrays(bound);
|
||||
GLStateManager.glDeleteVertexArrays(local);
|
||||
GLStateManager.glDeleteLists(localList, 1);
|
||||
GLStateManager.glDeleteLists(foreignList, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deferredTextureDeletesWaitForSplashAndScrubDrawableContexts() throws Exception {
|
||||
sharedDrawable = new SharedDrawable(Display.getDrawable());
|
||||
|
||||
+38
-39
@@ -4,7 +4,6 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
@@ -12,54 +11,54 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class BackendStartGuardTest {
|
||||
|
||||
private static File marker(Path dir) {
|
||||
return dir.resolve("angelica").resolve("sdlgpu-starting").toFile();
|
||||
@TempDir
|
||||
Path dir;
|
||||
|
||||
private boolean boot() {
|
||||
return boot(1);
|
||||
}
|
||||
|
||||
private boolean boot(int revision) {
|
||||
return BackendStartGuard.begin(dir.toFile(), true, false, revision, "");
|
||||
}
|
||||
|
||||
private File marker() {
|
||||
return dir.resolve("sdlgpu-starting").toFile();
|
||||
}
|
||||
|
||||
@Test
|
||||
void firstStartCreatesTheMarkerAndFinishRemovesIt(@TempDir Path dir) {
|
||||
final File marker = marker(dir);
|
||||
assertFalse(BackendStartGuard.begin(marker, true, false));
|
||||
assertFalse(BackendStartGuard.tripped());
|
||||
assertTrue(marker.isFile());
|
||||
BackendStartGuard.finish();
|
||||
assertFalse(marker.exists());
|
||||
void bootsThatReachTheBackendNeverCountAsStrikes() {
|
||||
for (int i = 0; i < BackendStartGuard.STRIKE_LIMIT; i++) {
|
||||
assertFalse(boot());
|
||||
BackendStartGuard.arm();
|
||||
BackendStartGuard.disarm();
|
||||
}
|
||||
assertFalse(boot());
|
||||
}
|
||||
|
||||
@Test
|
||||
void leftoverMarkerTripsOnceAndIsRemoved(@TempDir Path dir) {
|
||||
final File marker = marker(dir);
|
||||
assertFalse(BackendStartGuard.begin(marker, true, false));
|
||||
assertTrue(BackendStartGuard.begin(marker, true, false));
|
||||
void thirdDeathInTheDeviceWindowResets() {
|
||||
for (int i = 0; i < BackendStartGuard.STRIKE_LIMIT; i++) {
|
||||
assertFalse(boot());
|
||||
BackendStartGuard.arm();
|
||||
}
|
||||
assertTrue(boot());
|
||||
assertTrue(BackendStartGuard.tripped());
|
||||
assertFalse(marker.exists());
|
||||
BackendStartGuard.finish();
|
||||
assertFalse(marker.exists());
|
||||
assertFalse(BackendStartGuard.begin(marker, true, false));
|
||||
assertFalse(BackendStartGuard.tripped());
|
||||
BackendStartGuard.finish();
|
||||
assertFalse(marker().exists());
|
||||
}
|
||||
|
||||
@Test
|
||||
void openGlConfigWritesNoMarkerAndClearsAStaleOne(@TempDir Path dir) throws IOException {
|
||||
final File marker = marker(dir);
|
||||
assertFalse(BackendStartGuard.begin(marker, false, false));
|
||||
assertFalse(marker.exists());
|
||||
assertTrue(marker.getParentFile().mkdirs());
|
||||
assertTrue(marker.createNewFile());
|
||||
assertFalse(BackendStartGuard.begin(marker, false, false));
|
||||
assertFalse(BackendStartGuard.tripped());
|
||||
assertFalse(marker.exists());
|
||||
}
|
||||
void latchStopsArmingUntilTheRequirementsRevisionChanges() {
|
||||
assertFalse(boot());
|
||||
BackendStartGuard.arm();
|
||||
BackendStartGuard.disarm();
|
||||
BackendStartGuard.latch();
|
||||
assertFalse(boot());
|
||||
BackendStartGuard.arm();
|
||||
assertFalse(marker().exists());
|
||||
|
||||
@Test
|
||||
void forcedFlagNeverTripsOrWrites(@TempDir Path dir) throws IOException {
|
||||
final File marker = marker(dir);
|
||||
assertFalse(BackendStartGuard.begin(marker, true, true));
|
||||
assertFalse(marker.exists());
|
||||
assertTrue(marker.getParentFile().mkdirs());
|
||||
assertTrue(marker.createNewFile());
|
||||
assertFalse(BackendStartGuard.begin(marker, true, true));
|
||||
assertFalse(BackendStartGuard.tripped());
|
||||
assertFalse(boot(2));
|
||||
BackendStartGuard.arm();
|
||||
assertTrue(marker().isFile());
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class InstancedFFPDrawGLTest {
|
||||
GLSMHooks.pendingProgramSelection = null;
|
||||
GLSMHooks.blendHandler = null;
|
||||
log.clear();
|
||||
FfpFixture.IntegerInstances.delete();
|
||||
FfpFixture.EntityIdInstances.delete();
|
||||
FfpFixture.resetFfpState();
|
||||
CubeParityFixture.disableDirectionalLight();
|
||||
CubeParityFixture.deleteResources();
|
||||
@@ -196,14 +196,14 @@ class InstancedFFPDrawGLTest {
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.disableCull();
|
||||
|
||||
FfpFixture.IntegerInstances.build(0.66f);
|
||||
FfpFixture.EntityIdInstances.build(0.66f);
|
||||
|
||||
GLStateManager.glClearColor(0f, 0f, 0f, 1f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
|
||||
FfpFixture.IntegerInstances.draw();
|
||||
FfpFixture.EntityIdInstances.draw();
|
||||
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "instanced ivec4 draw must not raise a GL error");
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "instanced entity id draw must not raise a GL error");
|
||||
|
||||
assertPixel(51, 50, 255, 0, 0, "entity -1 must render red");
|
||||
assertPixel(150, 50, 0, 255, 0, "entity 7 must render green");
|
||||
|
||||
+14
-11
@@ -27,9 +27,10 @@ import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.params.provider.Arguments.arguments;
|
||||
|
||||
@@ -234,8 +235,9 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
final int list = recordSingleIndexedDraw(vao, ebo, GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT);
|
||||
|
||||
replay(list);
|
||||
final IndexedDrawBatch batch = firstBatch(list);
|
||||
final int sharedVAO = batch.getSharedVAO();
|
||||
final int sharedVAO = vaoOf(batch);
|
||||
final int sharedVBO = batch.getSharedVBO();
|
||||
final int sharedEBO = batch.getSharedEBO();
|
||||
assertTrue(GL15.glIsBuffer(sharedVBO), "shared VBO live");
|
||||
@@ -257,8 +259,9 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
final int list = recordSingleIndexedDraw(vao, ebo, GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT);
|
||||
|
||||
replay(list);
|
||||
final IndexedDrawBatch first = firstBatch(list);
|
||||
final int oldVAO = first.getSharedVAO();
|
||||
final int oldVAO = vaoOf(first);
|
||||
final int oldVBO = first.getSharedVBO();
|
||||
final int oldEBO = first.getSharedEBO();
|
||||
|
||||
@@ -341,8 +344,8 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
|
||||
final List<IndexedDrawBatch> batches = batchesOf(list);
|
||||
assertEquals(1, batches.size(), "same layout → one shared batch");
|
||||
|
||||
assertEquals(cmds.get(0).getSharedVAO(), cmds.get(1).getSharedVAO(), "shared VAO across draws in the same batch");
|
||||
assertEquals(batches.getFirst().getSharedVAO(), cmds.get(0).getSharedVAO());
|
||||
assertSame(batchOf(cmds.get(0)), batchOf(cmds.get(1)), "shared batch across draws in the same layout");
|
||||
assertSame(batches.getFirst(), batchOf(cmds.get(0)));
|
||||
|
||||
assertEquals(0L, cmds.get(0).getIndexByteOffset());
|
||||
assertEquals(3 * 4L, cmds.get(1).getIndexByteOffset(), "second draw offset = first.indexCount * sizeof(UINT)");
|
||||
@@ -396,7 +399,7 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
|
||||
final List<BatchedIndexedDrawCmd> cmds = allBatchedCmds(list);
|
||||
assertEquals(2, cmds.size());
|
||||
assertEquals(2, batchesOf(list).size(), "incompatible layouts → two batches");
|
||||
assertNotEquals(cmds.get(0).getSharedVAO(), cmds.get(1).getSharedVAO(), "VAOs from separate batches must be distinct");
|
||||
assertNotSame(batchOf(cmds.get(0)), batchOf(cmds.get(1)), "separate layouts must get distinct batches");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -435,7 +438,7 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
|
||||
final List<BatchedIndexedDrawCmd> cmds = allBatchedCmds(list);
|
||||
assertEquals(2, cmds.size());
|
||||
assertEquals(2, batchesOf(list).size(), "different enabled locations → two batches");
|
||||
assertNotEquals(cmds.get(0).getSharedVAO(), cmds.get(1).getSharedVAO(), "VAOs from separate batches must be distinct");
|
||||
assertNotSame(batchOf(cmds.get(0)), batchOf(cmds.get(1)), "separate layouts must get distinct batches");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -495,13 +498,13 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
|
||||
final List<BatchedIndexedDrawCmd> ba = allBatchedCmds(listBA);
|
||||
// Placeholders sit in command-stream order (A-first vs B-first).
|
||||
// Batches must be ordered so that placeholder[0] points at batch[0].
|
||||
assertEquals(ab.get(0).getSharedVAO(), batchesOf(listAB).get(0).getSharedVAO(),
|
||||
assertSame(batchesOf(listAB).get(0), batchOf(ab.get(0)),
|
||||
"listAB: batch[0] must match the first-recorded layout");
|
||||
assertEquals(ab.get(1).getSharedVAO(), batchesOf(listAB).get(1).getSharedVAO(),
|
||||
assertSame(batchesOf(listAB).get(1), batchOf(ab.get(1)),
|
||||
"listAB: batch[1] must match the second-recorded layout");
|
||||
assertEquals(ba.get(0).getSharedVAO(), batchesOf(listBA).get(0).getSharedVAO(),
|
||||
assertSame(batchesOf(listBA).get(0), batchOf(ba.get(0)),
|
||||
"listBA: batch[0] must match the first-recorded layout");
|
||||
assertEquals(ba.get(1).getSharedVAO(), batchesOf(listBA).get(1).getSharedVAO(),
|
||||
assertSame(batchesOf(listBA).get(1), batchOf(ba.get(1)),
|
||||
"listBA: batch[1] must match the second-recorded layout");
|
||||
}
|
||||
|
||||
|
||||
+10
-5
@@ -18,7 +18,10 @@ import java.util.List;
|
||||
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.drainErrors;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@@ -72,7 +75,8 @@ class IndexedDrawReplayTest extends DisplayListTestFixture {
|
||||
assertNotNull(batched);
|
||||
final IndexedDrawBatch batch = firstBatch(list);
|
||||
|
||||
GLStateManager.glBindVertexArray(batched.getSharedVAO());
|
||||
replay(list);
|
||||
GLStateManager.glBindVertexArray(vaoOf(batch));
|
||||
assertEquals(batch.getSharedEBO(), GLStateManager.getBoundEBO(),
|
||||
"batched VAO's EBO binding corrupted");
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
@@ -120,8 +124,6 @@ class IndexedDrawReplayTest extends DisplayListTestFixture {
|
||||
|
||||
final List<BatchedIndexedDrawCmd> cmds = allBatchedCmds(list);
|
||||
assertEquals(2, cmds.size());
|
||||
final int sharedVAO = cmds.getFirst().getSharedVAO();
|
||||
assertEquals(sharedVAO, cmds.get(1).getSharedVAO());
|
||||
|
||||
final int distractor = newVao();
|
||||
GLStateManager.glBindVertexArray(distractor);
|
||||
@@ -129,6 +131,9 @@ class IndexedDrawReplayTest extends DisplayListTestFixture {
|
||||
|
||||
compiledOf(list).render();
|
||||
|
||||
assertSame(batchOf(cmds.getFirst()), batchOf(cmds.get(1)));
|
||||
final int sharedVAO = vaoOf(batchOf(cmds.getFirst()));
|
||||
assertNotEquals(0, sharedVAO);
|
||||
assertEquals(sharedVAO, GLStateManager.getBoundVAO());
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
@@ -160,8 +165,8 @@ class IndexedDrawReplayTest extends DisplayListTestFixture {
|
||||
|
||||
try {
|
||||
assertThrows(IllegalStateException.class, builder::build);
|
||||
assertEquals(0, good.placeholder.getSharedVAO());
|
||||
assertEquals(0, huge.placeholder.getSharedVAO());
|
||||
assertNull(batchOf(good.placeholder));
|
||||
assertNull(batchOf(huge.placeholder));
|
||||
} finally {
|
||||
MemoryUtilities.memFree(tinyVtx);
|
||||
MemoryUtilities.memFree(tinyIdx);
|
||||
|
||||
+14
@@ -8,6 +8,7 @@ import com.gtnewhorizons.angelica.glsm.GLTypes;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.BatchedIndexedDrawCmd;
|
||||
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
@@ -176,6 +177,19 @@ public abstract class DisplayListTestFixture {
|
||||
return batchesOf(listId).getFirst();
|
||||
}
|
||||
|
||||
protected static void replay(int listId) {
|
||||
compiledOf(listId).render();
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
protected static IndexedDrawBatch batchOf(BatchedIndexedDrawCmd cmd) {
|
||||
return Reflect.get(cmd, "batch");
|
||||
}
|
||||
|
||||
protected static int vaoOf(IndexedDrawBatch batch) {
|
||||
return Reflect.<Integer>get(batch, "vao");
|
||||
}
|
||||
|
||||
protected static ByteBuffer readBufferBytes(int target, int bufferId, int size) {
|
||||
final int prev;
|
||||
if (target == GL15.GL_ARRAY_BUFFER) prev = GLStateManager.getBoundVBO();
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.gtnewhorizons.angelica.glsm.streaming;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.EntityIdsOnlyHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.GLCompatTest;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FfpFixture;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
|
||||
@GLCompatTest
|
||||
class TessellatorEntityIdsGLTest {
|
||||
|
||||
private static final int[] GREEN = { 0, 255, 0, 255 };
|
||||
private static final int[] BLUE = { 0, 0, 255, 255 };
|
||||
|
||||
private ImmediateExtendedAttribHandler previousHandler;
|
||||
private int program;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
previousHandler = EntityIdsOnlyHandler.install();
|
||||
program = GlTestSupport.program(TessellatorEntityIdsFixture.VERTEX, TessellatorEntityIdsFixture.FRAGMENT);
|
||||
GLStateManager.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS);
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.glViewport(0, 0, 8, 8);
|
||||
GLStateManager.glUseProgram(program);
|
||||
GLStateManager.glClearColor(0, 0, 0, 1);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
GLStateManager.glUseProgram(0);
|
||||
GLStateManager.glDeleteProgram(program);
|
||||
GLStateManager.glPopAttrib();
|
||||
GLSMHooks.immediateExtendedHandler = previousHandler;
|
||||
}
|
||||
|
||||
@Test
|
||||
void idsChangedMidBatchApplyToTheVerticesAddedAfterTheChange() {
|
||||
TessellatorEntityIdsFixture.drawIdsChangedMidBatch();
|
||||
|
||||
assertColor(1, GREEN, "left quad was added under item 7");
|
||||
assertColor(6, BLUE, "right quad was added under item 50020");
|
||||
}
|
||||
|
||||
@Test
|
||||
void idsChangedAfterTheLastVertexLeaveTheBatchAlone() {
|
||||
TessellatorEntityIdsFixture.drawIdsChangedAfterTheLastVertex();
|
||||
|
||||
assertColor(1, GREEN, "every vertex was added under item 7");
|
||||
assertColor(6, GREEN, "every vertex was added under item 7");
|
||||
}
|
||||
|
||||
private static void assertColor(int x, int[] expected, String message) {
|
||||
final int[] pixel = FfpFixture.readPixel(x, 4);
|
||||
assertArrayEquals(expected, pixel, message + ", got " + Arrays.toString(pixel));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
|
||||
public final class EntityIdsOnlyHandler implements ImmediateExtendedAttribHandler {
|
||||
|
||||
public static ImmediateExtendedAttribHandler install() {
|
||||
final ImmediateExtendedAttribHandler previous = GLSMHooks.immediateExtendedHandler;
|
||||
GLSMHooks.immediateExtendedHandler = new EntityIdsOnlyHandler();
|
||||
GLStateManager.ctx().dirtyEntityIdsAttrib = true;
|
||||
return previous;
|
||||
}
|
||||
|
||||
@Override public boolean wantsExtended() { return false; }
|
||||
@Override public boolean wantsTangent() { return false; }
|
||||
@Override public boolean wantsEntityIds() { return GLStateManager.getActiveProgram() != 0; }
|
||||
@Override public boolean wantsExtendedCapture() { return false; }
|
||||
@Override public boolean wantsFaceNormals() { return false; }
|
||||
@Override public void writeFaceNormals(int[] rawBuffer, int vertexCount) {}
|
||||
@Override public void build(int[] rawBuffer, int vertexCount, int vertsPerPrim, int normalIntIndex, long dstAddr, int dstStride) {}
|
||||
@Override public void buildPacked(long srcBase, int stride, int posOffset, int texOffset, int normalOffset,
|
||||
int vertexCount, int vertsPerPrim, long dstAddr, int dstStride) {}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public final class FfpFixture {
|
||||
|
||||
private FfpFixture() {}
|
||||
|
||||
public static final class IntegerInstances {
|
||||
public static final class EntityIdInstances {
|
||||
|
||||
private static final int INSTANCES = 3;
|
||||
|
||||
@@ -31,16 +31,17 @@ public final class FfpFixture {
|
||||
layout(location = 5) in vec4 a_InstRow0;
|
||||
layout(location = 6) in vec4 a_InstRow1;
|
||||
layout(location = 7) in vec4 a_InstRow2;
|
||||
layout(location = 11) in uvec4 iris_Entity;
|
||||
layout(location = 11) in vec4 iris_InstEntity;
|
||||
flat out vec3 v_Color;
|
||||
void main() {
|
||||
""";
|
||||
|
||||
private static final String VS_TAIL = """
|
||||
gl_Position = instMV * vec4(a_Position, 1.0);
|
||||
if (iris_Entity.x == 65535u) { v_Color = vec3(1.0, 0.0, 0.0); }
|
||||
else if (iris_Entity.x == 7u) { v_Color = vec3(0.0, 1.0, 0.0); }
|
||||
else if (iris_Entity.x == 50020u) { v_Color = vec3(0.0, 0.0, 1.0); }
|
||||
int entity = int(round(iris_InstEntity.x * 65535.0));
|
||||
if (entity == 65535) { v_Color = vec3(1.0, 0.0, 0.0); }
|
||||
else if (entity == 7) { v_Color = vec3(0.0, 1.0, 0.0); }
|
||||
else if (entity == 50020) { v_Color = vec3(0.0, 0.0, 1.0); }
|
||||
else { v_Color = vec3(1.0, 0.0, 1.0); }
|
||||
}
|
||||
""";
|
||||
@@ -59,7 +60,7 @@ public final class FfpFixture {
|
||||
private static int templateVbo;
|
||||
private static int instanceVbo;
|
||||
|
||||
private IntegerInstances() {}
|
||||
private EntityIdInstances() {}
|
||||
|
||||
public static void build(float spacing) {
|
||||
program = buildProgram();
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package com.gtnewhorizons.angelica.glsm.streaming;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
public final class TessellatorEntityIdsFixture {
|
||||
|
||||
public static final String VERTEX = """
|
||||
#version 330 core
|
||||
layout(location = 0) in vec3 a_Position;
|
||||
layout(location = %d) in vec4 iris_Entity;
|
||||
flat out vec3 v_Color;
|
||||
void main() {
|
||||
gl_Position = vec4(a_Position, 1.0);
|
||||
int item = int(round(iris_Entity.z * 65535.0));
|
||||
v_Color = item == 7 ? vec3(0.0, 1.0, 0.0) : item == 50020 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
|
||||
}
|
||||
""".formatted(ImmediateExtendedAttribHandler.LOC_ENTITY_IDS);
|
||||
|
||||
public static final String FRAGMENT = """
|
||||
#version 330 core
|
||||
flat in vec3 v_Color;
|
||||
out vec4 fragColor;
|
||||
void main() { fragColor = vec4(v_Color, 1.0); }
|
||||
""";
|
||||
|
||||
private TessellatorEntityIdsFixture() {}
|
||||
|
||||
public static void drawIdsChangedMidBatch() {
|
||||
draw(() -> {
|
||||
GLStateManager.setEntityIds(-1, 0, 7);
|
||||
startQuads();
|
||||
addQuad(-1.0f, 0.0f);
|
||||
GLStateManager.setEntityIds(-1, 0, 50020);
|
||||
addQuad(0.0f, 1.0f);
|
||||
GLStateManager.setEntityIds(-1, 0, -1);
|
||||
});
|
||||
}
|
||||
|
||||
public static void drawIdsChangedAfterTheLastVertex() {
|
||||
draw(() -> {
|
||||
GLStateManager.setEntityIds(-1, 0, 7);
|
||||
startQuads();
|
||||
addQuad(-1.0f, 1.0f);
|
||||
GLStateManager.setEntityIds(-1, 0, 50020);
|
||||
});
|
||||
}
|
||||
|
||||
private static void draw(Runnable fill) {
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
final int[] savedRawBuffer = tess.rawBuffer;
|
||||
final int savedRawBufferSize = tess.rawBufferSize;
|
||||
final int savedRawBufferIndex = tess.rawBufferIndex;
|
||||
final int savedVertexCount = tess.vertexCount;
|
||||
final int savedDrawMode = tess.drawMode;
|
||||
final boolean savedIsDrawing = tess.isDrawing;
|
||||
final boolean savedHasTexture = tess.hasTexture;
|
||||
final boolean savedHasColor = tess.hasColor;
|
||||
final boolean savedHasNormals = tess.hasNormals;
|
||||
final boolean savedHasBrightness = tess.hasBrightness;
|
||||
try {
|
||||
fill.run();
|
||||
TessellatorStreamingDrawer.draw(tess);
|
||||
} finally {
|
||||
TessellatorEntityIds.onReset(tess);
|
||||
GLStateManager.setEntityIds(-1, 0, -1);
|
||||
tess.rawBuffer = savedRawBuffer;
|
||||
tess.rawBufferSize = savedRawBufferSize;
|
||||
tess.rawBufferIndex = savedRawBufferIndex;
|
||||
tess.vertexCount = savedVertexCount;
|
||||
tess.drawMode = savedDrawMode;
|
||||
tess.isDrawing = savedIsDrawing;
|
||||
tess.hasTexture = savedHasTexture;
|
||||
tess.hasColor = savedHasColor;
|
||||
tess.hasNormals = savedHasNormals;
|
||||
tess.hasBrightness = savedHasBrightness;
|
||||
}
|
||||
}
|
||||
|
||||
private static void startQuads() {
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
tess.rawBuffer = new int[64];
|
||||
tess.rawBufferSize = tess.rawBuffer.length;
|
||||
tess.rawBufferIndex = 0;
|
||||
tess.vertexCount = 0;
|
||||
tess.drawMode = GL11.GL_QUADS;
|
||||
tess.hasTexture = false;
|
||||
tess.hasColor = false;
|
||||
tess.hasNormals = false;
|
||||
tess.hasBrightness = false;
|
||||
tess.isDrawing = true;
|
||||
}
|
||||
|
||||
private static void addQuad(float left, float right) {
|
||||
addVertex(left, -1.0f);
|
||||
addVertex(right, -1.0f);
|
||||
addVertex(right, 1.0f);
|
||||
addVertex(left, 1.0f);
|
||||
}
|
||||
|
||||
private static void addVertex(float x, float y) {
|
||||
final Tessellator tess = Tessellator.instance;
|
||||
final int base = tess.rawBufferIndex;
|
||||
tess.rawBuffer[base] = Float.floatToRawIntBits(x);
|
||||
tess.rawBuffer[base + 1] = Float.floatToRawIntBits(y);
|
||||
tess.rawBuffer[base + 2] = Float.floatToRawIntBits(0.0f);
|
||||
tess.rawBufferIndex += 8;
|
||||
tess.vertexCount++;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -213,7 +213,7 @@ curseForgeRelations = requiredDependency\:gtnhlib
|
||||
disableSpotless = true
|
||||
|
||||
# Uncomment this to disable Checkstyle checks (currently wildcard import check).
|
||||
# disableCheckstyle = true
|
||||
disableCheckstyle = true
|
||||
|
||||
# Override the IDEA build type. Valid values are: "" (leave blank, do not override), "idea" (force use native IDEA build), "gradle"
|
||||
# (force use delegated build).
|
||||
|
||||
@@ -6,7 +6,10 @@ import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
|
||||
val libs = the<LibrariesForLibs>()
|
||||
|
||||
tasks.withType<DowngradeJar>().configureEach { logLevel.set("FATAL") }
|
||||
tasks.withType<DowngradeFiles>().configureEach { logLevel.set("FATAL") }
|
||||
tasks.withType<DowngradeFiles>().configureEach {
|
||||
logLevel.set("FATAL")
|
||||
outputs.cacheIf { true }
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.dependencySubstitution {
|
||||
|
||||
@@ -7,7 +7,7 @@ asmCompile = "5.0.3"
|
||||
launchwrapper = "1.12"
|
||||
# End Frozen
|
||||
|
||||
lwjgl3 = "3.4.2-SNAPSHOT"
|
||||
lwjgl3 = "3.4.2"
|
||||
# Must equal the LWJGL release shipped by lwjgl3ify. Do not bump without bumping lwjgl3ify.
|
||||
lwjgl3Release = "3.4.2"
|
||||
lwjgl3ify = "3.0.31"
|
||||
@@ -30,10 +30,9 @@ joml = "1.10.8"
|
||||
commonsLang3 = "3.17.0"
|
||||
jvmDowngrader = "1.3.5"
|
||||
junit = "5.14.1"
|
||||
asmTest = "9.7"
|
||||
asmTest = "9.9.1"
|
||||
asmBuild = "9.7"
|
||||
mockito = "3.12.4"
|
||||
archunit = "1.4.1"
|
||||
ant = "1.8.2"
|
||||
distantHorizonsApi = "5.1.0"
|
||||
|
||||
@@ -64,11 +63,11 @@ junit-platform-engine = { module = "org.junit.platform:junit-platform-engine" }
|
||||
junit-platform-reporting = { module = "org.junit.platform:junit-platform-reporting" }
|
||||
|
||||
mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" }
|
||||
archunit-junit5 = { module = "com.tngtech.archunit:archunit-junit5", version.ref = "archunit" }
|
||||
ant = { module = "org.apache.ant:ant", version.ref = "ant" }
|
||||
distant-horizons-api = { module = "maven.modrinth:DistantHorizonsApi", version.ref = "distantHorizonsApi" }
|
||||
asm-tree-compile = { module = "org.ow2.asm:asm-tree", version.ref = "asmCompile" }
|
||||
asm-tree-test = { module = "org.ow2.asm:asm-tree", version.ref = "asmTest" }
|
||||
asm-commons-test = { module = "org.ow2.asm:asm-commons", version.ref = "asmTest" }
|
||||
asm-build = { module = "org.ow2.asm:asm", version.ref = "asmBuild" }
|
||||
lwjgl2 = { module = "org.lwjgl.lwjgl:lwjgl", version.ref = "lwjgl2" }
|
||||
lwjgl2-util = { module = "org.lwjgl.lwjgl:lwjgl_util", version.ref = "lwjgl2" }
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import java.time.Duration
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
`maven-publish`
|
||||
@@ -94,6 +96,7 @@ dependencies {
|
||||
}
|
||||
|
||||
val glsmSdlTag = "glsm-sdl"
|
||||
val sdlGpuDeviceTag = "sdl-gpu-device"
|
||||
|
||||
val glsmSdlStubs = sourceSets.create("glsmSdlStubs") {
|
||||
java.srcDir("src/glsmSdlStubs/java")
|
||||
@@ -109,15 +112,29 @@ dependencies {
|
||||
testImplementation(glsmSdlAgent.output)
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform {
|
||||
excludeTags(glsmSdlTag)
|
||||
}
|
||||
tasks.withType<Test>().configureEach {
|
||||
if (System.getProperty("os.name").startsWith("Mac")) {
|
||||
jvmArgs("-XstartOnFirstThread")
|
||||
}
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform {
|
||||
excludeTags(glsmSdlTag, sdlGpuDeviceTag)
|
||||
}
|
||||
}
|
||||
|
||||
val sdlGpuDeviceTest by tasks.registering(Test::class) {
|
||||
description = "Runs the tests that need a real SDL GPU device."
|
||||
group = "verification"
|
||||
testClassesDirs = sourceSets["test"].output.classesDirs
|
||||
classpath = sourceSets["test"].runtimeClasspath
|
||||
useJUnitPlatform {
|
||||
includeTags(sdlGpuDeviceTag)
|
||||
}
|
||||
mustRunAfter(tasks.test)
|
||||
}
|
||||
|
||||
val glsmSdlRedirectAgentJar by tasks.registering(Jar::class) {
|
||||
archiveClassifier = "glsm-sdl-redirect-agent"
|
||||
from(glsmSdlAgent.output)
|
||||
@@ -146,9 +163,6 @@ val glsmSdlTest by tasks.registering(Test::class) {
|
||||
"-Dangelica.sdlgpu.disablePresenterThread=true",
|
||||
"-Dceleritas.lwjglService=com.gtnewhorizons.angelica.sdlgpu.SDLGPULWJGLService",
|
||||
)
|
||||
if (System.getProperty("os.name").startsWith("Mac")) {
|
||||
jvmArgs("-XstartOnFirstThread")
|
||||
}
|
||||
testLogging {
|
||||
events("passed", "skipped", "failed")
|
||||
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
|
||||
@@ -156,7 +170,23 @@ val glsmSdlTest by tasks.registering(Test::class) {
|
||||
mustRunAfter(tasks.test)
|
||||
}
|
||||
|
||||
tasks.check { dependsOn(glsmSdlTest) }
|
||||
val deviceTests = listOf(glsmSdlTest, sdlGpuDeviceTest)
|
||||
|
||||
deviceTests.forEach {
|
||||
it.configure {
|
||||
outputs.cacheIf { false }
|
||||
timeout = Duration.ofMinutes(5)
|
||||
systemProperty("junit.jupiter.execution.timeout.default", "60s")
|
||||
systemProperty("junit.jupiter.execution.timeout.threaddump.enabled", "true")
|
||||
}
|
||||
}
|
||||
|
||||
val unpackLwjgl3Natives = registerLwjgl3NativesUnpack()
|
||||
(deviceTests + tasks.test).forEach { it.configure { loadLwjgl3NativesFrom(unpackLwjgl3Natives) } }
|
||||
|
||||
if (!providers.environmentVariable("CI").isPresent) {
|
||||
tasks.check { dependsOn(deviceTests) }
|
||||
}
|
||||
|
||||
tasks.named<JavaCompile>("compileJava") {
|
||||
val classesDir = destinationDirectory
|
||||
|
||||
+4
-2
@@ -39,9 +39,11 @@ public final class GlsmSdlRedirectAgent {
|
||||
|
||||
@Override
|
||||
public byte[] transform(ClassLoader loader, String className, Class<?> redefined, ProtectionDomain domain, byte[] classfile) {
|
||||
if (className == null || !className.startsWith(GTNHLIB)) return null;
|
||||
if (className == null) return null;
|
||||
try {
|
||||
if (!redirector.shouldTransform(classfile)) return null;
|
||||
final byte[] observed = MovedSamplerProbe.instrument(className, classfile);
|
||||
if (observed != null) return observed;
|
||||
if (!className.startsWith(GTNHLIB) || !redirector.shouldTransform(classfile)) return null;
|
||||
final ClassNode node = new ClassNode();
|
||||
new ClassReader(classfile).accept(node, 0);
|
||||
if (!redirector.transformClassNode(className.replace('/', '.'), node)) return null;
|
||||
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.InsnList;
|
||||
import org.objectweb.asm.tree.MethodInsnNode;
|
||||
import org.objectweb.asm.tree.VarInsnNode;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public final class MovedSamplerProbe implements AutoCloseable {
|
||||
private static final String COPIES = "com/gtnewhorizons/angelica/sdlgpu/sampler/MovedSamplerCopies";
|
||||
private static final String RESOURCES = "com/gtnewhorizons/angelica/sdlgpu/resource/ResourceManager";
|
||||
private static final String PROBE = "com/gtnewhorizons/angelica/sdlgpu/glsm/MovedSamplerProbe";
|
||||
private static volatile MovedSamplerProbe active;
|
||||
|
||||
private final AtomicInteger refreshCalls = new AtomicInteger();
|
||||
private final ConcurrentHashMap<Long, AtomicInteger> writes = new ConcurrentHashMap<>();
|
||||
|
||||
public MovedSamplerProbe() {
|
||||
if (active != null) throw new IllegalStateException("A moved-sampler probe is already active");
|
||||
active = this;
|
||||
}
|
||||
|
||||
public int refreshCalls() {
|
||||
return refreshCalls.get();
|
||||
}
|
||||
|
||||
public void watchWrites(long texture) {
|
||||
writes.putIfAbsent(texture, new AtomicInteger());
|
||||
}
|
||||
|
||||
public int writesTo(long texture) {
|
||||
return writes.get(texture).get();
|
||||
}
|
||||
|
||||
public static void onRefresh() {
|
||||
final MovedSamplerProbe probe = active;
|
||||
if (probe != null) probe.refreshCalls.incrementAndGet();
|
||||
}
|
||||
|
||||
public static void onWrite(long texture) {
|
||||
final MovedSamplerProbe probe = active;
|
||||
if (probe == null) return;
|
||||
final AtomicInteger count = probe.writes.get(texture);
|
||||
if (count != null) count.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
active = null;
|
||||
}
|
||||
|
||||
static byte[] instrument(String className, byte[] classfile) {
|
||||
final boolean writes = RESOURCES.equals(className);
|
||||
if (!writes && !COPIES.equals(className)) return null;
|
||||
final String methodName = writes ? "markTextureWritten" : "refresh";
|
||||
final ClassNode node = new ClassNode();
|
||||
new ClassReader(classfile).accept(node, 0);
|
||||
final var method = node.methods.stream().filter(m -> m.name.equals(methodName)).findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("Missing observed method: " + className + "." + methodName));
|
||||
final InsnList hook = new InsnList();
|
||||
if (writes) hook.add(new VarInsnNode(Opcodes.LLOAD, 1));
|
||||
hook.add(new MethodInsnNode(Opcodes.INVOKESTATIC, PROBE, writes ? "onWrite" : "onRefresh", writes ? "(J)V" : "()V", false));
|
||||
method.instructions.insert(hook);
|
||||
final ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
node.accept(writer);
|
||||
return writer.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import static org.lwjgl.sdl.SDLVideo.SDL_PROP_WINDOW_CREATE_METAL_BOOLEAN;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendOptions;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendStartGuard;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.loading.DependencyVerifier;
|
||||
import com.gtnewhorizons.angelica.glsm.loading.DependencyVerifier.Check;
|
||||
@@ -55,6 +56,7 @@ public final class SDLGPUGate {
|
||||
}
|
||||
|
||||
public static final String LWJGL3IFY_MIN_VERSION = "3.0.25";
|
||||
public static final int REQUIREMENTS_REVISION = 1;
|
||||
|
||||
private static final List<Check> REQUIREMENTS = List.of(
|
||||
new Check("/org/lwjgl/sdl/SDLGPU.class", "no lwjgl-sdl on the classpath"),
|
||||
@@ -90,12 +92,16 @@ public final class SDLGPUGate {
|
||||
boolean ok;
|
||||
try {
|
||||
Sys.initialize();
|
||||
BackendStartGuard.arm();
|
||||
ok = MainThreadExec.runOnMainThread(device()::createDevice);
|
||||
} catch (Throwable t) {
|
||||
LOG.error("SDL GPU device probe failed", t);
|
||||
ok = false;
|
||||
}
|
||||
if (!ok) resetSdlLogging();
|
||||
if (!ok) {
|
||||
resetSdlLogging();
|
||||
BackendStartGuard.disarm();
|
||||
}
|
||||
deviceReady = ok;
|
||||
return ok;
|
||||
}
|
||||
@@ -182,6 +188,7 @@ public final class SDLGPUGate {
|
||||
throw new IllegalStateException("Celeritas already selected the SDL GPU LWJGL service; falling back to OpenGL would leave it bound to a dead backend");
|
||||
}
|
||||
SDLGPULWJGLService.unbind();
|
||||
BackendStartGuard.disarm();
|
||||
disarmed = true;
|
||||
engaged = false;
|
||||
deviceReady = false;
|
||||
|
||||
@@ -32,6 +32,8 @@ import com.gtnewhorizons.angelica.sdlgpu.pipeline.AttachmentClear;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.DepthStencilReadback;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.DrawDispatch;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.FFPTrace;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.FullscreenPass;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.MovedSamplerConversion;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineApplier;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineStore;
|
||||
@@ -54,9 +56,11 @@ import com.gtnewhorizons.angelica.sdlgpu.resource.ReadbackShadows;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.TextureOps;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.TextureSamplerState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.TransferThread;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.sampler.MovedSamplerCopies;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.sampler.SamplerBinder;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.sampler.StorageBufferBinder;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.sampler.StorageTextureBinder;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.SamplerOverflow;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.splash.SplashDispatcher;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.OffscreenTarget;
|
||||
@@ -138,6 +142,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
private final Image3DClear image3DClear = new Image3DClear();
|
||||
private final ShaderManager shaderManager = new ShaderManager(device);
|
||||
private final PipelineStore pipelineStore = new PipelineStore(device);
|
||||
private final FullscreenPass fullscreenPass = new FullscreenPass(shaderManager, device);
|
||||
|
||||
private String cachedRenderer;
|
||||
private String cachedVendor;
|
||||
@@ -152,7 +157,9 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
private final LongArrayFIFOQueue frameSyncs = new LongArrayFIFOQueue();
|
||||
private final FBOClearTracker fboClearTracker = new FBOClearTracker(frameManager, resourceManager, shaderManager);
|
||||
private final SamplerBinder samplerBinder = new SamplerBinder(device, resourceManager, shaderManager);
|
||||
private final StorageTextureBinder storageTextureBinder = new StorageTextureBinder(resourceManager, shaderManager);
|
||||
private final MovedSamplerConversion movedSamplerConversion = new MovedSamplerConversion(frameManager, pipelineStore, fullscreenPass);
|
||||
private final MovedSamplerCopies movedSamplerCopies = new MovedSamplerCopies(device, frameManager, resourceManager, movedSamplerConversion);
|
||||
private final StorageTextureBinder storageTextureBinder = new StorageTextureBinder(resourceManager, shaderManager, movedSamplerCopies);
|
||||
private final StorageBufferBinder storageBufferBinder = new StorageBufferBinder(resourceManager, shaderManager);
|
||||
private final ComputeBinder computeBinder = new ComputeBinder(frameManager, resourceManager, shaderManager, samplerBinder);
|
||||
private final VoxelizationDispatcher voxelizationDispatcher =
|
||||
@@ -165,10 +172,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public void enqueue(TransferThread.DeferredUpload upload) { enqueueUpload(upload); }
|
||||
@Override public long nextSeq() { return TransferThread.nextSeq(); }
|
||||
});
|
||||
private final PipelineApplier pipelineApplier = new PipelineApplier(frameManager, resourceManager, shaderManager, pipelineStore, fboClearTracker, persistentSync, samplerBinder, storageTextureBinder, storageBufferBinder);
|
||||
private final PipelineApplier pipelineApplier = new PipelineApplier(frameManager, resourceManager, shaderManager, pipelineStore, fboClearTracker, persistentSync, samplerBinder, storageTextureBinder, storageBufferBinder, movedSamplerCopies);
|
||||
private final VertexWriteReplayer vertexWriteReplayer = new VertexWriteReplayer(shaderManager, resourceManager, pipelineApplier, computeBinder, SDLGPURenderBackend::s);
|
||||
private final AttachmentClear attachmentClear = new AttachmentClear(frameManager, pipelineStore, shaderManager);
|
||||
private final DepthStencilReadback depthStencilReadback = new DepthStencilReadback(frameManager, resourceManager, pipelineStore, shaderManager, fboClearTracker);
|
||||
private final AttachmentClear attachmentClear = new AttachmentClear(frameManager, pipelineStore, fullscreenPass);
|
||||
private final DepthStencilReadback depthStencilReadback = new DepthStencilReadback(frameManager, resourceManager, pipelineStore, fullscreenPass, fboClearTracker);
|
||||
private final DrawDispatch drawDispatch = new DrawDispatch(device, frameManager, resourceManager, pipelineApplier, this::enqueuePreCopied);
|
||||
private final FFPTrace ffpTrace = SystemProperties.FFP_TRACE ? new FFPTrace(resourceManager) : null;
|
||||
|
||||
@@ -306,6 +313,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
pipelineApplier.setDeferredUploadSink(this::enqueuePreCopied);
|
||||
frameManager.setRenderPassEndHook(vertexWriteReplayer::flush);
|
||||
resourceManager.setTextureReplaceHook(this::flushVertexWritesBeforeTextureChange);
|
||||
resourceManager.setTextureReleaseListener(movedSamplerCopies::releaseCopyOf);
|
||||
frameManager.setPreRenderPassHook(() -> {
|
||||
final ContextState st = s();
|
||||
pipelineApplier.flushUniformBlocks(st);
|
||||
@@ -361,6 +369,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
LOG.info("Splash offscreen target {}x{} (window {}x{}, max desktop {}x{})", splashTexW, splashTexH, splashW, splashH, maxDesktop[0], maxDesktop[1]);
|
||||
}
|
||||
|
||||
Lwjgl3GLCapabilitiesShim.installFunctionProvider();
|
||||
Lwjgl3GLCapabilitiesShim.installOnCurrentThread(advertisedCapabilities());
|
||||
|
||||
LOG.info("SDLGPURenderBackend initialized: renderer={}, version={}", cachedRenderer, cachedVersion);
|
||||
@@ -534,8 +543,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
attachmentClear.shutdown();
|
||||
depthStencilReadback.shutdown();
|
||||
fullscreenPass.shutdown();
|
||||
textureOps.shutdown();
|
||||
pipelineStore.shutdown();
|
||||
movedSamplerCopies.releaseAll();
|
||||
shaderManager.shutdown();
|
||||
resourceManager.shutdown();
|
||||
device.shutdown();
|
||||
@@ -671,8 +682,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (shutdown) return;
|
||||
if (splashTeardownPending) finishSplash();
|
||||
if (GLStateManager.isSplashComplete()) applyPendingVSync();
|
||||
final boolean wasActive = frameManager.isFrameActive();
|
||||
frameManager.beginFrame();
|
||||
beginFrameInit();
|
||||
if (!wasActive) movedSamplerCopies.releaseUnused();
|
||||
}
|
||||
|
||||
private void beginFrameInit() {
|
||||
@@ -2632,6 +2645,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
prog.locationSamplerUnit[location] = v0;
|
||||
prog.samplerTextureUnits.put(prog.locationName[location], v0);
|
||||
prog.samplerUnitsDirty = true;
|
||||
st.samplerBindGen++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -3425,6 +3439,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
private void downloadBufferBlocking(long handle, int offset, int size, ByteBuffer out) {
|
||||
if (size <= 0) return;
|
||||
flushAndSubmitMidFrame();
|
||||
final long cb = SDL_AcquireGPUCommandBuffer(device.getDevice());
|
||||
if (cb == 0) return;
|
||||
@@ -3734,7 +3749,9 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
frameManager.endRenderPassIfActive(FrameManager.PASS_END_COPY);
|
||||
frameManager.noteMipGen();
|
||||
textureOps.clearPendingMipGen(texture);
|
||||
SDL_GenerateMipmapsForGPUTexture(frameManager.getCommandBuffer(), resourceManager.getTextureHandle(texture));
|
||||
final long texHandle = resourceManager.getTextureHandle(texture);
|
||||
SDL_GenerateMipmapsForGPUTexture(frameManager.getCommandBuffer(), texHandle);
|
||||
resourceManager.markTextureWritten(texHandle);
|
||||
}
|
||||
@Override public void textureImage2DEXT(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
if (texture == 0) return;
|
||||
@@ -4015,8 +4032,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public int getInteger(int pname) {
|
||||
final ContextState cs = s();
|
||||
return switch (pname) {
|
||||
case GL20.GL_MAX_TEXTURE_IMAGE_UNITS -> 17;
|
||||
case GL20.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS -> 17;
|
||||
// Iris budgets unit assignments here; only 16 can be filtered, with up to 8 float 2D fetch-only samplers.
|
||||
case GL20.GL_MAX_TEXTURE_IMAGE_UNITS, GL20.GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS -> SamplerOverflow.SAMPLER_SLOTS + SamplerOverflow.TEXTURE_SLOTS;
|
||||
case GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS -> 32;
|
||||
case GL20.GL_MAX_VERTEX_ATTRIBS -> 16;
|
||||
case GL20.GL_MAX_DRAW_BUFFERS -> 8;
|
||||
@@ -4035,7 +4052,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
case GL43.GL_MAX_DEBUG_MESSAGE_LENGTH -> 1024;
|
||||
case GL11.GL_DRAW_BUFFER -> drawBufferEnum(cs.boundFboId, cs.defaultFboId);
|
||||
case GL11.GL_READ_BUFFER -> readBufferEnum(cs.boundReadFboId, cs.defaultFboId);
|
||||
case GL11.GL_TEXTURE_BINDING_2D, GL11.GL_TEXTURE_BINDING_1D, GL12.GL_TEXTURE_BINDING_3D, GL13.GL_TEXTURE_BINDING_CUBE_MAP, GL30.GL_TEXTURE_BINDING_1D_ARRAY, GL30.GL_TEXTURE_BINDING_2D_ARRAY, GL31.GL_TEXTURE_BINDING_RECTANGLE, GL40.GL_TEXTURE_BINDING_CUBE_MAP_ARRAY -> boundTextureOf(cs.activeTextureUnit, cs.boundTextures);
|
||||
case GL11.GL_TEXTURE_BINDING_2D, GL11.GL_TEXTURE_BINDING_1D, GL12.GL_TEXTURE_BINDING_3D, GL13.GL_TEXTURE_BINDING_CUBE_MAP, GL30.GL_TEXTURE_BINDING_1D_ARRAY, GL30.GL_TEXTURE_BINDING_2D_ARRAY, GL31.GL_TEXTURE_BINDING_RECTANGLE, GL40.GL_TEXTURE_BINDING_CUBE_MAP_ARRAY -> cs.boundTexture(cs.activeTextureUnit);
|
||||
case GL11.GL_DEPTH_BITS -> getFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_DEPTH, GL30.GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE);
|
||||
case GL11.GL_STENCIL_BITS -> getFramebufferAttachmentParameteri(GL30.GL_DRAW_FRAMEBUFFER, GL11.GL_STENCIL, GL30.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE);
|
||||
case GL11.GL_PACK_ALIGNMENT -> cs.pixelStore.packAlignment;
|
||||
@@ -4069,10 +4086,6 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
return bufferEnum(fboId, fbo == null ? -1 : fbo.readBufferIndex);
|
||||
}
|
||||
|
||||
static int boundTextureOf(int unit, int[] boundTextures) {
|
||||
return (unit >= 0 && unit < boundTextures.length) ? boundTextures[unit] : 0;
|
||||
}
|
||||
|
||||
static int deviceLimit(int pname) {
|
||||
return switch (pname) {
|
||||
case GL20.GL_MAX_VERTEX_UNIFORM_COMPONENTS, GL20.GL_MAX_FRAGMENT_UNIFORM_COMPONENTS -> MAX_UNIFORM_COMPONENTS_PER_STAGE;
|
||||
@@ -4319,6 +4332,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final var dst = SDL_GPUTextureLocation.calloc(stack)
|
||||
.texture(dstTex).mip_level(dstLevel).x(dstX).y(dstY).z(dstZ);
|
||||
SDL_CopyGPUTextureToTexture(cp, src, dst, srcWidth, srcHeight, Math.max(1, srcDepth), false);
|
||||
resourceManager.markTextureWritten(dstTex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
@@ -31,6 +31,16 @@ public final class Lwjgl3GLCapabilitiesShim {
|
||||
LOG.info("Cleared fabricated LWJGL3 GLCapabilities on {}", Thread.currentThread().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Aware mods probe extension entry points through {@code GL.getFunctionProvider()}, which is null with no GL context.
|
||||
* Every name resolves to 0 so those probes see the extension as absent and use the core path, which GLSM routes.
|
||||
*/
|
||||
public static void installFunctionProvider() {
|
||||
if (GL.getFunctionProvider() != null) return;
|
||||
GL.create(name -> 0L);
|
||||
LOG.info("Installed null-address LWJGL3 GL function provider");
|
||||
}
|
||||
|
||||
public static GLCapabilities create(Set<String> advertised) {
|
||||
final FunctionProvider provider = name -> SENTINEL_ADDRESS;
|
||||
final IntFunction<PointerBuffer> bufferFactory = BufferUtils::createPointerBuffer;
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.sampler.SamplerBinder;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.UniformStaging;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
|
||||
import org.lwjgl.PointerBuffer;
|
||||
import org.lwjgl.sdl.SDL_GPUStorageBufferReadWriteBinding;
|
||||
import org.lwjgl.sdl.SDL_GPUStorageTextureReadWriteBinding;
|
||||
@@ -251,6 +252,11 @@ public final class ComputeBinder implements ComputeDispatchSink {
|
||||
final long pass = SDL_BeginGPUComputePass(cb, rwTex, rwBuf);
|
||||
if (pass == 0) return 0;
|
||||
frameManager.noteComputePassBegun();
|
||||
if (rwTex != null) {
|
||||
for (int i = 0; i < rwTex.limit(); i++) {
|
||||
resourceManager.markTextureWritten(MemoryAccess.getAddress(rwTex.address(i) + SDL_GPUStorageTextureReadWriteBinding.TEXTURE));
|
||||
}
|
||||
}
|
||||
|
||||
final boolean ok = bindComputeSamplers(st, prog, pass, samplerSlots, map.samplerNames(), stack)
|
||||
&& bindRoStorageTextures(st, prog, pass, map, stack)
|
||||
|
||||
@@ -361,6 +361,10 @@ public final class Device {
|
||||
return isMetal(driverName);
|
||||
}
|
||||
|
||||
public boolean isVulkan() {
|
||||
return isVulkan(driverName);
|
||||
}
|
||||
|
||||
public boolean isFencePollEnabled() {
|
||||
return fencePollEnabled;
|
||||
}
|
||||
|
||||
@@ -173,6 +173,12 @@ public final class ContextState {
|
||||
public int lastAppliedStorageTexBindGen = -1;
|
||||
public int lastAppliedStorageTexProgram;
|
||||
public long lastAppliedStorageTexCb;
|
||||
public ShaderManager.ProgramObject lastPreparedMovedSamplerProgram;
|
||||
public final int[] lastPreparedVertexMovedSamplerTextures = new int[MAX_IMAGE_UNITS];
|
||||
public final int[] lastPreparedFragmentMovedSamplerTextures = new int[MAX_IMAGE_UNITS];
|
||||
public int lastPreparedMovedSamplerUnitsGen = -1;
|
||||
public long lastPreparedMovedSamplerWriteGen = -1;
|
||||
public long lastPreparedMovedSamplerPass = -1;
|
||||
public int ssboBindGen;
|
||||
public int lastAppliedStorageBufBindGen = -1;
|
||||
public int lastAppliedStorageBufProgram;
|
||||
@@ -304,6 +310,10 @@ public final class ContextState {
|
||||
public final int[] boundStorageTextureAccess = new int[MAX_IMAGE_UNITS];
|
||||
public final int[] boundStorageTextureFormat = new int[MAX_IMAGE_UNITS];
|
||||
public final int[] boundTextures = new int[32];
|
||||
|
||||
public int boundTexture(int unit) {
|
||||
return unit >= 0 && unit < boundTextures.length ? boundTextures[unit] : 0;
|
||||
}
|
||||
public final int[] boundSamplerObjects = new int[32];
|
||||
|
||||
public boolean computeBatchRequested;
|
||||
|
||||
@@ -290,13 +290,19 @@ public final class FrameManager {
|
||||
f.emptyFramesThisFrame++;
|
||||
}
|
||||
|
||||
static boolean swapchainClearNeedsPassBreak(ContextState st, FrameState f) {
|
||||
return (st.pendingSwapchainClear || st.pendingSwapchainDepthClear || st.pendingSwapchainStencilClear) && f.renderPass != 0 && f.activeLayoutHash == FBO0_LAYOUT_HASH;
|
||||
private static boolean isFbo0PassOpen(FrameState f) {
|
||||
return f.renderPass != 0 && f.activeLayoutHash == FBO0_LAYOUT_HASH;
|
||||
}
|
||||
|
||||
public static boolean fbo0PassStaysOpen(ContextState st, FrameState f) {
|
||||
return isFbo0PassOpen(f)
|
||||
&& !st.pendingSwapchainClear && !st.pendingSwapchainDepthClear && !st.pendingSwapchainStencilClear;
|
||||
}
|
||||
|
||||
public boolean ensureFbo0RenderPass(FrameState f, ContextState st) {
|
||||
if (fbo0PassStaysOpen(st, f)) return false;
|
||||
final boolean clearColor = st.pendingSwapchainClear;
|
||||
if (swapchainClearNeedsPassBreak(st, f)) {
|
||||
if (isFbo0PassOpen(f)) {
|
||||
endRenderPassIfActive(f, PASS_END_CLEAR);
|
||||
}
|
||||
final boolean applied = ensureFbo0RenderPass(f,
|
||||
@@ -316,9 +322,6 @@ public final class FrameManager {
|
||||
|
||||
private boolean ensureFbo0RenderPass(FrameState f, float clearR, float clearG, float clearB, float clearA, boolean clear, boolean clearDepth, float depthValue, boolean clearStencil, int stencilValue) {
|
||||
if (!finalTarget.isReady() || f.commandBuffer == 0) return false;
|
||||
if (f.renderPass != 0 && f.activeLayoutHash == FBO0_LAYOUT_HASH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
endActiveEncoders(f, PASS_END_TARGET);
|
||||
|
||||
@@ -452,6 +455,14 @@ public final class FrameManager {
|
||||
f.fboPassesThisFrame++;
|
||||
f.currentColorTarget = colorTargets != null && colorTargets.remaining() > 0 ? colorTargets.texture() : 0;
|
||||
f.currentDepthTarget = depthTarget != null ? MemoryAccess.getAddress(depthTarget.address() + SDL_GPUDepthStencilTargetInfo.TEXTURE) : 0;
|
||||
if (resourceManager != null) {
|
||||
if (colorTargets != null) {
|
||||
for (int i = colorTargets.position(); i < colorTargets.limit(); i++) {
|
||||
resourceManager.markTextureWritten(MemoryAccess.getAddress(colorTargets.address(i) + SDL_GPUColorTargetInfo.TEXTURE));
|
||||
}
|
||||
}
|
||||
resourceManager.markTextureWritten(f.currentDepthTarget);
|
||||
}
|
||||
return f.renderPass;
|
||||
}
|
||||
|
||||
|
||||
+12
-26
@@ -6,7 +6,6 @@ import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.lwjgl.sdl.SDL_GPUColorTargetDescription;
|
||||
import org.lwjgl.sdl.SDL_GPUDepthStencilState;
|
||||
@@ -48,8 +47,7 @@ public final class AttachmentClear {
|
||||
private final Target fbo0Target = new Target();
|
||||
|
||||
private final FullscreenPass fullscreen;
|
||||
private final long[] sdlFragmentShaders = new long[MAX_COLOR_TARGETS + 1];
|
||||
private final boolean[] fragmentFailed = new boolean[MAX_COLOR_TARGETS + 1];
|
||||
private final FullscreenPass.Fragment[] fragments = new FullscreenPass.Fragment[MAX_COLOR_TARGETS + 1];
|
||||
|
||||
private final long[] cacheKeys = new long[CACHE_SLOTS];
|
||||
private final long[] cachePipelines = new long[CACHE_SLOTS];
|
||||
@@ -65,10 +63,17 @@ public final class AttachmentClear {
|
||||
private int buildStencilMask;
|
||||
private int buildColorMask;
|
||||
|
||||
public AttachmentClear(FrameManager frameManager, PipelineStore pipelineStore, ShaderManager shaderManager) {
|
||||
public AttachmentClear(FrameManager frameManager, PipelineStore pipelineStore, FullscreenPass fullscreen) {
|
||||
this.frameManager = frameManager;
|
||||
this.pipelineStore = pipelineStore;
|
||||
this.fullscreen = new FullscreenPass(shaderManager);
|
||||
this.fullscreen = fullscreen;
|
||||
for (int i = 0; i <= MAX_COLOR_TARGETS; i++) {
|
||||
final int count = i;
|
||||
fragments[i] = fullscreen.fragment(FRAGMENT_SOURCE + " COLOR_TARGETS=" + count, count > 0 ? 16 : -1, () -> {
|
||||
final String source = ShaderLoader.getShaderSource(FRAGMENT_SOURCE);
|
||||
return count == 0 ? source : withColorOutputs(source, count);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private final class Builder implements LongSupplier {
|
||||
@@ -171,7 +176,7 @@ public final class AttachmentClear {
|
||||
}
|
||||
|
||||
long getOrCreatePipeline(Target t, int colorTargets, boolean depth, boolean stencil, int stencilMask, int colorMask) {
|
||||
if (!ensureShaders(colorTargets)) return 0;
|
||||
if (colorTargets < 0 || colorTargets > MAX_COLOR_TARGETS || fragments[colorTargets].shader() == 0) return 0;
|
||||
|
||||
final int[] formats = t.colorFormats;
|
||||
final int count = formats != null ? formats.length : 0;
|
||||
@@ -239,26 +244,10 @@ public final class AttachmentClear {
|
||||
.compare_op(SDL_GPU_COMPAREOP_ALWAYS);
|
||||
}
|
||||
|
||||
return fullscreen.createPipeline(pipelineStore, stack, sdlFragmentShaders[buildColorTargets], colorDesc, numColorTargets, depthStencil, t.depthFormat, "AttachmentClear");
|
||||
return fullscreen.createPipeline(pipelineStore, stack, fragments[buildColorTargets].shader(), colorDesc, numColorTargets, depthStencil, t.depthFormat, "AttachmentClear");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ensureShaders(int colorTargets) {
|
||||
if (colorTargets < 0 || colorTargets > MAX_COLOR_TARGETS) return false;
|
||||
if (sdlFragmentShaders[colorTargets] != 0) return true;
|
||||
if (fragmentFailed[colorTargets]) return false;
|
||||
fragmentFailed[colorTargets] = true;
|
||||
|
||||
final String baseSource = ShaderLoader.getShaderSource(FRAGMENT_SOURCE);
|
||||
final String fragmentSource = colorTargets == 0 ? baseSource : withColorOutputs(baseSource, colorTargets);
|
||||
final long fragmentShader = fullscreen.compileFragment(fragmentSource, colorTargets > 0 ? 16 : -1, "AttachmentClear: clear_attachment COLOR_TARGETS=" + colorTargets);
|
||||
if (fragmentShader == 0) return false;
|
||||
|
||||
sdlFragmentShaders[colorTargets] = fragmentShader;
|
||||
fragmentFailed[colorTargets] = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String withColorOutputs(String source, int count) {
|
||||
final GlslTransformUtils.QuietParse parsed = GlslTransformUtils.parseBothQuiet(source);
|
||||
final Transformer transformer = new Transformer(parsed.full());
|
||||
@@ -274,9 +263,6 @@ public final class AttachmentClear {
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
fullscreen.shutdown();
|
||||
Arrays.fill(sdlFragmentShaders, 0L);
|
||||
Arrays.fill(fragmentFailed, false);
|
||||
Arrays.fill(cacheKeys, 0L);
|
||||
Arrays.fill(cachePipelines, 0L);
|
||||
cacheNext = 0;
|
||||
|
||||
+11
-69
@@ -5,18 +5,12 @@ import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.embeddedt.embeddium.impl.render.shader.ShaderLoader;
|
||||
import org.lwjgl.sdl.SDLError;
|
||||
import org.lwjgl.sdl.SDL_FColor;
|
||||
import org.lwjgl.sdl.SDL_GPUColorTargetDescription;
|
||||
import org.lwjgl.sdl.SDL_GPUColorTargetInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUDepthStencilState;
|
||||
import org.lwjgl.sdl.SDL_GPUDepthStencilTargetInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUSamplerCreateInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUTextureSamplerBinding;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
@@ -29,8 +23,6 @@ import static org.lwjgl.system.MemoryStack.stackPush;
|
||||
|
||||
public final class DepthStencilReadback {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
|
||||
private static final String DEPTH_FRAGMENT_SOURCE = "angelica:sdlgpu/readback_depth.fsh";
|
||||
private static final String STENCIL_FRAGMENT_SOURCE = "angelica:sdlgpu/readback_stencil.fsh";
|
||||
private static final long KEY_SEED = 0x3C6EF372A54FF53AL;
|
||||
@@ -44,11 +36,8 @@ public final class DepthStencilReadback {
|
||||
private final FBOClearTracker fboClearTracker;
|
||||
private final FullscreenPass fullscreen;
|
||||
|
||||
private long depthFragmentShader;
|
||||
private boolean depthFragmentFailed;
|
||||
private long stencilFragmentShader;
|
||||
private boolean stencilFragmentFailed;
|
||||
private long nearestSampler;
|
||||
private final FullscreenPass.Fragment depthFragment;
|
||||
private final FullscreenPass.Fragment stencilFragment;
|
||||
|
||||
private long depthPipeline;
|
||||
private final long[] stencilPipelines = new long[STENCIL_BITS];
|
||||
@@ -58,12 +47,14 @@ public final class DepthStencilReadback {
|
||||
private int buildBit;
|
||||
private int buildDepthFormat;
|
||||
|
||||
public DepthStencilReadback(FrameManager frameManager, ResourceManager resourceManager, PipelineStore pipelineStore, ShaderManager shaderManager, FBOClearTracker fboClearTracker) {
|
||||
public DepthStencilReadback(FrameManager frameManager, ResourceManager resourceManager, PipelineStore pipelineStore, FullscreenPass fullscreen, FBOClearTracker fboClearTracker) {
|
||||
this.frameManager = frameManager;
|
||||
this.resourceManager = resourceManager;
|
||||
this.pipelineStore = pipelineStore;
|
||||
this.fboClearTracker = fboClearTracker;
|
||||
this.fullscreen = new FullscreenPass(shaderManager);
|
||||
this.fullscreen = fullscreen;
|
||||
depthFragment = fullscreen.fragment(DEPTH_FRAGMENT_SOURCE, -1);
|
||||
stencilFragment = fullscreen.fragment(STENCIL_FRAGMENT_SOURCE, 16);
|
||||
}
|
||||
|
||||
private final class Builder implements LongSupplier {
|
||||
@@ -75,7 +66,7 @@ public final class DepthStencilReadback {
|
||||
if (!canRender(f, depthTexture, width, height)) return 0L;
|
||||
if (depthTexture == resourceManager.getSwapchainDepthStencil() && !resourceManager.isSwapchainDepthStencilSampleable()) return 0L;
|
||||
final long pipeline = getDepthPipeline();
|
||||
final long sampler = getNearestSampler();
|
||||
final long sampler = fullscreen.getNearestSampler();
|
||||
if (pipeline == 0L || sampler == 0L) return 0L;
|
||||
applyPendingClears(st, f, depthTexture);
|
||||
final long scratch = ensureDepthScratch(st, width, height);
|
||||
@@ -178,7 +169,7 @@ public final class DepthStencilReadback {
|
||||
|
||||
private long getDepthPipeline() {
|
||||
if (depthPipeline != 0L) return depthPipeline;
|
||||
if (!ensureDepthShader()) return 0L;
|
||||
if (depthFragment.shader() == 0L) return 0L;
|
||||
buildBit = -1;
|
||||
buildDepthFormat = 0;
|
||||
depthPipeline = pipelineStore.getOrBuild(Hashing.fmix64(KEY_SEED, -1), builder);
|
||||
@@ -188,7 +179,7 @@ public final class DepthStencilReadback {
|
||||
private boolean ensureStencilPipelines(int depthFormat) {
|
||||
if (depthFormat == 0) return false;
|
||||
if (stencilPipelinesFormat == depthFormat) return true;
|
||||
if (!ensureStencilShader()) return false;
|
||||
if (stencilFragment.shader() == 0L) return false;
|
||||
buildDepthFormat = depthFormat;
|
||||
final long formatKey = Hashing.fmix64(KEY_SEED, depthFormat);
|
||||
for (int b = 0; b < STENCIL_BITS; b++) {
|
||||
@@ -210,7 +201,7 @@ public final class DepthStencilReadback {
|
||||
colorDesc.get(0).format(DEPTH_TARGET_FORMAT).blend_state().enable_blend(false);
|
||||
final SDL_GPUDepthStencilState depthStencil = SDL_GPUDepthStencilState.calloc(stack)
|
||||
.compare_op(SDL_GPU_COMPAREOP_ALWAYS);
|
||||
return fullscreen.createPipeline(pipelineStore, stack, depthFragmentShader, colorDesc, 1, depthStencil, 0, "DepthStencilReadback depth");
|
||||
return fullscreen.createPipeline(pipelineStore, stack, depthFragment.shader(), colorDesc, 1, depthStencil, 0, "DepthStencilReadback depth");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,46 +233,10 @@ public final class DepthStencilReadback {
|
||||
.pass_op(SDL_GPU_STENCILOP_KEEP)
|
||||
.depth_fail_op(SDL_GPU_STENCILOP_KEEP)
|
||||
.compare_op(SDL_GPU_COMPAREOP_EQUAL);
|
||||
return fullscreen.createPipeline(pipelineStore, stack, stencilFragmentShader, colorDesc, 1, depthStencil, depthFormat, "DepthStencilReadback stencil");
|
||||
return fullscreen.createPipeline(pipelineStore, stack, stencilFragment.shader(), colorDesc, 1, depthStencil, depthFormat, "DepthStencilReadback stencil");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ensureDepthShader() {
|
||||
if (depthFragmentShader != 0L) return true;
|
||||
if (depthFragmentFailed) return false;
|
||||
depthFragmentFailed = true;
|
||||
depthFragmentShader = fullscreen.compileFragment(ShaderLoader.getShaderSource(DEPTH_FRAGMENT_SOURCE), -1, "DepthStencilReadback: readback_depth");
|
||||
if (depthFragmentShader == 0L) return false;
|
||||
depthFragmentFailed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean ensureStencilShader() {
|
||||
if (stencilFragmentShader != 0L) return true;
|
||||
if (stencilFragmentFailed) return false;
|
||||
stencilFragmentFailed = true;
|
||||
stencilFragmentShader = fullscreen.compileFragment(ShaderLoader.getShaderSource(STENCIL_FRAGMENT_SOURCE), 16, "DepthStencilReadback: readback_stencil");
|
||||
if (stencilFragmentShader == 0L) return false;
|
||||
stencilFragmentFailed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private long getNearestSampler() {
|
||||
if (nearestSampler != 0L) return nearestSampler;
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final SDL_GPUSamplerCreateInfo ci = SDL_GPUSamplerCreateInfo.calloc(stack)
|
||||
.min_filter(SDL_GPU_FILTER_NEAREST)
|
||||
.mag_filter(SDL_GPU_FILTER_NEAREST)
|
||||
.mipmap_mode(SDL_GPU_SAMPLERMIPMAPMODE_NEAREST)
|
||||
.address_mode_u(SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE)
|
||||
.address_mode_v(SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE)
|
||||
.address_mode_w(SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE);
|
||||
nearestSampler = SDL_CreateGPUSampler(pipelineStore.device().getDevice(), ci);
|
||||
if (nearestSampler == 0L) LOG.error("DepthStencilReadback: sampler creation failed: {}", SDLError.SDL_GetError());
|
||||
}
|
||||
return nearestSampler;
|
||||
}
|
||||
|
||||
public void release(ContextState st) {
|
||||
if (st.readbackDepthScratch != 0L) resourceManager.releaseTextureHandle(st.readbackDepthScratch);
|
||||
st.readbackDepthScratch = 0L;
|
||||
@@ -294,19 +249,6 @@ public final class DepthStencilReadback {
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
final long dev = pipelineStore.device().getDevice();
|
||||
if (dev != 0L) {
|
||||
if (depthFragmentShader != 0L) SDL_ReleaseGPUShader(dev, depthFragmentShader);
|
||||
if (stencilFragmentShader != 0L) SDL_ReleaseGPUShader(dev, stencilFragmentShader);
|
||||
if (fullscreen.vertexShader() != 0L) SDL_ReleaseGPUShader(dev, fullscreen.vertexShader());
|
||||
if (nearestSampler != 0L) SDL_ReleaseGPUSampler(dev, nearestSampler);
|
||||
}
|
||||
fullscreen.shutdown();
|
||||
depthFragmentShader = 0L;
|
||||
depthFragmentFailed = false;
|
||||
stencilFragmentShader = 0L;
|
||||
stencilFragmentFailed = false;
|
||||
nearestSampler = 0L;
|
||||
depthPipeline = 0L;
|
||||
Arrays.fill(stencilPipelines, 0L);
|
||||
stencilPipelinesFormat = 0;
|
||||
|
||||
+110
-32
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.util.MemoryAccess;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -14,32 +15,90 @@ import org.lwjgl.sdl.SDL_GPUGraphicsPipelineCreateInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUGraphicsPipelineTargetInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUMultisampleState;
|
||||
import org.lwjgl.sdl.SDL_GPURasterizerState;
|
||||
import org.lwjgl.sdl.SDL_GPUSamplerCreateInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUVertexInputState;
|
||||
import org.lwjgl.sdl.SDL_GPUViewport;
|
||||
import org.lwjgl.sdl.SDL_Rect;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
|
||||
final class FullscreenPass {
|
||||
/** Shared fullscreen shaders and sampler, owned and released by the backend. */
|
||||
public final class FullscreenPass {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
|
||||
private static final String VERTEX_SOURCE = "angelica:sdlgpu/clear_attachment.vsh";
|
||||
|
||||
private final ShaderManager shaderManager;
|
||||
private final Device device;
|
||||
private final List<Fragment> fragments = new ArrayList<>();
|
||||
private int vertexShaderId;
|
||||
private long sdlVertexShader;
|
||||
private long nearestSampler;
|
||||
private boolean samplerAttempted;
|
||||
private boolean vertexFailed;
|
||||
|
||||
FullscreenPass(ShaderManager shaderManager) {
|
||||
public FullscreenPass(ShaderManager shaderManager, Device device) {
|
||||
this.shaderManager = shaderManager;
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
long vertexShader() {
|
||||
return sdlVertexShader;
|
||||
final class Fragment {
|
||||
private final String label;
|
||||
private final Supplier<String> source;
|
||||
private final int requiredUboSize;
|
||||
private boolean attempted;
|
||||
private long shader;
|
||||
|
||||
private Fragment(String label, Supplier<String> source, int requiredUboSize) {
|
||||
this.label = label;
|
||||
this.source = source;
|
||||
this.requiredUboSize = requiredUboSize;
|
||||
}
|
||||
|
||||
long shader() {
|
||||
if (!attempted) {
|
||||
attempted = true;
|
||||
shader = compileFragment(source.get(), requiredUboSize, label);
|
||||
}
|
||||
return shader;
|
||||
}
|
||||
}
|
||||
|
||||
long compileFragment(String fragmentSource, int requiredUboSize, String label) {
|
||||
Fragment fragment(String path, int requiredUboSize) {
|
||||
return fragment(path, requiredUboSize, () -> ShaderLoader.getShaderSource(path));
|
||||
}
|
||||
|
||||
Fragment fragment(String label, int requiredUboSize, Supplier<String> source) {
|
||||
final Fragment fragment = new Fragment(label, source, requiredUboSize);
|
||||
fragments.add(fragment);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
long getNearestSampler() {
|
||||
if (samplerAttempted) return nearestSampler;
|
||||
samplerAttempted = true;
|
||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||
final SDL_GPUSamplerCreateInfo ci = SDL_GPUSamplerCreateInfo.calloc(stack)
|
||||
.min_filter(SDL_GPU_FILTER_NEAREST)
|
||||
.mag_filter(SDL_GPU_FILTER_NEAREST)
|
||||
.mipmap_mode(SDL_GPU_SAMPLERMIPMAPMODE_NEAREST)
|
||||
.address_mode_u(SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE)
|
||||
.address_mode_v(SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE)
|
||||
.address_mode_w(SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE);
|
||||
nearestSampler = SDL_CreateGPUSampler(device.getDevice(), ci);
|
||||
if (nearestSampler == 0L) LOG.error("FullscreenPass: sampler creation failed: {}", SDLError.SDL_GetError());
|
||||
}
|
||||
return nearestSampler;
|
||||
}
|
||||
|
||||
private long compileFragment(String fragmentSource, int requiredUboSize, String label) {
|
||||
if (vertexFailed) return 0;
|
||||
if (vertexShaderId == 0) {
|
||||
vertexShaderId = shaderManager.createShader(GL20.GL_VERTEX_SHADER);
|
||||
shaderManager.shaderSource(vertexShaderId, ShaderLoader.getShaderSource(VERTEX_SOURCE));
|
||||
@@ -47,38 +106,41 @@ final class FullscreenPass {
|
||||
}
|
||||
|
||||
final int fs = shaderManager.createShader(GL20.GL_FRAGMENT_SHADER);
|
||||
shaderManager.shaderSource(fs, fragmentSource);
|
||||
shaderManager.compileShader(fs);
|
||||
|
||||
final int prog = shaderManager.createProgram();
|
||||
shaderManager.attachShader(prog, vertexShaderId);
|
||||
shaderManager.attachShader(prog, fs);
|
||||
shaderManager.linkProgram(prog);
|
||||
try {
|
||||
shaderManager.shaderSource(fs, fragmentSource);
|
||||
shaderManager.compileShader(fs);
|
||||
shaderManager.attachShader(prog, vertexShaderId);
|
||||
shaderManager.attachShader(prog, fs);
|
||||
shaderManager.linkProgram(prog);
|
||||
|
||||
final ShaderManager.ProgramObject po = shaderManager.getProgram(prog);
|
||||
if (po == null || !po.linked) {
|
||||
LOG.error("{}: program failed to link: {}", label, po == null ? "no program object" : po.infoLog);
|
||||
return 0;
|
||||
}
|
||||
if (requiredUboSize >= 0 && po.fragmentUboSize != requiredUboSize) {
|
||||
LOG.error("{}: fragment UBO size {} != {}", label, po.fragmentUboSize, requiredUboSize);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (sdlVertexShader == 0) {
|
||||
sdlVertexShader = shaderManager.createSDLShader(po.vertexSpirv, SDL_GPU_SHADERSTAGE_VERTEX, po.vertexResources.numSamplers(), po.vertexResources.numUBOs(), po.vertexResources.numStorageBuffers(), po.vertexResources.numStorageTextures());
|
||||
if (sdlVertexShader == 0) {
|
||||
LOG.error("{}: vertex SDL shader creation failed", label);
|
||||
final ShaderManager.ProgramObject po = shaderManager.getProgram(prog);
|
||||
if (po == null || !po.linked) {
|
||||
vertexFailed = !shaderManager.isCompiled(vertexShaderId);
|
||||
LOG.error("{}: program failed to link: {}", label, po == null ? "no program object" : po.infoLog);
|
||||
return 0;
|
||||
}
|
||||
if (requiredUboSize >= 0 && po.fragmentUboSize != requiredUboSize) {
|
||||
LOG.error("{}: fragment UBO size {} != {}", label, po.fragmentUboSize, requiredUboSize);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
final long fragmentShader = shaderManager.createSDLShader(po.fragmentSpirv, SDL_GPU_SHADERSTAGE_FRAGMENT, po.fragmentResources.numSamplers(), po.fragmentResources.numUBOs(), po.fragmentResources.numStorageBuffers(), po.fragmentResources.numStorageTextures());
|
||||
if (fragmentShader == 0) {
|
||||
LOG.error("{}: fragment SDL shader creation failed", label);
|
||||
return 0;
|
||||
if (sdlVertexShader == 0) {
|
||||
sdlVertexShader = shaderManager.createSDLShader(po.vertexSpirv, SDL_GPU_SHADERSTAGE_VERTEX, po.vertexResources.numSamplers(), po.vertexResources.numUBOs(), po.vertexResources.numStorageBuffers(), po.vertexResources.numStorageTextures());
|
||||
if (sdlVertexShader == 0) {
|
||||
vertexFailed = true;
|
||||
LOG.error("{}: vertex SDL shader creation failed", label);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
final long fragmentShader = shaderManager.createSDLShader(po.fragmentSpirv, SDL_GPU_SHADERSTAGE_FRAGMENT, po.fragmentResources.numSamplers(), po.fragmentResources.numUBOs(), po.fragmentResources.numStorageBuffers(), po.fragmentResources.numStorageTextures());
|
||||
if (fragmentShader == 0) LOG.error("{}: fragment SDL shader creation failed", label);
|
||||
return fragmentShader;
|
||||
} finally {
|
||||
shaderManager.releaseProgramFinal(prog);
|
||||
shaderManager.deleteShader(fs);
|
||||
}
|
||||
return fragmentShader;
|
||||
}
|
||||
|
||||
long createPipeline(PipelineStore pipelineStore, MemoryStack stack, long fragmentShader, SDL_GPUColorTargetDescription.Buffer colorDesc, int numColorTargets, SDL_GPUDepthStencilState depthStencil, int depthFormat, String label) {
|
||||
@@ -144,8 +206,24 @@ final class FullscreenPass {
|
||||
st.lastAppliedVboBindCb = 0;
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
public void shutdown() {
|
||||
final long dev = device.getDevice();
|
||||
if (dev != 0L) {
|
||||
for (Fragment fragment : fragments) {
|
||||
if (fragment.shader != 0L) SDL_ReleaseGPUShader(dev, fragment.shader);
|
||||
}
|
||||
if (sdlVertexShader != 0L) SDL_ReleaseGPUShader(dev, sdlVertexShader);
|
||||
if (nearestSampler != 0L) SDL_ReleaseGPUSampler(dev, nearestSampler);
|
||||
}
|
||||
for (Fragment fragment : fragments) {
|
||||
fragment.shader = 0;
|
||||
fragment.attempted = false;
|
||||
}
|
||||
if (vertexShaderId != 0) shaderManager.deleteShader(vertexShaderId);
|
||||
vertexShaderId = 0;
|
||||
sdlVertexShader = 0;
|
||||
nearestSampler = 0;
|
||||
samplerAttempted = false;
|
||||
vertexFailed = false;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -75,7 +75,7 @@ public final class LogicOpEmulator {
|
||||
cp = frameManager.ensureCopyPass(FrameManager.PASS_END_LOGIC_OP);
|
||||
resourceManager.flushBatchedUploads(cp);
|
||||
}
|
||||
TextureOps.copyTexture(cp, tex, targetLevel(fbo, loc), 0, 0, scratch, 0, 0, 0, w, h);
|
||||
TextureOps.copyTexture(resourceManager, cp, tex, targetLevel(fbo, loc), 0, 0, scratch, 0, 0, 0, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
|
||||
import org.lwjgl.sdl.SDL_GPUColorTargetDescription;
|
||||
import org.lwjgl.sdl.SDL_GPUColorTargetInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUDepthStencilState;
|
||||
import org.lwjgl.sdl.SDL_GPUTextureSamplerBinding;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.function.LongSupplier;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
import static org.lwjgl.system.MemoryStack.stackPush;
|
||||
|
||||
/**
|
||||
* Writes a texture SDL cannot give storage-read usage (depth, sRGB) into one it can.
|
||||
*/
|
||||
public final class MovedSamplerConversion {
|
||||
|
||||
private static final String FRAGMENT_SOURCE = "angelica:sdlgpu/moved_sampler_convert.fsh";
|
||||
private static final long KEY_SEED = 0x6A09E667F3BCC908L;
|
||||
|
||||
private final FrameManager frameManager;
|
||||
private final PipelineStore pipelineStore;
|
||||
private final FullscreenPass fullscreen;
|
||||
private final FullscreenPass.Fragment fragment;
|
||||
private final LongSupplier builder = this::createPipeline;
|
||||
private int buildFormat;
|
||||
|
||||
public MovedSamplerConversion(FrameManager frameManager, PipelineStore pipelineStore, FullscreenPass fullscreen) {
|
||||
this.frameManager = frameManager;
|
||||
this.pipelineStore = pipelineStore;
|
||||
this.fullscreen = fullscreen;
|
||||
fragment = fullscreen.fragment(FRAGMENT_SOURCE, 16);
|
||||
}
|
||||
|
||||
public boolean convert(ContextState st, long source, long target, int targetFormat, int width, int height, int levels) {
|
||||
final FrameState f = frameManager.frame();
|
||||
if (!f.frameActive) return false;
|
||||
final long pipeline = pipelineFor(targetFormat);
|
||||
final long sampler = fullscreen.getNearestSampler();
|
||||
if (pipeline == 0L || sampler == 0L) return false;
|
||||
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final SDL_GPUColorTargetInfo.Buffer color = SDL_GPUColorTargetInfo.calloc(1, stack);
|
||||
final SDL_GPUTextureSamplerBinding.Buffer binding = SDL_GPUTextureSamplerBinding.calloc(1, stack);
|
||||
binding.get(0).texture(source).sampler(sampler);
|
||||
final ByteBuffer level = stack.calloc(16);
|
||||
for (int l = 0; l < Math.max(1, levels); l++) {
|
||||
final int w = Math.max(1, width >> l);
|
||||
final int h = Math.max(1, height >> l);
|
||||
color.get(0).texture(target).mip_level(l).load_op(SDL_GPU_LOADOP_DONT_CARE).store_op(SDL_GPU_STOREOP_STORE);
|
||||
final long rp = frameManager.beginRenderPass(color, null);
|
||||
SDL_BindGPUGraphicsPipeline(rp, pipeline);
|
||||
FullscreenPass.setViewport(rp, st, w, h, 0f);
|
||||
FullscreenPass.setScissor(rp, st, 0, 0, w, h);
|
||||
SDL_BindGPUFragmentSamplers(rp, 0, binding);
|
||||
level.putInt(0, l);
|
||||
SDL_PushGPUFragmentUniformData(frameManager.getCommandBuffer(f), 0, level);
|
||||
SDL_DrawGPUPrimitives(rp, 3, 1, 0, 0);
|
||||
frameManager.endRenderPassIfActive(f, FrameManager.PASS_END_COPY);
|
||||
}
|
||||
}
|
||||
FullscreenPass.markStateClobbered(st);
|
||||
st.lastPushedProgramFs = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
private long pipelineFor(int targetFormat) {
|
||||
if (fragment.shader() == 0L) return 0L;
|
||||
buildFormat = targetFormat;
|
||||
return pipelineStore.getOrBuild(Hashing.fmix64(KEY_SEED, targetFormat), builder);
|
||||
}
|
||||
|
||||
private long createPipeline() {
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final SDL_GPUColorTargetDescription.Buffer colorDesc = SDL_GPUColorTargetDescription.calloc(1, stack);
|
||||
colorDesc.get(0).format(buildFormat).blend_state().enable_blend(false);
|
||||
final SDL_GPUDepthStencilState depthStencil = SDL_GPUDepthStencilState.calloc(stack).compare_op(SDL_GPU_COMPAREOP_ALWAYS);
|
||||
return fullscreen.createPipeline(pipelineStore, stack, fragment.shader(), colorDesc, 1, depthStencil, 0, "MovedSamplerConversion");
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-3
@@ -23,6 +23,7 @@ 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.MovedSamplerCopies;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.sampler.StorageBufferBinder;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.sampler.StorageTextureBinder;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.MatrixMarshal;
|
||||
@@ -57,6 +58,7 @@ public final class PipelineApplier {
|
||||
private final PersistentBufferSync persistentSync;
|
||||
private final SamplerBinder samplerBinder;
|
||||
private final StorageTextureBinder storageTextureBinder;
|
||||
private final MovedSamplerCopies movedSamplerCopies;
|
||||
private final StorageBufferBinder storageBufferBinder;
|
||||
private final LogicOpEmulator logicOpEmulator;
|
||||
|
||||
@@ -76,7 +78,7 @@ public final class PipelineApplier {
|
||||
|
||||
private final int[] scissorRect = new int[4];
|
||||
|
||||
public PipelineApplier(FrameManager frameManager, ResourceManager resourceManager, ShaderManager shaderManager, PipelineStore pipelineStore, FBOClearTracker fboClearTracker, PersistentBufferSync persistentSync, SamplerBinder samplerBinder, StorageTextureBinder storageTextureBinder, StorageBufferBinder storageBufferBinder) {
|
||||
public PipelineApplier(FrameManager frameManager, ResourceManager resourceManager, ShaderManager shaderManager, PipelineStore pipelineStore, FBOClearTracker fboClearTracker, PersistentBufferSync persistentSync, SamplerBinder samplerBinder, StorageTextureBinder storageTextureBinder, StorageBufferBinder storageBufferBinder, MovedSamplerCopies movedSamplerCopies) {
|
||||
this.frameManager = frameManager;
|
||||
this.resourceManager = resourceManager;
|
||||
this.shaderManager = shaderManager;
|
||||
@@ -85,6 +87,7 @@ public final class PipelineApplier {
|
||||
this.persistentSync = persistentSync;
|
||||
this.samplerBinder = samplerBinder;
|
||||
this.storageTextureBinder = storageTextureBinder;
|
||||
this.movedSamplerCopies = movedSamplerCopies;
|
||||
this.storageBufferBinder = storageBufferBinder;
|
||||
this.logicOpEmulator = new LogicOpEmulator(frameManager, resourceManager, shaderManager, this);
|
||||
this.instancedConstants = new InstancedConstantBuffers(resourceManager);
|
||||
@@ -122,7 +125,7 @@ public final class PipelineApplier {
|
||||
|
||||
public void ensureDrawRenderPass(ContextState st, FrameState f) {
|
||||
logicOpEmulator.beforeDraw(st, f);
|
||||
ensureRenderPass(st, f);
|
||||
ensureRenderPass(st, f, true);
|
||||
}
|
||||
|
||||
public void ensureRenderPass(ContextState st) {
|
||||
@@ -130,6 +133,10 @@ public final class PipelineApplier {
|
||||
}
|
||||
|
||||
public void ensureRenderPass(ContextState st, FrameState f) {
|
||||
ensureRenderPass(st, f, false);
|
||||
}
|
||||
|
||||
private void ensureRenderPass(ContextState st, FrameState f, boolean draw) {
|
||||
if (!f.frameActive) return;
|
||||
|
||||
fboClearTracker.flushPendingClearsForBoundSamplers(st);
|
||||
@@ -151,6 +158,7 @@ public final class PipelineApplier {
|
||||
}
|
||||
|
||||
if (st.boundFboId == 0) {
|
||||
if (draw) movedSamplerCopies.prepare(st, f, FrameManager.fbo0PassStaysOpen(st, f));
|
||||
if (frameManager.ensureFbo0RenderPass(f, st)) {
|
||||
st.pipeline.setDepthTargetFormat(resourceManager.getSwapchainDepthStencilFormat());
|
||||
}
|
||||
@@ -167,7 +175,10 @@ public final class PipelineApplier {
|
||||
if (!fbo.hasAnyColor && fbo.depthTexture == 0) return;
|
||||
|
||||
final long primaryTarget = fbo.primaryTarget;
|
||||
if (f.renderPass != 0 && f.activeLayoutHash == fbo.structuralLayoutHash && f.currentColorTarget == primaryTarget && f.currentDepthTarget == fbo.depthTexture && !FBOClearTracker.fboHasPendingClear(st, fbo)) {
|
||||
final boolean keepsOpenPass = f.renderPass != 0 && f.activeLayoutHash == fbo.structuralLayoutHash && f.currentColorTarget == primaryTarget && f.currentDepthTarget == fbo.depthTexture && !FBOClearTracker.fboHasPendingClear(st, fbo);
|
||||
if (draw) movedSamplerCopies.prepare(st, f, keepsOpenPass);
|
||||
// Preparing a stale sampler copy can end the pass even when the attachments still match.
|
||||
if (keepsOpenPass && f.renderPass != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+8
-3
@@ -106,6 +106,13 @@ public final class FBOClearTracker {
|
||||
final int vertSamplers = Math.min(prog.vertexResources.numSamplers(), ContextState.MAX_SAMPLERS);
|
||||
collectSamplerFlush(st, SamplerLookup.resolvedUnits(prog, false), vertSamplers, colorHandles, colorGlIds, depthHandles, depthGlIds);
|
||||
|
||||
if (prog.hasReadMovedSamplers) {
|
||||
final int[] fragMoved = SamplerLookup.movedSamplerUnits(prog, true);
|
||||
collectSamplerFlush(st, fragMoved, fragMoved.length, colorHandles, colorGlIds, depthHandles, depthGlIds);
|
||||
final int[] vertMoved = SamplerLookup.movedSamplerUnits(prog, false);
|
||||
collectSamplerFlush(st, vertMoved, vertMoved.length, colorHandles, colorGlIds, depthHandles, depthGlIds);
|
||||
}
|
||||
|
||||
if (!colorHandles.isEmpty() || !depthHandles.isEmpty()) {
|
||||
materializeFlush(st, colorHandles, colorGlIds, depthHandles, depthGlIds);
|
||||
}
|
||||
@@ -114,9 +121,7 @@ public final class FBOClearTracker {
|
||||
|
||||
private void collectSamplerFlush(ContextState st, int[] samplerUnits, int samplerCount, LongArrayList colorHandles, IntArrayList colorGlIds, LongArrayList depthHandles, IntArrayList depthGlIds) {
|
||||
for (int i = 0; i < samplerCount; i++) {
|
||||
final int glUnit = samplerUnits[i];
|
||||
if (glUnit < 0 || glUnit >= st.boundTextures.length) continue;
|
||||
final int glTexId = st.boundTextures[glUnit];
|
||||
final int glTexId = st.boundTexture(samplerUnits[i]);
|
||||
if (glTexId == 0) continue;
|
||||
final long handle = resourceManager.getTextureHandle(glTexId);
|
||||
if (handle == 0) continue;
|
||||
|
||||
+116
-41
@@ -222,7 +222,7 @@ public final class ResourceManager {
|
||||
}
|
||||
|
||||
private static int defaultUsage(int sdlFormat) {
|
||||
if (isDepthFormat(sdlFormat)) {
|
||||
if (PixelOps.isDepthFormat(sdlFormat)) {
|
||||
return SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
}
|
||||
if (isIntegerFormat(sdlFormat)) {
|
||||
@@ -371,6 +371,7 @@ public final class ResourceManager {
|
||||
releaseTextureDeferred(handle);
|
||||
return;
|
||||
}
|
||||
notifyTextureReleased(handle);
|
||||
releaseTextureHandle(handle);
|
||||
}
|
||||
|
||||
@@ -393,6 +394,46 @@ public final class ResourceManager {
|
||||
}
|
||||
|
||||
private final LongOpenHashSet definedContentTextures = new LongOpenHashSet();
|
||||
private volatile Long2ObjectOpenHashMap<AtomicLong> textureWriteVersions = new Long2ObjectOpenHashMap<>();
|
||||
private final AtomicLong textureWriteGeneration = new AtomicLong();
|
||||
|
||||
public void trackTextureWrites(long handle) {
|
||||
if (handle != 0) setWriteTracking(handle, true);
|
||||
}
|
||||
|
||||
public void stopTrackingTextureWrites(long handle) {
|
||||
if (textureWriteVersions.containsKey(handle)) setWriteTracking(handle, false);
|
||||
}
|
||||
|
||||
private void setWriteTracking(long handle, boolean track) {
|
||||
wLock.lock();
|
||||
try {
|
||||
if (textureWriteVersions.containsKey(handle) == track) return;
|
||||
final Long2ObjectOpenHashMap<AtomicLong> versions = new Long2ObjectOpenHashMap<>(textureWriteVersions);
|
||||
if (track) versions.put(handle, new AtomicLong());
|
||||
else versions.remove(handle);
|
||||
textureWriteVersions = versions;
|
||||
textureWriteGeneration.incrementAndGet();
|
||||
} finally {
|
||||
wLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public long textureWriteGeneration() {
|
||||
return textureWriteGeneration.get();
|
||||
}
|
||||
|
||||
public long textureWriteVersion(long handle) {
|
||||
final AtomicLong version = textureWriteVersions.get(handle);
|
||||
return version == null ? 0 : version.get();
|
||||
}
|
||||
|
||||
public void markTextureWritten(long handle) {
|
||||
final AtomicLong version = textureWriteVersions.get(handle);
|
||||
if (version == null) return;
|
||||
version.incrementAndGet();
|
||||
textureWriteGeneration.incrementAndGet();
|
||||
}
|
||||
|
||||
public boolean isTextureContentDefined(long handle) {
|
||||
if (handle == 0) return false;
|
||||
@@ -448,6 +489,7 @@ public final class ResourceManager {
|
||||
if (handle == 0) return;
|
||||
dropBatchedUploadsTargeting(handle);
|
||||
markTextureContentUndefined(handle);
|
||||
stopTrackingTextureWrites(handle);
|
||||
final boolean live;
|
||||
wLock.lock();
|
||||
try {
|
||||
@@ -605,7 +647,7 @@ public final class ResourceManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean copyAllMips(long srcTex, long dstTex, int glTarget, int width, int height, int depth, int levels) {
|
||||
public boolean copyAllMips(long srcTex, long dstTex, int glTarget, int width, int height, int depth, int levels) {
|
||||
final long cp = frameManager.ensureCopyPass();
|
||||
if (cp == 0) return false;
|
||||
flushBatchedUploads(cp);
|
||||
@@ -625,6 +667,7 @@ public final class ResourceManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
markTextureWritten(dstTex);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -838,6 +881,18 @@ public final class ResourceManager {
|
||||
} finally {
|
||||
wLock.unlock();
|
||||
}
|
||||
notifyTextureReleased(handle);
|
||||
}
|
||||
|
||||
private LongConsumer textureReleaseListener;
|
||||
|
||||
public void setTextureReleaseListener(LongConsumer listener) {
|
||||
textureReleaseListener = listener;
|
||||
}
|
||||
|
||||
private void notifyTextureReleased(long handle) {
|
||||
final LongConsumer listener = textureReleaseListener;
|
||||
if (listener != null) listener.accept(handle);
|
||||
}
|
||||
|
||||
private static volatile boolean singleThreadedReads;
|
||||
@@ -1585,6 +1640,7 @@ public final class ResourceManager {
|
||||
public boolean arenaUpload(ByteBuffer data, long dstHandle, long dstOffset, boolean cycle) {
|
||||
final FrameManager.FrameState f = frameManager.frame();
|
||||
final int size = data.remaining();
|
||||
if (size == 0) return true;
|
||||
if (!f.arena.fits(size)) {
|
||||
return false;
|
||||
}
|
||||
@@ -1682,6 +1738,7 @@ public final class ResourceManager {
|
||||
if (srcOff < 0 || size < 0 || srcOff + size > src.capacity()) {
|
||||
throw new IllegalStateException("uploadRangeToBuffer: bad source range srcOff=" + srcOff + " size=" + size + " src.capacity=" + src.capacity());
|
||||
}
|
||||
if (size == 0) return;
|
||||
final long xfer = acquireTransferBuffer(size);
|
||||
if (xfer == 0) return;
|
||||
|
||||
@@ -1779,6 +1836,7 @@ public final class ResourceManager {
|
||||
}
|
||||
}
|
||||
markTextureContentDefined(gpuTexture);
|
||||
markTextureWritten(gpuTexture);
|
||||
frameManager.recordUploadCommands(sliceBytes * d, d);
|
||||
return true;
|
||||
} finally {
|
||||
@@ -1832,6 +1890,8 @@ public final class ResourceManager {
|
||||
f.pendingTexLevels.add(level);
|
||||
f.pendingTexOffsets.add(f.batchOffset);
|
||||
f.batchOffset += size;
|
||||
// Mark queued uploads now: sampler copies test freshness before the copy pass flushes them.
|
||||
markTextureWritten(gpuTexture);
|
||||
}
|
||||
|
||||
public void flushBatchedUploads(long copyPass) {
|
||||
@@ -1935,6 +1995,7 @@ public final class ResourceManager {
|
||||
SDL_UploadToGPUTexture(copyPass, src, dst, false);
|
||||
}
|
||||
markTextureContentDefined(tex);
|
||||
markTextureWritten(tex);
|
||||
frameManager.recordUploadCommands(size, 1);
|
||||
} finally {
|
||||
returnTransferBufferThreadSafe(xfer, size);
|
||||
@@ -1964,6 +2025,7 @@ public final class ResourceManager {
|
||||
st.frameHighestEnqueuedSeq = seq;
|
||||
lastTextureUploadSeq = seq;
|
||||
markTextureContentDefined(tex);
|
||||
markTextureWritten(tex);
|
||||
tt.enqueue(TransferThread.TextureRegionUpload.acquire(xfer, tex, x, y, w, h, level, size, seq));
|
||||
tt.wake();
|
||||
}
|
||||
@@ -2140,51 +2202,67 @@ public final class ResourceManager {
|
||||
}
|
||||
|
||||
public long getOrCreateFallbackTexture() {
|
||||
if (fallbackTexture != 0) return fallbackTexture;
|
||||
if (fallbackTexture == 0) {
|
||||
fallbackTexture = createFallback(SDL_GPU_TEXTURETYPE_2D, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
|
||||
SDL_GPU_TEXTUREUSAGE_SAMPLER, new byte[] { 0, 0, 0, (byte) 0xFF });
|
||||
}
|
||||
return fallbackTexture;
|
||||
}
|
||||
|
||||
public long createStorageReadTexture(TextureMeta meta, int sdlFormat, int extraUsage) {
|
||||
final int type = mapTextureType(meta.glTarget());
|
||||
final int usage = SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ | extraUsage;
|
||||
if (!SDL_GPUTextureSupportsFormat(device.getDevice(), sdlFormat, type, usage)) return 0;
|
||||
try (var stack = stackPush()) {
|
||||
final SDL_GPUTextureCreateInfo ci = SDL_GPUTextureCreateInfo.calloc(stack)
|
||||
.type(SDL_GPU_TEXTURETYPE_2D)
|
||||
.format(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM)
|
||||
.usage(SDL_GPU_TEXTUREUSAGE_SAMPLER)
|
||||
.width(1)
|
||||
.height(1)
|
||||
.layer_count_or_depth(1)
|
||||
.num_levels(1);
|
||||
|
||||
fallbackTexture = SDL_CreateGPUTexture(device.getDevice(), ci);
|
||||
if (fallbackTexture == 0) {
|
||||
LOG.error("Failed to create fallback texture: {}", SDLError.SDL_GetError());
|
||||
.type(type)
|
||||
.format(sdlFormat)
|
||||
.usage(usage)
|
||||
.width(meta.width()).height(meta.height()).layer_count_or_depth(meta.depth()).num_levels(meta.levels());
|
||||
final long handle = SDL_CreateGPUTexture(device.getDevice(), ci);
|
||||
if (handle == 0) {
|
||||
LOG.error("Failed to create storage-read texture {}x{} sdlFmt=0x{}: {}", meta.width(), meta.height(), Integer.toHexString(sdlFormat), SDLError.SDL_GetError());
|
||||
return 0;
|
||||
}
|
||||
trackTextureHandle(fallbackTexture);
|
||||
trackTextureHandle(handle);
|
||||
return handle;
|
||||
}
|
||||
// GL 4.4 sec 11.1.3.5: sampling an incomplete texture yields (0,0,0,1).
|
||||
final byte[] texel = { 0, 0, 0, (byte) 0xFF };
|
||||
seedOneTexel(fallbackTexture, texel);
|
||||
return fallbackTexture;
|
||||
}
|
||||
|
||||
private long fallbackStorageTexture2D;
|
||||
public long getOrCreateFallbackStorageTexture2D() {
|
||||
if (fallbackStorageTexture2D == 0) {
|
||||
fallbackStorageTexture2D = createFallback(SDL_GPU_TEXTURETYPE_2D, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
|
||||
SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ, new byte[] { 0, 0, 0, (byte) 0xFF });
|
||||
}
|
||||
return fallbackStorageTexture2D;
|
||||
}
|
||||
|
||||
private long fallbackStorageTexture3D;
|
||||
public long getOrCreateFallbackStorageTexture3D() {
|
||||
if (fallbackStorageTexture3D != 0) return fallbackStorageTexture3D;
|
||||
if (fallbackStorageTexture3D == 0) {
|
||||
fallbackStorageTexture3D = createFallback(SDL_GPU_TEXTURETYPE_3D, SDL_GPU_TEXTUREFORMAT_R8_UINT,
|
||||
SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ | SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ
|
||||
| SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE, new byte[1]);
|
||||
}
|
||||
return fallbackStorageTexture3D;
|
||||
}
|
||||
|
||||
private long createFallback(int type, int format, int usage, byte[] texel) {
|
||||
final long texture;
|
||||
try (var stack = stackPush()) {
|
||||
final SDL_GPUTextureCreateInfo ci = SDL_GPUTextureCreateInfo.calloc(stack)
|
||||
.type(SDL_GPU_TEXTURETYPE_3D)
|
||||
.format(SDL_GPU_TEXTUREFORMAT_R8_UINT)
|
||||
.usage(SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ
|
||||
| SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ
|
||||
| SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE)
|
||||
.type(type).format(format).usage(usage)
|
||||
.width(1).height(1).layer_count_or_depth(1).num_levels(1);
|
||||
fallbackStorageTexture3D = SDL_CreateGPUTexture(device.getDevice(), ci);
|
||||
if (fallbackStorageTexture3D == 0) {
|
||||
LOG.error("Failed to create fallback storage 3D texture: {}", SDLError.SDL_GetError());
|
||||
return 0;
|
||||
}
|
||||
trackTextureHandle(fallbackStorageTexture3D);
|
||||
texture = SDL_CreateGPUTexture(device.getDevice(), ci);
|
||||
}
|
||||
seedOneTexel(fallbackStorageTexture3D, new byte[1]);
|
||||
return fallbackStorageTexture3D;
|
||||
if (texture == 0) {
|
||||
LOG.error("Failed to create fallback texture (type={} format={} usage={}): {}", type, format, usage, SDLError.SDL_GetError());
|
||||
return 0;
|
||||
}
|
||||
trackTextureHandle(texture);
|
||||
seedOneTexel(texture, texel);
|
||||
return texture;
|
||||
}
|
||||
|
||||
private long fallbackStorageBuffer;
|
||||
@@ -2277,6 +2355,7 @@ public final class ResourceManager {
|
||||
.w(1).h(1).d(1);
|
||||
SDL_UploadToGPUTexture(copyPass, src, dst, false);
|
||||
SDL_EndGPUCopyPass(copyPass);
|
||||
markTextureWritten(texture);
|
||||
}
|
||||
Submits.submit(cb);
|
||||
}
|
||||
@@ -2556,14 +2635,6 @@ public final class ResourceManager {
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean isDepthFormat(int sdlFormat) {
|
||||
return sdlFormat == SDL_GPU_TEXTUREFORMAT_D16_UNORM
|
||||
|| sdlFormat == SDL_GPU_TEXTUREFORMAT_D24_UNORM
|
||||
|| sdlFormat == SDL_GPU_TEXTUREFORMAT_D32_FLOAT
|
||||
|| sdlFormat == SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT
|
||||
|| sdlFormat == SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT;
|
||||
}
|
||||
|
||||
static boolean isIntegerFormat(int sdlFormat) {
|
||||
return sdlFormat == SDL_GPU_TEXTUREFORMAT_R8_UINT
|
||||
|| sdlFormat == SDL_GPU_TEXTUREFORMAT_R8G8_UINT
|
||||
@@ -2647,6 +2718,10 @@ public final class ResourceManager {
|
||||
releaseTextureHandle(fallbackStorageTexture3D);
|
||||
fallbackStorageTexture3D = 0;
|
||||
}
|
||||
if (fallbackStorageTexture2D != 0) {
|
||||
releaseTextureHandle(fallbackStorageTexture2D);
|
||||
fallbackStorageTexture2D = 0;
|
||||
}
|
||||
synchronized (computeStandInTextures) {
|
||||
for (long handle : computeStandInTextures.values()) releaseTextureHandle(handle);
|
||||
computeStandInTextures.clear();
|
||||
|
||||
@@ -198,15 +198,16 @@ public final class TextureOps {
|
||||
final long cp = frameManager.ensureCopyPass();
|
||||
if (cp == 0) return;
|
||||
resourceManager.flushBatchedUploads(cp);
|
||||
copyTexture(cp, srcTex, srcLevel, srcX, srcY, dstTex, dstLevel, dstX, dstY, w, h);
|
||||
copyTexture(resourceManager, cp, srcTex, srcLevel, srcX, srcY, dstTex, dstLevel, dstX, dstY, w, h);
|
||||
}
|
||||
|
||||
public static void copyTexture(long cp, long srcTex, int srcLevel, int srcX, int srcY, long dstTex, int dstLevel, int dstX, int dstY, int w, int h) {
|
||||
public static void copyTexture(ResourceManager resourceManager, long cp, long srcTex, int srcLevel, int srcX, int srcY, long dstTex, int dstLevel, int dstX, int dstY, int w, int h) {
|
||||
if (cp == 0) return;
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
final var src = SDL_GPUTextureLocation.calloc(stack).texture(srcTex).mip_level(srcLevel).x(srcX).y(srcY);
|
||||
final var dst = SDL_GPUTextureLocation.calloc(stack).texture(dstTex).mip_level(dstLevel).x(dstX).y(dstY);
|
||||
SDL_CopyGPUTextureToTexture(cp, src, dst, w, h, 1, false);
|
||||
resourceManager.markTextureWritten(dstTex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -228,6 +229,7 @@ public final class TextureOps {
|
||||
info.flip_mode(flipMode);
|
||||
frameManager.noteBlit();
|
||||
SDL_BlitGPUTexture(cb, info);
|
||||
resourceManager.markTextureWritten(dstTex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,6 +264,7 @@ public final class TextureOps {
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
|
||||
if (meta == null || meta.levels() <= 1) continue;
|
||||
SDL_GenerateMipmapsForGPUTexture(uploadCb, handle);
|
||||
resourceManager.markTextureWritten(handle);
|
||||
}
|
||||
|
||||
if (!Submits.submit(uploadCb)) {
|
||||
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.sampler;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.MovedSamplerConversion;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongArrayList;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_R32_FLOAT;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
|
||||
/**
|
||||
* SDL reads texture slots in layout GENERAL, but a sampleable texture sits in SHADER_READ_ONLY_OPTIMAL
|
||||
* and SDL won't give one texture both usages. So a moved sampler reads a GRAPHICS_STORAGE_READ copy of its texture,
|
||||
* refreshed after the source is written. Formats Vulkan can't storage-read are converted to what texelFetch returns:
|
||||
* depth to R32F, anything else (sRGB) to RGBA16F. D3D12 (one resource state for both usages) and Metal (no layouts)
|
||||
* bind the texture directly.
|
||||
*/
|
||||
public final class MovedSamplerCopies {
|
||||
private static final Logger LOG = LogManager.getLogger("Angelica-SDLGPU");
|
||||
private static final int UNUSED_FRAMES_BEFORE_RELEASE = 8;
|
||||
|
||||
private static final class Copy {
|
||||
final long texture;
|
||||
final ResourceManager.TextureMeta meta;
|
||||
final int convertedFormat;
|
||||
FrameState readFrame;
|
||||
long readPass = -1;
|
||||
long writeVersion = -1;
|
||||
long pendingWriteVersion;
|
||||
long lastUsedFrame;
|
||||
|
||||
Copy(long texture, ResourceManager.TextureMeta meta, int convertedFormat) {
|
||||
this.texture = texture;
|
||||
this.meta = meta;
|
||||
this.convertedFormat = convertedFormat;
|
||||
}
|
||||
}
|
||||
|
||||
private final Device device;
|
||||
private final FrameManager frameManager;
|
||||
private final ResourceManager resourceManager;
|
||||
private final MovedSamplerConversion conversion;
|
||||
private final Long2ObjectOpenHashMap<Copy> bySource = new Long2ObjectOpenHashMap<>();
|
||||
private final LongArrayList stale = new LongArrayList();
|
||||
private final IntOpenHashSet unsupportedWarned = new IntOpenHashSet();
|
||||
private long frameNumber;
|
||||
|
||||
public MovedSamplerCopies(Device device, FrameManager frameManager, ResourceManager resourceManager, MovedSamplerConversion conversion) {
|
||||
this.device = device;
|
||||
this.frameManager = frameManager;
|
||||
this.resourceManager = resourceManager;
|
||||
this.conversion = conversion;
|
||||
}
|
||||
|
||||
public void prepare(ContextState st, FrameState f, boolean keepsOpenPass) {
|
||||
if (!device.isVulkan()) return;
|
||||
final ShaderManager.ProgramObject prog = st.boundProgramObj;
|
||||
if (prog == null || !prog.linked || !prog.hasReadMovedSamplers) return;
|
||||
final int[] vertexUnits = SamplerLookup.movedSamplerUnits(prog, false);
|
||||
final int[] fragmentUnits = SamplerLookup.movedSamplerUnits(prog, true);
|
||||
final long writeGen = resourceManager.textureWriteGeneration();
|
||||
if (keepsOpenPass && st.lastPreparedMovedSamplerPass == f.renderPassGeneration
|
||||
&& st.lastPreparedMovedSamplerProgram == prog
|
||||
&& st.lastPreparedMovedSamplerUnitsGen == prog.samplerUnitsGeneration
|
||||
&& st.lastPreparedMovedSamplerWriteGen == writeGen
|
||||
&& bindingsMatch(st, vertexUnits, st.lastPreparedVertexMovedSamplerTextures)
|
||||
&& bindingsMatch(st, fragmentUnits, st.lastPreparedFragmentMovedSamplerTextures)) return;
|
||||
synchronized (this) {
|
||||
if (!refresh(st, f, vertexUnits, fragmentUnits, keepsOpenPass)) return;
|
||||
}
|
||||
st.lastPreparedMovedSamplerProgram = prog;
|
||||
snapshotBindings(st, vertexUnits, st.lastPreparedVertexMovedSamplerTextures);
|
||||
snapshotBindings(st, fragmentUnits, st.lastPreparedFragmentMovedSamplerTextures);
|
||||
st.lastPreparedMovedSamplerUnitsGen = prog.samplerUnitsGeneration;
|
||||
st.lastPreparedMovedSamplerWriteGen = writeGen;
|
||||
st.lastPreparedMovedSamplerPass = f.renderPassGeneration;
|
||||
}
|
||||
|
||||
private static boolean bindingsMatch(ContextState st, int[] units, int[] textures) {
|
||||
for (int i = 0; i < units.length; i++) {
|
||||
if (textures[i] != st.boundTexture(units[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void snapshotBindings(ContextState st, int[] units, int[] textures) {
|
||||
for (int i = 0; i < units.length; i++) textures[i] = st.boundTexture(units[i]);
|
||||
}
|
||||
|
||||
private boolean refresh(ContextState st, FrameState f, int[] vertexUnits, int[] fragmentUnits, boolean keepsOpenPass) {
|
||||
stale.clear();
|
||||
collectStale(st, f, vertexUnits, keepsOpenPass);
|
||||
collectStale(st, f, fragmentUnits, keepsOpenPass);
|
||||
for (int i = 0; i < stale.size(); i++) {
|
||||
final long source = stale.getLong(i);
|
||||
final Copy copy = bySource.get(source);
|
||||
if (copy == null) continue;
|
||||
final ResourceManager.TextureMeta m = copy.meta;
|
||||
final boolean refreshed = copy.convertedFormat == 0
|
||||
? resourceManager.copyAllMips(source, copy.texture, m.glTarget(), m.width(), m.height(), m.depth(), m.levels())
|
||||
: conversion.convert(st, source, copy.texture, copy.convertedFormat, m.width(), m.height(), m.levels());
|
||||
if (!refreshed) return false;
|
||||
copy.writeVersion = copy.pendingWriteVersion;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void collectStale(ContextState st, FrameState f, int[] units, boolean keepsOpenPass) {
|
||||
for (int unit : units) {
|
||||
final int glTexId = st.boundTexture(unit);
|
||||
if (glTexId == 0) continue;
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glTexId);
|
||||
if (!readThroughCopy(meta)) continue;
|
||||
final long source = resourceManager.getTextureHandle(glTexId);
|
||||
if (source == 0 || stale.contains(source)) continue;
|
||||
final Copy copy = copyFor(source, meta);
|
||||
if (copy == null) continue;
|
||||
copy.lastUsedFrame = frameNumber;
|
||||
final boolean readByOpenPass = keepsOpenPass && f.renderPass != 0 && copy.readFrame == f && copy.readPass == f.renderPassGeneration;
|
||||
if (!readByOpenPass) {
|
||||
final long writeVersion = resourceManager.textureWriteVersion(source);
|
||||
if (copy.writeVersion != writeVersion) {
|
||||
copy.pendingWriteVersion = writeVersion;
|
||||
stale.add(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean readThroughCopy(ResourceManager.TextureMeta meta) {
|
||||
return meta != null && (meta.usage() & SDL_GPU_TEXTUREUSAGE_SAMPLER) != 0;
|
||||
}
|
||||
|
||||
private Copy copyFor(long source, ResourceManager.TextureMeta meta) {
|
||||
Copy copy = bySource.get(source);
|
||||
if (copy != null && copy.meta.equals(meta)) return copy;
|
||||
if (copy != null) resourceManager.releaseTextureDeferred(copy.texture);
|
||||
int convertedFormat = 0;
|
||||
long texture = resourceManager.createStorageReadTexture(meta, meta.sdlFormat(), 0);
|
||||
if (texture == 0 && meta.glTarget() == GL11.GL_TEXTURE_2D) {
|
||||
convertedFormat = PixelOps.isDepthFormat(meta.sdlFormat()) ? SDL_GPU_TEXTUREFORMAT_R32_FLOAT : SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT;
|
||||
texture = resourceManager.createStorageReadTexture(meta, convertedFormat, SDL_GPU_TEXTUREUSAGE_COLOR_TARGET);
|
||||
}
|
||||
if (texture == 0) {
|
||||
bySource.remove(source);
|
||||
resourceManager.stopTrackingTextureWrites(source);
|
||||
if (unsupportedWarned.add(meta.sdlFormat())) {
|
||||
LOG.error("SDL_GPU cannot make a read-only texture-slot copy of format 0x{} (GL target 0x{}); samplers moved off the 16 sampler slots read black from it",
|
||||
Integer.toHexString(meta.sdlFormat()), Integer.toHexString(meta.glTarget()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
copy = new Copy(texture, meta, convertedFormat);
|
||||
bySource.put(source, copy);
|
||||
resourceManager.trackTextureWrites(source);
|
||||
return copy;
|
||||
}
|
||||
|
||||
public long textureFor(long source, int glTexId) {
|
||||
if (!device.isVulkan()) return source;
|
||||
synchronized (this) {
|
||||
final Copy copy = bySource.get(source);
|
||||
if (copy == null) return readThroughCopy(resourceManager.getTextureMeta(glTexId)) ? 0 : source;
|
||||
if (copy.writeVersion == -1) return 0;
|
||||
final FrameState f = frameManager.frame();
|
||||
copy.readFrame = f;
|
||||
copy.readPass = f.renderPassGeneration;
|
||||
return copy.texture;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void releaseCopyOf(long source) {
|
||||
final Copy copy = bySource.remove(source);
|
||||
if (copy != null) resourceManager.releaseTextureDeferred(copy.texture);
|
||||
resourceManager.stopTrackingTextureWrites(source);
|
||||
}
|
||||
|
||||
public synchronized void releaseUnused() {
|
||||
frameNumber++;
|
||||
if (bySource.isEmpty()) return;
|
||||
for (var it = bySource.long2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
|
||||
final Long2ObjectMap.Entry<Copy> e = it.next();
|
||||
if (frameNumber - e.getValue().lastUsedFrame > UNUSED_FRAMES_BEFORE_RELEASE) {
|
||||
resourceManager.stopTrackingTextureWrites(e.getLongKey());
|
||||
resourceManager.releaseTextureDeferred(e.getValue().texture);
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void releaseAll() {
|
||||
for (var it = bySource.long2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
|
||||
final Long2ObjectMap.Entry<Copy> e = it.next();
|
||||
resourceManager.stopTrackingTextureWrites(e.getLongKey());
|
||||
resourceManager.releaseTextureHandle(e.getValue().texture);
|
||||
}
|
||||
bySource.clear();
|
||||
}
|
||||
}
|
||||
@@ -149,7 +149,7 @@ public final class SamplerBinder {
|
||||
continue;
|
||||
}
|
||||
final int glUnit = samplerUnits[i];
|
||||
final int glTexId = (glUnit >= 0 && glUnit < st.boundTextures.length) ? st.boundTextures[glUnit] : 0;
|
||||
final int glTexId = st.boundTexture(glUnit);
|
||||
long texHandle = (glTexId != 0) ? resourceManager.getTextureHandle(glTexId) : 0;
|
||||
if (texHandle == 0) {
|
||||
texHandle = fallbackTexture;
|
||||
|
||||
+29
-15
@@ -6,19 +6,31 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import java.util.List;
|
||||
|
||||
public final class SamplerLookup {
|
||||
private static final int[] EMPTY_UNITS = new int[0];
|
||||
private SamplerLookup() {}
|
||||
|
||||
public static int[] resolvedUnits(ShaderManager.ProgramObject prog, boolean fragment) {
|
||||
if (prog.samplerUnitsDirty) {
|
||||
prog.vertexSamplerUnits = resolveStage(prog.vertexSamplerNames, prog.samplerTextureUnits, prog.vertexResources.numSamplers());
|
||||
prog.fragmentSamplerUnits = resolveStage(prog.fragmentSamplerNames, prog.samplerTextureUnits, prog.fragmentResources.numSamplers());
|
||||
prog.samplerUnitsDirty = false;
|
||||
}
|
||||
refresh(prog);
|
||||
return fragment ? prog.fragmentSamplerUnits : prog.vertexSamplerUnits;
|
||||
}
|
||||
|
||||
public static int[] movedSamplerUnits(ShaderManager.ProgramObject prog, boolean fragment) {
|
||||
refresh(prog);
|
||||
return fragment ? prog.fragmentMovedSamplerUnits : prog.vertexMovedSamplerUnits;
|
||||
}
|
||||
|
||||
private static void refresh(ShaderManager.ProgramObject prog) {
|
||||
if (!prog.samplerUnitsDirty) return;
|
||||
prog.vertexSamplerUnits = resolveStage(prog.vertexSamplerNames, prog.samplerTextureUnits, prog.vertexResources.numSamplers());
|
||||
prog.fragmentSamplerUnits = resolveStage(prog.fragmentSamplerNames, prog.samplerTextureUnits, prog.fragmentResources.numSamplers());
|
||||
prog.vertexMovedSamplerUnits = resolveMoved(prog.vertexImageNames, prog.vertexMovedSamplerRead, prog.samplerTextureUnits);
|
||||
prog.fragmentMovedSamplerUnits = resolveMoved(prog.fragmentImageNames, prog.fragmentMovedSamplerRead, prog.samplerTextureUnits);
|
||||
prog.samplerUnitsDirty = false;
|
||||
prog.samplerUnitsGeneration++;
|
||||
}
|
||||
|
||||
private static int[] resolveStage(List<String> samplerNames, Object2IntMap<String> textureUnits, int samplerCount) {
|
||||
if (samplerCount <= 0) return new int[0];
|
||||
if (samplerCount <= 0) return EMPTY_UNITS;
|
||||
final int[] units = new int[samplerCount];
|
||||
for (int i = 0; i < samplerCount; i++) {
|
||||
units[i] = getSamplerTextureUnit(samplerNames, textureUnits, i);
|
||||
@@ -26,17 +38,19 @@ public final class SamplerLookup {
|
||||
return units;
|
||||
}
|
||||
|
||||
private static int[] resolveMoved(List<String> imageNames, boolean[] read, Object2IntMap<String> textureUnits) {
|
||||
if (read.length == 0) return EMPTY_UNITS;
|
||||
final int[] units = new int[read.length];
|
||||
for (int i = 0; i < units.length; i++) {
|
||||
units[i] = read[i] ? getSamplerTextureUnit(imageNames, textureUnits, i) : -1;
|
||||
}
|
||||
return units;
|
||||
}
|
||||
|
||||
public static int getSamplerTextureUnit(List<String> samplerNames, Object2IntMap<String> textureUnits, int bindingIndex) {
|
||||
if (bindingIndex >= samplerNames.size()) return bindingIndex;
|
||||
if (bindingIndex >= samplerNames.size()) return 0;
|
||||
final String name = samplerNames.get(bindingIndex);
|
||||
final int unit = textureUnits.getInt(name);
|
||||
if (unit != -1) return unit;
|
||||
return switch (name) {
|
||||
case "tex", "texture", "gtexture" -> 0;
|
||||
case "lightmap" -> 1;
|
||||
case "normals" -> 2;
|
||||
case "specular" -> 3;
|
||||
default -> bindingIndex;
|
||||
};
|
||||
return unit != -1 ? unit : 0;
|
||||
}
|
||||
}
|
||||
+23
-8
@@ -19,11 +19,13 @@ public final class StorageTextureBinder {
|
||||
|
||||
private final ResourceManager resourceManager;
|
||||
private final ShaderManager shaderManager;
|
||||
private final MovedSamplerCopies movedSamplerCopies;
|
||||
private final IntOpenHashSet nullHandleWarned = new IntOpenHashSet();
|
||||
|
||||
public StorageTextureBinder(ResourceManager resourceManager, ShaderManager shaderManager) {
|
||||
public StorageTextureBinder(ResourceManager resourceManager, ShaderManager shaderManager, MovedSamplerCopies movedSamplerCopies) {
|
||||
this.resourceManager = resourceManager;
|
||||
this.shaderManager = shaderManager;
|
||||
this.movedSamplerCopies = movedSamplerCopies;
|
||||
}
|
||||
|
||||
public void bindStorageTextures(long renderPass, ContextState st, long bindCb) {
|
||||
@@ -63,18 +65,31 @@ public final class StorageTextureBinder {
|
||||
final boolean programChanged = st.boundProgram != lastProg;
|
||||
boolean anyChanged = programChanged;
|
||||
|
||||
final long fallback = resourceManager.getOrCreateFallbackStorageTexture3D();
|
||||
final int[] movedSamplerUnits = SamplerLookup.movedSamplerUnits(prog, fragment);
|
||||
final boolean[] movedSamplers = fragment ? prog.fragmentImageIsMovedSampler : prog.vertexImageIsMovedSampler;
|
||||
for (int i = 0; i < count; i++) {
|
||||
final String name = imgNames.get(i);
|
||||
final int glUnit = prog.imageTextureUnits.getInt(name);
|
||||
final int glTexId = (glUnit >= 0 && glUnit < ContextState.MAX_IMAGE_UNITS) ? st.boundStorageTextureByUnit[glUnit] : 0;
|
||||
final boolean movedSampler = movedSamplers[i];
|
||||
final int glUnit;
|
||||
final int glTexId;
|
||||
if (movedSampler) {
|
||||
glUnit = movedSamplerUnits[i];
|
||||
glTexId = st.boundTexture(glUnit);
|
||||
} else {
|
||||
glUnit = prog.imageTextureUnits.getInt(name);
|
||||
glTexId = (glUnit >= 0 && glUnit < ContextState.MAX_IMAGE_UNITS) ? st.boundStorageTextureByUnit[glUnit] : 0;
|
||||
}
|
||||
long texHandle = (glTexId != 0) ? resourceManager.getTextureHandle(glTexId) : 0;
|
||||
final boolean hasSource = texHandle != 0;
|
||||
if (movedSampler && hasSource) texHandle = movedSamplerCopies.textureFor(texHandle, glTexId);
|
||||
if (texHandle == 0) {
|
||||
if (nullHandleWarned.add(st.boundProgram)) {
|
||||
LOG.warn("StorageTextureBinder: program {} {} stage image '{}' has no bound texture (glUnit={} glTexId={}); substituting fallback", st.boundProgram, fragment ? "fragment" : "vertex", name, glUnit, glTexId);
|
||||
final boolean unreadMovedSampler = movedSampler && glUnit < 0;
|
||||
if (!unreadMovedSampler && nullHandleWarned.add(st.boundProgram)) {
|
||||
LOG.warn("StorageTextureBinder: program {} {} stage image '{}' {} (glUnit={} glTexId={}); substituting fallback", st.boundProgram, fragment ? "fragment" : "vertex", name,
|
||||
hasSource ? "has no usable texture-slot copy" : "has no bound texture", glUnit, glTexId);
|
||||
}
|
||||
if (fallback == 0) return;
|
||||
texHandle = fallback;
|
||||
texHandle = movedSampler ? resourceManager.getOrCreateFallbackStorageTexture2D() : resourceManager.getOrCreateFallbackStorageTexture3D();
|
||||
if (texHandle == 0) return;
|
||||
}
|
||||
ptrs.put(i, texHandle);
|
||||
if (texHandle != lastTex[i]) {
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntSet;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* SDL_GPU binds 16 samplers per stage; GL drivers give 32. A sampler read only through texelFetch/textureSize never
|
||||
* uses its sampler state, so it can move to one of the 8 read-only texture slots. Moved samplers get {@link #PREFIX};
|
||||
* link strips it and binds them from their texture unit (through a copy on Vulkan, see {@code MovedSamplerCopies}).
|
||||
*/
|
||||
public final class SamplerOverflow {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica-SDLGPU");
|
||||
|
||||
public static final int SAMPLER_SLOTS = ContextState.MAX_SAMPLERS;
|
||||
public static final int TEXTURE_SLOTS = ContextState.MAX_IMAGE_UNITS;
|
||||
|
||||
public static final String PREFIX = "angelica_moved_sampler_";
|
||||
|
||||
private static final Set<String> SAMPLERLESS_CALLS = Set.of("texelFetch", "texelFetchOffset", "textureSize", "textureQueryLevels");
|
||||
|
||||
private SamplerOverflow() {}
|
||||
|
||||
public static boolean isMoved(String name) {
|
||||
return name.startsWith(PREFIX);
|
||||
}
|
||||
|
||||
public static String originalName(String name) {
|
||||
return isMoved(name) ? name.substring(PREFIX.length()) : name;
|
||||
}
|
||||
|
||||
private record SamplerDecl(String name, GLSLParser.Type_specifier_nonarrayContext type, boolean movable) {}
|
||||
|
||||
/**
|
||||
* Returns true when it moved a sampler, which then needs {@link GlslVulkanPreprocess#SAMPLERLESS_EXTENSION}.
|
||||
* Every declared texture, image and sampler {@link SamplerStripper} kept reaches SPIR-V and takes a slot, used or not.
|
||||
*/
|
||||
static boolean collectEdits(ShaderIndex index, List<TextureDeclarations.Declaration> declarations, Set<String> strippedSamplers, String debugName, List<Edit> edits) {
|
||||
final List<SamplerDecl> samplers = new ArrayList<>();
|
||||
int textureSlotsTaken = 0;
|
||||
for (TextureDeclarations.Declaration decl : declarations) {
|
||||
if (!decl.sampler()) {
|
||||
textureSlotsTaken += decl.names().size();
|
||||
continue;
|
||||
}
|
||||
final GLSLParser.Type_specifier_nonarrayContext type = decl.type();
|
||||
final List<GLSLParser.Typeless_declarationContext> declarators = decl.declarators();
|
||||
// Conversion and the missing-texture fallback both require a float 2D view.
|
||||
final boolean movable = declarators.size() == 1 && !decl.array() && "sampler2D".equals(type.getText());
|
||||
for (GLSLParser.Typeless_declarationContext td : declarators) {
|
||||
if (td.IDENTIFIER() == null) continue;
|
||||
final String name = td.IDENTIFIER().getText();
|
||||
if (strippedSamplers.contains(name)) continue;
|
||||
samplers.add(new SamplerDecl(name, type, movable && td.array_specifier() == null));
|
||||
}
|
||||
}
|
||||
|
||||
final int excess = samplers.size() - SAMPLER_SLOTS;
|
||||
final int freeTextureSlots = TEXTURE_SLOTS - textureSlotsTaken;
|
||||
if (excess <= 0 || freeTextureSlots <= 0) return false;
|
||||
|
||||
final int toMove = Math.min(excess, freeTextureSlots);
|
||||
final ShaderIndex.Directives directives = index.directives();
|
||||
final IntSet samplerlessArguments = samplerlessArgumentStarts(index);
|
||||
final List<String> moved = new ArrayList<>();
|
||||
for (SamplerDecl decl : samplers) {
|
||||
if (moved.size() == toMove) break;
|
||||
if (!decl.movable() || namedInDirectives(directives, decl.name()) || GlslVulkanPreprocess.renamesIdentifier(decl.name())
|
||||
|| !onlyReadWithoutSampler(index, decl.name(), samplerlessArguments)) continue;
|
||||
edits.add(new Edit(GlslVulkanPreprocess.startIdx(decl.type()), GlslVulkanPreprocess.stopIdx(decl.type()), "texture2D"));
|
||||
for (Token tok : index.occurrences(decl.name())) {
|
||||
edits.add(new Edit(tok.getStartIndex(), tok.getStartIndex() - 1, PREFIX));
|
||||
}
|
||||
moved.add(decl.name());
|
||||
}
|
||||
if (moved.isEmpty()) return false;
|
||||
LOGGER.debug("'{}' uses {} samplers; moved {} that are only read with texelFetch/textureSize to texture slots: {}", debugName, samplers.size(), moved.size(), moved);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean onlyReadWithoutSampler(ShaderIndex index, String name, IntSet samplerlessArguments) {
|
||||
int otherOccurrences = 0;
|
||||
for (Token tok : index.occurrences(name)) {
|
||||
if (samplerlessArguments.contains(tok.getStartIndex())) continue;
|
||||
if (++otherOccurrences > 1) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static IntSet samplerlessArgumentStarts(ShaderIndex index) {
|
||||
final IntSet starts = new IntOpenHashSet();
|
||||
final Set<String> overloaded = userFunctionNames(index);
|
||||
final ShaderIndex.Directives directives = index.directives();
|
||||
for (String fn : SAMPLERLESS_CALLS) {
|
||||
if (overloaded.contains(fn) || namedInDirectives(directives, fn)) continue;
|
||||
for (GLSLParser.Postfix_expressionContext call : index.callsTo(fn)) {
|
||||
final GLSLParser.Function_call_parametersContext params = call.function_call_parameters();
|
||||
if (params == null || params.assignment_expression().isEmpty()) continue;
|
||||
final GLSLParser.Assignment_expressionContext first = params.assignment_expression(0);
|
||||
if (first.getStart() == first.getStop()) starts.add(first.getStart().getStartIndex());
|
||||
}
|
||||
}
|
||||
return starts;
|
||||
}
|
||||
|
||||
private static boolean namedInDirectives(ShaderIndex.Directives directives, String name) {
|
||||
return directives.identifiers().contains(name) || directives.definedMacros().contains(name);
|
||||
}
|
||||
|
||||
private static Set<String> userFunctionNames(ShaderIndex index) {
|
||||
final Set<String> names = new HashSet<>();
|
||||
for (GLSLParser.External_declarationContext external : index.root().external_declaration()) {
|
||||
final GLSLParser.Function_definitionContext function = external.function_definition();
|
||||
final GLSLParser.DeclarationContext declaration = function == null ? external.declaration() : null;
|
||||
final GLSLParser.Function_prototypeContext prototype = function != null ? function.function_prototype() : declaration != null ? declaration.function_prototype() : null;
|
||||
if (prototype != null && prototype.IDENTIFIER() != null) names.add(prototype.IDENTIFIER().getText());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
}
|
||||
+14
-43
@@ -2,86 +2,57 @@ package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.TextureDeclarations.Declaration;
|
||||
import org.antlr.v4.runtime.Token;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public final class SamplerStripper {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica-SDLGPU");
|
||||
|
||||
private SamplerStripper() {}
|
||||
|
||||
private record SamplerDecl(List<String> names, int start, int stop) {}
|
||||
|
||||
public static void collectEdits(ShaderIndex index, List<Edit> edits) {
|
||||
final List<SamplerDecl> samplers = new ArrayList<>();
|
||||
for (GLSLParser.DeclarationContext ctx : index.globalDeclarations()) {
|
||||
final SamplerDecl decl = samplerDeclaration(ctx);
|
||||
if (decl != null) samplers.add(decl);
|
||||
}
|
||||
if (samplers.isEmpty()) return;
|
||||
|
||||
static Set<String> collectEdits(ShaderIndex index, List<Declaration> declarations, List<Edit> edits) {
|
||||
Set<String> directiveRefs = null;
|
||||
for (SamplerDecl s : samplers) {
|
||||
if (!allUnreferenced(s, samplers, index)) continue;
|
||||
final Set<String> removed = new HashSet<>();
|
||||
for (Declaration s : declarations) {
|
||||
if (!s.sampler() || !allUnreferenced(s, declarations, index)) continue;
|
||||
if (directiveRefs == null) directiveRefs = index.directives().identifiers();
|
||||
if (anyIn(s, directiveRefs)) {
|
||||
LOGGER.debug("Keeping sampler {}: referenced only inside a preprocessor block", s.names());
|
||||
continue;
|
||||
}
|
||||
edits.add(new Edit(s.start(), s.stop(), ""));
|
||||
removed.addAll(s.names());
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
private static @Nullable SamplerDecl samplerDeclaration(GLSLParser.DeclarationContext ctx) {
|
||||
final GLSLParser.Init_declarator_listContext idl = ctx.init_declarator_list();
|
||||
if (idl == null) return null;
|
||||
final GLSLParser.Single_declarationContext single = idl.single_declaration();
|
||||
if (single == null || single.fully_specified_type() == null) return null;
|
||||
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
|
||||
if (!ShaderIndex.hasStorageQualifier(fst.type_qualifier(), "uniform")) return null;
|
||||
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return null;
|
||||
if (!isSamplerType(fst.type_specifier().type_specifier_nonarray().getText())) return null;
|
||||
|
||||
final List<String> names = new ArrayList<>(1);
|
||||
for (GLSLParser.Typeless_declarationContext td : ShaderIndex.declarators(idl)) {
|
||||
if (td.IDENTIFIER() != null) names.add(td.IDENTIFIER().getText());
|
||||
}
|
||||
return names.isEmpty() ? null : new SamplerDecl(names, ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex());
|
||||
}
|
||||
|
||||
private static boolean insideAny(List<SamplerDecl> samplers, int pos) {
|
||||
for (SamplerDecl s : samplers) {
|
||||
if (pos >= s.start() && pos <= s.stop()) return true;
|
||||
private static boolean insideAnySampler(List<Declaration> declarations, int pos) {
|
||||
for (Declaration s : declarations) {
|
||||
if (s.sampler() && pos >= s.start() && pos <= s.stop()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean allUnreferenced(SamplerDecl s, List<SamplerDecl> samplers, ShaderIndex index) {
|
||||
private static boolean allUnreferenced(Declaration s, List<Declaration> declarations, ShaderIndex index) {
|
||||
for (String name : s.names()) {
|
||||
for (Token tok : index.occurrences(name)) {
|
||||
if (!insideAny(samplers, tok.getStartIndex())) return false;
|
||||
if (!insideAnySampler(declarations, tok.getStartIndex())) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean anyIn(SamplerDecl s, Set<String> names) {
|
||||
private static boolean anyIn(Declaration s, Set<String> names) {
|
||||
for (String name : s.names()) {
|
||||
if (names.contains(name)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isSamplerType(String t) {
|
||||
return t.startsWith("sampler") || t.startsWith("isampler") || t.startsWith("usampler");
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import com.gtnewhorizons.angelica.sdlgpu.shader.dxbc.DxbcCrossCompile;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.msl.MslCrossCompile;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.lwjgl.sdl.SDL_GPUComputePipelineCreateInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUShaderCreateInfo;
|
||||
import org.lwjgl.sdl.SDLError;
|
||||
@@ -335,18 +336,22 @@ public final class ShaderManager {
|
||||
if (glShaderType == GL20.GL_VERTEX_SHADER) {
|
||||
ClipZRemap.collectEdits(tree, edits);
|
||||
}
|
||||
SamplerStripper.collectEdits(index, edits);
|
||||
final List<TextureDeclarations.Declaration> textureDeclarations = TextureDeclarations.collect(index);
|
||||
final Set<String> strippedSamplers = SamplerStripper.collectEdits(index, textureDeclarations, edits);
|
||||
final boolean movedSamplers = isGraphics(glShaderType) && SamplerOverflow.collectEdits(index, textureDeclarations, strippedSamplers, debugName, edits);
|
||||
if (glShaderType == GL20.GL_FRAGMENT_SHADER) {
|
||||
DerivativeHoisting.collectEdits(index, edits);
|
||||
}
|
||||
final boolean needsSamplerless = meta.needsSamplerless() || movedSamplers;
|
||||
if (headerLen == 0) {
|
||||
if (movedSamplers && !meta.needsSamplerless()) GlslVulkanPreprocess.addSamplerlessExtension(index, edits);
|
||||
return new PrewarmTransformResult(GlslVulkanPreprocess.applyEdits(source, edits), meta.boolUniforms());
|
||||
}
|
||||
if (edits.isEmpty() && !meta.needsSamplerless()) {
|
||||
if (edits.isEmpty() && !needsSamplerless) {
|
||||
return new PrewarmTransformResult(source, meta.boolUniforms());
|
||||
}
|
||||
String header = source.substring(0, headerLen);
|
||||
if (meta.needsSamplerless()) {
|
||||
if (needsSamplerless) {
|
||||
final int nl = header.indexOf('\n');
|
||||
header = header.substring(0, nl) + "\n" + GlslVulkanPreprocess.SAMPLERLESS_EXTENSION + header.substring(nl);
|
||||
}
|
||||
@@ -579,11 +584,10 @@ public final class ShaderManager {
|
||||
prog.allSamplerNames.clear();
|
||||
prog.allSamplerNames.addAll(prog.vertexSamplerNames);
|
||||
prog.allSamplerNames.addAll(prog.fragmentSamplerNames);
|
||||
prog.vertexImageNames = vs.reflection.storageImageNames();
|
||||
prog.fragmentImageNames = fs.reflection.storageImageNames();
|
||||
prog.allImageNames.clear();
|
||||
prog.allImageNames.addAll(prog.vertexImageNames);
|
||||
prog.allImageNames.addAll(prog.fragmentImageNames);
|
||||
prog.hasReadMovedSamplers = false;
|
||||
applyStageImages(prog, vs.reflection, true);
|
||||
applyStageImages(prog, fs.reflection, false);
|
||||
prog.imageTextureUnits.clear();
|
||||
prog.boolUniforms.clear();
|
||||
prog.boolUniforms.addAll(vs.boolUniforms);
|
||||
@@ -601,6 +605,14 @@ public final class ShaderManager {
|
||||
prog.infoLog = "graphics-stage RW storage image present";
|
||||
return;
|
||||
}
|
||||
String overBudget = slotBudgetError("vertex", prog.vertexResources, prog.vertexImageIsMovedSampler);
|
||||
if (overBudget == null) overBudget = slotBudgetError("fragment", prog.fragmentResources, prog.fragmentImageIsMovedSampler);
|
||||
if (overBudget != null) {
|
||||
LOG.error("Program {}: {}. Rejecting link.", program, overBudget);
|
||||
prog.linked = false;
|
||||
prog.infoLog = overBudget;
|
||||
return;
|
||||
}
|
||||
|
||||
prog.linked = true;
|
||||
prog.infoLog = "";
|
||||
@@ -614,11 +626,63 @@ public final class ShaderManager {
|
||||
|
||||
registerSamplerLocations(prog, vs.reflection, fs.reflection);
|
||||
for (final String name : prog.allImageNames) ensureLocation(prog, name);
|
||||
registerMovedSamplerLocations(prog, prog.vertexImageNames, prog.vertexMovedSamplerRead);
|
||||
registerMovedSamplerLocations(prog, prog.fragmentImageNames, prog.fragmentMovedSamplerRead);
|
||||
|
||||
prog.buildUniformSlotArrays();
|
||||
|
||||
}
|
||||
|
||||
private static void applyStageImages(ProgramObject prog, StageReflection stage, boolean vertex) {
|
||||
final List<String> reflected = stage.storageImageNames();
|
||||
final List<String> names = new ArrayList<>(reflected.size());
|
||||
final boolean[] moved = new boolean[reflected.size()];
|
||||
final boolean[] read = new boolean[reflected.size()];
|
||||
for (int i = 0; i < reflected.size(); i++) {
|
||||
final String name = reflected.get(i);
|
||||
if (SamplerOverflow.isMoved(name)) {
|
||||
final String original = SamplerOverflow.originalName(name);
|
||||
names.add(original);
|
||||
moved[i] = true;
|
||||
read[i] = !stage.unusedSamplerNames().contains(name);
|
||||
prog.hasReadMovedSamplers |= read[i];
|
||||
prog.allSamplerNames.add(original);
|
||||
} else {
|
||||
names.add(name);
|
||||
prog.allImageNames.add(name);
|
||||
}
|
||||
}
|
||||
if (vertex) {
|
||||
prog.vertexImageNames = names;
|
||||
prog.vertexImageIsMovedSampler = moved;
|
||||
prog.vertexMovedSamplerRead = read;
|
||||
} else {
|
||||
prog.fragmentImageNames = names;
|
||||
prog.fragmentImageIsMovedSampler = moved;
|
||||
prog.fragmentMovedSamplerRead = read;
|
||||
}
|
||||
}
|
||||
|
||||
private static void registerMovedSamplerLocations(ProgramObject prog, List<String> imageNames, boolean[] read) {
|
||||
for (int i = 0; i < read.length; i++) {
|
||||
if (read[i]) ensureLocation(prog, imageNames.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable String slotBudgetError(String stage, ResourceCounts counts, boolean[] movedSamplers) {
|
||||
if (counts.numSamplers() > SamplerOverflow.SAMPLER_SLOTS) {
|
||||
int moved = 0;
|
||||
for (boolean m : movedSamplers) if (m) moved++;
|
||||
final String limit = "Too many " + stage + " shader texture samplers (" + counts.numSamplers() + ", maximum " + SamplerOverflow.SAMPLER_SLOTS + ")";
|
||||
return moved == 0 ? limit : limit + ", even after moving " + moved + " that are only read through texelFetch/textureSize to texture slots";
|
||||
}
|
||||
if (counts.numStorageTextures() > SamplerOverflow.TEXTURE_SLOTS) {
|
||||
return stage + " shader reads " + counts.numStorageTextures() + " images and textures without a sampler but SDL_GPU binds at most "
|
||||
+ SamplerOverflow.TEXTURE_SLOTS + " per stage";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void linkComputeProgram(int program, ProgramObject prog) {
|
||||
final ShaderObject cs = shaderObjects.get(prog.computeShader);
|
||||
if (cs == null || !cs.compiled || cs.spirv == null) {
|
||||
@@ -647,9 +711,11 @@ public final class ShaderManager {
|
||||
prog.allSamplerNames.clear();
|
||||
prog.allSamplerNames.addAll(refl.samplerNames());
|
||||
prog.vertexImageNames = List.of();
|
||||
prog.fragmentImageNames = refl.storageImageNames();
|
||||
prog.vertexImageIsMovedSampler = ProgramObject.NO_FLAGS;
|
||||
prog.vertexMovedSamplerRead = ProgramObject.NO_FLAGS;
|
||||
prog.allImageNames.clear();
|
||||
prog.allImageNames.addAll(refl.storageImageNames());
|
||||
prog.hasReadMovedSamplers = false;
|
||||
applyStageImages(prog, refl, false);
|
||||
prog.imageTextureUnits.clear();
|
||||
prog.boolUniforms.clear();
|
||||
prog.boolUniforms.addAll(cs.boolUniforms);
|
||||
@@ -1902,12 +1968,18 @@ public final class ShaderManager {
|
||||
}
|
||||
final IntOpenHashSet active = new IntOpenHashSet();
|
||||
forEachResource(pActiveResources.get(0), Spvc.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, stack, (i, r) -> active.add(r.id()));
|
||||
forEachResource(pActiveResources.get(0), Spvc.SPVC_RESOURCE_TYPE_SEPARATE_IMAGE, stack, (i, r) -> active.add(r.id()));
|
||||
final List<String> unused = new ArrayList<>();
|
||||
forEachResource(resources, Spvc.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, stack, (i, r) -> {
|
||||
if (active.contains(r.id())) return;
|
||||
final String name = Spvc.spvc_compiler_get_name(compiler, r.id());
|
||||
if (name != null && !name.isEmpty()) unused.add(name);
|
||||
});
|
||||
forEachResource(resources, Spvc.SPVC_RESOURCE_TYPE_SEPARATE_IMAGE, stack, (i, r) -> {
|
||||
if (active.contains(r.id())) return;
|
||||
final String name = Spvc.spvc_compiler_get_name(compiler, r.id());
|
||||
if (name != null && SamplerOverflow.isMoved(name)) unused.add(name);
|
||||
});
|
||||
return unused;
|
||||
}
|
||||
|
||||
@@ -1962,6 +2034,11 @@ public final class ShaderManager {
|
||||
public List<String> vertexSamplerNames = List.of();
|
||||
public List<String> fragmentImageNames = List.of();
|
||||
public List<String> vertexImageNames = List.of();
|
||||
public boolean[] fragmentImageIsMovedSampler = NO_FLAGS;
|
||||
public boolean[] vertexImageIsMovedSampler = NO_FLAGS;
|
||||
public boolean[] fragmentMovedSamplerRead = NO_FLAGS;
|
||||
public boolean[] vertexMovedSamplerRead = NO_FLAGS;
|
||||
public boolean hasReadMovedSamplers;
|
||||
public final HashSet<String> allImageNames = new HashSet<>();
|
||||
public ResourceCounts computeResources = ResourceCounts.EMPTY;
|
||||
public ResourceCounts fragmentResources = ResourceCounts.EMPTY;
|
||||
@@ -2009,7 +2086,10 @@ public final class ShaderManager {
|
||||
|
||||
public int[] vertexSamplerUnits = EMPTY_UNITS;
|
||||
public int[] fragmentSamplerUnits = EMPTY_UNITS;
|
||||
public int[] vertexMovedSamplerUnits = EMPTY_UNITS;
|
||||
public int[] fragmentMovedSamplerUnits = EMPTY_UNITS;
|
||||
public boolean samplerUnitsDirty = true;
|
||||
public int samplerUnitsGeneration;
|
||||
|
||||
public static final byte LOCATION_KIND_PLAIN = 0;
|
||||
public static final byte LOCATION_KIND_SAMPLER = 1;
|
||||
@@ -2020,6 +2100,7 @@ public final class ShaderManager {
|
||||
public int[] locationSamplerUnit = EMPTY_UNITS;
|
||||
|
||||
private static final int[] EMPTY_UNITS = new int[0];
|
||||
private static final boolean[] NO_FLAGS = new boolean[0];
|
||||
private static final byte[] EMPTY_KIND = new byte[0];
|
||||
private static final String[] EMPTY_NAMES = new String[0];
|
||||
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.shader.ShaderIndex;
|
||||
import org.taumc.glsl.grammar.GLSLParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
final class TextureDeclarations {
|
||||
private TextureDeclarations() {}
|
||||
|
||||
record Declaration(List<String> names, List<GLSLParser.Typeless_declarationContext> declarators,
|
||||
GLSLParser.Type_specifier_nonarrayContext type, boolean sampler, boolean array, int start, int stop) {}
|
||||
|
||||
static List<Declaration> collect(ShaderIndex index) {
|
||||
final List<Declaration> declarations = new ArrayList<>();
|
||||
for (GLSLParser.DeclarationContext ctx : index.globalDeclarations()) {
|
||||
final GLSLParser.Init_declarator_listContext list = ctx.init_declarator_list();
|
||||
if (list == null || list.single_declaration() == null) continue;
|
||||
final GLSLParser.Fully_specified_typeContext fst = list.single_declaration().fully_specified_type();
|
||||
if (fst == null || fst.type_specifier() == null || !ShaderIndex.hasStorageQualifier(fst.type_qualifier(), "uniform")) continue;
|
||||
final GLSLParser.Type_specifier_nonarrayContext type = fst.type_specifier().type_specifier_nonarray();
|
||||
if (type == null) continue;
|
||||
final String text = type.getText();
|
||||
final boolean sampler = text.startsWith("sampler") || text.startsWith("isampler") || text.startsWith("usampler");
|
||||
if (!sampler && !text.startsWith("image") && !text.startsWith("iimage") && !text.startsWith("uimage")
|
||||
&& !text.startsWith("texture") && !text.startsWith("itexture") && !text.startsWith("utexture")) continue;
|
||||
final List<GLSLParser.Typeless_declarationContext> declarators = ShaderIndex.declarators(list);
|
||||
final List<String> names = new ArrayList<>(declarators.size());
|
||||
for (GLSLParser.Typeless_declarationContext td : declarators) {
|
||||
if (td.IDENTIFIER() != null) names.add(td.IDENTIFIER().getText());
|
||||
}
|
||||
if (!names.isEmpty()) declarations.add(new Declaration(names, declarators, type, sampler,
|
||||
fst.type_specifier().array_specifier() != null, ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex()));
|
||||
}
|
||||
return declarations;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#version 460 core
|
||||
uniform sampler2D source;
|
||||
uniform ivec4 sourceLevel;
|
||||
layout(location = 0) out vec4 converted;
|
||||
void main() { converted = texelFetch(source, ivec2(gl_FragCoord.xy), sourceLevel.x); }
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
@@ -28,19 +29,21 @@ class SDLGPUBufferQueryTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void boundTextureReadsTheActiveUnitsSlot() {
|
||||
final int[] bound = {11, 22, 33, 44};
|
||||
assertEquals(11, SDLGPURenderBackend.boundTextureOf(0, bound));
|
||||
assertEquals(44, SDLGPURenderBackend.boundTextureOf(3, bound));
|
||||
void boundTextureReadsTheUnitsSlot() {
|
||||
final ContextState st = new ContextState();
|
||||
st.boundTextures[0] = 11;
|
||||
st.boundTextures[3] = 44;
|
||||
assertEquals(11, st.boundTexture(0));
|
||||
assertEquals(44, st.boundTexture(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
void outOfRangeUnitReadsZero() {
|
||||
final int[] bound = {11, 22};
|
||||
assertEquals(0, SDLGPURenderBackend.boundTextureOf(-1, bound));
|
||||
assertEquals(0, SDLGPURenderBackend.boundTextureOf(2, bound));
|
||||
assertEquals(0, SDLGPURenderBackend.boundTextureOf(-33984, bound));
|
||||
assertEquals(0, SDLGPURenderBackend.boundTextureOf(0, new int[0]));
|
||||
final ContextState st = new ContextState();
|
||||
st.boundTextures[0] = 11;
|
||||
assertEquals(0, st.boundTexture(-1));
|
||||
assertEquals(0, st.boundTexture(st.boundTextures.length));
|
||||
assertEquals(0, st.boundTexture(-33984));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.lwjgl.sdl.SDLError;
|
||||
import org.lwjgl.sdl.SDLHints;
|
||||
import org.lwjgl.sdl.SDLInit;
|
||||
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_CreateGPUDevice;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_DestroyGPUDevice;
|
||||
import static org.lwjgl.sdl.SDLGPU.SDL_GetGPUDeviceDriver;
|
||||
@@ -90,13 +90,13 @@ public final class SdlTestRig {
|
||||
|
||||
public static void initSdlVideo() {
|
||||
SDLHints.SDL_SetHint(SDLHints.SDL_HINT_MAC_BACKGROUND_APP, "1");
|
||||
assumeTrue(SDLInit.SDL_Init(SDL_INIT_VIDEO), () -> "SDL_Init failed: " + SDLError.SDL_GetError());
|
||||
assertTrue(SDLInit.SDL_Init(SDL_INIT_VIDEO), () -> "SDL_Init failed: " + SDLError.SDL_GetError());
|
||||
}
|
||||
|
||||
public static SdlTestRig acquireRealDevice() {
|
||||
initSdlVideo();
|
||||
realSdlDevice = SDL_CreateGPUDevice(SHADER_FORMATS, true, (CharSequence) null);
|
||||
assumeTrue(realSdlDevice != 0, "No SDL GPU device: " + SDLError.SDL_GetError());
|
||||
assertTrue(realSdlDevice != 0, "No SDL GPU device: " + SDLError.SDL_GetError());
|
||||
|
||||
final SdlTestRig rig = new SdlTestRig(realSdlDevice);
|
||||
rig.frameManager.setResourceManager(rig.resourceManager);
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ class UniformNegativeLocationTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
applier = new PipelineApplier(null, null, null, null, null, null, null, null, null);
|
||||
applier = new PipelineApplier(null, null, null, null, null, null, null, null, null, null);
|
||||
st = new ContextState();
|
||||
prog = new ShaderManager.ProgramObject();
|
||||
prog.linked = true;
|
||||
|
||||
+15
@@ -87,6 +87,21 @@ class Lwjgl3GLCapabilitiesShimTest {
|
||||
assertThrows(IllegalStateException.class, GL::getCapabilities, "clearing on release must stop the thread answering yes to runningOnRenderThread()");
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionProviderResolvesEveryNameToAbsent() {
|
||||
final boolean preinstalled = GL.getFunctionProvider() != null;
|
||||
Lwjgl3GLCapabilitiesShim.installFunctionProvider();
|
||||
try {
|
||||
assertNotNull(GL.getFunctionProvider());
|
||||
if (!preinstalled) {
|
||||
assertEquals(0L, GL.getFunctionProvider().getFunctionAddress("glBindVertexArrayAPPLE"));
|
||||
}
|
||||
Lwjgl3GLCapabilitiesShim.installFunctionProvider();
|
||||
} finally {
|
||||
if (!preinstalled) GL.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void advertisedSetHasNoDuplicatesOrBlanks() {
|
||||
final Set<String> advertised = SDLGPURenderBackend.advertisedCapabilities();
|
||||
|
||||
+3
-1
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import it.unimi.dsi.fastutil.longs.Long2BooleanOpenHashMap;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@@ -13,6 +14,7 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
@Tag("sdl-gpu-device")
|
||||
class DeviceImageAtomicsGpuTest {
|
||||
|
||||
private Device device;
|
||||
@@ -34,7 +36,7 @@ class DeviceImageAtomicsGpuTest {
|
||||
@Test
|
||||
void verdictsAreCachedPerSizeAndDroppedWithTheDevice() {
|
||||
device = new Device();
|
||||
assumeTrue(device.createDevice(), "no SDL GPU device on this machine");
|
||||
assertTrue(device.createDevice(), "no SDL GPU device on this machine");
|
||||
assumeTrue(device.metalTextureAtomics(), "needs native Metal compiling at MSL 3.1 or newer");
|
||||
|
||||
assertFalse(device.supportsImageAtomics(40, 10, 2));
|
||||
|
||||
+4
-3
@@ -3,13 +3,14 @@ package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
@Tag("sdl-gpu-device")
|
||||
class DeviceLifecycleGpuTest {
|
||||
|
||||
private Device device;
|
||||
@@ -27,7 +28,7 @@ class DeviceLifecycleGpuTest {
|
||||
@Test
|
||||
void createsWithoutAWindowAndIsMemoized() {
|
||||
device = new Device();
|
||||
assumeTrue(device.createDevice(), "no SDL GPU device on this machine");
|
||||
assertTrue(device.createDevice(), "no SDL GPU device on this machine");
|
||||
|
||||
final long handle = device.getDevice();
|
||||
assertNotEquals(0L, handle);
|
||||
@@ -38,7 +39,7 @@ class DeviceLifecycleGpuTest {
|
||||
@Test
|
||||
void destroyIsIdempotentOnAnUnclaimedDevice() {
|
||||
device = new Device();
|
||||
assumeTrue(device.createDevice(), "no SDL GPU device on this machine");
|
||||
assertTrue(device.createDevice(), "no SDL GPU device on this machine");
|
||||
|
||||
device.destroyDevice();
|
||||
assertEquals(0L, device.getDevice());
|
||||
|
||||
+2
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.sdlgpu.device;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.RealDeviceExtension;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
@@ -18,6 +19,7 @@ import static org.lwjgl.sdl.SDLVersion.SDL_GetVersion;
|
||||
import static org.lwjgl.sdl.SDLVersion.SDL_VERSIONNUM;
|
||||
|
||||
@ExtendWith(RealDeviceExtension.class)
|
||||
@Tag("sdl-gpu-device")
|
||||
class FenceProbeRealDeviceTest {
|
||||
|
||||
@Test
|
||||
|
||||
+7
-7
@@ -2,7 +2,7 @@ package com.gtnewhorizons.angelica.sdlgpu.frame;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.swapchainClearNeedsPassBreak;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.fbo0PassStaysOpen;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@@ -19,23 +19,23 @@ class SwapchainClearPassBreakTest {
|
||||
void aPendingColorClearBreaksTheActivePass() {
|
||||
final ContextState st = new ContextState();
|
||||
st.pendingSwapchainClear = true;
|
||||
assertTrue(swapchainClearNeedsPassBreak(st, activeFbo0Pass()), "otherwise the clear is deferred to the next pass and wipes what this one drew");
|
||||
assertFalse(fbo0PassStaysOpen(st, activeFbo0Pass()), "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, activeFbo0Pass()));
|
||||
assertFalse(fbo0PassStaysOpen(depth, activeFbo0Pass()));
|
||||
|
||||
final ContextState stencil = new ContextState();
|
||||
stencil.pendingSwapchainStencilClear = true;
|
||||
assertTrue(swapchainClearNeedsPassBreak(stencil, activeFbo0Pass()));
|
||||
assertFalse(fbo0PassStaysOpen(stencil, activeFbo0Pass()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nothingPendingLeavesThePassAlone() {
|
||||
assertFalse(swapchainClearNeedsPassBreak(new ContextState(), activeFbo0Pass()));
|
||||
assertTrue(fbo0PassStaysOpen(new ContextState(), activeFbo0Pass()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -45,7 +45,7 @@ class SwapchainClearPassBreakTest {
|
||||
|
||||
final FrameManager.FrameState f = activeFbo0Pass();
|
||||
f.renderPass = 0;
|
||||
assertFalse(swapchainClearNeedsPassBreak(st, f));
|
||||
assertFalse(fbo0PassStaysOpen(st, f));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -55,6 +55,6 @@ class SwapchainClearPassBreakTest {
|
||||
|
||||
final FrameManager.FrameState f = activeFbo0Pass();
|
||||
f.activeLayoutHash = 0xF80L;
|
||||
assertFalse(swapchainClearNeedsPassBreak(st, f));
|
||||
assertFalse(fbo0PassStaysOpen(st, f));
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.CubeInstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.InstancedAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlAttribBindingTest {
|
||||
|
||||
private static final String INSTANCED_VERTEX = """
|
||||
#version 330 core
|
||||
layout(location = 0) in vec3 a_Position;
|
||||
layout(location = %d) in vec4 iris_InstEntity;
|
||||
layout(location = %d) in vec4 iris_CubeTex;
|
||||
void main() { gl_Position = vec4(a_Position, 1.0) + iris_InstEntity + iris_CubeTex; }
|
||||
""".formatted(InstancedAttribs.LOC_ENTITY, CubeInstancedAttribs.LOC_CUBE_TEX);
|
||||
|
||||
private static final String IMMEDIATE_VERTEX = """
|
||||
#version 330 core
|
||||
layout(location = 0) in vec3 a_Position;
|
||||
layout(location = %d) in vec4 mc_Entity;
|
||||
layout(location = %d) in vec4 iris_Entity;
|
||||
void main() { gl_Position = vec4(a_Position, 1.0) + mc_Entity + iris_Entity; }
|
||||
""".formatted(ImmediateExtendedAttribHandler.LOC_MC_ENTITY, ImmediateExtendedAttribHandler.LOC_ENTITY_IDS);
|
||||
|
||||
@Test
|
||||
void instancedEntityIdsStayAtTheInstanceSlot() {
|
||||
final int program = linkWithIrisBindings(INSTANCED_VERTEX);
|
||||
try {
|
||||
assertEquals(InstancedAttribs.LOC_ENTITY, GLStateManager.glGetAttribLocation(program, "iris_InstEntity"));
|
||||
assertEquals(CubeInstancedAttribs.LOC_CUBE_TEX, GLStateManager.glGetAttribLocation(program, "iris_CubeTex"));
|
||||
} finally {
|
||||
GLStateManager.glDeleteProgram(program);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void immediateEntityIdsDoNotMoveOntoMcEntity() {
|
||||
final int program = linkWithIrisBindings(IMMEDIATE_VERTEX);
|
||||
try {
|
||||
assertEquals(ImmediateExtendedAttribHandler.LOC_MC_ENTITY, GLStateManager.glGetAttribLocation(program, "mc_Entity"));
|
||||
assertEquals(ImmediateExtendedAttribHandler.LOC_ENTITY_IDS, GLStateManager.glGetAttribLocation(program, "iris_Entity"));
|
||||
} finally {
|
||||
GLStateManager.glDeleteProgram(program);
|
||||
}
|
||||
}
|
||||
|
||||
private static int linkWithIrisBindings(String vertexSource) {
|
||||
final int program = GLStateManager.glCreateProgram();
|
||||
GLStateManager.glAttachShader(program, GlTestSupport.compile(GL20.GL_VERTEX_SHADER, vertexSource));
|
||||
GLStateManager.glAttachShader(program, GlTestSupport.compile(GL20.GL_FRAGMENT_SHADER, GlTestSupport.TRIVIAL_FS));
|
||||
GLStateManager.glBindAttribLocation(program, InstancedAttribs.LOC_ENTITY, "iris_InstEntity");
|
||||
GLStateManager.glBindAttribLocation(program, ImmediateExtendedAttribHandler.LOC_MC_ENTITY, "mc_Entity");
|
||||
GLStateManager.glLinkProgram(program);
|
||||
return program;
|
||||
}
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlEmptyBufferTransferTest {
|
||||
|
||||
private int id;
|
||||
private ByteBuffer empty;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
id = GLStateManager.glGenBuffers();
|
||||
empty = MemoryUtil.memAlloc(16);
|
||||
empty.limit(0);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
MemoryUtil.memFree(empty);
|
||||
GLStateManager.glDeleteBuffers(id);
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyBufferDataReplacesTheStoreWithoutACopy() {
|
||||
final int target = GL40.GL_DRAW_INDIRECT_BUFFER;
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferData(target, 256L, GL15.GL_STATIC_DRAW);
|
||||
GlsmSdlHeadlessRig.resourceManager().flushUploadArena();
|
||||
GLStateManager.glBufferData(target, empty, GL15.GL_STATIC_DRAW);
|
||||
assertEquals(0L, GlsmSdlHeadlessRig.frameManager().frame().arenaCopyPass);
|
||||
assertEquals(0, GLStateManager.glGetBufferParameteri(target, GL15.GL_BUFFER_SIZE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyStorageBufferSubDataIssuesNoCopy() {
|
||||
final int target = GL43.GL_SHADER_STORAGE_BUFFER;
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferData(target, 256L, GL15.GL_DYNAMIC_DRAW);
|
||||
final int before = GlsmSdlHeadlessRig.frameManager().frame().pendingUploadCommands;
|
||||
GLStateManager.glBufferSubData(target, 0, empty);
|
||||
assertEquals(before, GlsmSdlHeadlessRig.frameManager().frame().pendingUploadCommands);
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyGetBufferSubDataDoesNotReachTheDevice() {
|
||||
final int target = GL43.GL_SHADER_STORAGE_BUFFER;
|
||||
GLStateManager.glBindBuffer(target, id);
|
||||
GLStateManager.glBufferData(target, 256L, GL15.GL_DYNAMIC_DRAW);
|
||||
GLStateManager.glGetBufferSubData(target, 0, empty);
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ class GlsmSdlFfpTest {
|
||||
GLStateManager.setFfpInstancing(Instancing.NONE);
|
||||
CubeParityFixture.deleteResources();
|
||||
ParticleParityFixture.deleteResources();
|
||||
FfpFixture.IntegerInstances.delete();
|
||||
FfpFixture.EntityIdInstances.delete();
|
||||
}
|
||||
|
||||
private static void bindBaseTexture() {
|
||||
@@ -172,11 +172,11 @@ class GlsmSdlFfpTest {
|
||||
|
||||
@Test
|
||||
void entityAttribSelectsColorPerInstance() {
|
||||
FfpFixture.IntegerInstances.build(0.6f);
|
||||
FfpFixture.EntityIdInstances.build(0.6f);
|
||||
|
||||
GlsmSdlHeadlessRig.beginFrameAndClear();
|
||||
|
||||
FfpFixture.IntegerInstances.draw();
|
||||
FfpFixture.EntityIdInstances.draw();
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
|
||||
|
||||
+9
-2
@@ -24,12 +24,19 @@ import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
public final class GlsmSdlHeadlessRig {
|
||||
|
||||
public static final int SIZE = 64;
|
||||
|
||||
public static final String FULLSCREEN_VERTEX = """
|
||||
#version 330 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);
|
||||
}
|
||||
""";
|
||||
|
||||
public static final int RED = 0xFFFF0000;
|
||||
public static final int GREEN = 0xFF00FF00;
|
||||
public static final int BLUE = 0xFF0000FF;
|
||||
@@ -48,7 +55,7 @@ public final class GlsmSdlHeadlessRig {
|
||||
public static void boot() {
|
||||
if (booted) return;
|
||||
SdlTestRig.initSdlVideo();
|
||||
assumeTrue(SDLGPUGate.device().createDevice(), () -> "no SDL GPU device: " + SDLError.SDL_GetError());
|
||||
assertTrue(SDLGPUGate.device().createDevice(), () -> "no SDL GPU device: " + SDLError.SDL_GetError());
|
||||
Reflect.setStatic(SDLGPUGate.class, "engaged", true);
|
||||
|
||||
final RenderBackend backend = BackendManager.RENDER_BACKEND;
|
||||
|
||||
+44
@@ -1,12 +1,15 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@@ -49,6 +52,47 @@ class GlsmSdlSamplerBindingTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void pointingASamplerAtAnotherUnitRebindsItBeforeTheNextDraw() {
|
||||
final int program = GlTestSupport.program(GlsmSdlHeadlessRig.FULLSCREEN_VERTEX, """
|
||||
#version 330 core
|
||||
uniform sampler2D s;
|
||||
out vec4 fragColor;
|
||||
void main() { fragColor = texture(s, vec2(0.5)); }
|
||||
""");
|
||||
final int red = GlsmSdlHeadlessRig.createSolidTexture(0xFFFF0000);
|
||||
final int green = GlsmSdlHeadlessRig.createSolidTexture(0xFF00FF00);
|
||||
final int vao = GLStateManager.glGenVertexArrays();
|
||||
try {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, red);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, green);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glUseProgram(program);
|
||||
final int location = GLStateManager.glGetUniformLocation(program, "s");
|
||||
GLStateManager.glUniform1i(location, 0);
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
GLStateManager.glUniform1i(location, 1);
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
GLStateManager.glUseProgram(0);
|
||||
|
||||
final int center = GlsmSdlHeadlessRig.readTarget(GlsmSdlHeadlessRig.SIZE / 2, GlsmSdlHeadlessRig.SIZE / 2, 1, 1)[0];
|
||||
assertEquals(Integer.toHexString(GlsmSdlHeadlessRig.GREEN), Integer.toHexString(center), "the second draw must sample unit 1");
|
||||
} finally {
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
GLStateManager.glDeleteVertexArrays(vao);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glDeleteTextures(red);
|
||||
GLStateManager.glDeleteTextures(green);
|
||||
GLStateManager.glDeleteProgram(program);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void unitCountMatchesTheSdlSamplerArray() {
|
||||
GLStateManager.glBindSampler(0, 0);
|
||||
|
||||
+685
@@ -0,0 +1,685 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPUGate;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
import org.lwjgl.opengl.GL21;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
/**
|
||||
* A stage reading more samplers than SDL_GPU's 16 slots moves the texelFetch-only ones into texture slots,
|
||||
* which bind from the sampler's texture unit rather than an image unit.
|
||||
*/
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlSamplerOverflowTest {
|
||||
|
||||
private static final int SAMPLERS = 18;
|
||||
private static final int FETCH_RED = 16;
|
||||
private static final int FETCH_GREEN = 17;
|
||||
private static final int FILTERED_BLUE = 15;
|
||||
private static final int EXPECTED = 0xFF4080C0;
|
||||
|
||||
private final int[] textures = new int[SAMPLERS];
|
||||
private int program;
|
||||
private int vao;
|
||||
private int redLod;
|
||||
private MovedSamplerProbe copyProbe;
|
||||
|
||||
@BeforeEach
|
||||
void observeCopies() {
|
||||
copyProbe = new MovedSamplerProbe();
|
||||
}
|
||||
|
||||
private static String name(int sampler, String redName) {
|
||||
return sampler == FETCH_RED ? redName : "t" + sampler;
|
||||
}
|
||||
|
||||
private static String fragment(String redName, boolean lastTwoFetchOnly) {
|
||||
final StringBuilder sb = new StringBuilder("#version 330 core\n");
|
||||
for (int i = 0; i < SAMPLERS; i++) sb.append("uniform sampler2D ").append(name(i, redName)).append(";\n");
|
||||
sb.append("uniform int redLod;\nout vec4 fragColor;\nvoid main() {\n vec4 unused = vec4(0.0);\n");
|
||||
final int filtered = lastTwoFetchOnly ? FETCH_RED : SAMPLERS;
|
||||
for (int i = 0; i < filtered; i++) sb.append(" unused += texture(").append(name(i, redName)).append(", vec2(0.5)) * 0.0;\n");
|
||||
if (lastTwoFetchOnly) {
|
||||
sb.append(" fragColor = vec4(texelFetch(").append(redName).append(", ivec2(0), redLod).r, texelFetch(t").append(FETCH_GREEN)
|
||||
.append(", ivec2(0), 0).g, texture(t").append(FILTERED_BLUE).append(", vec2(0.5)).b, 1.0) + unused;\n");
|
||||
} else {
|
||||
sb.append(" fragColor = vec4(1.0) + unused;\n");
|
||||
}
|
||||
return sb.append("}\n").toString();
|
||||
}
|
||||
|
||||
private static int colorFor(int sampler) {
|
||||
return switch (sampler) {
|
||||
case FETCH_RED -> 0xFF401122;
|
||||
case FETCH_GREEN -> 0xFF338044;
|
||||
case FILTERED_BLUE -> 0xFF5566C0;
|
||||
default -> 0xFF000000;
|
||||
};
|
||||
}
|
||||
|
||||
private void setUp(String redName, int[] units) {
|
||||
setUp(redName, units, () -> GlsmSdlHeadlessRig.createSolidTexture(colorFor(FETCH_RED)));
|
||||
}
|
||||
|
||||
private void setUp(String redName, int[] units, IntSupplier redTexture) {
|
||||
program = GlTestSupport.program(GlsmSdlHeadlessRig.FULLSCREEN_VERTEX, fragment(redName, true));
|
||||
final ShaderManager.ProgramObject po = shaderManager().getProgram(program);
|
||||
assertEquals(16, po.fragmentResources.numSamplers(), "fragment sampler slots");
|
||||
assertEquals(2, po.fragmentResources.numStorageTextures(), "fetch-only samplers moved to texture slots");
|
||||
for (int i = 0; i < SAMPLERS; i++) textures[i] = i == FETCH_RED ? redTexture.getAsInt() : GlsmSdlHeadlessRig.createSolidTexture(colorFor(i));
|
||||
for (int i = 0; i < SAMPLERS; i++) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + units[i]);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, textures[i]);
|
||||
}
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
vao = GLStateManager.glGenVertexArrays();
|
||||
}
|
||||
|
||||
private int drawCenter(String redName, int[] units) {
|
||||
bindDrawState(redName, units);
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
GLStateManager.glUseProgram(0);
|
||||
assertMovedSamplersReadCopiesOnVulkan();
|
||||
return GlsmSdlHeadlessRig.readTarget(GlsmSdlHeadlessRig.SIZE / 2, GlsmSdlHeadlessRig.SIZE / 2, 1, 1)[0];
|
||||
}
|
||||
|
||||
private void bindDrawState(String redName, int[] units) {
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glUseProgram(program);
|
||||
GLStateManager.glUniform1i(GLStateManager.glGetUniformLocation(program, "redLod"), redLod);
|
||||
for (int i = 0; i < SAMPLERS; i++) {
|
||||
final int location = GLStateManager.glGetUniformLocation(program, name(i, redName));
|
||||
assertTrue(location >= 0, name(i, redName) + " has no location");
|
||||
GLStateManager.glUniform1i(location, units[i]);
|
||||
}
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
}
|
||||
|
||||
private void assertMovedSamplersReadCopiesOnVulkan() {
|
||||
if (!SDLGPUGate.device().isVulkan()) return;
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
for (int slot = 0; slot < 2; slot++) {
|
||||
final long bound = st.lastFragStorageTex[slot];
|
||||
assertNotEquals(0L, bound, "texture slot " + slot + " unbound");
|
||||
for (int texture : textures) {
|
||||
assertNotEquals(GlsmSdlHeadlessRig.resourceManager().getTextureHandle(texture), bound, "texture slot " + slot + " bound a sampleable texture directly");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] shuffledUnits() {
|
||||
final int[] units = new int[SAMPLERS];
|
||||
for (int i = 0; i < SAMPLERS; i++) units[i] = (i + 5) % SAMPLERS;
|
||||
return units;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanUp() {
|
||||
copyProbe.close();
|
||||
GLStateManager.glDisable(GL11.GL_COLOR_LOGIC_OP);
|
||||
GLStateManager.glLogicOp(GL11.GL_COPY);
|
||||
GLStateManager.glBindImageTexture(0, 0, 0, false, 0, GL15.GL_WRITE_ONLY, GL11.GL_RGBA8);
|
||||
GLStateManager.glBindVertexArray(0);
|
||||
if (vao != 0) GLStateManager.glDeleteVertexArrays(vao);
|
||||
for (int unit = 0; unit < SAMPLERS; unit++) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + unit);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
}
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
for (int i = 0; i < SAMPLERS; i++) {
|
||||
if (textures[i] != 0) GLStateManager.glDeleteTextures(textures[i]);
|
||||
textures[i] = 0;
|
||||
}
|
||||
if (program != 0) GLStateManager.glDeleteProgram(program);
|
||||
program = 0;
|
||||
vao = 0;
|
||||
}
|
||||
|
||||
@Test
|
||||
void fetchOnlySamplersPastTheSlotLimitReadTheTexturesOnTheirUnits() {
|
||||
final int[] units = shuffledUnits();
|
||||
setUp("t" + FETCH_RED, units);
|
||||
assertEquals(Integer.toHexString(EXPECTED), Integer.toHexString(drawCenter("t" + FETCH_RED, units)),
|
||||
"red and green from the moved samplers, blue from a sampler slot");
|
||||
}
|
||||
|
||||
@Test
|
||||
void unboundMovedSamplerReadsTheFloat2DFallback() {
|
||||
final int[] units = shuffledUnits();
|
||||
setUp("t" + FETCH_RED, units);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + units[FETCH_RED]);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
assertEquals(0xFF0080C0, drawCenter("t" + FETCH_RED, units));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"uniform", "texture binding"})
|
||||
void changingAMovedSamplerPreparesAndBindsTheNewTexture(String change) {
|
||||
final int[] units = shuffledUnits();
|
||||
setUp("t" + FETCH_RED, units);
|
||||
bindDrawState("t" + FETCH_RED, units);
|
||||
for (int i = 0; i < 3; i++) GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
|
||||
if (change.equals("uniform")) {
|
||||
GLStateManager.glUniform1i(GLStateManager.glGetUniformLocation(program, "t" + FETCH_RED), units[0]);
|
||||
} else {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + units[FETCH_RED]);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, textures[0]);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
}
|
||||
final int refreshes = copyProbe.refreshCalls();
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
if (SDLGPUGate.device().isVulkan()) assertEquals(refreshes + 1, copyProbe.refreshCalls(), "a changed moved sampler must invalidate preparation");
|
||||
GLStateManager.glUseProgram(0);
|
||||
assertEquals(0xFF0080C0, GlsmSdlHeadlessRig.readTarget(32, 32, 1, 1)[0], "the new unit supplies black instead of red");
|
||||
}
|
||||
|
||||
@Test
|
||||
void movedSamplerSeesAClearIssuedBeforeTheDraw() {
|
||||
final int[] units = shuffledUnits();
|
||||
setUp("t" + FETCH_RED, units);
|
||||
final int fbo = GlsmSdlHeadlessRig.fboWithColor(textures[FETCH_RED], 0);
|
||||
try {
|
||||
GLStateManager.glViewport(0, 0, 2, 2);
|
||||
GLStateManager.glClearColor(0x60 / 255.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
assertEquals(Integer.toHexString(0xFF6080C0), Integer.toHexString(drawCenter("t" + FETCH_RED, units)),
|
||||
"the moved sampler must read the cleared texture, not its uploaded texels");
|
||||
} finally {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLStateManager.glDeleteFramebuffers(fbo);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void movedSamplerReadsAnSrgbTextureDecoded() {
|
||||
final int[] units = shuffledUnits();
|
||||
setUp("t" + FETCH_RED, units, () -> {
|
||||
final int texture = GlsmSdlHeadlessRig.newNearestTexture();
|
||||
final ByteBuffer texels = MemoryUtil.memAlloc(2 * 2 * 4);
|
||||
try {
|
||||
for (int i = 0; i < 4; i++) GlsmSdlHeadlessRig.putArgb(texels, i, colorFor(FETCH_RED));
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL21.GL_SRGB8_ALPHA8, 2, 2, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
|
||||
} finally {
|
||||
MemoryUtil.memFree(texels);
|
||||
}
|
||||
return texture;
|
||||
});
|
||||
// sRGB 0x40 decodes to linear 0.0513, which the RGBA8 target stores as 13.
|
||||
assertChannels(drawCenter("t" + FETCH_RED, units), 13, 0x80, 0xC0, "red decoded from the sRGB texture");
|
||||
if (SDLGPUGate.device().isVulkan()) {
|
||||
final long copy = watchCopy("t" + FETCH_RED);
|
||||
final int writes = copyProbe.writesTo(copy);
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
assertChannels(drawCenter("t" + FETCH_RED, units), 13, 0x80, 0xC0, "cached conversion keeps the decoded red");
|
||||
assertEquals(writes, copyProbe.writesTo(copy), "unchanged sRGB content must not be converted again");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void movedSamplerReadsADepthTexture() {
|
||||
final int[] units = shuffledUnits();
|
||||
setUp("t" + FETCH_RED, units, () -> {
|
||||
final int texture = GlsmSdlHeadlessRig.newNearestTexture();
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL30.GL_DEPTH_COMPONENT32F, 2, 2, 0, GL11.GL_DEPTH_COMPONENT, GL11.GL_FLOAT, (ByteBuffer) null);
|
||||
final int fbo = GlsmSdlHeadlessRig.fboWithColor(GlsmSdlHeadlessRig.createSolidTexture(0xFF000000), 0);
|
||||
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_DEPTH_ATTACHMENT, GL11.GL_TEXTURE_2D, texture, 0);
|
||||
GLStateManager.glViewport(0, 0, 2, 2);
|
||||
GLStateManager.glDepthMask(true);
|
||||
GLStateManager.glClearDepth(0.25);
|
||||
GLStateManager.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLStateManager.glDeleteFramebuffers(fbo);
|
||||
return texture;
|
||||
});
|
||||
assertChannels(drawCenter("t" + FETCH_RED, units), 64, 0x80, 0xC0, "red is the cleared depth 0.25");
|
||||
}
|
||||
|
||||
private static void assertChannels(int argb, int r, int g, int b, String message) {
|
||||
final int gotR = (argb >> 16) & 0xFF, gotG = (argb >> 8) & 0xFF, gotB = argb & 0xFF;
|
||||
assertTrue(Math.abs(gotR - r) <= 1 && Math.abs(gotG - g) <= 1 && Math.abs(gotB - b) <= 1,
|
||||
() -> message + ": expected rgb(" + r + "," + g + "," + b + ") but was rgb(" + gotR + "," + gotG + "," + gotB + ")");
|
||||
}
|
||||
|
||||
@Test
|
||||
void copiesOfDeletedTexturesAreReleased() {
|
||||
assumeTrue(SDLGPUGate.device().isVulkan(), "copies exist only on Vulkan");
|
||||
final int[] units = shuffledUnits();
|
||||
setUp("t" + FETCH_RED, units);
|
||||
drawCenter("t" + FETCH_RED, units);
|
||||
final int beforeDelete = liveTextureCount();
|
||||
|
||||
GLStateManager.glDeleteTextures(textures[FETCH_RED]);
|
||||
textures[FETCH_RED] = 0;
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
assertEquals(beforeDelete - 1, liveTextureCount(), "the copy is released while GLSM retains a 1x1 placeholder for the deleted name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void copiesNothingReadsAnyMoreAreReleasedAfterAFewFrames() {
|
||||
assumeTrue(SDLGPUGate.device().isVulkan(), "copies exist only on Vulkan");
|
||||
final int[] units = shuffledUnits();
|
||||
setUp("t" + FETCH_RED, units);
|
||||
drawCenter("t" + FETCH_RED, units);
|
||||
final int beforeExpiry = liveTextureCount();
|
||||
|
||||
GLStateManager.glDeleteProgram(program);
|
||||
program = 0;
|
||||
for (int frame = 0; frame < 12; frame++) {
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
}
|
||||
assertEquals(beforeExpiry - 2, liveTextureCount(), "both unused copies must be released");
|
||||
}
|
||||
|
||||
private static int liveTextureCount() {
|
||||
final LongSet handles = Reflect.get(GlsmSdlHeadlessRig.resourceManager(), "liveTextureHandles");
|
||||
return handles.size();
|
||||
}
|
||||
|
||||
private long boundCopy(String name) {
|
||||
return SdlTestRig.contextState().lastFragStorageTex[shaderManager().getProgram(program).fragmentImageNames.indexOf(name)];
|
||||
}
|
||||
|
||||
private long watchCopy(String name) {
|
||||
final long copy = boundCopy(name);
|
||||
assertNotEquals(0L, copy);
|
||||
copyProbe.watchWrites(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
@Test
|
||||
void unchangedSourcesReuseCopiesAcrossRenderPassesAndFrames() {
|
||||
assumeTrue(SDLGPUGate.device().isVulkan(), "copies exist only on Vulkan");
|
||||
final int[] units = shuffledUnits();
|
||||
setUp("t" + FETCH_RED, units);
|
||||
bindDrawState("t" + FETCH_RED, units);
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
final long copy = watchCopy("t" + FETCH_RED);
|
||||
final int writes = copyProbe.writesTo(copy);
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
final int refreshes = copyProbe.refreshCalls();
|
||||
assertTrue(refreshes > 0, "the probe must observe preparation before the cache can skip it");
|
||||
final var frame = GlsmSdlHeadlessRig.frameManager().frame();
|
||||
final long pass = frame.renderPassGeneration;
|
||||
final int bindGen = SdlTestRig.contextState().samplerBindGen;
|
||||
final long writeGen = GlsmSdlHeadlessRig.resourceManager().textureWriteGeneration();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + units[0]);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, textures[(i + 1) % 2]);
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
}
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
assertEquals(pass, frame.renderPassGeneration, "the draws keep the same render pass");
|
||||
assertNotEquals(bindGen, SdlTestRig.contextState().samplerBindGen, "the entity texture changes reached the backend");
|
||||
assertEquals(writeGen, GlsmSdlHeadlessRig.resourceManager().textureWriteGeneration(), "observing copies must not change write tracking");
|
||||
assertEquals(refreshes, copyProbe.refreshCalls(), "binding unrelated entity textures must skip preparation");
|
||||
assertEquals(writes, copyProbe.writesTo(copy), "repeated draws must not copy unchanged content");
|
||||
|
||||
GlsmSdlHeadlessRig.frameManager().endRenderPassIfActive();
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
assertEquals(refreshes + 1, copyProbe.refreshCalls(), "a new pass must recheck its snapshots");
|
||||
assertEquals(writes, copyProbe.writesTo(copy), "a new pass must reuse unchanged content");
|
||||
GLStateManager.glUseProgram(0);
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
assertEquals(EXPECTED, drawCenter("t" + FETCH_RED, units));
|
||||
assertEquals(copy, boundCopy("t" + FETCH_RED), "the allocation is reused");
|
||||
assertEquals(writes, copyProbe.writesTo(copy), "a new frame must reuse unchanged content");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(strings = {"upload", "copy", "blit", "clear", "mipmap", "compute", "copyTexSubImage"})
|
||||
void writesRefreshOnlyTheChangedSource(String write) {
|
||||
assumeTrue(SDLGPUGate.device().isVulkan(), "copies exist only on Vulkan");
|
||||
final int[] units = shuffledUnits();
|
||||
if (write.equals("mipmap")) {
|
||||
redLod = 1;
|
||||
setUp("t" + FETCH_RED, units, () -> {
|
||||
final int texture = GlsmSdlHeadlessRig.createSolidTexture(0xFF000000);
|
||||
GlsmSdlHeadlessRig.uploadSolidLevel(1, 1, colorFor(FETCH_RED));
|
||||
return texture;
|
||||
});
|
||||
} else {
|
||||
setUp("t" + FETCH_RED, units);
|
||||
}
|
||||
if (write.equals("compute")) GLStateManager.glBindImageTexture(0, textures[FETCH_RED], 0, false, 0, GL15.GL_WRITE_ONLY, GL11.GL_RGBA8);
|
||||
final int fbo = GlsmSdlHeadlessRig.fboWithColor(textures[FETCH_RED], 0);
|
||||
try {
|
||||
assertEquals(EXPECTED, drawCenter("t" + FETCH_RED, units));
|
||||
final long redCopy = watchCopy("t" + FETCH_RED);
|
||||
final long greenCopy = watchCopy("t" + FETCH_GREEN);
|
||||
final int redWrites = copyProbe.writesTo(redCopy);
|
||||
final int greenWrites = copyProbe.writesTo(greenCopy);
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
switch (write) {
|
||||
case "upload" -> {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + units[FETCH_RED]);
|
||||
final ByteBuffer texels = MemoryUtil.memAlloc(16);
|
||||
try {
|
||||
for (int i = 0; i < 4; i++) GlsmSdlHeadlessRig.putArgb(texels, i, 0xFF000000);
|
||||
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
|
||||
} finally {
|
||||
MemoryUtil.memFree(texels);
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
}
|
||||
}
|
||||
case "copy" -> BackendManager.RENDER_BACKEND.copyImageSubData(textures[0], GL11.GL_TEXTURE_2D, 0, 0, 0, 0,
|
||||
textures[FETCH_RED], GL11.GL_TEXTURE_2D, 0, 0, 0, 0, 2, 2, 1);
|
||||
case "blit" -> {
|
||||
final int srcFbo = GlsmSdlHeadlessRig.fboWithColor(textures[0], 0);
|
||||
try {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, srcFbo);
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, fbo);
|
||||
GLStateManager.glBlitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_NEAREST);
|
||||
} finally {
|
||||
GLStateManager.glDeleteFramebuffers(srcFbo);
|
||||
}
|
||||
}
|
||||
case "clear" -> {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
|
||||
GLStateManager.glClearColor(0, 0, 0, 1);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
case "mipmap" -> BackendManager.RENDER_BACKEND.generateTextureMipmap(textures[FETCH_RED]);
|
||||
case "compute" -> computeBlack();
|
||||
case "copyTexSubImage" -> {
|
||||
final int srcFbo = GlsmSdlHeadlessRig.fboWithColor(textures[0], 0);
|
||||
try {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + units[FETCH_RED]);
|
||||
GLStateManager.glCopyTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, 0, 0, 2, 2);
|
||||
} finally {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLStateManager.glDeleteFramebuffers(srcFbo);
|
||||
}
|
||||
}
|
||||
default -> throw new AssertionError(write);
|
||||
}
|
||||
assertEquals(0xFF0080C0, drawCenter("t" + FETCH_RED, units), write + " changes the sampled red");
|
||||
assertEquals(redWrites + 1, copyProbe.writesTo(redCopy), "changed source copied once");
|
||||
assertEquals(greenWrites, copyProbe.writesTo(greenCopy), "unchanged source not copied again");
|
||||
} finally {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLStateManager.glDeleteFramebuffers(fbo);
|
||||
}
|
||||
}
|
||||
|
||||
private static void computeBlack() {
|
||||
final int shader = GlTestSupport.compile(GL43.GL_COMPUTE_SHADER, """
|
||||
#version 430 core
|
||||
layout(local_size_x = 1, local_size_y = 1) in;
|
||||
layout(rgba8, binding = 0) uniform writeonly image2D target;
|
||||
void main() { imageStore(target, ivec2(gl_GlobalInvocationID.xy), vec4(0.0, 0.0, 0.0, 1.0)); }
|
||||
""");
|
||||
final int compute = GLStateManager.glCreateProgram();
|
||||
try {
|
||||
GLStateManager.glAttachShader(compute, shader);
|
||||
GLStateManager.glLinkProgram(compute);
|
||||
assertEquals(1, GLStateManager.glGetProgrami(compute, GL20.GL_LINK_STATUS), GLStateManager.glGetProgramInfoLog(compute, 4096));
|
||||
GLStateManager.glUseProgram(compute);
|
||||
GLStateManager.glDispatchCompute(2, 2, 1);
|
||||
} finally {
|
||||
GLStateManager.glUseProgram(0);
|
||||
GLStateManager.glDeleteProgram(compute);
|
||||
GLStateManager.glDeleteShader(shader);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void partialClearDoesNotPrepareMovedSamplers() {
|
||||
assumeTrue(SDLGPUGate.device().isVulkan(), "copies exist only on Vulkan");
|
||||
final int[] units = shuffledUnits();
|
||||
setUp("t" + FETCH_RED, units);
|
||||
bindDrawState("t" + FETCH_RED, units);
|
||||
final var frame = GlsmSdlHeadlessRig.frameManager().frame();
|
||||
final long before = frame.renderPassGeneration;
|
||||
GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GLStateManager.glScissor(0, 0, 1, 1);
|
||||
GLStateManager.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
assertEquals(before + 1, frame.renderPassGeneration, "only the clear's render pass is opened");
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
assertNotEquals(0L, boundCopy("t" + FETCH_RED), "the first draw prepares the copy");
|
||||
GLStateManager.glUseProgram(0);
|
||||
assertEquals(EXPECTED, GlsmSdlHeadlessRig.readTarget(32, 32, 1, 1)[0]);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({"sampler1D, int", "sampler3D, ivec3", "sampler2DArray, ivec3", "isampler2D, ivec2", "usampler2D, ivec2"})
|
||||
void overflowRejectsTypesWithoutAMatchingFallback(String type, String coord) {
|
||||
final String source = "#version 330 core\n" + declareFiltered(16) + "uniform " + type + " fetched;\nout vec4 fragColor;\n"
|
||||
+ "void main() { vec4 c = vec4(0.0);\n" + readFiltered(16, "c", "")
|
||||
+ "fragColor = c + vec4(texelFetch(fetched, " + coord + "(0), 0)); }\n";
|
||||
final int vs = GlTestSupport.compile(GL20.GL_VERTEX_SHADER, GlsmSdlHeadlessRig.FULLSCREEN_VERTEX);
|
||||
final int fs = GlTestSupport.compile(GL20.GL_FRAGMENT_SHADER, source);
|
||||
program = GLStateManager.glCreateProgram();
|
||||
try {
|
||||
GLStateManager.glAttachShader(program, vs);
|
||||
GLStateManager.glAttachShader(program, fs);
|
||||
GLStateManager.glLinkProgram(program);
|
||||
assertEquals(0, GLStateManager.glGetProgrami(program, GL20.GL_LINK_STATUS));
|
||||
assertTrue(GLStateManager.glGetProgramInfoLog(program, 4096).contains("Too many fragment shader texture samplers"));
|
||||
} finally {
|
||||
GLStateManager.glDeleteShader(vs);
|
||||
GLStateManager.glDeleteShader(fs);
|
||||
}
|
||||
}
|
||||
|
||||
private static String declareFiltered(int count) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < count; i++) sb.append("uniform sampler2D f").append(i).append(";\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String readFiltered(int count, String into, String scale) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < count; i++) sb.append(" ").append(into).append(" += texture(f").append(i).append(", vec2(0.5))").append(scale).append(";\n");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Test
|
||||
void unusedSamplerDeclaredWithAUsedOneStillTakesASlot() {
|
||||
final StringBuilder sb = new StringBuilder("#version 330 core\n").append(declareFiltered(14));
|
||||
sb.append("uniform sampler2D f14, neverRead;\n");
|
||||
for (int i = 0; i < 3; i++) sb.append("uniform sampler2D x").append(i).append(";\n");
|
||||
sb.append("out vec4 fragColor;\nvoid main() {\n vec4 c = vec4(0.0);\n").append(readFiltered(15, "c", ""));
|
||||
for (int i = 0; i < 3; i++) sb.append(" c += texelFetch(x").append(i).append(", ivec2(0), 0);\n");
|
||||
sb.append(" fragColor = c;\n}\n");
|
||||
|
||||
program = GlTestSupport.program(GlsmSdlHeadlessRig.FULLSCREEN_VERTEX, sb.toString());
|
||||
final ShaderManager.ProgramObject po = shaderManager().getProgram(program);
|
||||
assertEquals(16, po.fragmentResources.numSamplers(), "15 read samplers plus the unread one declared beside f14");
|
||||
assertEquals(3, po.fragmentResources.numStorageTextures(), "all three fetch-only samplers moved");
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "helper inside #if: {0}")
|
||||
@ValueSource(booleans = {false, true})
|
||||
void samplerPassedToTheShadersOwnTextureSizeStaysASampler(boolean helperInConditional) {
|
||||
final StringBuilder sb = new StringBuilder("#version 330 core\n").append(declareFiltered(15));
|
||||
sb.append("uniform sampler2D helperArgument;\nuniform sampler2D fetched;\nout vec4 fragColor;\n");
|
||||
if (helperInConditional) sb.append("#if 1\n");
|
||||
sb.append("ivec2 textureSize(sampler2D s) { return textureSize(s, 0); }\n");
|
||||
if (helperInConditional) sb.append("#endif\n");
|
||||
sb.append("void main() {\n vec4 c = vec4(0.0);\n").append(readFiltered(15, "c", ""));
|
||||
sb.append(" c.xy += vec2(textureSize(helperArgument));\n c += texelFetch(fetched, ivec2(0), 0);\n fragColor = c;\n}\n");
|
||||
|
||||
program = GlTestSupport.program(GlsmSdlHeadlessRig.FULLSCREEN_VERTEX, sb.toString());
|
||||
final ShaderManager.ProgramObject po = shaderManager().getProgram(program);
|
||||
assertEquals(16, po.fragmentResources.numSamplers(), "helperArgument must stay a sampler; only 'fetched' moves");
|
||||
assertEquals(1, po.fragmentResources.numStorageTextures(), "one sampler moved");
|
||||
}
|
||||
|
||||
@Test
|
||||
void samplerReadThroughATexelFetchMacroStaysASampler() {
|
||||
final StringBuilder sb = new StringBuilder("#version 330 core\n");
|
||||
sb.append("#define texelFetch(s, p, l) textureLod(s, vec2(p), float(l))\n").append(declareFiltered(15));
|
||||
sb.append("uniform sampler2D throughMacro;\nuniform sampler2D sized0;\nuniform sampler2D sized1;\nout vec4 fragColor;\n");
|
||||
sb.append("void main() {\n vec4 c = vec4(0.0);\n").append(readFiltered(15, "c", ""));
|
||||
sb.append(" c += texelFetch(throughMacro, ivec2(0), 0);\n");
|
||||
sb.append(" c.xy += vec2(textureSize(sized0, 0) + textureSize(sized1, 0));\n fragColor = c;\n}\n");
|
||||
|
||||
program = GlTestSupport.program(GlsmSdlHeadlessRig.FULLSCREEN_VERTEX, sb.toString());
|
||||
final ShaderManager.ProgramObject po = shaderManager().getProgram(program);
|
||||
assertEquals(16, po.fragmentResources.numSamplers(), "throughMacro must stay a sampler; the two textureSize-only ones move");
|
||||
assertEquals(2, po.fragmentResources.numStorageTextures(), "two samplers moved");
|
||||
}
|
||||
|
||||
@Test
|
||||
void macroSharingAnUnusedSamplersNameDoesNotKeepTheSampler() {
|
||||
final StringBuilder sb = new StringBuilder("#version 330 core\n#define helper(x) (x)\n").append(declareFiltered(16));
|
||||
sb.append("uniform sampler2D helper;\nout vec4 fragColor;\nvoid main() {\n vec4 c = vec4(0.0);\n").append(readFiltered(16, "c", ""));
|
||||
sb.append(" fragColor = c;\n}\n");
|
||||
|
||||
program = GlTestSupport.program(GlsmSdlHeadlessRig.FULLSCREEN_VERTEX, sb.toString());
|
||||
assertEquals(16, shaderManager().getProgram(program).fragmentResources.numSamplers(), "the unread sampler 'helper' must still be stripped");
|
||||
}
|
||||
|
||||
@Test
|
||||
void samplerNamedThroughAMacroKeepsTheExpandedName() {
|
||||
final StringBuilder sb = new StringBuilder("#version 330 core\n#define alias realTex\n").append(declareFiltered(15));
|
||||
sb.append("uniform sampler2D alias;\nuniform sampler2D fetched;\nout vec4 fragColor;\n");
|
||||
sb.append("void main() {\n vec4 c = vec4(0.0);\n").append(readFiltered(15, "c", ""));
|
||||
sb.append(" c += texelFetch(alias, ivec2(0), 0) + texelFetch(fetched, ivec2(0), 0);\n fragColor = c;\n}\n");
|
||||
|
||||
program = GlTestSupport.program(GlsmSdlHeadlessRig.FULLSCREEN_VERTEX, sb.toString());
|
||||
assertEquals(1, shaderManager().getProgram(program).fragmentResources.numStorageTextures(), "only 'fetched' moves");
|
||||
assertTrue(GLStateManager.glGetUniformLocation(program, "realTex") >= 0, "the uniform keeps the name the macro expands to");
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {false, true})
|
||||
void movedSamplerOnlyReadByAnUncalledFunctionUsesFallbackWithoutCopying(boolean fragment) {
|
||||
final StringBuilder sb = new StringBuilder("#version 330 core\n").append(declareFiltered(16));
|
||||
sb.append("uniform sampler2D shadowtex0;\n");
|
||||
if (fragment) sb.append("out vec4 fragColor;\n");
|
||||
sb.append("vec4 neverCalled() { return texelFetch(shadowtex0, ivec2(0), 0); }\n");
|
||||
sb.append("void main() {\n vec4 c = vec4(0.0);\n").append(readFiltered(16, "c", " * 0.0"));
|
||||
if (fragment) {
|
||||
sb.append(" fragColor = vec4(0.0, 1.0, 0.0, 1.0) + c;\n}\n");
|
||||
} else {
|
||||
sb.append(" vec2 p = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2);\n gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0) + c;\n}\n");
|
||||
}
|
||||
|
||||
program = GlTestSupport.program(fragment ? GlsmSdlHeadlessRig.FULLSCREEN_VERTEX : sb.toString(), fragment ? sb.toString() : """
|
||||
#version 330 core
|
||||
out vec4 fragColor;
|
||||
void main() { fragColor = vec4(0.0, 1.0, 0.0, 1.0); }
|
||||
""");
|
||||
final ShaderManager.ProgramObject po = shaderManager().getProgram(program);
|
||||
assertEquals(1, (fragment ? po.fragmentResources : po.vertexResources).numStorageTextures(), "shadowtex0 moved");
|
||||
assertEquals(-1, GLStateManager.glGetUniformLocation(program, "shadowtex0"), "an unread sampler must report -1, or Iris treats shadows as used");
|
||||
assertTrue(GLStateManager.glGetUniformLocation(program, "f0") >= 0, "read samplers keep their location");
|
||||
|
||||
textures[0] = GlsmSdlHeadlessRig.createSolidTexture(GlsmSdlHeadlessRig.RED);
|
||||
vao = GLStateManager.glGenVertexArrays();
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
GLStateManager.glUseProgram(program);
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
final var rm = GlsmSdlHeadlessRig.resourceManager();
|
||||
final long fallback = rm.getOrCreateFallbackStorageTexture2D();
|
||||
final long before = rm.textureWriteGeneration();
|
||||
for (int i = 0; i < 3; i++) GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
final ContextState st = SdlTestRig.contextState();
|
||||
assertEquals(fallback, (fragment ? st.lastFragStorageTex : st.lastVertStorageTex)[0], "the unread slot uses the 2D fallback");
|
||||
assertEquals(before, rm.textureWriteGeneration(), "the unread sampler must not start tracking or copying unit 0");
|
||||
GLStateManager.glUseProgram(0);
|
||||
assertEquals(GlsmSdlHeadlessRig.GREEN, GlsmSdlHeadlessRig.readTarget(32, 32, 1, 1)[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void copyIsRefreshedWhenTheDrawMovesToAnotherFramebuffer() {
|
||||
final StringBuilder sb = new StringBuilder("#version 330 core\n").append(declareFiltered(16));
|
||||
sb.append("uniform sampler2D written;\nuniform int readWritten;\nout vec4 fragColor;\nvoid main() {\n vec4 unused = vec4(0.0);\n");
|
||||
sb.append(readFiltered(16, "unused", " * 0.0"));
|
||||
sb.append(" float size = float(textureSize(written, 0).x) * 0.0;\n");
|
||||
sb.append(" fragColor = (readWritten == 1 ? texelFetch(written, ivec2(0), 0) : vec4(0x40 / 255.0, 0.0, 0.0, 1.0)) + unused + size;\n}\n");
|
||||
program = GlTestSupport.program(GlsmSdlHeadlessRig.FULLSCREEN_VERTEX, sb.toString());
|
||||
assertEquals(1, shaderManager().getProgram(program).fragmentResources.numStorageTextures(), "'written' moved");
|
||||
|
||||
for (int i = 0; i < 16; i++) textures[i] = GlsmSdlHeadlessRig.createSolidTexture(0xFF000000);
|
||||
textures[16] = GlsmSdlHeadlessRig.createSolidTexture(0xFF00FF00);
|
||||
for (int i = 0; i <= 16; i++) {
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + i);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, textures[i]);
|
||||
}
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
vao = GLStateManager.glGenVertexArrays();
|
||||
final int fbo = GlsmSdlHeadlessRig.fboWithColor(textures[16], 0);
|
||||
try {
|
||||
GLStateManager.glUseProgram(program);
|
||||
for (int i = 0; i < 16; i++) GLStateManager.glUniform1i(GLStateManager.glGetUniformLocation(program, "f" + i), i);
|
||||
GLStateManager.glUniform1i(GLStateManager.glGetUniformLocation(program, "written"), 16);
|
||||
GLStateManager.glBindVertexArray(vao);
|
||||
|
||||
GLStateManager.glViewport(0, 0, 2, 2);
|
||||
GLStateManager.glUniform1i(GLStateManager.glGetUniformLocation(program, "readWritten"), 0);
|
||||
for (int i = 0; i < 3; i++) GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
|
||||
GlsmSdlHeadlessRig.bindTarget();
|
||||
GLStateManager.glUniform1i(GLStateManager.glGetUniformLocation(program, "readWritten"), 1);
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
GLStateManager.glUseProgram(0);
|
||||
|
||||
assertChannels(GlsmSdlHeadlessRig.readTarget(GlsmSdlHeadlessRig.SIZE / 2, GlsmSdlHeadlessRig.SIZE / 2, 1, 1)[0], 0x40, 0, 0,
|
||||
"the second draw must see what the first draw wrote, not the texture's earlier green");
|
||||
} finally {
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLStateManager.glDeleteFramebuffers(fbo);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void tooManyFilteredSamplersFailLinkInsteadOfDraw() {
|
||||
final int vs = GlTestSupport.compile(GL20.GL_VERTEX_SHADER, GlsmSdlHeadlessRig.FULLSCREEN_VERTEX);
|
||||
final int fs = GlTestSupport.compile(GL20.GL_FRAGMENT_SHADER, fragment("t" + FETCH_RED, false));
|
||||
final int failed = GLStateManager.glCreateProgram();
|
||||
try {
|
||||
GLStateManager.glAttachShader(failed, vs);
|
||||
GLStateManager.glAttachShader(failed, fs);
|
||||
GLStateManager.glLinkProgram(failed);
|
||||
assertEquals(0, GLStateManager.glGetProgrami(failed, GL20.GL_LINK_STATUS), "18 filtered samplers cannot fit 16 slots");
|
||||
final String log = GLStateManager.glGetProgramInfoLog(failed, 4096);
|
||||
assertTrue(log.contains("Too many fragment shader texture samplers (18, maximum 16)"), () -> "info log should name the count: " + log);
|
||||
} finally {
|
||||
GLStateManager.glDeleteShader(vs);
|
||||
GLStateManager.glDeleteShader(fs);
|
||||
GLStateManager.glDeleteProgram(failed);
|
||||
}
|
||||
}
|
||||
|
||||
private static ShaderManager shaderManager() {
|
||||
return Reflect.get(BackendManager.RENDER_BACKEND, "shaderManager");
|
||||
}
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.EntityIdsOnlyHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorEntityIdsFixture;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.BLUE;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.GREEN;
|
||||
import static com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig.SIZE;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
class GlsmSdlTessellatorEntityIdsTest {
|
||||
|
||||
private ImmediateExtendedAttribHandler previousHandler;
|
||||
private int program;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
previousHandler = EntityIdsOnlyHandler.install();
|
||||
program = GlTestSupport.program(TessellatorEntityIdsFixture.VERTEX, TessellatorEntityIdsFixture.FRAGMENT);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
GLStateManager.glUseProgram(0);
|
||||
GLStateManager.glDeleteProgram(program);
|
||||
GLSMHooks.immediateExtendedHandler = previousHandler;
|
||||
}
|
||||
|
||||
@Test
|
||||
void idsChangedMidBatchApplyToTheVerticesAddedAfterTheChange() {
|
||||
GlsmSdlHeadlessRig.beginFrameAndClear();
|
||||
GLStateManager.glUseProgram(program);
|
||||
TessellatorEntityIdsFixture.drawIdsChangedMidBatch();
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 4, SIZE / 2, GREEN, "left quad was added under item 7");
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 3 * SIZE / 4, SIZE / 2, BLUE, "right quad was added under item 50020");
|
||||
}
|
||||
|
||||
@Test
|
||||
void idsChangedAfterTheLastVertexLeaveTheBatchAlone() {
|
||||
GlsmSdlHeadlessRig.beginFrameAndClear();
|
||||
GLStateManager.glUseProgram(program);
|
||||
TessellatorEntityIdsFixture.drawIdsChangedAfterTheLastVertex();
|
||||
|
||||
final int[] pixels = GlsmSdlHeadlessRig.readTarget();
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, SIZE / 4, SIZE / 2, GREEN, "every vertex was added under item 7");
|
||||
GlsmSdlHeadlessRig.assertPixel(pixels, SIZE, 3 * SIZE / 4, SIZE / 2, GREEN, "every vertex was added under item 7");
|
||||
}
|
||||
}
|
||||
+14
-1
@@ -5,6 +5,8 @@ import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
@@ -13,11 +15,13 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@ExtendWith(RealDeviceExtension.class)
|
||||
@Tag("sdl-gpu-device")
|
||||
class AttachmentClearTest {
|
||||
|
||||
private static SdlTestRig rig;
|
||||
private static ShaderManager sm;
|
||||
private static PipelineStore store;
|
||||
private static FullscreenPass fullscreen;
|
||||
private static AttachmentClear attachmentClear;
|
||||
|
||||
private static final int SIZE = 32;
|
||||
@@ -32,7 +36,8 @@ class AttachmentClearTest {
|
||||
rig = RealDeviceExtension.rig();
|
||||
sm = new ShaderManager(rig.device);
|
||||
store = new PipelineStore(rig.device);
|
||||
attachmentClear = new AttachmentClear(rig.frameManager, store, sm);
|
||||
fullscreen = new FullscreenPass(sm, rig.device);
|
||||
attachmentClear = new AttachmentClear(rig.frameManager, store, fullscreen);
|
||||
|
||||
final RealDeviceExtension.Target color = RealDeviceExtension.colorTarget(9700, SIZE, SIZE);
|
||||
colorTexture = color.texture();
|
||||
@@ -59,6 +64,14 @@ class AttachmentClearTest {
|
||||
return fbo;
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void tearDown() {
|
||||
if (attachmentClear != null) attachmentClear.shutdown();
|
||||
if (store != null) store.shutdown();
|
||||
if (fullscreen != null) fullscreen.shutdown();
|
||||
if (sm != null) sm.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
void onePipelinePerLayoutModeAndMask() {
|
||||
final FboState fbo = makeFbo();
|
||||
|
||||
+6
-2
@@ -44,6 +44,7 @@ class DepthStencilReadbackTest {
|
||||
private static FrameManager frameManager;
|
||||
private static ShaderManager sm;
|
||||
private static PipelineStore store;
|
||||
private static FullscreenPass fullscreen;
|
||||
private static FBOClearTracker clearTracker;
|
||||
private static TextureOps textureOps;
|
||||
private static AttachmentClear attachmentClear;
|
||||
@@ -60,8 +61,9 @@ class DepthStencilReadbackTest {
|
||||
store = new PipelineStore(rig.device);
|
||||
clearTracker = new FBOClearTracker(frameManager, rig.resourceManager, sm);
|
||||
textureOps = new TextureOps(rig.device, frameManager, rig.resourceManager, clearTracker);
|
||||
attachmentClear = new AttachmentClear(frameManager, store, sm);
|
||||
readback = new DepthStencilReadback(frameManager, rig.resourceManager, store, sm, clearTracker);
|
||||
fullscreen = new FullscreenPass(sm, rig.device);
|
||||
attachmentClear = new AttachmentClear(frameManager, store, fullscreen);
|
||||
readback = new DepthStencilReadback(frameManager, rig.resourceManager, store, fullscreen, clearTracker);
|
||||
st = new ContextState();
|
||||
|
||||
final RealDeviceExtension.Target depth = RealDeviceExtension.depthStencilTarget(9801, W, H);
|
||||
@@ -77,6 +79,8 @@ class DepthStencilReadbackTest {
|
||||
readback.shutdown();
|
||||
}
|
||||
if (store != null) store.shutdown();
|
||||
if (fullscreen != null) fullscreen.shutdown();
|
||||
if (sm != null) sm.shutdown();
|
||||
}
|
||||
|
||||
private static void clearRegion(float depth, int stencil) {
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class FullscreenPassTest {
|
||||
@Test
|
||||
void failedFragmentsDoNotRetryOrLeakCompilerResources() {
|
||||
final Device device = new Device();
|
||||
final ShaderManager shaders = new ShaderManager(device);
|
||||
final FullscreenPass fullscreen = new FullscreenPass(shaders, device);
|
||||
final AtomicInteger loads = new AtomicInteger();
|
||||
final Map<?, ?> programs = Reflect.get(shaders, "programObjects");
|
||||
final Map<?, ?> shaderObjects = Reflect.get(shaders, "shaderObjects");
|
||||
try {
|
||||
final FullscreenPass.Fragment fragment = fullscreen.fragment("wrong-ubo-size", 16, () -> {
|
||||
loads.incrementAndGet();
|
||||
return "#version 330 core\nout vec4 color;\nvoid main() { color = vec4(1.0); }\n";
|
||||
});
|
||||
assertEquals(0, fragment.shader());
|
||||
assertEquals(0, fragment.shader());
|
||||
assertEquals(1, loads.get(), "a failed variant must only be attempted once");
|
||||
assertEquals(0, programs.size(), "the temporary link program must be released even on failure");
|
||||
assertEquals(1, shaderObjects.size(), "only the reusable vertex shader remains");
|
||||
fullscreen.shutdown();
|
||||
assertEquals(0, shaderObjects.size(), "shutdown releases the shared vertex shader");
|
||||
} finally {
|
||||
fullscreen.shutdown();
|
||||
shaders.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager.ProgramObject;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager.UniformMemberInfo;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.util.spvc.Spvc;
|
||||
|
||||
@@ -26,7 +27,7 @@ class PerFrameBlockDedupTest {
|
||||
return prog;
|
||||
}
|
||||
|
||||
private final PipelineApplier applier = new PipelineApplier(null, null, null, null, null, null, null, null, null);
|
||||
private final PipelineApplier applier = new PipelineApplier(null, null, null, null, null, null, null, null, null, null);
|
||||
private final ContextState st = new ContextState();
|
||||
|
||||
@Test
|
||||
@@ -97,6 +98,7 @@ class PerFrameBlockDedupTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Tag("sdl-gpu-device")
|
||||
void flushUniformBlocksEndsTheCopyPassOnceForTwoDirtyBlocks() throws Exception {
|
||||
final SdlTestRig rig = SdlTestRig.acquireRealDevice();
|
||||
try {
|
||||
@@ -106,7 +108,7 @@ class PerFrameBlockDedupTest {
|
||||
final int[] endCount = { 0 };
|
||||
rig.frameManager.setBeforeEndCopyPassCallback(() -> endCount[0]++);
|
||||
|
||||
final PipelineApplier realApplier = new PipelineApplier(rig.frameManager, rig.resourceManager, null, null, null, null, null, null, null);
|
||||
final PipelineApplier realApplier = new PipelineApplier(rig.frameManager, rig.resourceManager, null, null, null, null, null, null, null, null);
|
||||
final ContextState cs = new ContextState();
|
||||
cs.boundProgramObj = programWithBlockMemberAt(16, ShaderManager.BLOCK_PER_FRAME);
|
||||
realApplier.putUniform(cs, 0, new float[]{0.5f});
|
||||
|
||||
+4
-9
@@ -3,8 +3,10 @@ package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.RealDeviceExtension;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL20;
|
||||
@@ -21,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
|
||||
@ExtendWith(RealDeviceExtension.class)
|
||||
@Tag("sdl-gpu-device")
|
||||
class StencilMaskGpuTest {
|
||||
|
||||
private static SdlTestRig rig;
|
||||
@@ -37,14 +40,6 @@ class StencilMaskGpuTest {
|
||||
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;",
|
||||
@@ -83,7 +78,7 @@ class StencilMaskGpuTest {
|
||||
|
||||
private static long createPipeline(String fragmentSource, boolean writesMask) {
|
||||
final int vs = sm.createShader(GL20.GL_VERTEX_SHADER);
|
||||
sm.shaderSource(vs, VERTEX);
|
||||
sm.shaderSource(vs, GlsmSdlHeadlessRig.FULLSCREEN_VERTEX);
|
||||
sm.compileShader(vs);
|
||||
final int fs = sm.createShader(GL20.GL_FRAGMENT_SHADER);
|
||||
sm.shaderSource(fs, fragmentSource);
|
||||
|
||||
+2
@@ -5,6 +5,7 @@ import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
@@ -14,6 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@ExtendWith(RealDeviceExtension.class)
|
||||
@Tag("sdl-gpu-device")
|
||||
class BufferSizeGpuExactnessTest {
|
||||
|
||||
private static Device device;
|
||||
|
||||
+2
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.Timeout;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -21,6 +22,7 @@ import static org.lwjgl.sdl.SDLGPU.SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
|
||||
@ExtendWith(RealDeviceExtension.class)
|
||||
@Timeout(120)
|
||||
@Tag("sdl-gpu-device")
|
||||
class CopyTexSubImageFromFbo0GpuTest {
|
||||
|
||||
private static final int SIZE = 64;
|
||||
|
||||
+2
@@ -8,6 +8,7 @@ import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.MethodOrderer;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestMethodOrder;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -28,6 +29,7 @@ import static org.lwjgl.system.MemoryStack.stackPush;
|
||||
|
||||
@ExtendWith(RealDeviceExtension.class)
|
||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||
@Tag("sdl-gpu-device")
|
||||
class UploadArenaGpuRoundTripTest {
|
||||
|
||||
private static Device device;
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.sampler;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
|
||||
class SamplerLookupTest {
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = {false, true})
|
||||
void movedAndUnmovedSamplersShareDefaultsAndExplicitAssignments(boolean fragment) {
|
||||
final ShaderManager.ProgramObject ordinary = program(fragment,
|
||||
List.of("tex", "lightmap", "normals", "specular", "custom"), List.of(), new boolean[0]);
|
||||
final ShaderManager.ProgramObject overflow = program(fragment, List.of("tex"),
|
||||
List.of("storageImage", "custom", "specular", "lightmap", "normals"), new boolean[]{false, true, true, true, true});
|
||||
|
||||
assertArrayEquals(new int[]{0, 0, 0, 0, 0}, SamplerLookup.resolvedUnits(ordinary, fragment));
|
||||
assertArrayEquals(new int[]{0}, SamplerLookup.resolvedUnits(overflow, fragment));
|
||||
assertArrayEquals(new int[]{-1, 0, 0, 0, 0}, SamplerLookup.movedSamplerUnits(overflow, fragment),
|
||||
"moving a sampler must preserve its default, while real storage images do not resolve sampler units");
|
||||
|
||||
for (ShaderManager.ProgramObject prog : List.of(ordinary, overflow)) {
|
||||
prog.samplerTextureUnits.put("lightmap", 1);
|
||||
prog.samplerTextureUnits.put("normals", 2);
|
||||
prog.samplerTextureUnits.put("specular", 3);
|
||||
prog.samplerTextureUnits.put("custom", 9);
|
||||
prog.samplerUnitsDirty = true;
|
||||
}
|
||||
assertArrayEquals(new int[]{0, 1, 2, 3, 9}, SamplerLookup.resolvedUnits(ordinary, fragment));
|
||||
assertArrayEquals(new int[]{-1, 9, 3, 1, 2}, SamplerLookup.movedSamplerUnits(overflow, fragment));
|
||||
}
|
||||
|
||||
private static ShaderManager.ProgramObject program(boolean fragment, List<String> samplers, List<String> images, boolean[] moved) {
|
||||
final ShaderManager.ProgramObject prog = new ShaderManager.ProgramObject();
|
||||
final ShaderManager.ResourceCounts resources = new ShaderManager.ResourceCounts(samplers.size(), 0, 0, images.size());
|
||||
if (fragment) {
|
||||
prog.fragmentResources = resources;
|
||||
prog.fragmentSamplerNames = samplers;
|
||||
prog.fragmentImageNames = images;
|
||||
prog.fragmentImageIsMovedSampler = moved;
|
||||
prog.fragmentMovedSamplerRead = moved;
|
||||
} else {
|
||||
prog.vertexResources = resources;
|
||||
prog.vertexSamplerNames = samplers;
|
||||
prog.vertexImageNames = images;
|
||||
prog.vertexImageIsMovedSampler = moved;
|
||||
prog.vertexMovedSamplerRead = moved;
|
||||
}
|
||||
prog.samplerUnitsDirty = true;
|
||||
return prog;
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -62,11 +62,13 @@ class FontFilterVertexSamplerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void samplerNamedLightmapResolvesToTextureUnitOne() {
|
||||
void lightmapStartsOnUnitZeroAndHonorsTheFontsExplicitAssignment() {
|
||||
final List<String> samplers = reflect(runSdlPipeline(FONT_FILTER_VSH)).samplerNames();
|
||||
final Object2IntOpenHashMap<String> noExplicitUnits = new Object2IntOpenHashMap<>();
|
||||
noExplicitUnits.defaultReturnValue(-1);
|
||||
final Object2IntOpenHashMap<String> units = new Object2IntOpenHashMap<>();
|
||||
units.defaultReturnValue(-1);
|
||||
|
||||
assertEquals(1, SamplerLookup.getSamplerTextureUnit(samplers, noExplicitUnits, 0), "even without a glUniform1i the SDL fallback must put a sampler named 'lightmap' on unit 1");
|
||||
assertEquals(0, SamplerLookup.getSamplerTextureUnit(samplers, units, 0), "all samplers start on unit 0");
|
||||
units.put("lightmap", 1);
|
||||
assertEquals(1, SamplerLookup.getSamplerTextureUnit(samplers, units, 0), "the font renderer assigns lightmap explicitly");
|
||||
}
|
||||
}
|
||||
+4
-9
@@ -2,7 +2,9 @@ package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.RealDeviceExtension;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.glsm.GlsmSdlHeadlessRig;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
@@ -31,6 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
|
||||
@ExtendWith(RealDeviceExtension.class)
|
||||
@Tag("sdl-gpu-device")
|
||||
class FragmentStorageTextureReadGpuTest {
|
||||
|
||||
private static SdlTestRig rig;
|
||||
@@ -45,14 +48,6 @@ class FragmentStorageTextureReadGpuTest {
|
||||
sm = new ShaderManager(rig.device);
|
||||
}
|
||||
|
||||
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 String fragment(int samplerCount) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("#version 460 core\n");
|
||||
@@ -101,7 +96,7 @@ class FragmentStorageTextureReadGpuTest {
|
||||
|
||||
private int linkProgram(int samplerCount) {
|
||||
final int vs = sm.createShader(GL20.GL_VERTEX_SHADER);
|
||||
sm.shaderSource(vs, VERTEX);
|
||||
sm.shaderSource(vs, GlsmSdlHeadlessRig.FULLSCREEN_VERTEX);
|
||||
sm.compileShader(vs);
|
||||
final int fs = sm.createShader(GL20.GL_FRAGMENT_SHADER);
|
||||
sm.shaderSource(fs, fragment(samplerCount));
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ class SDLGPUUniformDedupTest {
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
applier = new PipelineApplier(null, null, null, null, null, null, null, null, null);
|
||||
applier = new PipelineApplier(null, null, null, null, null, null, null, null, null, null);
|
||||
st = new ContextState();
|
||||
prog = new ShaderManager.ProgramObject();
|
||||
prog.linked = true;
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ final class ShaderTransformChain {
|
||||
|
||||
static String stripUnused(String source) {
|
||||
if (!source.contains("sampler")) return source;
|
||||
return edit(source, SamplerStripper::collectEdits);
|
||||
return edit(source, (index, edits) -> SamplerStripper.collectEdits(index, TextureDeclarations.collect(index), edits));
|
||||
}
|
||||
|
||||
static String inject(String source, PerFrameUniformBlock perFrame, PerFrameUniformBlock perPass) {
|
||||
|
||||
@@ -38,6 +38,7 @@ public final class ImmediateExtendedAttribs implements ImmediateExtendedAttribHa
|
||||
|
||||
private static final int WANTS_MID = 1;
|
||||
private static final int WANTS_TANGENT = 2;
|
||||
private static final int WANTS_ENTITY_IDS = 4;
|
||||
|
||||
private static final int NEUTRAL_TANGENT = NormI8.pack(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
@@ -57,6 +58,7 @@ public final class ImmediateExtendedAttribs implements ImmediateExtendedAttribHa
|
||||
wants = 0;
|
||||
if (GLStateManager.glGetAttribLocation(program, "mc_midTexCoord") >= 0) wants |= WANTS_MID;
|
||||
if (GLStateManager.glGetAttribLocation(program, "at_tangent") >= 0) wants |= WANTS_TANGENT;
|
||||
if (GLStateManager.glGetAttribLocation(program, "iris_Entity") == LOC_ENTITY_IDS) wants |= WANTS_ENTITY_IDS;
|
||||
programWantsExtCache.put(program, wants);
|
||||
}
|
||||
currentProgramWants = wants;
|
||||
@@ -83,8 +85,7 @@ public final class ImmediateExtendedAttribs implements ImmediateExtendedAttribHa
|
||||
|
||||
@Override
|
||||
public boolean wantsExtended() {
|
||||
GLSMHooks.resolvePendingProgram();
|
||||
return Iris.enabled && currentProgramWants != 0 && GLStateManager.getActiveProgram() != 0;
|
||||
return programWants(WANTS_MID | WANTS_TANGENT);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,6 +93,17 @@ public final class ImmediateExtendedAttribs implements ImmediateExtendedAttribHa
|
||||
return (currentProgramWants & WANTS_TANGENT) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wantsEntityIds() {
|
||||
return programWants(WANTS_ENTITY_IDS);
|
||||
}
|
||||
|
||||
private static boolean programWants(int mask) {
|
||||
if (!Iris.enabled) return false;
|
||||
GLSMHooks.resolvePendingProgram();
|
||||
return (currentProgramWants & mask) != 0 && GLStateManager.getActiveProgram() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean wantsExtendedCapture() {
|
||||
return Iris.enabled && Iris.getCurrentPack().isPresent();
|
||||
|
||||
@@ -26,6 +26,7 @@ import net.coderbot.iris.pipeline.DeferredWorldRenderingPipeline;
|
||||
import net.coderbot.iris.pipeline.WorldRenderingPipeline;
|
||||
import net.coderbot.iris.samplers.IrisSamplers;
|
||||
import net.coderbot.iris.texture.pbr.PBRTextureManager;
|
||||
import net.coderbot.iris.uniforms.CapturedRenderingState;
|
||||
import net.coderbot.iris.uniforms.SystemTimeUniforms;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
@@ -65,6 +66,9 @@ public class IrisGLSMBridge {
|
||||
|
||||
public static void installImmediateExtendedHandler() {
|
||||
GLSMHooks.immediateExtendedHandler = new ImmediateExtendedAttribs();
|
||||
final CapturedRenderingState state = CapturedRenderingState.INSTANCE;
|
||||
state.setEntityIdsListener(() -> GLStateManager.setEntityIds(
|
||||
state.getCurrentRenderedEntity(), state.getCurrentRenderedBlockEntity(), state.getCurrentRenderedItem()));
|
||||
}
|
||||
|
||||
public static void installPostTransformHook() {
|
||||
|
||||
@@ -72,8 +72,7 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
|
||||
// Angelica Config
|
||||
ConfigurationManager.registerConfig(AngelicaConfig.class);
|
||||
TracyOptions.latch(AngelicaConfig.enableTracy, AngelicaConfig.tracyAllowRemote, AngelicaConfig.tracyFineZones, AngelicaConfig.tracyMaxSrcLocs);
|
||||
if (BackendStartGuard.begin(new File(Launch.minecraftHome, "angelica" + File.separator + "sdlgpu-starting"), AngelicaConfig.sdlGpuConfigured(), SystemProperties.SDL_GPU_OVERRIDE != null)) {
|
||||
LOGGER.warn("The last start with SDL GPU did not reach the main menu; resetting the render backend to OpenGL");
|
||||
if (BackendStartGuard.begin(new File(Launch.minecraftHome, "angelica"), AngelicaConfig.sdlGpuConfigured(), SystemProperties.SDL_GPU_OVERRIDE != null, SDLGPUGate.REQUIREMENTS_REVISION, BackendOptions.sdlGpuDriver(AngelicaConfig.sdlGpuDriverName()))) {
|
||||
AngelicaConfig.renderBackend = RenderBackendChoice.OPENGL;
|
||||
ConfigurationManager.save(AngelicaConfig.class);
|
||||
}
|
||||
|
||||
@@ -85,6 +85,12 @@ public enum Mixins implements IMixins {
|
||||
.addClientMixins("client.starminer.MixinTransformClientHelper")
|
||||
),
|
||||
|
||||
RAILCRAFT_LANTERN_CONNECTIONS(new MixinBuilder("Fix Railcraft lanterns rendering with Angelica")
|
||||
.setPhase(Phase.LATE)
|
||||
.addRequiredMod(TargetedMod.RAILCRAFT)
|
||||
.addClientMixins("client.railcraft.MixinRenderBlockLamp")
|
||||
),
|
||||
|
||||
ANGELICA_ENTITY_OVERLAYS(new MixinBuilder()
|
||||
.setPhase(Phase.EARLY)
|
||||
.setApplyIf(() -> AngelicaConfig.entityOverlayFixes)
|
||||
@@ -193,6 +199,7 @@ public enum Mixins implements IMixins {
|
||||
"angelica.entity.MixinRendererLivingEntity_GlintClock",
|
||||
"angelica.entity.MixinRendererLivingEntity_ModelPassDraws",
|
||||
"angelica.entity.MixinRender_BatchEligibility",
|
||||
"angelica.entity.MixinRender_NameTagDraws",
|
||||
"angelica.entity.MixinRender_ShadowBatch",
|
||||
"angelica.entity.MixinTextureManager",
|
||||
"angelica.tesr.MixinTileEntitySpecialRenderer_BatchEligibility"
|
||||
|
||||
@@ -27,6 +27,7 @@ public enum TargetedMod implements ITargetMod {
|
||||
IC2("ic2.core.coremod.IC2core", "IC2"),
|
||||
NATURA(null, "Natura"),
|
||||
OPENBLOCKS(null, "OpenBlocks"),
|
||||
RAILCRAFT(null, "Railcraft"),
|
||||
SECURITYCRAFT(null, "securitycraft"),
|
||||
STARMINER("jp.mc.ancientred.starminer.core.SMPlugin", "modJ_StarMiner"),
|
||||
SUPERNOVA("com.mitchej123.supernova.core.SupernovaCore", "supernova"),
|
||||
|
||||
Loaded 100 of 135 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user