mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-14 09:15:06 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
996fb54cb0 | ||
|
|
d1e7838cf9 | ||
|
|
a12d2bb4b0 | ||
|
|
60d1a3de3c | ||
|
|
367f9d9161 | ||
|
|
d69fd20a8b | ||
|
|
9493d630a0 | ||
|
|
aee989d23b | ||
|
|
3876218405 | ||
|
|
a6d014a9eb | ||
|
|
52444510d0 | ||
|
|
8840c2109f | ||
|
|
e64a4e7214 | ||
|
|
e46eb46343 | ||
|
|
5da38c9180 | ||
|
|
85bde5827f | ||
|
|
ea2d34589e | ||
|
|
2b46b4f602 | ||
|
|
8e099e9529 | ||
|
|
bbbdc12b2a |
No files matched your search
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Install a software Vulkan driver
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y xvfb mesa-vulkan-drivers
|
||||
sudo apt-get install -y xvfb mesa-vulkan-drivers libgl1-mesa-dri
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
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
|
||||
run: xvfb-run -a --server-args="-screen 0 1366x768x24" ./gradlew --build-cache --stacktrace --continue :sdl-gpu:glsmSdlTest :sdl-gpu:sdlGpuDeviceTest :lwjgl3-backend:glesTest
|
||||
- name: Check LWJGL did not extract natives to the shared temp dir
|
||||
run: test ! -e "/tmp/lwjgl_$USER"
|
||||
- name: Attach SDL-GPU test results
|
||||
@@ -48,3 +48,6 @@ jobs:
|
||||
sdl-gpu/build/test-results/
|
||||
sdl-gpu/build/reports/tests/
|
||||
sdl-gpu/hs_err_pid*.log
|
||||
lwjgl3-backend/build/test-results/
|
||||
lwjgl3-backend/build/reports/tests/
|
||||
lwjgl3-backend/hs_err_pid*.log
|
||||
@@ -186,6 +186,8 @@ DependencyHandlerScope.of(dependencies).apply {
|
||||
|
||||
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
|
||||
|
||||
compileOnly("com.github.GTNewHorizons:LiquidLogged:0.2.0:dev") { isTransitive = false }
|
||||
|
||||
compileOnly("com.cardinalstar.cubicchunks:CubicChunks1710:v0.1.5-alpha:dev") { isTransitive = false }
|
||||
}
|
||||
|
||||
@@ -210,6 +212,7 @@ afterEvaluate {
|
||||
glsmTestFixtures(testFixtures(project(":glsm")) as ModuleDependency) { isTransitive = false }
|
||||
testCompileOnly(testFixtures(project(":glsm")))
|
||||
testImplementation("mega:fluidlogged-mc1.7.10:0.1.2")
|
||||
testImplementation("com.github.GTNewHorizons:LiquidLogged:0.2.0:dev") { isTransitive = false }
|
||||
|
||||
testRuntimeOnly(libs.fastutil)
|
||||
testRuntimeOnly(libs.joml)
|
||||
|
||||
@@ -67,6 +67,22 @@ artifacts {
|
||||
}
|
||||
}
|
||||
|
||||
// LWJGL2-API stubs for test JVMs that strip the lwjgl-2 jars (glsmSdlTest, glesTest).
|
||||
val lwjgl2StubsSourceSet = sourceSets.create("lwjgl2Stubs") {
|
||||
java.srcDir("src/lwjgl2Stubs/java")
|
||||
}
|
||||
|
||||
val lwjgl2Stubs by configurations.creating {
|
||||
isCanBeConsumed = true
|
||||
isCanBeResolved = false
|
||||
}
|
||||
|
||||
artifacts {
|
||||
add(lwjgl2Stubs.name, lwjgl2StubsSourceSet.java.classesDirectory) {
|
||||
builtBy(tasks.named("lwjgl2StubsClasses"))
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "Mojang"
|
||||
@@ -111,6 +127,7 @@ val spirvTestRuntime by configurations.creating {
|
||||
dependencies {
|
||||
// MC compile stubs
|
||||
compileOnly(sourceSets["stubs"].output)
|
||||
"lwjgl2StubsCompileOnly"(libs.lwjgl3.opengl)
|
||||
|
||||
// LWJGL
|
||||
compileOnly(libs.lwjgl2)
|
||||
|
||||
File renamed without changes.
+19
@@ -1,5 +1,7 @@
|
||||
package org.lwjgl.opengl;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class ContextCapabilities {
|
||||
|
||||
public boolean OpenGL30 = true;
|
||||
@@ -21,11 +23,28 @@ public class ContextCapabilities {
|
||||
public boolean GL_ARB_sampler_objects = true;
|
||||
public boolean GL_ARB_shader_image_load_store = true;
|
||||
public boolean GL_ARB_shader_storage_buffer_object = true;
|
||||
public boolean GL_ARB_stencil_texturing = false;
|
||||
public boolean GL_ARB_tessellation_shader = false;
|
||||
public boolean GL_ARB_texture_storage = false;
|
||||
|
||||
public boolean GL_EXT_direct_state_access = false;
|
||||
public boolean GL_EXT_gpu_shader4 = true;
|
||||
public boolean GL_EXT_shader_image_load_store = true;
|
||||
|
||||
public boolean GL_NVX_gpu_memory_info = false;
|
||||
|
||||
public ContextCapabilities() {
|
||||
if ("lwjgl3".equals(System.getProperty("glsm.stubs.capabilities"))) copyFromCurrentContext();
|
||||
}
|
||||
|
||||
private void copyFromCurrentContext() {
|
||||
final GLCapabilities caps = GL.getCapabilities();
|
||||
for (Field field : ContextCapabilities.class.getFields()) {
|
||||
try {
|
||||
field.setBoolean(this, GLCapabilities.class.getField(field.getName()).getBoolean(caps));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new IllegalStateException("LWJGL3 GLCapabilities has no " + field.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,13 @@
|
||||
package org.lwjgl.opengl;
|
||||
|
||||
public final class GLContext {
|
||||
|
||||
private static ContextCapabilities capabilities;
|
||||
|
||||
private GLContext() {}
|
||||
|
||||
public static ContextCapabilities getCapabilities() {
|
||||
if (capabilities == null) capabilities = new ContextCapabilities();
|
||||
return capabilities;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ public final class SystemProperties {
|
||||
public static final String GL_PROFILE = System.getProperty("angelica.glProfile", "");
|
||||
public static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
|
||||
public static final boolean DISABLE_LTW_WORKAROUND = Boolean.getBoolean("angelica.disableLtwWorkaround");
|
||||
public static final boolean EMULATE_CLIP_DISTANCES = Boolean.getBoolean("angelica.emulateClipDistances");
|
||||
|
||||
// Redirector
|
||||
public static final UnmappedGLMode UNMAPPED_GL = parseEnum("angelica.unmappedGL", UnmappedGLMode.WARN, UnmappedGLMode.WARN, UnmappedGLMode.class);
|
||||
@@ -45,6 +46,8 @@ public final class SystemProperties {
|
||||
public static final Boolean TRACY_FINE_ZONES_OVERRIDE = parseBooleanOverride("angelica.tracy.fineZones");
|
||||
public static final String TRACY_DIR = System.getProperty("angelica.tracy.dir", "angelica" + File.separator + "natives" + File.separator + "tracy");
|
||||
public static final Integer TRACY_MAX_SRC_LOCS_OVERRIDE = Integer.getInteger("angelica.tracy.maxSrcLocs");
|
||||
public static final Boolean TRACY_FRAME_IMAGES_OVERRIDE = parseBooleanOverride("angelica.tracy.frameImages");
|
||||
public static final Integer TRACY_FRAME_IMAGE_RATE_OVERRIDE = Integer.getInteger("angelica.tracy.frameImageRate");
|
||||
|
||||
// Profiling
|
||||
public static final String PROFILE_OPTS = System.getProperty("angelica.profile.opts", "event=wall,interval=5ms,alloc=512k,lock=10ms");
|
||||
@@ -63,6 +66,8 @@ public final class SystemProperties {
|
||||
public static final String FLYBY_COMMANDS = System.getProperty("angelica.flyby.commands", "");
|
||||
public static final String FLYBY_ORIGIN = System.getProperty("angelica.flyby.origin", "");
|
||||
public static final Integer FLYBY_DIMENSION = Integer.getInteger("angelica.flyby.dimension");
|
||||
public static final String FLYBY_BIOME = System.getProperty("angelica.flyby.biome", "");
|
||||
public static final boolean FLYBY_SURVIVAL = Boolean.getBoolean("angelica.flyby.survival");
|
||||
public static final FlybyPacing FLYBY_PACING = parseEnum("angelica.flyby.pacing", FlybyPacing.UNCAPPED, FlybyPacing.UNCAPPED, FlybyPacing.class);
|
||||
public static final FlybyWeather FLYBY_WEATHER = parseEnum("angelica.flyby.weather", FlybyWeather.CLEAR, FlybyWeather.CLEAR, FlybyWeather.class);
|
||||
public static final FlybyCamera FLYBY_CAMERA = parseEnum("angelica.flyby.camera", FlybyCamera.FIRST_PERSON, FlybyCamera.FIRST_PERSON, FlybyCamera.class);
|
||||
@@ -77,6 +82,8 @@ public final class SystemProperties {
|
||||
public static final Boolean FLYBY_FPS_GRAPH = parseBooleanOverride("angelica.flyby.fpsGraph");
|
||||
public static final boolean FLYBY_DEBUG_DUMP = Boolean.getBoolean("angelica.flyby.debugDump");
|
||||
public static final String FLYBY_CRASH_TEST = System.getProperty("angelica.flyby.crashTest", "");
|
||||
public static final boolean FLYBY_HEAP_CHECK = Boolean.getBoolean("angelica.flyby.heapCheck");
|
||||
public static final int FLYBY_VISITS = Integer.getInteger("angelica.flyby.visits", 1);
|
||||
|
||||
// Debug
|
||||
public static final boolean LWJGL_DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
|
||||
|
||||
@@ -25,11 +25,21 @@ public final class CaptureGate {
|
||||
public static final boolean FORCED = SystemProperties.DEBUG_MARKERS || SystemProperties.LWJGL_DEBUG;
|
||||
public static final boolean TOOL_ATTACHED = detectCaptureTool();
|
||||
public static boolean markersThisFrame = true;
|
||||
public static boolean tracyConnected;
|
||||
private static boolean toolAttached = TOOL_ATTACHED;
|
||||
|
||||
private CaptureGate() {}
|
||||
|
||||
public static void refresh() {
|
||||
markersThisFrame = gateValue(FORCED, TOOL_ATTACHED, Tracy.isConnected());
|
||||
tracyConnected = Tracy.isConnected();
|
||||
markersThisFrame = gateValue(FORCED, toolAttached, tracyConnected);
|
||||
}
|
||||
|
||||
static void probeGlTool() {
|
||||
if (RenderSystem.hasExtension("GL_EXT_debug_tool")) {
|
||||
toolAttached = true;
|
||||
LOGGER.info("Capture tool detected via GL_EXT_debug_tool; instrumentation enabled");
|
||||
}
|
||||
}
|
||||
|
||||
static boolean gateValue(boolean forced, boolean toolAttached, boolean tracyConnected) {
|
||||
@@ -37,7 +47,7 @@ public final class CaptureGate {
|
||||
}
|
||||
|
||||
public static boolean enabledAtStartup() {
|
||||
return FORCED || TOOL_ATTACHED;
|
||||
return FORCED || toolAttached;
|
||||
}
|
||||
|
||||
private static boolean detectCaptureTool() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
|
||||
|
||||
public final class ClipPlaneBooleanState extends BooleanStateStack {
|
||||
@@ -13,12 +14,22 @@ public final class ClipPlaneBooleanState extends BooleanStateStack {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean forwardsToBackend() {
|
||||
return !RenderSystem.isClipDistanceEmulated();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnabledChanged() {
|
||||
final int before = owner.clipPlaneEnabledMask;
|
||||
if (isEnabled()) {
|
||||
owner.clipPlaneEnabledMask |= (1 << index);
|
||||
} else {
|
||||
owner.clipPlaneEnabledMask &= ~(1 << index);
|
||||
}
|
||||
owner.clipPlaneGeneration++;
|
||||
if ((before == 0) != (owner.clipPlaneEnabledMask == 0) && RenderSystem.isClipDistanceEmulated() && GLSMHooks.CLIP_PLANES_IN_USE.hasListeners()) {
|
||||
GLSMHooks.CLIP_PLANES_IN_USE.post(GLSMHooks.clipPlanesInUseEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,7 +249,7 @@ public class CompatUniformManager {
|
||||
final int cpGen = glCtx.clipPlaneGeneration;
|
||||
if (cpGen != pu.lastClipPlaneGen) {
|
||||
pu.lastClipPlaneGen = cpGen;
|
||||
uploadClipPlanes(locs);
|
||||
uploadClipPlanes(locs, glCtx.clipPlaneEnabledMask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,16 +468,14 @@ public class CompatUniformManager {
|
||||
RENDER_BACKEND.uniform1f(locs[LOC_MAT_BASE + MF_SHININESS], mat.shininess);
|
||||
}
|
||||
|
||||
private static void uploadClipPlanes(int[] locs) {
|
||||
private static void uploadClipPlanes(int[] locs, int enabledMask) {
|
||||
if (locs[LOC_CLIP_PLANES_ENABLED] != -1) {
|
||||
RENDER_BACKEND.uniform1i(locs[LOC_CLIP_PLANES_ENABLED], GLSMRasterizer.anyClipPlaneEnabled() ? 1 : 0);
|
||||
RENDER_BACKEND.uniform1i(locs[LOC_CLIP_PLANES_ENABLED], enabledMask != 0 ? 1 : 0);
|
||||
}
|
||||
if (locs[LOC_CLIP_PLANES] != -1) {
|
||||
final ClipPlaneState cps = GLSMRasterizer.getClipPlaneState();
|
||||
clipPlaneBuf.clear();
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
cps.putEyePlane(i, clipPlaneBuf);
|
||||
}
|
||||
cps.putEffectivePlanes(enabledMask, clipPlaneBuf);
|
||||
clipPlaneBuf.flip();
|
||||
RENDER_BACKEND.uniform4(locs[LOC_CLIP_PLANES], clipPlaneBuf);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
/*
|
||||
* Ported from Mesa's util_idalloc (src/util/u_idalloc.c).
|
||||
* Ported from Mesa's util_idalloc and util_idalloc_sparse (src/util/u_idalloc.c).
|
||||
*
|
||||
* Original copyright and license:
|
||||
*
|
||||
@@ -32,17 +32,15 @@ package com.gtnewhorizons.angelica.glsm;
|
||||
*/
|
||||
|
||||
/**
|
||||
* Bitmap ID allocator for display list IDs.
|
||||
*
|
||||
* <p>Bitmap-based: each bit in the {@code data} array represents one ID.
|
||||
* Bit set = allocated, bit clear = free. ID 0 is reserved (never returned).
|
||||
* Sparse bitmap ID allocator for display list IDs; ID 0 is reserved (never returned).
|
||||
*/
|
||||
public class DisplayListIDAllocator {
|
||||
|
||||
private int[] data;
|
||||
private int numElements; // number of uint32 words allocated
|
||||
private int numSetElements; // highest word index with any bit set + 1
|
||||
private int lowestFreeIdx; // optimization: skip fully-allocated words
|
||||
private static final int SEGMENT_SHIFT = 22;
|
||||
private static final int SEGMENT_MASK = (1 << SEGMENT_SHIFT) - 1;
|
||||
private static final int WORDS_PER_SEGMENT = (1 << SEGMENT_SHIFT) / 32;
|
||||
|
||||
private final Segment[] segments = new Segment[512];
|
||||
|
||||
public DisplayListIDAllocator() {
|
||||
this(32); // 1024 IDs initially
|
||||
@@ -50,138 +48,191 @@ public class DisplayListIDAllocator {
|
||||
|
||||
public DisplayListIDAllocator(int initialWords) {
|
||||
if (initialWords < 1) initialWords = 1;
|
||||
this.data = new int[initialWords];
|
||||
this.numElements = initialWords;
|
||||
this.numSetElements = 0;
|
||||
this.lowestFreeIdx = 0;
|
||||
final Segment first = new Segment(initialWords);
|
||||
// Reserve ID 0 (never returned), matching Mesa's skip_zero pattern
|
||||
data[0] = 1;
|
||||
numSetElements = 1;
|
||||
first.data[0] = 1;
|
||||
first.numSetElements = 1;
|
||||
segments[0] = first;
|
||||
}
|
||||
|
||||
private Segment segment(int index) {
|
||||
Segment s = segments[index];
|
||||
if (s == null) {
|
||||
s = new Segment(1);
|
||||
segments[index] = s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public synchronized int allocRange(int count) {
|
||||
if (count <= 0) throw new IllegalArgumentException("count must be > 0");
|
||||
if (count == 1) return allocSingle();
|
||||
return allocMultiple(count);
|
||||
}
|
||||
|
||||
private int allocSingle() {
|
||||
for (int i = lowestFreeIdx; i < numElements; i++) {
|
||||
if (data[i] == 0xFFFFFFFF) continue;
|
||||
|
||||
int bit = Integer.numberOfTrailingZeros(~data[i]);
|
||||
data[i] |= 1 << bit;
|
||||
lowestFreeIdx = i;
|
||||
numSetElements = Math.max(numSetElements, i + 1);
|
||||
return i * 32 + bit;
|
||||
}
|
||||
|
||||
// No slots available, grow. Record old end, then mark first bit there.
|
||||
int oldNumElements = numElements;
|
||||
resize(Math.max(numElements, 1) * 2);
|
||||
lowestFreeIdx = oldNumElements;
|
||||
data[oldNumElements] |= 1;
|
||||
numSetElements = Math.max(numSetElements, oldNumElements + 1);
|
||||
return oldNumElements * 32;
|
||||
}
|
||||
|
||||
private int allocMultiple(int num) {
|
||||
int numWords = divRoundUp(num, 32);
|
||||
int base = findFreeBlock(lowestFreeIdx);
|
||||
boolean found = false;
|
||||
|
||||
while (!found) {
|
||||
int i;
|
||||
for (i = base; i < numElements && i - base < numWords && data[i] == 0; i++)
|
||||
;
|
||||
|
||||
if (i - base == numWords) {
|
||||
found = true;
|
||||
} else if (i == numElements) {
|
||||
// Hit end — base is valid: data[base..end] are zero, resize extends the run into newly zeroed words.
|
||||
resize(numElements * 2 + numWords);
|
||||
found = true;
|
||||
} else {
|
||||
// Non-zero word at i, skip past it
|
||||
base = i + 1;
|
||||
if (count == 1) {
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
final Segment s = segment(i);
|
||||
if (s.lowestFreeIdx < WORDS_PER_SEGMENT) return (i << SEGMENT_SHIFT) | s.allocSingle();
|
||||
}
|
||||
throw new IllegalStateException("all display list IDs are in use");
|
||||
}
|
||||
|
||||
// Mark bits as used
|
||||
int fullWords = num / 32;
|
||||
int remainder = num % 32;
|
||||
for (int i = base; i < base + fullWords; i++) {
|
||||
data[i] = 0xFFFFFFFF;
|
||||
final int numWords = Segment.divRoundUp(count, 32);
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
final Segment s = segment(i);
|
||||
if (s.lowestFreeIdx + numWords > WORDS_PER_SEGMENT) continue;
|
||||
final int base = s.allocMultiple(count);
|
||||
if (base + count <= 1 << SEGMENT_SHIFT) return (i << SEGMENT_SHIFT) | base;
|
||||
for (int j = 0; j < count; j++) s.free(base + j);
|
||||
}
|
||||
if (remainder != 0) {
|
||||
data[base + fullWords] |= (1 << remainder) - 1;
|
||||
}
|
||||
|
||||
if (lowestFreeIdx == base) {
|
||||
lowestFreeIdx = base + num / 32;
|
||||
}
|
||||
numSetElements = Math.max(numSetElements, base + numWords);
|
||||
|
||||
return base * 32;
|
||||
throw new IllegalStateException("no free consecutive range of " + count + " display list IDs");
|
||||
}
|
||||
|
||||
private int findFreeBlock(int start) {
|
||||
for (int i = start; i < numElements; i++) {
|
||||
if (data[i] == 0) return i;
|
||||
}
|
||||
return numElements;
|
||||
public synchronized void reserve(int id) {
|
||||
if (id <= 0) return;
|
||||
segment(id >>> SEGMENT_SHIFT).reserve(id & SEGMENT_MASK);
|
||||
}
|
||||
|
||||
public synchronized void free(int id) {
|
||||
if (id <= 0) return; // ID 0 is reserved
|
||||
int idx = id / 32;
|
||||
if (idx >= numElements) return;
|
||||
|
||||
lowestFreeIdx = Math.min(idx, lowestFreeIdx);
|
||||
data[idx] &= ~(1 << (id % 32));
|
||||
|
||||
if (numSetElements == idx + 1) {
|
||||
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
|
||||
numSetElements--;
|
||||
}
|
||||
}
|
||||
final Segment s = segments[id >>> SEGMENT_SHIFT];
|
||||
if (s != null) s.free(id & SEGMENT_MASK);
|
||||
}
|
||||
|
||||
public synchronized void freeRange(int base, int count) {
|
||||
if (count <= 0 || base <= 0) return;
|
||||
|
||||
int startIdx = base / 32;
|
||||
if (startIdx >= numElements) return;
|
||||
|
||||
int end = base + count;
|
||||
for (int id = base; id < end; id++) {
|
||||
int idx = id / 32;
|
||||
if (idx >= numElements) break;
|
||||
data[idx] &= ~(1 << (id % 32));
|
||||
}
|
||||
|
||||
lowestFreeIdx = Math.min(startIdx, lowestFreeIdx);
|
||||
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
|
||||
numSetElements--;
|
||||
final long last = Math.min((long) base + count - 1, Integer.MAX_VALUE);
|
||||
long id = base;
|
||||
while (id <= last) {
|
||||
final int index = (int) (id >>> SEGMENT_SHIFT);
|
||||
final Segment s = segments[index];
|
||||
if (s == null || (id & SEGMENT_MASK) >= (long) s.numElements * 32) {
|
||||
id = (long) (index + 1) << SEGMENT_SHIFT;
|
||||
continue;
|
||||
}
|
||||
s.free((int) id & SEGMENT_MASK);
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean isAllocated(int id) {
|
||||
if (id <= 0) return false;
|
||||
int idx = id / 32;
|
||||
if (idx >= numSetElements) return false;
|
||||
return (data[idx] & (1 << (id % 32))) != 0;
|
||||
final Segment s = segments[id >>> SEGMENT_SHIFT];
|
||||
return s != null && s.isAllocated(id & SEGMENT_MASK);
|
||||
}
|
||||
|
||||
private void resize(int newNumElements) {
|
||||
if (newNumElements <= numElements) return;
|
||||
int[] newData = new int[newNumElements];
|
||||
System.arraycopy(data, 0, newData, 0, numElements);
|
||||
data = newData;
|
||||
numElements = newNumElements;
|
||||
}
|
||||
private static final class Segment {
|
||||
private int[] data;
|
||||
private int numElements; // number of uint32 words allocated
|
||||
private int numSetElements; // highest word index with any bit set + 1
|
||||
private int lowestFreeIdx; // optimization: skip fully-allocated words
|
||||
|
||||
private static int divRoundUp(int a, int b) {
|
||||
return (a + b - 1) / b;
|
||||
Segment(int initialWords) {
|
||||
this.data = new int[initialWords];
|
||||
this.numElements = initialWords;
|
||||
}
|
||||
|
||||
int allocSingle() {
|
||||
for (int i = lowestFreeIdx; i < numElements; i++) {
|
||||
if (data[i] == 0xFFFFFFFF) continue;
|
||||
|
||||
int bit = Integer.numberOfTrailingZeros(~data[i]);
|
||||
data[i] |= 1 << bit;
|
||||
lowestFreeIdx = i;
|
||||
numSetElements = Math.max(numSetElements, i + 1);
|
||||
return i * 32 + bit;
|
||||
}
|
||||
|
||||
// No slots available, grow. Record old end, then mark first bit there.
|
||||
int oldNumElements = numElements;
|
||||
resize(Math.max(numElements, 1) * 2);
|
||||
lowestFreeIdx = oldNumElements;
|
||||
data[oldNumElements] |= 1;
|
||||
numSetElements = Math.max(numSetElements, oldNumElements + 1);
|
||||
return oldNumElements * 32;
|
||||
}
|
||||
|
||||
int allocMultiple(int num) {
|
||||
int numWords = divRoundUp(num, 32);
|
||||
int base = findFreeBlock(lowestFreeIdx);
|
||||
boolean found = false;
|
||||
|
||||
while (!found) {
|
||||
int i;
|
||||
for (i = base; i < numElements && i - base < numWords && data[i] == 0; i++)
|
||||
;
|
||||
|
||||
if (i - base == numWords) {
|
||||
found = true;
|
||||
} else if (i == numElements) {
|
||||
// Hit end — base is valid: data[base..end] are zero, resize extends the run into newly zeroed words.
|
||||
resize(numElements * 2 + numWords);
|
||||
found = true;
|
||||
} else {
|
||||
// Non-zero word at i, skip past it
|
||||
base = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark bits as used
|
||||
int fullWords = num / 32;
|
||||
int remainder = num % 32;
|
||||
for (int i = base; i < base + fullWords; i++) {
|
||||
data[i] = 0xFFFFFFFF;
|
||||
}
|
||||
if (remainder != 0) {
|
||||
data[base + fullWords] |= (1 << remainder) - 1;
|
||||
}
|
||||
|
||||
if (lowestFreeIdx == base) {
|
||||
lowestFreeIdx = base + num / 32;
|
||||
}
|
||||
numSetElements = Math.max(numSetElements, base + numWords);
|
||||
|
||||
return base * 32;
|
||||
}
|
||||
|
||||
int findFreeBlock(int start) {
|
||||
for (int i = start; i < numElements; i++) {
|
||||
if (data[i] == 0) return i;
|
||||
}
|
||||
return numElements;
|
||||
}
|
||||
|
||||
void free(int id) {
|
||||
int idx = id / 32;
|
||||
if (idx >= numElements) return;
|
||||
|
||||
lowestFreeIdx = Math.min(idx, lowestFreeIdx);
|
||||
data[idx] &= ~(1 << (id % 32));
|
||||
|
||||
if (numSetElements == idx + 1) {
|
||||
while (numSetElements > 0 && data[numSetElements - 1] == 0) {
|
||||
numSetElements--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isAllocated(int id) {
|
||||
int idx = id / 32;
|
||||
if (idx >= numSetElements) return false;
|
||||
return (data[idx] & (1 << (id % 32))) != 0;
|
||||
}
|
||||
|
||||
void reserve(int id) {
|
||||
final int idx = id / 32;
|
||||
if (idx >= numElements) resize((idx + 1) * 2);
|
||||
data[idx] |= 1 << (id % 32);
|
||||
numSetElements = Math.max(numSetElements, idx + 1);
|
||||
}
|
||||
|
||||
private void resize(int newNumElements) {
|
||||
if (newNumElements <= numElements) return;
|
||||
int[] newData = new int[newNumElements];
|
||||
System.arraycopy(data, 0, newData, 0, numElements);
|
||||
data = newData;
|
||||
numElements = newNumElements;
|
||||
}
|
||||
|
||||
private static int divRoundUp(int a, int b) {
|
||||
return (a + b - 1) / b;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1046,9 +1046,12 @@ public class DisplayListManager {
|
||||
}
|
||||
final boolean locked = GLStateManager.acquireDrawLock();
|
||||
try {
|
||||
final CompiledDisplayList previous = displayListCache.put(glListId, compiled);
|
||||
if (previous != null && previous != CompiledDisplayList.EMPTY && previous != compiled) {
|
||||
previous.delete();
|
||||
synchronized (GLSMDraws.displayListIdAllocator) {
|
||||
final CompiledDisplayList previous = displayListCache.put(glListId, compiled);
|
||||
if (previous != null && previous != CompiledDisplayList.EMPTY && previous != compiled) {
|
||||
previous.delete();
|
||||
}
|
||||
GLSMDraws.displayListIdAllocator.reserve(glListId);
|
||||
}
|
||||
} finally {
|
||||
if (locked) GLStateManager.releaseDrawLock();
|
||||
|
||||
@@ -11,11 +11,8 @@ public final class GLESCaps {
|
||||
|
||||
private GLESCaps() {}
|
||||
|
||||
public static boolean isCapAllowed(int cap, boolean isGLES, boolean hasClipCullDistance) {
|
||||
public static boolean isCapAllowed(int cap, boolean isGLES) {
|
||||
if (!isGLES) return true;
|
||||
if (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + GLStateManager.MAX_CLIP_PLANES) {
|
||||
return hasClipCullDistance;
|
||||
}
|
||||
return switch (cap) {
|
||||
case GL11.GL_LINE_SMOOTH,
|
||||
GL11.GL_POLYGON_SMOOTH,
|
||||
|
||||
@@ -110,7 +110,7 @@ public final class GLSMAttribStack {
|
||||
if (!vaos.vaoMap.containsKey(saved.boundVAO)) return;
|
||||
GLSMVertexInput.bindVertexArrayInternal(glCtx, saved.boundVAO);
|
||||
glCtx.clientActiveTextureUnit = saved.clientActiveTextureUnit;
|
||||
if (vaos.isBufferLive(saved.boundVBO, saved)) {
|
||||
if (vaos.isSavedVboLive(saved)) {
|
||||
final int changed = vaos.restoreClientArrays(saved);
|
||||
for (int pointers = changed & 0xFFFF; pointers != 0; pointers &= pointers - 1) {
|
||||
GLSMVertexInput.pointAttrib(glCtx, Integer.numberOfTrailingZeros(pointers));
|
||||
@@ -120,7 +120,7 @@ public final class GLSMAttribStack {
|
||||
}
|
||||
if (glCtx.boundVBO != saved.boundVBO) GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, saved.boundVBO);
|
||||
}
|
||||
if (vaos.boundEBO != saved.boundEBO && vaos.isBufferLive(saved.boundEBO, saved)) GLSMBuffers.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, saved.boundEBO);
|
||||
if (vaos.boundEBO != saved.boundEBO && vaos.isSavedEboLive(saved)) GLSMBuffers.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, saved.boundEBO);
|
||||
}
|
||||
|
||||
public static void pushState(int mask) {
|
||||
|
||||
@@ -64,9 +64,12 @@ public final class GLSMBuffers {
|
||||
}
|
||||
}
|
||||
|
||||
public static long bufferDeletes;
|
||||
|
||||
private static void invalidateDeletedBuffer(int buffer) {
|
||||
final GLContextState glCtx = ctx();
|
||||
if (buffer == 0) return;
|
||||
bufferDeletes++;
|
||||
final boolean locked = acquireDrawLock();
|
||||
try {
|
||||
FfpExtendedAttribs.onDeleteBuffer(glCtx, buffer);
|
||||
@@ -87,7 +90,7 @@ public final class GLSMBuffers {
|
||||
}
|
||||
writeMappedBuffers.remove(buffer);
|
||||
|
||||
glCtx.vaos.onDeleteBuffer(buffer);
|
||||
glCtx.vaos.onDeleteBuffer(buffer, glCtx.clientAttribSavedArrays);
|
||||
} finally {
|
||||
if (locked) releaseDrawLock();
|
||||
}
|
||||
|
||||
@@ -481,7 +481,7 @@ public final class GLSMDraws {
|
||||
DisplayListManager.glEndList();
|
||||
}
|
||||
|
||||
private static final DisplayListIDAllocator displayListIdAllocator = new DisplayListIDAllocator();
|
||||
static final DisplayListIDAllocator displayListIdAllocator = new DisplayListIDAllocator();
|
||||
|
||||
public static int glGenLists(int range) {
|
||||
rejectExitCapture();
|
||||
|
||||
@@ -30,7 +30,6 @@ public final class GLSMEnable {
|
||||
// Handle clip planes dynamically (supports up to MAX_CLIP_PLANES)
|
||||
if (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + MAX_CLIP_PLANES) {
|
||||
glCtx.clipPlaneStates[cap - GL11.GL_CLIP_PLANE0].enable();
|
||||
glCtx.clipPlaneGeneration++;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -119,7 +118,6 @@ public final class GLSMEnable {
|
||||
// Handle clip planes dynamically (supports up to MAX_CLIP_PLANES)
|
||||
if (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + MAX_CLIP_PLANES) {
|
||||
glCtx.clipPlaneStates[cap - GL11.GL_CLIP_PLANE0].disable();
|
||||
glCtx.clipPlaneGeneration++;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager.RecordMode;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.stacks.IntegerStateStack;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
@@ -326,6 +327,11 @@ public final class GLSMFramebuffer {
|
||||
RENDER_BACKEND.readPixels(x, y, width, height, format, GLSMTextures.remapPixelTypeForGLES(format, type), pixels);
|
||||
}
|
||||
|
||||
public static ScreenCapture createScreenCapture(int width, int height) {
|
||||
rejectExitCapture();
|
||||
return RENDER_BACKEND.createScreenCapture(width, height);
|
||||
}
|
||||
|
||||
public static int getDrawFramebufferGeneration() {
|
||||
return ctx().drawFramebufferGeneration;
|
||||
}
|
||||
|
||||
@@ -88,8 +88,11 @@ public final class GLSMRasterizer {
|
||||
static void probePointSizeRange(GLContextState glCtx) {
|
||||
final FloatBuffer range = glCtx.queryScratch;
|
||||
range.clear();
|
||||
RENDER_BACKEND.getFloat(GL11.GL_POINT_SIZE_RANGE, range);
|
||||
final float smooth = range.get(1);
|
||||
float smooth = 0.0f;
|
||||
if (!RenderSystem.isGLES()) {
|
||||
RENDER_BACKEND.getFloat(GL11.GL_POINT_SIZE_RANGE, range);
|
||||
smooth = range.get(1);
|
||||
}
|
||||
RENDER_BACKEND.getFloat(GL12.GL_ALIASED_POINT_SIZE_RANGE, range);
|
||||
maxPointSize = Math.max(smooth, range.get(1));
|
||||
}
|
||||
@@ -506,6 +509,14 @@ public final class GLSMRasterizer {
|
||||
return glCtx.clipPlaneEnabledMask != 0;
|
||||
}
|
||||
|
||||
public static boolean clipEmulationActive() {
|
||||
return RenderSystem.isClipDistanceEmulated() && anyClipPlaneEnabled();
|
||||
}
|
||||
|
||||
public static boolean clipEmulationActive(GLContextState glCtx) {
|
||||
return RenderSystem.isClipDistanceEmulated() && anyClipPlaneEnabled(glCtx);
|
||||
}
|
||||
|
||||
public static void glSampleCoverage(float value, boolean invert) {
|
||||
if (DisplayListManager.isRecording()) {
|
||||
throw DisplayListManager.unsupportedInList("glSampleCoverage");
|
||||
|
||||
@@ -1211,6 +1211,14 @@ public final class GLSMTextures {
|
||||
public static void restorePixelUnpackState(PixelStoreState applied) {
|
||||
PixelStoreState.applyDiff(applied, ctx().pixelUnpackState, false);
|
||||
}
|
||||
|
||||
public static void forcePixelPackState(PixelStoreState target) {
|
||||
PixelStoreState.applyDiff(ctx().pixelPackState, target, true);
|
||||
}
|
||||
|
||||
public static void restorePixelPackState(PixelStoreState applied) {
|
||||
PixelStoreState.applyDiff(applied, ctx().pixelPackState, true);
|
||||
}
|
||||
public static boolean glIsTexture(int texture) { rejectExitCapture(); return !GLSMTextureLifetime.isDeferredDelete(texture) && RENDER_BACKEND.isTexture(texture); }
|
||||
public static boolean glIsSampler(int sampler) { rejectExitCapture(); return RENDER_BACKEND.isSampler(sampler); }
|
||||
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
|
||||
import com.gtnewhorizons.angelica.glsm.states.PixelStoreState;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL21;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public final class GLScreenCapture extends ScreenCapture {
|
||||
|
||||
private final int[] textures = new int[RING];
|
||||
private final int[] framebuffers = new int[RING];
|
||||
private final int[] buffers = new int[RING];
|
||||
|
||||
public GLScreenCapture(int width, int height) {
|
||||
super(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean start(int slot, int srcFramebuffer, int srcWidth, int srcHeight) {
|
||||
final int width = width();
|
||||
final int height = height();
|
||||
final int previousRead = GLSMFramebuffer.getReadFramebuffer();
|
||||
final int previousDraw = GLSMFramebuffer.getDrawFramebuffer();
|
||||
final int previousPack = GLStateManager.ctx().boundPixelPackBuffer;
|
||||
final boolean scissor = GLSMRasterizer.getScissorTest().isEnabled();
|
||||
BatchStateGuard.suspend();
|
||||
try {
|
||||
if (framebuffers[slot] == 0) create(slot);
|
||||
if (scissor) GLSMEnable.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, srcFramebuffer);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, framebuffers[slot]);
|
||||
GLSMFramebuffer.glBlitFramebuffer(0, 0, srcWidth, srcHeight, 0, 0, width, height, GL11.GL_COLOR_BUFFER_BIT, GL11.GL_LINEAR);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, framebuffers[slot]);
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, buffers[slot]);
|
||||
GLSMTextures.forcePixelPackState(PixelStoreState.DEFAULT);
|
||||
GLSMFramebuffer.glReadPixels(0, 0, width, height, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, 0L);
|
||||
return true;
|
||||
} finally {
|
||||
GLSMTextures.restorePixelPackState(PixelStoreState.DEFAULT);
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, previousPack);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, previousRead);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, previousDraw);
|
||||
if (scissor) GLSMEnable.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
BatchStateGuard.resume();
|
||||
}
|
||||
}
|
||||
|
||||
private void create(int slot) {
|
||||
final int previousTexture = GLSMTextures.getBoundTextureForServerState();
|
||||
final int texture;
|
||||
try {
|
||||
texture = GLSMTextures.glGenTextures();
|
||||
GLSMTextures.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
|
||||
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST);
|
||||
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
|
||||
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
|
||||
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL12.GL_TEXTURE_MAX_LEVEL, 0);
|
||||
GLSMTextures.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, width(), height(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
} finally {
|
||||
GLSMTextures.glBindTexture(GL11.GL_TEXTURE_2D, previousTexture);
|
||||
}
|
||||
textures[slot] = texture;
|
||||
framebuffers[slot] = GLSMFramebuffer.glGenFramebuffers();
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, framebuffers[slot]);
|
||||
GLSMFramebuffer.glFramebufferTexture2D(GL30.GL_DRAW_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, texture, 0);
|
||||
buffers[slot] = GLSMBuffers.glGenBuffers();
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, buffers[slot]);
|
||||
GLSMBuffers.glBufferData(GL21.GL_PIXEL_PACK_BUFFER, (long) width() * height() * 4, GL15.GL_STREAM_READ);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long map(int slot) {
|
||||
final int previousPack = GLStateManager.ctx().boundPixelPackBuffer;
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, buffers[slot]);
|
||||
try {
|
||||
return GLSMBuffers.glMapBufferRangeAddress(GL21.GL_PIXEL_PACK_BUFFER, 0L, (long) width() * height() * 4, GL30.GL_MAP_READ_BIT);
|
||||
} finally {
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, previousPack);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unmap(int slot) {
|
||||
final int previousPack = GLStateManager.ctx().boundPixelPackBuffer;
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, buffers[slot]);
|
||||
GLSMBuffers.glUnmapBuffer(GL21.GL_PIXEL_PACK_BUFFER);
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, previousPack);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteSlot(int slot) {
|
||||
if (buffers[slot] != 0) GLSMBuffers.glDeleteBuffers(buffers[slot]);
|
||||
if (framebuffers[slot] != 0) GLSMFramebuffer.glDeleteFramebuffers(framebuffers[slot]);
|
||||
if (textures[slot] != 0) GLSMTextures.glDeleteTextures(textures[slot]);
|
||||
buffers[slot] = 0;
|
||||
framebuffers[slot] = 0;
|
||||
textures[slot] = 0;
|
||||
}
|
||||
}
|
||||
@@ -458,6 +458,7 @@ public class GLStateManager {
|
||||
static void init(Runnable initCallback) {
|
||||
final GLContextState glCtx = ctx();
|
||||
RenderSystem.initRenderer();
|
||||
CaptureGate.probeGlTool();
|
||||
|
||||
GLSMFramebuffer.queryDefaultDrawBuffer();
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ public class RenderSystem {
|
||||
private static volatile boolean isGLES;
|
||||
private static volatile int glesVersion;
|
||||
private static volatile boolean glesDetected;
|
||||
private static volatile boolean hasClipCullDistance;
|
||||
private static boolean clipDistanceEmulated;
|
||||
|
||||
private static volatile boolean isLTW;
|
||||
private static volatile boolean ltwDetected;
|
||||
@@ -132,7 +132,12 @@ public class RenderSystem {
|
||||
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
|
||||
supportsStencilTexturing = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_stencil_texturing;
|
||||
supportsTextureStorage = isGLES ? glesVersion >= 300 : GLStateManager.capabilities.OpenGL42 || GLStateManager.capabilities.GL_ARB_texture_storage;
|
||||
hasClipCullDistance = !isGLES || hasExtension("GL_EXT_clip_cull_distance");
|
||||
final boolean esClipExt = !isGLES || hasExtension("GL_EXT_clip_cull_distance");
|
||||
final int maxClipDistances = esClipExt ? RENDER_BACKEND.getInteger(GL30.GL_MAX_CLIP_DISTANCES) : 0;
|
||||
clipDistanceEmulated = SystemProperties.EMULATE_CLIP_DISTANCES || maxClipDistances < GLStateManager.MAX_CLIP_PLANES;
|
||||
if (clipDistanceEmulated) {
|
||||
GLStateManager.LOGGER.info("Clip distances emulated (GL_MAX_CLIP_DISTANCES={}, forced={})", maxClipDistances, SystemProperties.EMULATE_CLIP_DISTANCES);
|
||||
}
|
||||
|
||||
// Cache maximum image units
|
||||
if (supportsImageLoadStore) {
|
||||
@@ -434,11 +439,11 @@ public class RenderSystem {
|
||||
return supportsImageLoadStore;
|
||||
}
|
||||
|
||||
public static boolean hasClipCullDistance() {
|
||||
return hasClipCullDistance;
|
||||
public static boolean isClipDistanceEmulated() {
|
||||
return clipDistanceEmulated;
|
||||
}
|
||||
|
||||
private static boolean hasExtension(String name) {
|
||||
static boolean hasExtension(String name) {
|
||||
final int count = GLSMQueries.glGetInteger(GL30.GL_NUM_EXTENSIONS);
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (name.equals(GLSMQueries.glGetStringi(GL11.GL_EXTENSIONS, i))) return true;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLScreenCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import org.lwjgl.BufferUtils;
|
||||
@@ -659,6 +660,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
|
||||
GL11.glReadBuffer(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScreenCapture createScreenCapture(int width, int height) {
|
||||
return new GLScreenCapture(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
GL11.glReadPixels(x, y, width, height, format, type, pixels);
|
||||
|
||||
@@ -479,6 +479,7 @@ public abstract class RenderBackend {
|
||||
public abstract void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels);
|
||||
public abstract void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels);
|
||||
public abstract void readPixels(int x, int y, int width, int height, int format, int type, long pixelBufferOffset);
|
||||
public abstract ScreenCapture createScreenCapture(int width, int height);
|
||||
public abstract void getTexImage(int target, int level, int format, int type, ByteBuffer pixels);
|
||||
public abstract void getTexImage(int target, int level, int format, int type, IntBuffer pixels);
|
||||
public abstract void getTexImage(int target, int level, int format, int type, long pixelBufferOffset);
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.gtnewhorizons.angelica.glsm.backend;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
|
||||
/** Async downscaled RGBA8 copy of a framebuffer's color. */
|
||||
public abstract class ScreenCapture {
|
||||
|
||||
public static final int RING = RenderBackend.MAX_FRAMES_IN_FLIGHT + 2;
|
||||
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final long[] tags = new long[RING];
|
||||
private final long[] syncs = new long[RING];
|
||||
private int head;
|
||||
private int tail;
|
||||
private long mapped;
|
||||
private boolean deleted;
|
||||
|
||||
protected ScreenCapture(int width, int height) {
|
||||
if (width <= 0 || height <= 0) throw new IllegalArgumentException("capture size " + width + "x" + height);
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public final int width() { return width; }
|
||||
public final int height() { return height; }
|
||||
|
||||
public final boolean idle() { return head == tail; }
|
||||
|
||||
public final boolean begin(int srcFramebuffer, int srcWidth, int srcHeight, long tag) {
|
||||
if (deleted || head - tail == RING || srcWidth <= 0 || srcHeight <= 0) return false;
|
||||
final int slot = head % RING;
|
||||
if (!start(slot, srcFramebuffer, srcWidth, srcHeight)) return false;
|
||||
final long sync = GLSMBuffers.glFenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
if (sync == 0L) return false;
|
||||
syncs[slot] = sync;
|
||||
tags[slot] = tag;
|
||||
head++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public final boolean poll() {
|
||||
if (mapped != 0L) throw new IllegalStateException("poll() before release() of the previous capture");
|
||||
while (tail != head) {
|
||||
final int slot = tail % RING;
|
||||
final int status = GLSMBuffers.glClientWaitSync(syncs[slot], 0, 0L);
|
||||
if (status == GL32.GL_TIMEOUT_EXPIRED) return false;
|
||||
dropSync(slot);
|
||||
if (status != GL32.GL_WAIT_FAILED) {
|
||||
mapped = map(slot);
|
||||
if (mapped != 0L) return true;
|
||||
}
|
||||
tail++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public final long address() {
|
||||
if (mapped == 0L) throw new IllegalStateException("no polled capture");
|
||||
return mapped;
|
||||
}
|
||||
|
||||
public final long tag() {
|
||||
if (mapped == 0L) throw new IllegalStateException("no polled capture");
|
||||
return tags[tail % RING];
|
||||
}
|
||||
|
||||
public final void release() {
|
||||
if (mapped == 0L) throw new IllegalStateException("no polled capture");
|
||||
mapped = 0L;
|
||||
unmap(tail % RING);
|
||||
tail++;
|
||||
}
|
||||
|
||||
public final void delete() {
|
||||
if (deleted) return;
|
||||
if (mapped != 0L) release();
|
||||
deleted = true;
|
||||
for (int slot = 0; slot < RING; slot++) {
|
||||
dropSync(slot);
|
||||
deleteSlot(slot);
|
||||
}
|
||||
head = tail = 0;
|
||||
}
|
||||
|
||||
private void dropSync(int slot) {
|
||||
if (syncs[slot] == 0L) return;
|
||||
GLSMBuffers.glDeleteSync(syncs[slot]);
|
||||
syncs[slot] = 0L;
|
||||
}
|
||||
|
||||
/** Returns false when nothing was recorded; the slot then stays free. */
|
||||
protected abstract boolean start(int slot, int srcFramebuffer, int srcWidth, int srcHeight);
|
||||
|
||||
protected abstract long map(int slot);
|
||||
|
||||
protected abstract void unmap(int slot);
|
||||
|
||||
/** Called for every slot, including ones never started. */
|
||||
protected abstract void deleteSlot(int slot);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
public final class ClipDistanceGlsl {
|
||||
|
||||
public static final String VS_OUT_DIST0 = "out vec4 angelica_ClipDist0;";
|
||||
public static final String VS_OUT_DIST1 = "out vec4 angelica_ClipDist1;";
|
||||
public static final String FS_IN_DIST0 = "in vec4 angelica_ClipDist0;";
|
||||
public static final String FS_IN_DIST1 = "in vec4 angelica_ClipDist1;";
|
||||
public static final String FS_DISCARD = "if (any(lessThan(angelica_ClipDist0, vec4(0.0))) || any(lessThan(angelica_ClipDist1, vec4(0.0)))) discard;\n";
|
||||
|
||||
private ClipDistanceGlsl() {}
|
||||
|
||||
public static void appendVsAssign(StringBuilder sb, String[] distances) {
|
||||
sb.append(" angelica_ClipDist0 = vec4(").append(distances[0]).append(", ").append(distances[1]).append(", ").append(distances[2]).append(", ").append(distances[3]).append(");\n");
|
||||
sb.append(" angelica_ClipDist1 = vec4(").append(distances[4]).append(", ").append(distances[5]).append(", ").append(distances[6]).append(", ").append(distances[7]).append(");\n");
|
||||
}
|
||||
}
|
||||
@@ -108,11 +108,13 @@ public final class FFPUniformBlock {
|
||||
public static final int FOG_PARAMS = vec4("u_FogParams");
|
||||
public static final int FOG_COLOR = vec4("u_FogColor");
|
||||
|
||||
// Rain and snow, for Instancing.WEATHER. 0 = (translate.xyz, 1/radius),
|
||||
// 1 = (cameraFrac.xy, partialTicks, age), 2 = (rainScroll, snowScroll, rainStrength, unused).
|
||||
// Weather columns, for Instancing.WEATHER. 0 = (translate.xyz, 1/radius),
|
||||
// 1 = (cameraFrac.xy, partialTicks, age), 2 = (vanillaTick, providerTick, rainStrength, providerAge),
|
||||
// 3 = (provider 1/radius, unused, unused, unused).
|
||||
public static final int WEATHER_PARAMS_0 = vec4("u_WeatherParams0");
|
||||
public static final int WEATHER_PARAMS_1 = vec4("u_WeatherParams1");
|
||||
public static final int WEATHER_PARAMS_2 = vec4("u_WeatherParams2");
|
||||
public static final int WEATHER_PARAMS_3 = vec4("u_WeatherParams3");
|
||||
|
||||
public static final int SIZE;
|
||||
public static final String GLSL_DECL;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMLighting;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
@@ -15,12 +16,75 @@ public final class FFPVertexLighting {
|
||||
private static final Vector4f scratchEyePos = new Vector4f();
|
||||
private static final Matrix3f scratchNormalMat = new Matrix3f();
|
||||
private static final Vector3f scratchVec = new Vector3f();
|
||||
private static final Vector3f scratchFactor = new Vector3f();
|
||||
private static final Vector3f base = new Vector3f();
|
||||
private static final Vector3f[] lightDirs = new Vector3f[VertexKey.FFP_LIGHT_COUNT];
|
||||
private static final Vector3f[] lightDiffuse = new Vector3f[VertexKey.FFP_LIGHT_COUNT];
|
||||
private static int litCount;
|
||||
private static boolean normalize;
|
||||
private static float rescale;
|
||||
|
||||
static {
|
||||
for (int i = 0; i < VertexKey.FFP_LIGHT_COUNT; i++) {
|
||||
lightDirs[i] = new Vector3f();
|
||||
lightDiffuse[i] = new Vector3f();
|
||||
}
|
||||
}
|
||||
|
||||
private FFPVertexLighting() {}
|
||||
|
||||
public static boolean modulatesVertexColor(Vector3f destFactor) {
|
||||
destFactor.set(1.0f);
|
||||
if (!prepare(false)) return false;
|
||||
|
||||
factor(ShaderManager.getCurrentNormal(), scratchFactor);
|
||||
final float r = scratchFactor.x;
|
||||
final float g = scratchFactor.y;
|
||||
final float b = scratchFactor.z;
|
||||
if (r == 1.0f && g == 1.0f && b == 1.0f) return false;
|
||||
if (!Float.isFinite(r + g + b)) return false;
|
||||
destFactor.set(r, g, b);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean bakeColors(int[] rawBuffer, int vertexCount, boolean hasNormals, boolean hasColor) {
|
||||
if (vertexCount <= 0 || !prepare(true)) return false;
|
||||
|
||||
if (!hasNormals) factor(ShaderManager.getCurrentNormal(), scratchFactor);
|
||||
float cr = 0.0f, cg = 0.0f, cb = 0.0f;
|
||||
int alphaBits = 0;
|
||||
if (!hasColor) {
|
||||
final var color = GLSMCurrentAttribs.getColor();
|
||||
cr = clamp01(color.getRed());
|
||||
cg = clamp01(color.getGreen());
|
||||
cb = clamp01(color.getBlue());
|
||||
alphaBits = toByte(clamp01(color.getAlpha())) << 24;
|
||||
}
|
||||
|
||||
boolean haveNormal = false;
|
||||
int lastNormal = 0;
|
||||
for (int v = 0, i = 0; v < vertexCount; v++, i += 8) {
|
||||
if (hasNormals) {
|
||||
final int packed = rawBuffer[i + 6];
|
||||
if (!haveNormal || packed != lastNormal) {
|
||||
factor((byte) packed / 127.0f, (byte) (packed >> 8) / 127.0f, (byte) (packed >> 16) / 127.0f, scratchFactor);
|
||||
lastNormal = packed;
|
||||
haveNormal = true;
|
||||
}
|
||||
}
|
||||
if (hasColor) {
|
||||
final int packed = rawBuffer[i + 5];
|
||||
cr = (packed & 0xFF) / 255.0f;
|
||||
cg = ((packed >>> 8) & 0xFF) / 255.0f;
|
||||
cb = ((packed >>> 16) & 0xFF) / 255.0f;
|
||||
alphaBits = packed & 0xFF000000;
|
||||
}
|
||||
rawBuffer[i + 5] = alphaBits | (toByte(clamp01(cb * scratchFactor.z)) << 16) | (toByte(clamp01(cg * scratchFactor.y)) << 8) | toByte(clamp01(cr * scratchFactor.x));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean prepare(boolean directionalOnly) {
|
||||
if (!GLSMLighting.getLightingState().isEnabled()) return false;
|
||||
if (!GLSMLighting.getColorMaterial().isEnabled()) return false;
|
||||
if (VertexKey.encodeColorMaterialMode(GLSMLighting.getColorMaterialParameter().getValue()) != VertexKey.CM_AMBIENT_AND_DIFFUSE) return false;
|
||||
@@ -32,44 +96,58 @@ public final class FFPVertexLighting {
|
||||
final boolean materialHasSpecular = material.shininess > 0.0f && (material.specular.x != 0.0f || material.specular.y != 0.0f || material.specular.z != 0.0f);
|
||||
|
||||
final Vector4f lightModelAmbient = GLSMLighting.getLightModel().ambient;
|
||||
float r = lightModelAmbient.x;
|
||||
float g = lightModelAmbient.y;
|
||||
float b = lightModelAmbient.z;
|
||||
base.set(lightModelAmbient.x, lightModelAmbient.y, lightModelAmbient.z);
|
||||
|
||||
final var lightEnables = GLSMLighting.getLightStates();
|
||||
final var lightData = GLSMLighting.getLightDataStates();
|
||||
boolean normalReady = false;
|
||||
litCount = 0;
|
||||
|
||||
for (int i = 0; i < VertexKey.FFP_LIGHT_COUNT; i++) {
|
||||
if (!lightEnables[i].isEnabled()) continue;
|
||||
final LightState light = lightData[i];
|
||||
if (materialHasSpecular && (light.specular.x != 0.0f || light.specular.y != 0.0f || light.specular.z != 0.0f))
|
||||
return false;
|
||||
if (!normalReady) {
|
||||
transformCurrentNormal();
|
||||
normalReady = true;
|
||||
}
|
||||
final float nDotVP = Math.max(scratchNormal.dot(lightDirection(light)), 0.0f);
|
||||
r += light.ambient.x + nDotVP * light.diffuse.x;
|
||||
g += light.ambient.y + nDotVP * light.diffuse.y;
|
||||
b += light.ambient.z + nDotVP * light.diffuse.z;
|
||||
if (directionalOnly && light.position.w != 0.0f) return false;
|
||||
base.add(light.ambient.x, light.ambient.y, light.ambient.z);
|
||||
lightDirs[litCount].set(lightDirection(light));
|
||||
lightDiffuse[litCount].set(light.diffuse.x, light.diffuse.y, light.diffuse.z);
|
||||
litCount++;
|
||||
}
|
||||
|
||||
if (r == 1.0f && g == 1.0f && b == 1.0f) return false;
|
||||
if (!Float.isFinite(r + g + b)) return false;
|
||||
destFactor.set(r, g, b);
|
||||
if (litCount > 0) {
|
||||
GLSMMatrices.getModelViewMatrix().normal(scratchNormalMat);
|
||||
normalize = GLSMLighting.getNormalizeState().isEnabled();
|
||||
rescale = !normalize && GLSMLighting.getRescaleNormalState().isEnabled() ? Uniforms.rescaleFactor(scratchNormalMat, scratchVec) : 1.0f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void transformCurrentNormal() {
|
||||
GLSMMatrices.getModelViewMatrix().normal(scratchNormalMat);
|
||||
scratchNormal.set(ShaderManager.getCurrentNormal());
|
||||
private static void factor(Vector3f normal, Vector3f dest) {
|
||||
factor(normal.x, normal.y, normal.z, dest);
|
||||
}
|
||||
|
||||
private static void factor(float nx, float ny, float nz, Vector3f dest) {
|
||||
dest.set(base);
|
||||
if (litCount == 0) return;
|
||||
scratchNormal.set(nx, ny, nz);
|
||||
scratchNormalMat.transform(scratchNormal);
|
||||
if (GLSMLighting.getNormalizeState().isEnabled()) {
|
||||
if (normalize) {
|
||||
scratchNormal.normalize();
|
||||
} else if (GLSMLighting.getRescaleNormalState().isEnabled()) {
|
||||
scratchNormal.mul(Uniforms.rescaleFactor(scratchNormalMat, scratchVec));
|
||||
} else {
|
||||
scratchNormal.mul(rescale);
|
||||
}
|
||||
for (int i = 0; i < litCount; i++) {
|
||||
final float nDotVP = Math.max(scratchNormal.dot(lightDirs[i]), 0.0f);
|
||||
dest.fma(nDotVP, lightDiffuse[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private static float clamp01(float value) {
|
||||
return Math.min(Math.max(value, 0.0f), 1.0f);
|
||||
}
|
||||
|
||||
private static int toByte(float unit) {
|
||||
return (int) (unit * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
private static Vector3f lightDirection(LightState light) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexEnvState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
@@ -79,6 +80,7 @@ public final class FragmentKey {
|
||||
private static final int GLOBAL_BITS = 14;
|
||||
private static final int BIT_COMBINED_GLINT = 61;
|
||||
private static final int BIT_GLINT_REPLACE_ALPHA = 62;
|
||||
private static final int BIT_CLIP_DISCARD = 63;
|
||||
private static final int BIT_FOG_MODE = 0; // 2 bits
|
||||
private static final int BIT_ALPHA_TEST = 2; // 1 bit
|
||||
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
|
||||
@@ -126,6 +128,7 @@ public final class FragmentKey {
|
||||
long global = 0;
|
||||
if (CombinedGlint.isActive()) global |= 1L << BIT_COMBINED_GLINT;
|
||||
if (CombinedGlint.replacesAlpha()) global |= 1L << BIT_GLINT_REPLACE_ALPHA;
|
||||
if (GLSMRasterizer.clipEmulationActive(glCtx)) global |= 1L << BIT_CLIP_DISCARD;
|
||||
|
||||
// Fog
|
||||
global |= activeFogMode(glCtx);
|
||||
@@ -259,6 +262,7 @@ public final class FragmentKey {
|
||||
|
||||
public int fogMode() { return (int) (packed[0] & 0x3); }
|
||||
public boolean combinedGlint() { return (packed[0] & (1L << BIT_COMBINED_GLINT)) != 0; }
|
||||
public boolean clipDiscard() { return (packed[0] & (1L << BIT_CLIP_DISCARD)) != 0; }
|
||||
public boolean glintReplaceAlpha() { return (packed[0] & (1L << BIT_GLINT_REPLACE_ALPHA)) != 0; }
|
||||
public int textureSupersamplingSamples() { return 1 << (int) ((packed[0] >> BIT_TEXTURE_SUPERSAMPLING) & 0x7); }
|
||||
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
|
||||
|
||||
@@ -34,6 +34,9 @@ public final class FragmentShaderGenerator {
|
||||
}
|
||||
|
||||
sb.append("void main() {\n");
|
||||
if (key.clipDiscard()) {
|
||||
sb.append(ClipDistanceGlsl.FS_DISCARD);
|
||||
}
|
||||
emitLineStipple(sb, key);
|
||||
emitTextureSampling(sb, key);
|
||||
emitTexEnvChain(sb, key);
|
||||
@@ -49,6 +52,9 @@ public final class FragmentShaderGenerator {
|
||||
private static void emitInputs(StringBuilder sb, FragmentKey key) {
|
||||
sb.append("// Inputs from vertex shader\n");
|
||||
sb.append("in vec4 v_Color;\n");
|
||||
if (key.clipDiscard()) {
|
||||
sb.append(ClipDistanceGlsl.FS_IN_DIST0).append('\n').append(ClipDistanceGlsl.FS_IN_DIST1).append('\n');
|
||||
}
|
||||
if (key.overlayInstanced()) {
|
||||
sb.append("in vec4 v_Overlay;\n");
|
||||
}
|
||||
|
||||
@@ -39,17 +39,16 @@ public final class InstancedGlslHelpers {
|
||||
vec2 {p}wColumn = {span}.xy;
|
||||
float {p}wBottom = {span}.z;
|
||||
float {p}wTop = {span}.w;
|
||||
vec2 {p}wLightmap = {params}.xy;
|
||||
float {p}wHash = {params}.z;
|
||||
bool {p}wSnow = {params}.w > 0.5;
|
||||
float {p}wLightV = floor({params}.x / 65536.0);
|
||||
vec2 {p}wLightmap = vec2({params}.x - {p}wLightV * 65536.0, {p}wLightV);
|
||||
bool {p}wProvider = {params}.w > 0.5;
|
||||
vec3 {p}wTranslate = {p}WeatherParams0.xyz;
|
||||
float {p}wInvRadius = {p}WeatherParams0.w;
|
||||
float {p}wInvRadius = {p}wProvider ? {p}WeatherParams3.x : {p}WeatherParams0.w;
|
||||
vec2 {p}wCameraFrac = {p}WeatherParams1.xy;
|
||||
float {p}wPartialTicks = {p}WeatherParams1.z;
|
||||
float {p}wAge = {p}WeatherParams1.w;
|
||||
float {p}wRainScroll = {p}WeatherParams2.x;
|
||||
float {p}wSnowScroll = {p}WeatherParams2.y;
|
||||
float {p}wStrength = {p}WeatherParams2.z;
|
||||
float {p}wAge = {p}wProvider ? {p}WeatherParams2.w : {p}WeatherParams1.w;
|
||||
float {p}wTick = {p}wProvider ? {p}WeatherParams2.y : {p}WeatherParams2.x;
|
||||
float {p}wStrength = {p}wProvider ? 1.0 : {p}WeatherParams2.z;
|
||||
|
||||
vec2 {p}wHalfWidth = vec2(-{p}wColumn.y, {p}wColumn.x) * (0.5 / sqrt(dot({p}wColumn, {p}wColumn)));
|
||||
vec2 {p}wCornerXZ = {p}wColumn + vec2(0.5) + ({corner}.x * 2.0 - 1.0) * {p}wHalfWidth;
|
||||
@@ -58,22 +57,13 @@ public final class InstancedGlslHelpers {
|
||||
|
||||
float {p}wDistance = length({p}wColumn + vec2(0.5) - {p}wCameraFrac) * {p}wInvRadius;
|
||||
float {p}wFade = 1.0 - {p}wDistance * {p}wDistance;
|
||||
float {p}wScroll;
|
||||
vec2 {p}wDrift;
|
||||
float {p}wAlpha;
|
||||
if ({p}wSnow) {
|
||||
{p}wScroll = ({p}wSnowScroll + {p}wPartialTicks) / 512.0;
|
||||
{p}wDrift = vec2({jitter}.x + {p}wAge * 0.01 * {jitter}.y, {jitter}.z + {p}wAge * {jitter}.w * 0.001);
|
||||
{p}wAlpha = {p}wFade * 0.3 + 0.5;
|
||||
} else {
|
||||
{p}wScroll = (mod({p}wRainScroll + {p}wHash, 32.0) + {p}wPartialTicks) / 32.0 * (3.0 + {jitter}.x);
|
||||
{p}wDrift = vec2(0.0);
|
||||
{p}wAlpha = {p}wFade * 0.5 + 0.5;
|
||||
}
|
||||
float {p}wScroll = (mod({p}wTick + {params}.y, {layer}.x) + {p}wPartialTicks) / {layer}.x * {params}.z;
|
||||
vec2 {p}wDrift = vec2({jitter}.x + {p}wAge * {layer}.y * {jitter}.y, {jitter}.z + {p}wAge * {layer}.z * {jitter}.w);
|
||||
float {p}wAlpha = {p}wFade * {layer}.w + 0.5;
|
||||
|
||||
{tex0} = vec4({corner}.x + {p}wDrift.x, {p}wCornerY * 0.25 + {p}wScroll + {p}wDrift.y, 0.0, 1.0);
|
||||
{tex1} = vec4({p}wLightmap, 0.0, 1.0);
|
||||
{color} = vec4(1.0, 1.0, 1.0, {p}wAlpha * {p}wStrength);""";
|
||||
{color} = floor(clamp(vec4({colorAttr}.rgb, {p}wAlpha * {colorAttr}.a * {p}wStrength), 0.0, 1.0) * 255.0) / 255.0;""";
|
||||
|
||||
public static String weatherPrologue(String uniformPrefix, String attribPrefix, String corner,
|
||||
String vertex, String color, String tex0, String tex1, String sep) {
|
||||
@@ -82,6 +72,8 @@ public final class InstancedGlslHelpers {
|
||||
.replace("{span}", attribPrefix + "InstColumnSpan")
|
||||
.replace("{jitter}", attribPrefix + "InstJitter")
|
||||
.replace("{params}", attribPrefix + "InstWeatherParams")
|
||||
.replace("{layer}", attribPrefix + "InstWeatherLayer")
|
||||
.replace("{colorAttr}", attribPrefix + "InstWeatherColor")
|
||||
.replace("{corner}", corner)
|
||||
.replace("{vertex}", vertex)
|
||||
.replace("{color}", color)
|
||||
@@ -125,6 +117,8 @@ public final class InstancedGlslHelpers {
|
||||
decls.add(decl(WeatherInstancedAttribs.LOC_COLUMN_SPAN, "vec4", prefix + "InstColumnSpan"));
|
||||
decls.add(decl(WeatherInstancedAttribs.LOC_JITTER, "vec4", prefix + "InstJitter"));
|
||||
decls.add(decl(WeatherInstancedAttribs.LOC_PARAMS, "vec4", prefix + "InstWeatherParams"));
|
||||
decls.add(decl(WeatherInstancedAttribs.LOC_COLOR, "vec4", prefix + "InstWeatherColor"));
|
||||
decls.add(decl(WeatherInstancedAttribs.LOC_LAYER, "vec4", prefix + "InstWeatherLayer"));
|
||||
}
|
||||
}
|
||||
return decls.toArray(String[]::new);
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.FogState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.LightState;
|
||||
@@ -330,9 +329,7 @@ public class Uniforms {
|
||||
private void stageClipPlanes(GLContextState glCtx) {
|
||||
final ClipPlaneState cps = glCtx.clipPlaneState;
|
||||
clipPlaneBuf.clear();
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
cps.putEyePlane(i, clipPlaneBuf);
|
||||
}
|
||||
cps.putEffectivePlanes(glCtx.clipPlaneEnabledMask, clipPlaneBuf);
|
||||
final long src = memAddress0(clipPlaneBuf);
|
||||
for (int i = 0; i < 32 * 4; i += 4) {
|
||||
putInt(FFPUniformBlock.CLIP_PLANES + i, memGetInt(src + i));
|
||||
@@ -394,8 +391,9 @@ public class Uniforms {
|
||||
WeatherParams.translateZ, WeatherParams.invRadius);
|
||||
putVec4(FFPUniformBlock.WEATHER_PARAMS_1, WeatherParams.cameraFracX, WeatherParams.cameraFracZ,
|
||||
WeatherParams.partialTicks, WeatherParams.age);
|
||||
putVec4(FFPUniformBlock.WEATHER_PARAMS_2, WeatherParams.rainScroll, WeatherParams.snowScroll,
|
||||
WeatherParams.rainStrength, 0.0f);
|
||||
putVec4(FFPUniformBlock.WEATHER_PARAMS_2, WeatherParams.vanillaTick, WeatherParams.providerTick,
|
||||
WeatherParams.rainStrength, WeatherParams.providerAge);
|
||||
putVec4(FFPUniformBlock.WEATHER_PARAMS_3, WeatherParams.providerInvRadius, 0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
private void putVec4(int offset, Vector4f v) {
|
||||
|
||||
@@ -123,15 +123,63 @@ public final class VAOManager {
|
||||
private int clientSideEnabledCount = 0;
|
||||
private int arrayVersion = 0;
|
||||
private int versionClock = 0;
|
||||
private static final int DELETED_NAME_PRUNE_THRESHOLD = 4096;
|
||||
private static final int DEAD_EPOCH = -1;
|
||||
private final int pruneThreshold;
|
||||
private int bufferEpoch = 0;
|
||||
private final Int2IntOpenHashMap bufferDeletedAt = new Int2IntOpenHashMap();
|
||||
|
||||
public VAOManager() {
|
||||
this(DELETED_NAME_PRUNE_THRESHOLD);
|
||||
}
|
||||
|
||||
VAOManager(int pruneThreshold) {
|
||||
this.pruneThreshold = pruneThreshold;
|
||||
}
|
||||
|
||||
private void touch() {
|
||||
arrayVersion = ++versionClock;
|
||||
}
|
||||
|
||||
private boolean isBufferLive(int buffer, int sinceEpoch) {
|
||||
return bufferDeletedAt.get(buffer) <= sinceEpoch;
|
||||
return buffer == 0 || bufferDeletedAt.get(buffer) <= sinceEpoch;
|
||||
}
|
||||
|
||||
private int retiredEpoch(int buffer, int epoch, boolean rebase) {
|
||||
if (rebase) return epoch == DEAD_EPOCH ? DEAD_EPOCH : 0;
|
||||
return isBufferLive(buffer, epoch) ? epoch : DEAD_EPOCH;
|
||||
}
|
||||
|
||||
private void retire(@Nullable Attrib[] slots, boolean rebase) {
|
||||
if (slots == null) return;
|
||||
for (final Attrib a : slots) {
|
||||
if (a != null) a.epoch = retiredEpoch(a.vboId, a.epoch, rebase);
|
||||
}
|
||||
}
|
||||
|
||||
private void retireAll(ClientArrays[] savedStack, boolean rebase) {
|
||||
for (final VAOData data : vaoMap.values()) {
|
||||
retire(data.attribs, rebase);
|
||||
retire(data.generic, rebase);
|
||||
retire(data.conventional, rebase);
|
||||
}
|
||||
retire(currentAttribs, rebase);
|
||||
retire(currentGeneric, rebase);
|
||||
retire(currentConventional, rebase);
|
||||
for (final ClientArrays saved : savedStack) {
|
||||
if (saved == null) continue;
|
||||
saved.vboEpoch = retiredEpoch(saved.boundVBO, saved.vboEpoch, rebase);
|
||||
saved.eboEpoch = retiredEpoch(saved.boundEBO, saved.eboEpoch, rebase);
|
||||
retire(saved.generic, rebase);
|
||||
retire(saved.conventional, rebase);
|
||||
}
|
||||
}
|
||||
|
||||
private void pruneDeletedNames(ClientArrays[] savedStack) {
|
||||
retireAll(savedStack, false);
|
||||
bufferDeletedAt.clear();
|
||||
retireAll(savedStack, true);
|
||||
bufferEpoch = 0;
|
||||
}
|
||||
|
||||
public boolean isBufferLive(Attrib a) {
|
||||
@@ -206,9 +254,10 @@ public final class VAOManager {
|
||||
return detached;
|
||||
}
|
||||
|
||||
public void onDeleteBuffer(int buffer) {
|
||||
public void onDeleteBuffer(int buffer, ClientArrays[] savedStack) {
|
||||
if (buffer == 0) return;
|
||||
bufferDeletedAt.put(buffer, ++bufferEpoch);
|
||||
if (bufferEpoch >= pruneThreshold) pruneDeletedNames(savedStack);
|
||||
if (currentAttribs != null) {
|
||||
boolean detached = detach(currentGeneric != null ? currentGeneric : currentAttribs, buffer);
|
||||
if (currentConventional != null) detached |= detach(currentConventional, buffer);
|
||||
@@ -447,7 +496,8 @@ public final class VAOManager {
|
||||
public int boundEBO;
|
||||
public int clientActiveTextureUnit;
|
||||
int arrayVersion;
|
||||
int bufferEpoch;
|
||||
int vboEpoch;
|
||||
int eboEpoch;
|
||||
int vertexFlags;
|
||||
int genericEnabled;
|
||||
int conventionalEnabled;
|
||||
@@ -463,13 +513,18 @@ public final class VAOManager {
|
||||
saved[i].copyPointer(source);
|
||||
}
|
||||
|
||||
public boolean isBufferLive(int buffer, ClientArrays saved) {
|
||||
return isBufferLive(buffer, saved.bufferEpoch);
|
||||
public boolean isSavedVboLive(ClientArrays saved) {
|
||||
return isBufferLive(saved.boundVBO, saved.vboEpoch);
|
||||
}
|
||||
|
||||
public boolean isSavedEboLive(ClientArrays saved) {
|
||||
return isBufferLive(saved.boundEBO, saved.eboEpoch);
|
||||
}
|
||||
|
||||
public void saveClientArrays(ClientArrays out) {
|
||||
out.arrayVersion = arrayVersion;
|
||||
out.bufferEpoch = bufferEpoch;
|
||||
out.vboEpoch = bufferEpoch;
|
||||
out.eboEpoch = bufferEpoch;
|
||||
out.vertexFlags = currentVertexFlags;
|
||||
out.genericEnabled = genericEnabled;
|
||||
out.conventionalEnabled = conventionalEnabled;
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import org.joml.Matrix4f;
|
||||
@@ -53,6 +54,7 @@ public final class VertexKey {
|
||||
private static final int BIT_UNIT23_UV_FROM_UNIT0 = BIT_UNIT_TEXMAT_BASE + MAX_UNITS;
|
||||
private static final int BIT_LINE_STIPPLE = BIT_UNIT23_UV_FROM_UNIT0 + 1;
|
||||
private static final int BIT_INSTANCING = BIT_LINE_STIPPLE + 1;
|
||||
private static final int BIT_CLIP_EMULATED = BIT_INSTANCING + 3;
|
||||
private static final long INSTANCING_MASK = 0x7L;
|
||||
|
||||
static {
|
||||
@@ -105,6 +107,7 @@ public final class VertexKey {
|
||||
public int texGenModeQ() { return (int)((packed >> BIT_TEXGEN_Q) & 0x7); }
|
||||
public boolean texGenEnabled() { return texGenModeS() != TG_NONE || texGenModeT() != TG_NONE || texGenModeR() != TG_NONE || texGenModeQ() != TG_NONE; }
|
||||
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
|
||||
public boolean clipEmulated() { return bit(BIT_CLIP_EMULATED); }
|
||||
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
|
||||
public boolean lineQuads() { return bit(BIT_LINE_QUADS); }
|
||||
public Instancing instancing() { return Instancing.VALUES[(int) ((packed >> BIT_INSTANCING) & INSTANCING_MASK)]; }
|
||||
@@ -238,6 +241,7 @@ public final class VertexKey {
|
||||
|
||||
if (GLSMRasterizer.anyClipPlaneEnabled(glCtx)) {
|
||||
bits |= (1L << BIT_CLIP_PLANES);
|
||||
if (RenderSystem.isClipDistanceEmulated()) bits |= 1L << BIT_CLIP_EMULATED;
|
||||
}
|
||||
|
||||
if (glCtx.lineStippleActive) {
|
||||
@@ -279,6 +283,6 @@ public final class VertexKey {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("FFPVertexKey[0x%012X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b lquads=%b inst=%s]", packed,lightingEnabled(), lightEnabled(0), lightEnabled(1), colorMaterialEnabled(), fogEnabled(), unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0, unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0, hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(), texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), lineQuads(), instancing());
|
||||
return String.format("FFPVertexKey[0x%012X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b clipEmu=%b lquads=%b inst=%s]", packed,lightingEnabled(), lightEnabled(0), lightEnabled(1), colorMaterialEnabled(), fogEnabled(), unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0, unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0, hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(), texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), clipEmulated(), lineQuads(), instancing());
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,9 @@ public final class VertexShaderGenerator {
|
||||
if (key.lineStipple()) {
|
||||
sb.append("flat out vec2 v_LineStart;\n");
|
||||
}
|
||||
if (key.clipEmulated()) {
|
||||
sb.append(ClipDistanceGlsl.VS_OUT_DIST0).append('\n').append(ClipDistanceGlsl.VS_OUT_DIST1).append('\n');
|
||||
}
|
||||
if (key.separateSpecular()) {
|
||||
sb.append("out vec3 v_SpecularColor;\n");
|
||||
}
|
||||
@@ -398,10 +401,19 @@ public final class VertexShaderGenerator {
|
||||
|
||||
private static void emitClipDistances(StringBuilder sb, VertexKey key) {
|
||||
sb.append(" // Clip distances\n");
|
||||
final boolean emulated = key.clipEmulated();
|
||||
final String[] distances = emulated ? new String[8] : null;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
final String own = "dot(u_ClipPlane[" + i + "], eyePos)";
|
||||
final String distance = key.lineQuads() ? acrossLine(own, "dot(u_ClipPlane[" + i + "], lineEyeOther)") : own;
|
||||
sb.append(" gl_ClipDistance[").append(i).append("] = ").append(distance).append(";\n");
|
||||
if (emulated) {
|
||||
distances[i] = distance;
|
||||
} else {
|
||||
sb.append(" gl_ClipDistance[").append(i).append("] = ").append(distance).append(";\n");
|
||||
}
|
||||
}
|
||||
if (emulated) {
|
||||
ClipDistanceGlsl.appendVsAssign(sb, distances);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+47
-2
@@ -3,31 +3,76 @@ package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* One rain or snow column per instance.
|
||||
* One weather column per instance.
|
||||
*/
|
||||
public final class WeatherInstancedAttribs {
|
||||
|
||||
public static final int LOC_COLUMN_SPAN = ParticleInstancedAttribs.LOC_CENTER_HALF;
|
||||
public static final int LOC_JITTER = ParticleInstancedAttribs.LOC_UV;
|
||||
public static final int LOC_PARAMS = InstancedAttribs.LOC_COLOR;
|
||||
public static final int LOC_COLOR = InstancedAttribs.LOC_ROW2;
|
||||
public static final int LOC_LAYER = InstancedAttribs.LOC_OVERLAY;
|
||||
|
||||
public static final int OFFSET_COLUMN_SPAN = 0;
|
||||
public static final int OFFSET_JITTER = 16;
|
||||
public static final int OFFSET_PARAMS = 32;
|
||||
public static final int STRIDE = 48;
|
||||
public static final int OFFSET_COLOR = 48;
|
||||
public static final int OFFSET_LAYER = 64;
|
||||
public static final int STRIDE = 80;
|
||||
|
||||
public static final int RAIN_PERIOD = 32;
|
||||
public static final float RAIN_SCROLL_BASE = 3.0F;
|
||||
public static final float RAIN_FADE = 0.5F;
|
||||
public static final int SNOW_PERIOD = 512;
|
||||
public static final float SNOW_U_DRIFT = 0.01F;
|
||||
public static final float SNOW_V_DRIFT = 0.001F;
|
||||
public static final float SNOW_FADE = 0.3F;
|
||||
public static final float SOURCE_VANILLA = 0.0F;
|
||||
public static final float SOURCE_PROVIDER = 1.0F;
|
||||
|
||||
public static void putColumn(ByteBuffer data, int index, float x, float z, float bottom, float top, float uBase, float uDrift,
|
||||
float vBase, float vDrift, float brightness, int scrollOffset, float scrollScale, float source, float r, float g, float b,
|
||||
float a, int period, float uDriftScale, float vDriftScale, float fadeScale) {
|
||||
final int base = index * STRIDE;
|
||||
data.putFloat(base + OFFSET_COLUMN_SPAN, x);
|
||||
data.putFloat(base + OFFSET_COLUMN_SPAN + 4, z);
|
||||
data.putFloat(base + OFFSET_COLUMN_SPAN + 8, bottom);
|
||||
data.putFloat(base + OFFSET_COLUMN_SPAN + 12, top);
|
||||
data.putFloat(base + OFFSET_JITTER, uBase);
|
||||
data.putFloat(base + OFFSET_JITTER + 4, uDrift);
|
||||
data.putFloat(base + OFFSET_JITTER + 8, vBase);
|
||||
data.putFloat(base + OFFSET_JITTER + 12, vDrift);
|
||||
data.putFloat(base + OFFSET_PARAMS, brightness);
|
||||
data.putFloat(base + OFFSET_PARAMS + 4, scrollOffset & (period - 1));
|
||||
data.putFloat(base + OFFSET_PARAMS + 8, scrollScale);
|
||||
data.putFloat(base + OFFSET_PARAMS + 12, source);
|
||||
data.putFloat(base + OFFSET_COLOR, r);
|
||||
data.putFloat(base + OFFSET_COLOR + 4, g);
|
||||
data.putFloat(base + OFFSET_COLOR + 8, b);
|
||||
data.putFloat(base + OFFSET_COLOR + 12, a);
|
||||
data.putFloat(base + OFFSET_LAYER, period);
|
||||
data.putFloat(base + OFFSET_LAYER + 4, uDriftScale);
|
||||
data.putFloat(base + OFFSET_LAYER + 8, vDriftScale);
|
||||
data.putFloat(base + OFFSET_LAYER + 12, fadeScale);
|
||||
}
|
||||
|
||||
public static void pointInstanceAttribs(long base) {
|
||||
GLSMVertexInput.glVertexAttribPointer(LOC_COLUMN_SPAN, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_COLUMN_SPAN);
|
||||
GLSMVertexInput.glVertexAttribPointer(LOC_JITTER, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_JITTER);
|
||||
GLSMVertexInput.glVertexAttribPointer(LOC_PARAMS, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_PARAMS);
|
||||
GLSMVertexInput.glVertexAttribPointer(LOC_COLOR, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_COLOR);
|
||||
GLSMVertexInput.glVertexAttribPointer(LOC_LAYER, 4, GL11.GL_FLOAT, false, STRIDE, base + OFFSET_LAYER);
|
||||
}
|
||||
|
||||
static void enableInstanceArrays() {
|
||||
InstancedAttribs.enableInstanced(LOC_COLUMN_SPAN);
|
||||
InstancedAttribs.enableInstanced(LOC_JITTER);
|
||||
InstancedAttribs.enableInstanced(LOC_PARAMS);
|
||||
InstancedAttribs.enableInstanced(LOC_COLOR);
|
||||
InstancedAttribs.enableInstanced(LOC_LAYER);
|
||||
}
|
||||
|
||||
private WeatherInstancedAttribs() {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
/**
|
||||
* The per-frame half of the rain and snow instancing, set once per weather draw.
|
||||
* The per-frame half of the weather column instancing, set once per frame.
|
||||
*/
|
||||
public final class WeatherParams {
|
||||
|
||||
@@ -15,9 +15,14 @@ public final class WeatherParams {
|
||||
public static float partialTicks;
|
||||
public static float age;
|
||||
|
||||
public static float rainScroll;
|
||||
public static float snowScroll;
|
||||
public static float vanillaTick;
|
||||
public static float providerTick;
|
||||
public static float rainStrength;
|
||||
public static float providerAge;
|
||||
|
||||
public static float providerInvRadius;
|
||||
|
||||
public static final int TICK_WRAP = 1024;
|
||||
|
||||
public static int generation;
|
||||
|
||||
@@ -25,7 +30,7 @@ public final class WeatherParams {
|
||||
|
||||
public static void set(float translateX, float translateY, float translateZ, float invRadius,
|
||||
float cameraFracX, float cameraFracZ, float partialTicks, float age,
|
||||
float rainScroll, float snowScroll, float rainStrength) {
|
||||
int vanillaTick, float rainStrength, int providerTick, float providerAge, float providerInvRadius) {
|
||||
WeatherParams.translateX = translateX;
|
||||
WeatherParams.translateY = translateY;
|
||||
WeatherParams.translateZ = translateZ;
|
||||
@@ -34,9 +39,11 @@ public final class WeatherParams {
|
||||
WeatherParams.cameraFracZ = cameraFracZ;
|
||||
WeatherParams.partialTicks = partialTicks;
|
||||
WeatherParams.age = age;
|
||||
WeatherParams.rainScroll = rainScroll;
|
||||
WeatherParams.snowScroll = snowScroll;
|
||||
WeatherParams.vanillaTick = vanillaTick & (TICK_WRAP - 1);
|
||||
WeatherParams.providerTick = providerTick & (TICK_WRAP - 1);
|
||||
WeatherParams.rainStrength = rainStrength;
|
||||
WeatherParams.providerAge = providerAge;
|
||||
WeatherParams.providerInvRadius = providerInvRadius;
|
||||
generation++;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* The four corners every rain and snow column is expanded from. x picks the end of the billboard, y picks
|
||||
* The four corners every weather column is expanded from. x picks the end of the billboard, y picks
|
||||
* the bottom or top edge.
|
||||
*/
|
||||
public final class WeatherQuadMesh {
|
||||
@@ -49,14 +49,6 @@ public final class WeatherQuadMesh {
|
||||
return vao;
|
||||
}
|
||||
|
||||
public static void bindInstanceBuffer(int bufferId) {
|
||||
GLSMVertexInput.glBindVertexArray(vao());
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferId);
|
||||
WeatherInstancedAttribs.pointInstanceAttribs(0L);
|
||||
GLSMVertexInput.glBindVertexArray(0);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
public static void delete() {
|
||||
if (vbo != 0) {
|
||||
GLSMBuffers.glDeleteBuffers(vbo);
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.gtnewhorizons.angelica.glsm.GLDebug;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMTextureLifetime;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
|
||||
|
||||
@@ -16,11 +17,18 @@ public final class FrameHooks {
|
||||
private static boolean gameLoopStarted;
|
||||
private static long frameCounter;
|
||||
private static boolean frameGroupActive;
|
||||
private static String framePrefix = "frame:";
|
||||
private static String buildLabel = "";
|
||||
|
||||
public static void bootstrapFirstFrame() {
|
||||
DisplayListManager.abortIfLeaked();
|
||||
if (!gameLoopStarted) {
|
||||
gameLoopStarted = true;
|
||||
final GLSMInitConfig config = GLStateManager.getInitConfig();
|
||||
if (config != null && !config.getBuildLabel().isEmpty()) {
|
||||
buildLabel = config.getBuildLabel();
|
||||
framePrefix = buildLabel + " frame:";
|
||||
}
|
||||
RENDER_BACKEND.onFrameBegin();
|
||||
pushFrameGroup();
|
||||
}
|
||||
@@ -48,8 +56,10 @@ public final class FrameHooks {
|
||||
}
|
||||
|
||||
private static void pushFrameGroup() {
|
||||
final boolean wasConnected = CaptureGate.tracyConnected;
|
||||
CaptureGate.refresh();
|
||||
GLDebug.pushGroup("frame:", frameCounter);
|
||||
if (CaptureGate.tracyConnected && !wasConnected && !buildLabel.isEmpty()) Tracy.message(buildLabel);
|
||||
GLDebug.pushGroup(framePrefix, frameCounter);
|
||||
frameGroupActive = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ public final class GLSMHooks {
|
||||
public static final EventBus<ProgramDeleteEvent> PROGRAM_DELETE = EventBus.create(ProgramDeleteEvent.class);
|
||||
public static final EventBus<BlendFuncChangeEvent> BLEND_FUNC_CHANGE = EventBus.create(BlendFuncChangeEvent.class);
|
||||
public static final EventBus<FogStateChangeEvent> FOG_STATE_CHANGE = EventBus.create(FogStateChangeEvent.class);
|
||||
public static final EventBus<ClipPlanesInUseEvent> CLIP_PLANES_IN_USE = EventBus.create(ClipPlanesInUseEvent.class);
|
||||
public static final EventBus<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
|
||||
public static final EventBus<AlphaStateChangeEvent> ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.class);
|
||||
public static final EventBus<ShaderColorChangeEvent> SHADER_COLOR_CHANGE = EventBus.create(ShaderColorChangeEvent.class);
|
||||
@@ -46,6 +47,7 @@ public final class GLSMHooks {
|
||||
public static final ProgramDeleteEvent programDeleteEvent = new ProgramDeleteEvent();
|
||||
public static final BlendFuncChangeEvent blendFuncChangeEvent = new BlendFuncChangeEvent();
|
||||
public static final FogStateChangeEvent fogStateChangeEvent = new FogStateChangeEvent();
|
||||
public static final ClipPlanesInUseEvent clipPlanesInUseEvent = new ClipPlanesInUseEvent();
|
||||
public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent();
|
||||
public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent();
|
||||
public static final ShaderColorChangeEvent shaderColorChangeEvent = new ShaderColorChangeEvent();
|
||||
|
||||
@@ -17,6 +17,7 @@ public final class GLSMInitConfig {
|
||||
@Getter private final Runnable postInitCallback;
|
||||
private final boolean dsaEnabled;
|
||||
@Getter private final boolean noErrorChecks;
|
||||
@Getter private final String buildLabel;
|
||||
|
||||
private GLSMInitConfig(Builder builder) {
|
||||
this.lwjglDebug = builder.lwjglDebug;
|
||||
@@ -27,6 +28,7 @@ public final class GLSMInitConfig {
|
||||
this.displayHeight = builder.displayHeight;
|
||||
this.postInitCallback = builder.postInitCallback;
|
||||
this.dsaEnabled = builder.dsaEnabled;
|
||||
this.buildLabel = builder.buildLabel;
|
||||
}
|
||||
|
||||
public boolean isDSAEnabled() {
|
||||
@@ -46,6 +48,7 @@ public final class GLSMInitConfig {
|
||||
private Runnable postInitCallback = null;
|
||||
private boolean dsaEnabled = true;
|
||||
private boolean noErrorChecks;
|
||||
private String buildLabel = "";
|
||||
|
||||
private Builder() {}
|
||||
|
||||
@@ -85,6 +88,11 @@ public final class GLSMInitConfig {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder buildLabel(String buildLabel) {
|
||||
this.buildLabel = buildLabel == null ? "" : buildLabel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GLSMInitConfig build() {
|
||||
return new GLSMInitConfig(this);
|
||||
}
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.gtnewhorizons.angelica.glsm.hooks.events;
|
||||
|
||||
import net.minecraftforge.eventbus.api.event.MutableEvent;
|
||||
|
||||
public final class ClipPlanesInUseEvent extends MutableEvent {}
|
||||
@@ -152,11 +152,11 @@ public final class Tracy {
|
||||
return backend;
|
||||
}
|
||||
|
||||
private static final String ABI_SENTINEL = "sectionEnter";
|
||||
private static final String ABI_SENTINEL = "frameImage";
|
||||
|
||||
private static boolean implementsCurrentAbi(TracyBackend backend) {
|
||||
try {
|
||||
backend.getClass().getMethod(ABI_SENTINEL, int.class, String.class);
|
||||
backend.getClass().getMethod(ABI_SENTINEL, long.class, int.class, int.class, int.class, boolean.class);
|
||||
return true;
|
||||
} catch (NoSuchMethodException e) {
|
||||
LOGGER.warn("Tracy backend {} was built against an older TracyBackend interface; profiling disabled", backend.getClass().getName());
|
||||
@@ -337,6 +337,12 @@ public final class Tracy {
|
||||
if (ptr != 0) BACKEND.frameMark(ptr);
|
||||
}
|
||||
|
||||
/** Copies {@code rgba} before returning; width and height must be multiples of 4. */
|
||||
public static void frameImage(long rgba, int width, int height, int offset, boolean flip) {
|
||||
if (!ENABLED || SHUTTING_DOWN) return;
|
||||
BACKEND.frameImage(rgba, width, height, offset, flip);
|
||||
}
|
||||
|
||||
public static void plotInt(String name, long value) {
|
||||
plotInt(name, value, TracyBackend.PLOT_FORMAT_NUMBER);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ public interface TracyBackend {
|
||||
|
||||
void frameMark();
|
||||
void frameMark(long namePtr);
|
||||
void frameImage(long rgba, int width, int height, int offset, boolean flip);
|
||||
|
||||
long internFrameName(String name);
|
||||
long internPlotName(String name, int format);
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.glsm.recording;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.ImmediateExtendedAttribHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.streaming.LineQuads;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
@@ -99,26 +100,31 @@ public final class DisplayListVBO {
|
||||
|
||||
public void render() {
|
||||
if (vao == null) return;
|
||||
if (lineQuads != null && GLSMDraws.ffpWidensLineQuads()) {
|
||||
renderLineQuads();
|
||||
return;
|
||||
}
|
||||
vao.bind();
|
||||
if (pendingExtVbo != 0) {
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, pendingExtVbo);
|
||||
ImmediateExtendedAttribHandler.setupExtAttribPointers(0L, ImmediateExtendedAttribHandler.EXT_STRIDE);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
pendingExtVbo = 0;
|
||||
}
|
||||
if (count <= 0) { vao.unbind(); return; }
|
||||
VAOManager.setCurrentVertexFlags(vertexFlags);
|
||||
FfpExtendedAttribs.beginInternalDraw();
|
||||
final int prevVAO = GLSMVertexInput.getBoundVAO();
|
||||
try {
|
||||
vao.draw(drawMode, start, count);
|
||||
if (lineQuads != null && GLSMDraws.ffpWidensLineQuads()) {
|
||||
renderLineQuads();
|
||||
return;
|
||||
}
|
||||
vao.bind();
|
||||
if (pendingExtVbo != 0) {
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, pendingExtVbo);
|
||||
ImmediateExtendedAttribHandler.setupExtAttribPointers(0L, ImmediateExtendedAttribHandler.EXT_STRIDE);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
pendingExtVbo = 0;
|
||||
}
|
||||
if (count <= 0) { vao.unbind(); return; }
|
||||
VAOManager.setCurrentVertexFlags(vertexFlags);
|
||||
FfpExtendedAttribs.beginInternalDraw();
|
||||
try {
|
||||
vao.draw(drawMode, start, count);
|
||||
} finally {
|
||||
FfpExtendedAttribs.endInternalDraw();
|
||||
}
|
||||
vao.unbind();
|
||||
} finally {
|
||||
FfpExtendedAttribs.endInternalDraw();
|
||||
GLSMVertexInput.glBindVertexArray(prevVAO);
|
||||
}
|
||||
vao.unbind();
|
||||
}
|
||||
|
||||
private void renderLineQuads() {
|
||||
|
||||
+8
-2
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
|
||||
|
||||
public final class BatchedIndexedDrawCmd implements DisplayListCommand {
|
||||
private IndexedDrawBatch batch;
|
||||
@@ -25,8 +26,13 @@ public final class BatchedIndexedDrawCmd implements DisplayListCommand {
|
||||
@Override
|
||||
public void execute() {
|
||||
if (batch == null) return;
|
||||
batch.bind();
|
||||
GLSMDraws.glDrawElements(drawMode, indexCount, indexType, indexByteOffset);
|
||||
final int prevVAO = GLSMVertexInput.getBoundVAO();
|
||||
try {
|
||||
batch.bind();
|
||||
GLSMDraws.glDrawElements(drawMode, indexCount, indexType, indexByteOffset);
|
||||
} finally {
|
||||
GLSMVertexInput.glBindVertexArray(prevVAO);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+15
@@ -1,9 +1,12 @@
|
||||
package com.gtnewhorizons.angelica.glsm.recording.commands;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
public final class IndexedDrawBatch {
|
||||
@@ -34,17 +37,29 @@ public final class IndexedDrawBatch {
|
||||
GLSMVertexInput.glBindVertexArray(v);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, sharedVBO);
|
||||
final int stride = layout.stride();
|
||||
int vertexFlags = 0;
|
||||
for (int i = 0; i < layout.locations.length; i++) {
|
||||
final int loc = layout.locations[i];
|
||||
GLSMVertexInput.glVertexAttribPointer(loc, layout.sizes[i], layout.types[i], layout.normalized[i], stride, layout.offset(i));
|
||||
GLSMVertexInput.glEnableVertexAttribArray(loc);
|
||||
vertexFlags |= vertexFlag(loc);
|
||||
}
|
||||
// glVertexAttribPointer doesn't set these, and the FFP shader only reads the inputs they name. Saved with the VAO.
|
||||
VAOManager.setCurrentVertexFlags(vertexFlags);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEBO);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
|
||||
vao = v;
|
||||
vaoCtx = glCtx;
|
||||
}
|
||||
|
||||
private static int vertexFlag(int location) {
|
||||
if (location == Usage.COLOR.getAttributeLocation()) return VertexFlags.COLOR_BIT;
|
||||
if (location == Usage.NORMAL.getAttributeLocation()) return VertexFlags.NORMAL_BIT;
|
||||
if (location == Usage.PRIMARY_UV.getAttributeLocation()) return VertexFlags.TEXTURE_BIT;
|
||||
if (location == Usage.SECONDARY_UV.getAttributeLocation()) return VertexFlags.BRIGHTNESS_BIT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (deleted) return;
|
||||
deleted = true;
|
||||
|
||||
@@ -59,7 +59,7 @@ final class GLSMOwners {
|
||||
"disableFog", "enableFog", "fogColor", "getFogColor", "getFogMode", "getFogState", "glFog", "glFogf",
|
||||
"glFogi"},
|
||||
{"com/gtnewhorizons/angelica/glsm/GLSMFramebuffer",
|
||||
"attachTextureForReading", "framebufferCompletenessIsMeaningful", "getDrawBuffer", "getDrawFramebuffer",
|
||||
"attachTextureForReading", "createScreenCapture", "framebufferCompletenessIsMeaningful", "getDrawBuffer", "getDrawFramebuffer",
|
||||
"getDrawFramebufferGeneration", "getReadFramebuffer", "glBindFramebuffer", "glBindRenderbuffer",
|
||||
"glBlitFramebuffer", "glCheckFramebufferStatus", "glClear", "glDeleteFramebuffers", "glDeleteRenderbuffers",
|
||||
"glDrawBuffer", "glDrawBuffers", "glFramebufferRenderbuffer", "glFramebufferTexture",
|
||||
@@ -106,22 +106,22 @@ final class GLSMOwners {
|
||||
"glGetIntegeri", "glGetIntegeri_v", "glGetQueryObjecti", "glGetQueryObjectui", "glGetQueryObjectui64",
|
||||
"glGetString", "glGetStringi", "glIsQuery", "glQueryCounter"},
|
||||
{"com/gtnewhorizons/angelica/glsm/GLSMRasterizer",
|
||||
"anyClipPlaneEnabled", "disableCull", "disableScissorTest", "enableCull", "enableScissorTest",
|
||||
"getAutoNormalState", "getClipPlaneState", "getClipPlaneStates", "getCullState", "getDitherState",
|
||||
"getEdgeFlagState", "getHintState", "getLineSmoothState", "getLineState", "getLineStippleState",
|
||||
"getMap1Color4State", "getMap1IndexState", "getMap1NormalState", "getMap1TextureCoord1State",
|
||||
"getMap1TextureCoord2State", "getMap1TextureCoord3State", "getMap1TextureCoord4State",
|
||||
"getMap1Vertex3State", "getMap1Vertex4State", "getMap2Color4State", "getMap2IndexState",
|
||||
"getMap2NormalState", "getMap2TextureCoord1State", "getMap2TextureCoord2State", "getMap2TextureCoord3State",
|
||||
"getMap2TextureCoord4State", "getMap2Vertex3State", "getMap2Vertex4State", "getMultisampleState",
|
||||
"getPointSmoothState", "getPointSpriteState", "getPointState", "getPolygonOffsetFillState",
|
||||
"getPolygonOffsetLineState", "getPolygonOffsetPointState", "getPolygonSmoothState", "getPolygonState",
|
||||
"getPolygonStippleState", "getSampleAlphaToCoverageState", "getSampleAlphaToOneState",
|
||||
"getSampleCoverageState", "getScissorState", "getScissorTest", "getShadeModelState", "getViewportState",
|
||||
"glClipPlane", "glCullFace", "glFrontFace", "glGetClipPlane", "glHint", "glLineStipple", "glLineWidth",
|
||||
"glPointParameter", "glPointParameterf", "glPointParameteri", "glPointSize", "glPolygonMode",
|
||||
"glPolygonOffset", "glPolygonOffsetClamp", "glPolygonStipple", "glSampleCoverage", "glScissor",
|
||||
"glShadeModel", "glViewport"},
|
||||
"anyClipPlaneEnabled", "clipEmulationActive", "disableCull", "disableScissorTest", "enableCull",
|
||||
"enableScissorTest", "getAutoNormalState", "getClipPlaneState", "getClipPlaneStates", "getCullState",
|
||||
"getDitherState", "getEdgeFlagState", "getHintState", "getLineSmoothState", "getLineState",
|
||||
"getLineStippleState", "getMap1Color4State", "getMap1IndexState", "getMap1NormalState",
|
||||
"getMap1TextureCoord1State", "getMap1TextureCoord2State", "getMap1TextureCoord3State",
|
||||
"getMap1TextureCoord4State", "getMap1Vertex3State", "getMap1Vertex4State", "getMap2Color4State",
|
||||
"getMap2IndexState", "getMap2NormalState", "getMap2TextureCoord1State", "getMap2TextureCoord2State",
|
||||
"getMap2TextureCoord3State", "getMap2TextureCoord4State", "getMap2Vertex3State", "getMap2Vertex4State",
|
||||
"getMultisampleState", "getPointSmoothState", "getPointSpriteState", "getPointState",
|
||||
"getPolygonOffsetFillState", "getPolygonOffsetLineState", "getPolygonOffsetPointState",
|
||||
"getPolygonSmoothState", "getPolygonState", "getPolygonStippleState", "getSampleAlphaToCoverageState",
|
||||
"getSampleAlphaToOneState", "getSampleCoverageState", "getScissorState", "getScissorTest",
|
||||
"getShadeModelState", "getViewportState", "glClipPlane", "glCullFace", "glFrontFace", "glGetClipPlane",
|
||||
"glHint", "glLineStipple", "glLineWidth", "glPointParameter", "glPointParameterf", "glPointParameteri",
|
||||
"glPointSize", "glPolygonMode", "glPolygonOffset", "glPolygonOffsetClamp", "glPolygonStipple",
|
||||
"glSampleCoverage", "glScissor", "glShadeModel", "glViewport"},
|
||||
{"com/gtnewhorizons/angelica/glsm/GLSMTexEnvGen",
|
||||
"glGetTexEnv", "glGetTexEnvf", "glGetTexEnvi", "glGetTexGen", "glGetTexGend", "glGetTexGenf",
|
||||
"glGetTexGeni", "glTexEnv", "glTexEnvf", "glTexEnvi", "glTexGen", "glTexGend", "glTexGenf", "glTexGeni"},
|
||||
@@ -129,7 +129,7 @@ final class GLSMOwners {
|
||||
"deferredTexturesAwaitingRelease", "onFrameEnd"},
|
||||
{"com/gtnewhorizons/angelica/glsm/GLSMTextures",
|
||||
"abandonTextureStaging", "beginTextureStaging", "commitTextureStaging", "disableTexture", "enableTexture",
|
||||
"forcePixelUnpackState", "getActiveTextureUnit", "getActiveTextureUnitForServerState",
|
||||
"forcePixelPackState", "forcePixelUnpackState", "getActiveTextureUnit", "getActiveTextureUnitForServerState",
|
||||
"getActiveTextureUnitStack", "getBoundTextureForServerState", "getClientActiveTextureUnit",
|
||||
"getImageUnitBinding", "getMaxBoundTextureUnit", "getSamplerBinding", "getSamplerUnitCount", "getTextures",
|
||||
"glActiveTexture", "glActiveTextureARB", "glBindImageTexture", "glBindImageTextures",
|
||||
@@ -142,7 +142,7 @@ final class GLSMOwners {
|
||||
"glTexImage1D", "glTexImage2D", "glTexImage3D", "glTexParameter", "glTexParameterf", "glTexParameteri",
|
||||
"glTexStorage2D", "glTexSubImage1D", "glTexSubImage2D", "glTexSubImage3D", "lookupTexParameteri",
|
||||
"markTextureAsAtlas", "remapTexClamp", "remapTexClampBuffer", "remapTexImageForGLES",
|
||||
"restorePixelUnpackState", "setActiveTexture", "setFilter", "setTextureSupersamplingSamples",
|
||||
"restorePixelPackState", "restorePixelUnpackState", "setActiveTexture", "setFilter", "setTextureSupersamplingSamples",
|
||||
"trackMaxBoundTextureUnit", "trimTextureStaging", "unbindAllSamplers", "updateTexParameterfCache",
|
||||
"updateTexParameteriCache"},
|
||||
{"com/gtnewhorizons/angelica/glsm/GLSMVertexInput",
|
||||
@@ -158,6 +158,8 @@ final class GLSMOwners {
|
||||
static final String[][] FIELDS = {
|
||||
{"com/gtnewhorizons/angelica/glsm/GLSMAttribStack",
|
||||
"attribBackendCalls", "attribDiscards", "attribPushes", "attribSlotsSaved", "attribValueRestores"},
|
||||
{"com/gtnewhorizons/angelica/glsm/GLSMBuffers",
|
||||
"bufferDeletes"},
|
||||
{"com/gtnewhorizons/angelica/glsm/GLSMDraws",
|
||||
"drawCalls"},
|
||||
{"com/gtnewhorizons/angelica/glsm/GLSMFramebuffer",
|
||||
|
||||
+17
-4
@@ -15,8 +15,10 @@ import org.taumc.glsl.grammar.GLSLParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public final class GlslVulkanPreprocess {
|
||||
@@ -56,7 +58,9 @@ public final class GlslVulkanPreprocess {
|
||||
private GlslVulkanPreprocess() {}
|
||||
|
||||
private record CacheKey(String source, int glShaderType, boolean separateReadOnlyImages) {}
|
||||
public record Result(String rewrittenSource, Set<String> boolUniforms, Set<String> explicitVsInputs) {}
|
||||
public record Result(String rewrittenSource, Set<String> boolUniforms, Set<String> explicitVsInputs, Map<String, UniformBlock> uniformBlocks) {}
|
||||
|
||||
public record UniformBlock(String qualifierText, @Nullable String instanceName) {}
|
||||
|
||||
/** Replace {@code [startIdx, stopIdx]} with {@code replacement}. */
|
||||
public record Edit(int startIdx, int stopIdx, String replacement) {}
|
||||
@@ -109,7 +113,7 @@ public final class GlslVulkanPreprocess {
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
final Metadata meta = collectEdits(new ShaderIndex(root, source), glShaderType, debugName, separateReadOnlyImages, edits);
|
||||
|
||||
final Result out = new Result(applyEdits(source, edits), meta.boolUniforms(), meta.explicitVsInputs());
|
||||
final Result out = new Result(applyEdits(source, edits), meta.boolUniforms(), meta.explicitVsInputs(), meta.uniformBlocks());
|
||||
synchronized (CACHE) {
|
||||
CACHE.putAndMoveToFirst(key, out);
|
||||
while (CACHE.size() > CACHE_MAX) CACHE.removeLast();
|
||||
@@ -117,11 +121,12 @@ public final class GlslVulkanPreprocess {
|
||||
return out;
|
||||
}
|
||||
|
||||
public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs, boolean needsSamplerless) {}
|
||||
public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs, boolean needsSamplerless, Map<String, UniformBlock> uniformBlocks) {}
|
||||
|
||||
public static Metadata collectEdits(ShaderIndex index, int glShaderType, String debugName, boolean separateReadOnlyImages, List<Edit> edits) {
|
||||
final boolean isVertex = glShaderType == GL20.GL_VERTEX_SHADER;
|
||||
final Set<String> bools = new HashSet<>();
|
||||
Map<String, UniformBlock> uniformBlocks = Map.of();
|
||||
final Set<String> explicitInputs = new HashSet<>();
|
||||
final Set<String> readOnlyImages = new HashSet<>();
|
||||
boolean needsSamplerless = false;
|
||||
@@ -130,6 +135,14 @@ public final class GlslVulkanPreprocess {
|
||||
final List<Integer> unlocatedVsInputStarts = new ArrayList<>();
|
||||
|
||||
for (GLSLParser.DeclarationContext declaration : index.globalDeclarations()) {
|
||||
if (declaration.LEFT_BRACE() != null && ShaderIndex.hasStorageQualifier(declaration.type_qualifier(), "uniform")) {
|
||||
final GLSLParser.Type_qualifierContext blockQualifier = declaration.type_qualifier();
|
||||
final String qualifierText = index.source().substring(startIdx(blockQualifier), stopIdx(blockQualifier) + 1);
|
||||
final String instanceName = declaration.IDENTIFIER().size() > 1 ? declaration.IDENTIFIER(1).getText() : null;
|
||||
if (uniformBlocks.isEmpty()) uniformBlocks = new HashMap<>();
|
||||
uniformBlocks.put(declaration.IDENTIFIER(0).getText(), new UniformBlock(qualifierText, instanceName));
|
||||
continue;
|
||||
}
|
||||
final GLSLParser.Init_declarator_listContext list = declaration.init_declarator_list();
|
||||
if (list == null) continue;
|
||||
final GLSLParser.Single_declarationContext single = list.single_declaration();
|
||||
@@ -223,7 +236,7 @@ public final class GlslVulkanPreprocess {
|
||||
}
|
||||
}
|
||||
|
||||
return new Metadata(bools, explicitInputs, needsSamplerless);
|
||||
return new Metadata(bools, explicitInputs, needsSamplerless, uniformBlocks);
|
||||
}
|
||||
|
||||
public static boolean renamesIdentifier(String name) {
|
||||
|
||||
+74
-37
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.glsm.shader;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
|
||||
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.UniformBlock;
|
||||
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
|
||||
import org.antlr.v4.runtime.ParserRuleContext;
|
||||
import org.antlr.v4.runtime.tree.ParseTree;
|
||||
@@ -26,8 +27,10 @@ import org.taumc.glsl.grammar.GLSLParserBaseListener;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.startIdx;
|
||||
@@ -65,13 +68,13 @@ public final class SpirvShaderTranslator {
|
||||
try {
|
||||
final String emitted = emitGlslEs(cr.spirv(), debugName, glShaderType, pre.explicitVsInputs());
|
||||
if (emitted == null) return null;
|
||||
return postProcessEsOutput(emitted, pre.boolUniforms());
|
||||
return postProcessEsOutput(emitted, pre.boolUniforms(), pre.uniformBlocks());
|
||||
} finally {
|
||||
memFree(cr.spirv());
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable String postProcessEsOutput(String glslEs, Set<String> boolUniformNames) {
|
||||
private static @Nullable String postProcessEsOutput(String glslEs, Set<String> boolUniformNames, Map<String, UniformBlock> uniformBlocks) {
|
||||
final GLSLParser.Translation_unitContext root;
|
||||
try {
|
||||
root = GlslTransformUtils.parseFullQuiet(glslEs);
|
||||
@@ -80,21 +83,42 @@ public final class SpirvShaderTranslator {
|
||||
return null;
|
||||
}
|
||||
|
||||
final DefaultBlockInfo block = findDefaultBlock(root);
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
final BlockFinder finder = new BlockFinder(glslEs, uniformBlocks);
|
||||
ParseTreeWalker.DEFAULT.walk(finder, root);
|
||||
|
||||
if (block != null) {
|
||||
edits.add(new Edit(startIdx(block.structExternalDecl), stopIdx(block.structExternalDecl), buildLooseUniforms(block.members, boolUniformNames)));
|
||||
final List<Edit> edits = new ArrayList<>();
|
||||
BlockInfo defaultBlock = null;
|
||||
final Map<String, Set<String>> bareMembersByVar = new HashMap<>();
|
||||
for (BlockInfo block : finder.blocks) {
|
||||
final String structReplacement;
|
||||
if (block.source == null) {
|
||||
defaultBlock = block;
|
||||
structReplacement = buildLooseUniforms(block.members, boolUniformNames);
|
||||
bareMembersByVar.put(block.varName, block.memberNames());
|
||||
} else if (block.source.instanceName() == null) {
|
||||
structReplacement = block.source.qualifierText() + " " + block.structName + " " + block.body + ";";
|
||||
bareMembersByVar.put(block.varName, block.memberNames());
|
||||
} else {
|
||||
structReplacement = block.source.qualifierText() + " " + block.structName + " " + block.body + " " + block.declaratorText + ";";
|
||||
}
|
||||
edits.add(new Edit(startIdx(block.structExternalDecl), stopIdx(block.structExternalDecl), structReplacement));
|
||||
edits.add(new Edit(startIdx(block.uniformExternalDecl), stopIdx(block.uniformExternalDecl), ""));
|
||||
}
|
||||
|
||||
final UseSiteRewriter rewriter = new UseSiteRewriter(block, boolUniformNames, edits);
|
||||
final Set<String> defaultMembers = defaultBlock == null ? Set.of() : bareMembersByVar.get(defaultBlock.varName);
|
||||
final UseSiteRewriter rewriter = new UseSiteRewriter(defaultBlock, defaultMembers, boolUniformNames, bareMembersByVar, edits);
|
||||
ParseTreeWalker.DEFAULT.walk(rewriter, root);
|
||||
|
||||
return GlslVulkanPreprocess.applyEdits(glslEs, edits);
|
||||
}
|
||||
|
||||
private record DefaultBlockInfo(ParserRuleContext structExternalDecl, ParserRuleContext uniformExternalDecl, String blockVarName, List<MemberInfo> members) {}
|
||||
private record BlockInfo(String structName, @Nullable UniformBlock source, ParserRuleContext structExternalDecl, String body, List<MemberInfo> members, ParserRuleContext uniformExternalDecl, String varName, String declaratorText) {
|
||||
Set<String> memberNames() {
|
||||
final Set<String> names = new HashSet<>();
|
||||
for (MemberInfo m : members) names.add(m.name);
|
||||
return names;
|
||||
}
|
||||
}
|
||||
|
||||
private record MemberInfo(String typePrefix, String name) {}
|
||||
|
||||
@@ -111,31 +135,38 @@ public final class SpirvShaderTranslator {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static @Nullable DefaultBlockInfo findDefaultBlock(GLSLParser.Translation_unitContext root) {
|
||||
final DefaultBlockFinder finder = new DefaultBlockFinder();
|
||||
ParseTreeWalker.DEFAULT.walk(finder, root);
|
||||
if (finder.structExternalDecl == null || finder.uniformExternalDecl == null) return null;
|
||||
return new DefaultBlockInfo(finder.structExternalDecl, finder.uniformExternalDecl, finder.blockVarName, finder.members);
|
||||
private static boolean isDefaultBlockStruct(String structName) {
|
||||
return structName.startsWith("_RESERVED_IDENTIFIER_FIXUP_") && structName.endsWith("DefaultUniformBlock");
|
||||
}
|
||||
|
||||
private static final class DefaultBlockFinder extends GLSLParserBaseListener {
|
||||
String structName;
|
||||
ParserRuleContext structExternalDecl;
|
||||
ParserRuleContext uniformExternalDecl;
|
||||
String blockVarName;
|
||||
final List<MemberInfo> members = new ArrayList<>();
|
||||
private static final class BlockFinder extends GLSLParserBaseListener {
|
||||
private record PendingStruct(@Nullable UniformBlock source, ParserRuleContext externalDecl, String body, List<MemberInfo> members) {}
|
||||
|
||||
private final String glslEs;
|
||||
private final Map<String, UniformBlock> sourceBlocks;
|
||||
private final Map<String, PendingStruct> pending = new HashMap<>();
|
||||
final List<BlockInfo> blocks = new ArrayList<>();
|
||||
|
||||
BlockFinder(String glslEs, Map<String, UniformBlock> sourceBlocks) {
|
||||
this.glslEs = glslEs;
|
||||
this.sourceBlocks = sourceBlocks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterStruct_specifier(GLSLParser.Struct_specifierContext ctx) {
|
||||
if (structExternalDecl != null) return;
|
||||
final TerminalNode id = ctx.IDENTIFIER();
|
||||
if (id == null) return;
|
||||
final String idText = id.getText();
|
||||
if (!idText.startsWith("_RESERVED_IDENTIFIER_FIXUP_") || !idText.endsWith("DefaultUniformBlock")) return;
|
||||
final UniformBlock source = sourceBlocks.get(idText);
|
||||
if (source == null && !isDefaultBlockStruct(idText)) return;
|
||||
|
||||
structName = idText;
|
||||
structExternalDecl = findExternalDeclaration(ctx);
|
||||
final List<MemberInfo> members = source != null && source.instanceName() != null ? List.of() : collectMembers(ctx);
|
||||
final String body = glslEs.substring(ctx.LEFT_BRACE().getSymbol().getStartIndex(), ctx.RIGHT_BRACE().getSymbol().getStopIndex() + 1);
|
||||
pending.putIfAbsent(idText, new PendingStruct(source, findExternalDeclaration(ctx), body, members));
|
||||
}
|
||||
|
||||
private static List<MemberInfo> collectMembers(GLSLParser.Struct_specifierContext ctx) {
|
||||
final List<MemberInfo> members = new ArrayList<>();
|
||||
for (GLSLParser.Struct_declarationContext sd : ctx.struct_declaration_list().struct_declaration()) {
|
||||
final StringBuilder typePrefix = new StringBuilder();
|
||||
if (sd.type_qualifier() != null) typePrefix.append(textWithSpaces(sd.type_qualifier())).append(' ');
|
||||
@@ -146,11 +177,12 @@ public final class SpirvShaderTranslator {
|
||||
members.add(new MemberInfo(typePrefix + arraySuffix, decl.IDENTIFIER().getText()));
|
||||
}
|
||||
}
|
||||
return members;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enterInit_declarator_list(GLSLParser.Init_declarator_listContext ctx) {
|
||||
if (structName == null || uniformExternalDecl != null) return;
|
||||
if (pending.isEmpty()) return;
|
||||
final GLSLParser.Single_declarationContext single = ctx.single_declaration();
|
||||
if (single == null || single.typeless_declaration() == null) return;
|
||||
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
|
||||
@@ -159,10 +191,14 @@ public final class SpirvShaderTranslator {
|
||||
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return;
|
||||
final GLSLParser.Type_specifier_nonarrayContext ts = fst.type_specifier().type_specifier_nonarray();
|
||||
if (ts.type_name() == null) return;
|
||||
if (!structName.equals(ts.type_name().getText())) return;
|
||||
final String structName = ts.type_name().getText();
|
||||
final PendingStruct struct = pending.remove(structName);
|
||||
if (struct == null) return;
|
||||
|
||||
blockVarName = single.typeless_declaration().IDENTIFIER().getText();
|
||||
uniformExternalDecl = findExternalDeclaration(ctx);
|
||||
final GLSLParser.Typeless_declarationContext declarator = single.typeless_declaration();
|
||||
final String declaratorText = glslEs.substring(startIdx(declarator), stopIdx(declarator) + 1);
|
||||
blocks.add(new BlockInfo(structName, struct.source, struct.externalDecl, struct.body, struct.members,
|
||||
findExternalDeclaration(ctx), declarator.IDENTIFIER().getText(), declaratorText));
|
||||
}
|
||||
|
||||
private static String textWithSpaces(ParseTree tree) {
|
||||
@@ -192,17 +228,18 @@ public final class SpirvShaderTranslator {
|
||||
}
|
||||
|
||||
private static final class UseSiteRewriter extends GLSLParserBaseListener {
|
||||
private final @Nullable DefaultBlockInfo block;
|
||||
private final @Nullable BlockInfo block;
|
||||
private final Set<String> boolNames;
|
||||
private final Map<String, Set<String>> bareMembersByVar;
|
||||
private final List<Edit> edits;
|
||||
private final Set<String> memberNames;
|
||||
|
||||
UseSiteRewriter(@Nullable DefaultBlockInfo block, Set<String> boolNames, List<Edit> edits) {
|
||||
UseSiteRewriter(@Nullable BlockInfo block, Set<String> memberNames, Set<String> boolNames, Map<String, Set<String>> bareMembersByVar, List<Edit> edits) {
|
||||
this.block = block;
|
||||
this.memberNames = memberNames;
|
||||
this.boolNames = boolNames;
|
||||
this.bareMembersByVar = bareMembersByVar;
|
||||
this.edits = edits;
|
||||
this.memberNames = new HashSet<>();
|
||||
if (block != null) for (MemberInfo m : block.members) memberNames.add(m.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -240,11 +277,11 @@ public final class SpirvShaderTranslator {
|
||||
}
|
||||
}
|
||||
|
||||
if (block != null && ctx.getChildCount() == 3 && ctx.DOT() != null && ctx.field_selection() != null) {
|
||||
final ParseTree inner = ctx.getChild(0);
|
||||
if (inner.getText().equals(block.blockVarName)) {
|
||||
if (ctx.getChildCount() == 3 && ctx.DOT() != null && ctx.field_selection() != null) {
|
||||
final Set<String> bareMembers = bareMembersByVar.get(ctx.getChild(0).getText());
|
||||
if (bareMembers != null) {
|
||||
final String member = ctx.field_selection().getText();
|
||||
if (memberNames.contains(member) && !coveredByAncestorBoolCompare(ctx)) {
|
||||
if (bareMembers.contains(member) && !coveredByAncestorBoolCompare(ctx)) {
|
||||
edits.add(new Edit(startIdx(ctx), stopIdx(ctx), member));
|
||||
}
|
||||
}
|
||||
@@ -254,8 +291,8 @@ public final class SpirvShaderTranslator {
|
||||
private @Nullable String resolveBoolOperand(ParseTree operand) {
|
||||
final String text = operand.getText();
|
||||
if (boolNames.contains(text)) return text;
|
||||
if (block != null && text.startsWith(block.blockVarName + ".")) {
|
||||
final String member = text.substring(block.blockVarName.length() + 1);
|
||||
if (block != null && text.startsWith(block.varName + ".")) {
|
||||
final String member = text.substring(block.varName.length() + 1);
|
||||
if (boolNames.contains(member) && memberNames.contains(member)) return member;
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -23,8 +23,12 @@ public class BooleanState implements ISettableState<BooleanState> {
|
||||
this.ffpStateOnly = ffpStateOnly;
|
||||
}
|
||||
|
||||
protected boolean forwardsToBackend() {
|
||||
return !ffpStateOnly;
|
||||
}
|
||||
|
||||
public void applyToBackend() {
|
||||
if (ffpStateOnly) return;
|
||||
if (!forwardsToBackend()) return;
|
||||
if (enabled) {
|
||||
RENDER_BACKEND.enable(glCap);
|
||||
} else {
|
||||
@@ -51,7 +55,7 @@ public class BooleanState implements ISettableState<BooleanState> {
|
||||
stateUnknown = false;
|
||||
this.enabled = enabled;
|
||||
onEnabledChanged();
|
||||
if (!ffpStateOnly) {
|
||||
if (forwardsToBackend()) {
|
||||
if (enabled) {
|
||||
RENDER_BACKEND.enable(this.glCap);
|
||||
} else {
|
||||
|
||||
@@ -27,10 +27,16 @@ public class ClipPlaneState {
|
||||
dest[3] = tempPlane.w;
|
||||
}
|
||||
|
||||
/** Writes the 4-component eye-space plane equation into {@code buf} at its current position. */
|
||||
public void putEyePlane(int index, FloatBuffer buf) {
|
||||
final float[] p = eyePlanes[index];
|
||||
buf.put(p[0]).put(p[1]).put(p[2]).put(p[3]);
|
||||
// Vulkan and clip emulation have no per-distance enable: a disabled plane must read as distance 0.
|
||||
public void putEffectivePlanes(int enabledMask, FloatBuffer buf) {
|
||||
for (int i = 0; i < eyePlanes.length; i++) {
|
||||
if ((enabledMask & (1 << i)) != 0) {
|
||||
final float[] p = eyePlanes[i];
|
||||
buf.put(p[0]).put(p[1]).put(p[2]).put(p[3]);
|
||||
} else {
|
||||
buf.put(0f).put(0f).put(0f).put(0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void getEyePlane(int index, DoubleBuffer buf) {
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.gtnewhorizons.angelica.glsm.texture;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL40;
|
||||
|
||||
/** Emulates GL proxy textures for backends that lack them. */
|
||||
public final class ProxyTexture {
|
||||
|
||||
private final int maxTextureSize;
|
||||
private final int max3DTextureSize;
|
||||
private final int maxCubeMapTextureSize;
|
||||
private final int maxArrayTextureLayers;
|
||||
|
||||
private int target;
|
||||
private int width;
|
||||
private int height;
|
||||
private int depth;
|
||||
private int internalFormat;
|
||||
|
||||
public ProxyTexture(int maxTextureSize, int max3DTextureSize, int maxCubeMapTextureSize, int maxArrayTextureLayers) {
|
||||
this.maxTextureSize = maxTextureSize;
|
||||
this.max3DTextureSize = max3DTextureSize;
|
||||
this.maxCubeMapTextureSize = maxCubeMapTextureSize;
|
||||
this.maxArrayTextureLayers = maxArrayTextureLayers;
|
||||
}
|
||||
|
||||
public void record(int target, int level, int internalFormat, int width, int height, int depth, boolean formatSupported) {
|
||||
final boolean layersInHeight = layersInHeight(target);
|
||||
final boolean layersInDepth = layersInDepth(target);
|
||||
final int dimLimit = dimensionLimit(target);
|
||||
final int w = levelZeroEquivalentSize(width, level);
|
||||
final int h = layersInHeight ? height : levelZeroEquivalentSize(height, level);
|
||||
final int d = layersInDepth ? depth : levelZeroEquivalentSize(depth, level);
|
||||
final int hLimit = layersInHeight ? maxArrayTextureLayers : dimLimit;
|
||||
final int dLimit = layersInDepth ? maxArrayTextureLayers : dimLimit;
|
||||
final boolean fits = w >= 1 && w <= dimLimit && h >= 1 && h <= hLimit && d >= 1 && d <= dLimit && formatSupported;
|
||||
if (fits) {
|
||||
accept(target, w, h, d, internalFormat);
|
||||
} else {
|
||||
reject(target);
|
||||
}
|
||||
}
|
||||
|
||||
public void reject(int target) {
|
||||
accept(target, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
private void accept(int target, int width, int height, int depth, int internalFormat) {
|
||||
this.target = target;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.depth = depth;
|
||||
this.internalFormat = internalFormat;
|
||||
}
|
||||
|
||||
public int levelParameter(int target, int level, int pname) {
|
||||
if (this.target != target) return 0;
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> TextureInfo.mipLevelSize(width, level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> layersInHeight(target) ? height : TextureInfo.mipLevelSize(height, level);
|
||||
case GL12.GL_TEXTURE_DEPTH -> layersInDepth(target) ? depth : TextureInfo.mipLevelSize(depth, level);
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> internalFormat;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
|
||||
public int dimensionLimit(int target) {
|
||||
return switch (target) {
|
||||
case GL12.GL_PROXY_TEXTURE_3D -> max3DTextureSize;
|
||||
case GL13.GL_PROXY_TEXTURE_CUBE_MAP, GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> maxCubeMapTextureSize;
|
||||
default -> maxTextureSize;
|
||||
};
|
||||
}
|
||||
|
||||
public static int levelZeroEquivalentSize(int size, int level) {
|
||||
if (size <= 0 || level < 0 || level >= Integer.SIZE - 1) return 0;
|
||||
final long scaled = (long) size << level;
|
||||
return scaled > Integer.MAX_VALUE ? 0 : (int) scaled;
|
||||
}
|
||||
|
||||
private static boolean layersInHeight(int target) {
|
||||
return target == GL30.GL_PROXY_TEXTURE_1D_ARRAY;
|
||||
}
|
||||
|
||||
private static boolean layersInDepth(int target) {
|
||||
return target == GL30.GL_PROXY_TEXTURE_2D_ARRAY || target == GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY || target == GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,12 @@ public class TextureInfo {
|
||||
private static final int UNSET_INT = Integer.MIN_VALUE;
|
||||
private static final float UNSET_FLOAT = Float.NEGATIVE_INFINITY;
|
||||
|
||||
public static int mipLevelSize(int base, int level) {
|
||||
if (base <= 0 || level < 0) return 0;
|
||||
if (level >= Integer.SIZE - 1) return 1;
|
||||
return Math.max(1, base >> level);
|
||||
}
|
||||
|
||||
@Getter protected final int id;
|
||||
@Getter @Setter protected int target = GL11.GL_TEXTURE_2D;
|
||||
protected int internalFormat = -1;
|
||||
|
||||
@@ -241,4 +241,28 @@ class DisplayListIdAllocatorTest {
|
||||
int base2 = alloc.allocRange(64);
|
||||
assertEquals(base1, base2, "Freed range should be reused");
|
||||
}
|
||||
|
||||
@Test
|
||||
void reserveRoutesByIdWithoutDisturbingLowIds() {
|
||||
final DisplayListIDAllocator alloc = new DisplayListIDAllocator();
|
||||
alloc.reserve(-5);
|
||||
alloc.reserve(0);
|
||||
alloc.reserve(Integer.MAX_VALUE);
|
||||
assertTrue(alloc.isAllocated(Integer.MAX_VALUE));
|
||||
assertFalse(alloc.isAllocated(Integer.MAX_VALUE & ((1 << 22) - 1)));
|
||||
assertEquals(1, alloc.allocRange(1));
|
||||
alloc.free(Integer.MAX_VALUE);
|
||||
assertFalse(alloc.isAllocated(Integer.MAX_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rangeAllocationDoesNotSpanSegments() {
|
||||
final DisplayListIDAllocator alloc = new DisplayListIDAllocator();
|
||||
assertEquals(32, alloc.allocRange((1 << 22) - 64));
|
||||
assertEquals(1 << 22, alloc.allocRange(64));
|
||||
assertFalse(alloc.isAllocated((1 << 22) - 32));
|
||||
assertTrue(alloc.isAllocated(1 << 22));
|
||||
alloc.freeRange((1 << 22) - 1, 2);
|
||||
assertFalse(alloc.isAllocated(1 << 22));
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ class GLESCapsTest {
|
||||
};
|
||||
|
||||
private static boolean onGles(int cap) {
|
||||
return GLESCaps.isCapAllowed(cap, true, true);
|
||||
return GLESCaps.isCapAllowed(cap, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -60,25 +60,10 @@ class GLESCapsTest {
|
||||
@Test
|
||||
void desktopContextAllowsEveryCap() {
|
||||
for (int cap : DESKTOP_ONLY) {
|
||||
assertTrue(GLESCaps.isCapAllowed(cap, false, false), () -> "cap 0x" + Integer.toHexString(cap) + " is legal on desktop core and the gate must never suppress it there");
|
||||
assertTrue(GLESCaps.isCapAllowed(cap, false), () -> "cap 0x" + Integer.toHexString(cap) + " is legal on desktop core and the gate must never suppress it there");
|
||||
}
|
||||
for (int cap : VALID_ON_GLES) {
|
||||
assertTrue(GLESCaps.isCapAllowed(cap, false, false));
|
||||
assertTrue(GLESCaps.isCapAllowed(cap, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void clipPlanesFollowClipCullDistance() {
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
final int cap = GL11.GL_CLIP_PLANE0 + i;
|
||||
assertTrue(GLESCaps.isCapAllowed(cap, true, true), () -> "clip plane " + cap + " works where EXT_clip_cull_distance is present and must not be suppressed");
|
||||
assertFalse(GLESCaps.isCapAllowed(cap, true, false), () -> "clip plane " + cap + " is invalid on GLES without EXT_clip_cull_distance");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void capsOutsideTheClipPlaneRangeAreUnaffectedByClipCullDistance() {
|
||||
assertTrue(GLESCaps.isCapAllowed(GL11.GL_BLEND, true, false));
|
||||
assertTrue(GLESCaps.isCapAllowed(GL11.GL_CLIP_PLANE0 + GLStateManager.MAX_CLIP_PLANES, true, false));
|
||||
}
|
||||
}
|
||||
@@ -487,29 +487,39 @@ public class GLSM_ClientArrays_GLTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void popKeepsCurrentArraysWhenTheSavedArrayBufferWasDeleted() {
|
||||
void popKeepsCurrentStateWhenTheSavedBuffersWereDeleted() {
|
||||
final int vbo2 = GLSMBuffers.glGenBuffers();
|
||||
final int ebo = GLSMBuffers.glGenBuffers();
|
||||
final int ebo2 = GLSMBuffers.glGenBuffers();
|
||||
try {
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
GLSMTextures.glClientActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLSMVertexInput.glVertexPointer(3, GL11.GL_FLOAT, 12, 0L);
|
||||
|
||||
GLSMAttribStack.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo2);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo2);
|
||||
GLSMVertexInput.glVertexPointer(2, GL11.GL_SHORT, 4, 0L);
|
||||
GLSMTextures.glClientActiveTexture(GL13.GL_TEXTURE1);
|
||||
GLSMBuffers.glDeleteBuffers(vbo);
|
||||
GLSMBuffers.glDeleteBuffers(ebo);
|
||||
GLSMAttribStack.glPopClientAttrib();
|
||||
|
||||
assertEquals(2, GLSMQueries.glGetInteger(GL11.GL_VERTEX_ARRAY_SIZE), "array state is not restored once its array buffer is gone");
|
||||
assertEquals(vbo2, GLSMQueries.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING));
|
||||
assertEquals(vbo2, GL11.glGetInteger(GL15.GL_ARRAY_BUFFER_BINDING));
|
||||
assertEquals(ebo2, GLSMQueries.glGetInteger(GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING), "a deleted element buffer is not rebound by the pop");
|
||||
assertEquals(ebo2, GL11.glGetInteger(GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING));
|
||||
assertEquals(GL13.GL_TEXTURE0, GLSMQueries.glGetInteger(GL13.GL_CLIENT_ACTIVE_TEXTURE));
|
||||
} finally {
|
||||
drainClientAttribStack();
|
||||
GLSMTextures.glClientActiveTexture(GL13.GL_TEXTURE0);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
GLSMBuffers.glDeleteBuffers(vbo2);
|
||||
GLSMBuffers.glDeleteBuffers(ebo2);
|
||||
GLSMBuffers.glDeleteBuffers(ebo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,6 +531,12 @@ public class GLSM_ClientArrays_GLTest {
|
||||
GLSMDraws.glNewList(list, GL11.GL_COMPILE);
|
||||
GLSMAttribStack.glPushClientAttrib(GL11.GL_CLIENT_VERTEX_ARRAY_BIT);
|
||||
GLSMAttribStack.glPopClientAttrib();
|
||||
GLSMDraws.glBegin(GL11.GL_QUADS);
|
||||
GLSMDraws.glVertex2f(-1f, -1f);
|
||||
GLSMDraws.glVertex2f(1f, -1f);
|
||||
GLSMDraws.glVertex2f(1f, 1f);
|
||||
GLSMDraws.glVertex2f(-1f, 1f);
|
||||
GLSMDraws.glEnd();
|
||||
GLSMDraws.glEndList();
|
||||
|
||||
GLSMVertexInput.glBindVertexArray(other);
|
||||
|
||||
@@ -14,6 +14,7 @@ import static com.gtnewhorizons.angelica.glsm.DisplayListTestHelper.assertModelv
|
||||
import static com.gtnewhorizons.angelica.glsm.DisplayListTestHelper.modelviewAfterCall;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
@@ -209,6 +210,25 @@ class GLSM_DisplayList_Stress_Test {
|
||||
assertEquals(10.0f, modelviewAfterCall(list).get(12), 0.0001f, "Recompilation should translate by 10");
|
||||
}
|
||||
|
||||
@Test
|
||||
void compiledNameOutsideGeneratedRangeIsNotReissued() {
|
||||
final int base = GLSMDraws.glGenLists(1);
|
||||
int extra = base + 1;
|
||||
while (GLSMDraws.glIsList(extra)) extra++;
|
||||
testLists.add(base);
|
||||
testLists.add(extra);
|
||||
|
||||
GLSMDraws.glNewList(extra, GL11.GL_COMPILE);
|
||||
GLSMMatrices.glTranslatef(5.0f, 0.0f, 0.0f);
|
||||
GLSMDraws.glEndList();
|
||||
final int next = GLSMDraws.glGenLists(1);
|
||||
testLists.add(next);
|
||||
assertNotEquals(extra, next, "glGenLists must not reissue a name installed by glEndList");
|
||||
|
||||
GLSMDraws.glDeleteLists(extra, 1);
|
||||
assertFalse(GLSMDraws.glIsList(extra), "glDeleteLists must release a reserved name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testComplexTransformSequence() {
|
||||
// Test a complex sequence of transforms that could expose numerical issues
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL21;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@GLCoreTest
|
||||
class GLScreenCapture_GLTest {
|
||||
|
||||
private static final int SRC_W = 64;
|
||||
private static final int SRC_H = 32;
|
||||
private static final int DST_W = 16;
|
||||
private static final int DST_H = 8;
|
||||
|
||||
private record Target(int texture, int framebuffer) {
|
||||
void delete() {
|
||||
GLSMFramebuffer.glDeleteFramebuffers(framebuffer);
|
||||
GLSMTextures.glDeleteTextures(texture);
|
||||
}
|
||||
}
|
||||
|
||||
private static Target quadrantTarget() {
|
||||
final int texture = GLSMTextures.glGenTextures();
|
||||
GLSMTextures.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GLSMTextures.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SRC_W, SRC_H, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
GLSMTextures.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
final int framebuffer = GLSMFramebuffer.glGenFramebuffers();
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
|
||||
GLSMFramebuffer.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, texture, 0);
|
||||
GlTestSupport.fillQuadrants(SRC_W, SRC_H);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
return new Target(texture, framebuffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void downscalesFramebufferQuadrantsBottomUp() {
|
||||
final Target target = quadrantTarget();
|
||||
final ScreenCapture capture = GLSMFramebuffer.createScreenCapture(DST_W, DST_H);
|
||||
try {
|
||||
assertTrue(capture.begin(target.framebuffer(), SRC_W, SRC_H, 42L));
|
||||
GL11.glFinish();
|
||||
assertTrue(capture.poll());
|
||||
assertEquals(42L, capture.tag());
|
||||
GlTestSupport.assertQuadrantsBottomUp(capture.address(), DST_W, DST_H);
|
||||
capture.release();
|
||||
} finally {
|
||||
capture.delete();
|
||||
target.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void ringRefusesWhenFullAndReturnsInBeginOrder() {
|
||||
final Target target = quadrantTarget();
|
||||
final ScreenCapture capture = GLSMFramebuffer.createScreenCapture(DST_W, DST_H);
|
||||
try {
|
||||
for (int i = 0; i < ScreenCapture.RING; i++) assertTrue(capture.begin(target.framebuffer(), SRC_W, SRC_H, i), "capture " + i + " was not recorded");
|
||||
assertFalse(capture.begin(target.framebuffer(), SRC_W, SRC_H, 99L), "a full ring must refuse a new capture");
|
||||
GL11.glFinish();
|
||||
for (int i = 0; i < ScreenCapture.RING; i++) {
|
||||
assertTrue(capture.poll(), "capture " + i + " not ready after glFinish");
|
||||
assertEquals(i, capture.tag(), "captures must come back in begin order");
|
||||
capture.release();
|
||||
}
|
||||
assertFalse(capture.poll());
|
||||
assertTrue(capture.idle());
|
||||
assertTrue(capture.begin(target.framebuffer(), SRC_W, SRC_H, 100L), "a drained ring must accept captures again");
|
||||
} finally {
|
||||
capture.delete();
|
||||
target.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void restoresBindingsAndPixelStore() {
|
||||
final Target target = quadrantTarget();
|
||||
final int readFbo = GLSMFramebuffer.glGenFramebuffers();
|
||||
final int drawFbo = GLSMFramebuffer.glGenFramebuffers();
|
||||
final int packBuffer = GLSMBuffers.glGenBuffers();
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, readFbo);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, drawFbo);
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, packBuffer);
|
||||
GLSMBuffers.glBufferData(GL21.GL_PIXEL_PACK_BUFFER, 16L, GL15.GL_STREAM_READ);
|
||||
GLSMTextures.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 1);
|
||||
GLSMEnable.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
final ScreenCapture capture = GLSMFramebuffer.createScreenCapture(DST_W, DST_H);
|
||||
try {
|
||||
assertTrue(capture.begin(target.framebuffer(), SRC_W, SRC_H, 1L));
|
||||
GL11.glFinish();
|
||||
assertTrue(capture.poll());
|
||||
capture.release();
|
||||
|
||||
assertEquals(readFbo, GLSMFramebuffer.getReadFramebuffer());
|
||||
assertEquals(drawFbo, GLSMFramebuffer.getDrawFramebuffer());
|
||||
assertEquals(packBuffer, GLStateManager.ctx().boundPixelPackBuffer);
|
||||
assertEquals(1, GLStateManager.ctx().pixelPackState.alignment());
|
||||
assertTrue(GLSMRasterizer.getScissorTest().isEnabled());
|
||||
assertEquals(readFbo, GL11.glGetInteger(GL30.GL_READ_FRAMEBUFFER_BINDING));
|
||||
assertEquals(drawFbo, GL11.glGetInteger(GL30.GL_DRAW_FRAMEBUFFER_BINDING));
|
||||
assertEquals(packBuffer, GL11.glGetInteger(GL21.GL_PIXEL_PACK_BUFFER_BINDING));
|
||||
assertEquals(1, GL11.glGetInteger(GL11.GL_PACK_ALIGNMENT));
|
||||
assertTrue(GL11.glIsEnabled(GL11.GL_SCISSOR_TEST));
|
||||
} finally {
|
||||
capture.delete();
|
||||
GLSMEnable.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
GLSMTextures.glPixelStorei(GL11.GL_PACK_ALIGNMENT, 4);
|
||||
GLSMBuffers.glBindBuffer(GL21.GL_PIXEL_PACK_BUFFER, 0);
|
||||
GLSMBuffers.glDeleteBuffers(packBuffer);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLSMFramebuffer.glDeleteFramebuffers(readFbo);
|
||||
GLSMFramebuffer.glDeleteFramebuffers(drawFbo);
|
||||
target.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMAttribStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@GLCoreTest
|
||||
class ClipPlaneGLTest {
|
||||
|
||||
private static final int SIZE = 64;
|
||||
private static final int BACKGROUND = 0xFF000000;
|
||||
private static final int QUAD = 0xFF0000FF;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
FfpFixture.resetFfpState();
|
||||
FfpFixture.activateFfp();
|
||||
GLSMRasterizer.glViewport(0, 0, SIZE, SIZE);
|
||||
GLSMMatrices.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLSMMatrices.glLoadIdentity();
|
||||
GLSMMatrices.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLSMMatrices.glLoadIdentity();
|
||||
GLSMEnable.glDisable(GL11.GL_DEPTH_TEST);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
|
||||
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE0 + i);
|
||||
}
|
||||
FfpFixture.resetFfpState();
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "emulated={0}")
|
||||
@ValueSource(booleans = {false, true})
|
||||
void disabledPlaneDoesNotClip(boolean emulated) {
|
||||
try (var ignored = ClipTestSupport.emulation(emulated)) {
|
||||
ClipTestSupport.setPlane(1, 0, 0, 0, -1);
|
||||
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE1);
|
||||
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE1);
|
||||
ClipTestSupport.setPlane(0, 1, 0, 0, 0);
|
||||
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE0);
|
||||
assertRightHalfOnly(drawQuad(), "plane1 disabled, plane0 enabled");
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "emulated={0}")
|
||||
@ValueSource(booleans = {false, true})
|
||||
void attribPopRestoresClipping(boolean emulated) {
|
||||
try (var ignored = ClipTestSupport.emulation(emulated)) {
|
||||
ClipTestSupport.setPlane(0, 1, 0, 0, 0);
|
||||
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE0);
|
||||
assertDriverClipPlane0(!emulated, "after glEnable");
|
||||
GLSMAttribStack.glPushAttrib(GL11.GL_ENABLE_BIT);
|
||||
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE0);
|
||||
final int[] unclipped = drawQuad();
|
||||
GLSMAttribStack.glPopAttrib();
|
||||
assertDriverClipPlane0(!emulated, "after glPopAttrib");
|
||||
GLSMAttribStack.replayStateToBackend();
|
||||
assertDriverClipPlane0(!emulated, "after replayStateToBackend");
|
||||
final int[] restored = drawQuad();
|
||||
|
||||
for (int i = 0; i < unclipped.length; i++) {
|
||||
final int px = i;
|
||||
assertEquals(QUAD, unclipped[i], () -> "plane disabled inside the pushed scope, pixel " + px % SIZE + "," + px / SIZE);
|
||||
}
|
||||
assertRightHalfOnly(restored, "after glPopAttrib");
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertDriverClipPlane0(boolean expected, String when) {
|
||||
assertEquals(expected, GL11.glIsEnabled(GL11.GL_CLIP_PLANE0), () -> when + ": clip plane reaches the driver only when clip distances are native");
|
||||
}
|
||||
|
||||
private static void assertRightHalfOnly(int[] pixels, String what) {
|
||||
for (int i = 0; i < pixels.length; i++) {
|
||||
final int x = i % SIZE, y = i / SIZE;
|
||||
assertEquals(x < SIZE / 2 ? BACKGROUND : QUAD, pixels[i], () -> what + ": first mismatch at pixel " + x + "," + y);
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] drawQuad() {
|
||||
final int vao = GLSMVertexInput.glGenVertexArrays();
|
||||
GLSMVertexInput.glBindVertexArray(vao);
|
||||
final int vbo = GLSMBuffers.glGenBuffers();
|
||||
try {
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
|
||||
final FloatBuffer positions = BufferUtils.createFloatBuffer(18);
|
||||
positions.put(new float[] { -1, -1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0 }).flip();
|
||||
GLSMBuffers.glBufferData(GL15.GL_ARRAY_BUFFER, positions, GL15.GL_STATIC_DRAW);
|
||||
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, 12, 0);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
VAOManager.setCurrentVertexFlags(0);
|
||||
|
||||
FfpFixture.clear();
|
||||
GLSMCurrentAttribs.glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLSMDraws.glDrawArrays(GL11.GL_TRIANGLES, 0, 6);
|
||||
GLSMVertexInput.glBindVertexArray(0);
|
||||
return FfpFixture.readRegion(SIZE);
|
||||
} finally {
|
||||
GLSMBuffers.glDeleteBuffers(vbo);
|
||||
GLSMVertexInput.glDeleteVertexArrays(vao);
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.lwjgl.PointerBuffer;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
import org.lwjgl.util.shaderc.Shaderc;
|
||||
@@ -55,6 +56,30 @@ class FFPUniformBlockSpirvLayoutTest {
|
||||
MemoryUtil.memFree(result.spirv());
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "emulated={0}")
|
||||
@ValueSource(booleans = {false, true})
|
||||
void clipDistanceCapabilityFollowsEmulation(boolean emulated) {
|
||||
long vbits = bit("BIT_CLIP_PLANES");
|
||||
if (emulated) vbits |= bit("BIT_CLIP_EMULATED");
|
||||
final String vs = VertexShaderGenerator.generate(VertexKey.fromPacked(vbits));
|
||||
assertEquals(!emulated, declaresClipDistance(vs, Shaderc.shaderc_vertex_shader), "vertex ClipDistance capability");
|
||||
}
|
||||
|
||||
private static boolean declaresClipDistance(String glsl, int shaderKind) {
|
||||
final SpirvCompiler.Result result = SpirvCompiler.compile(glsl, shaderKind, "clip-emulation-test", SpirvCompiler.Options.vulkanForced460Core());
|
||||
assertNotNull(result.spirv(), () -> "SPIR-V compile failed: " + result.error() + "\n" + glsl);
|
||||
try {
|
||||
final IntBuffer words = result.spirv().asIntBuffer();
|
||||
for (int i = 5; i < words.limit(); i += Math.max(1, words.get(i) >>> 16)) {
|
||||
final int word = words.get(i);
|
||||
if ((word & 0xFFFF) == 17 && words.get(i + 1) == 32) return true;
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
MemoryUtil.memFree(result.spirv());
|
||||
}
|
||||
}
|
||||
|
||||
private static VertexKey cubeKey(boolean lightmapUnit, boolean unit0TextureMatrix, boolean texGen) {
|
||||
long bits = bit("BIT_HAS_VERTEX_COLOR") | bit("BIT_HAS_VERTEX_NORMAL") | bit("BIT_HAS_VERTEX_TEX") | bit("BIT_UNIT_TEX_BASE");
|
||||
if (lightmapUnit) bits |= 1L << (position("BIT_UNIT_TEX_BASE") + 1);
|
||||
|
||||
+74
-5
@@ -27,10 +27,12 @@ import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags.COLOR_BIT;
|
||||
import static com.gtnewhorizons.angelica.glsm.GlTestSupport.floats;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
@@ -82,6 +84,7 @@ class FFPVertexLightingGLTest {
|
||||
GLSMEnable.glDisable(GL11.GL_LIGHT1);
|
||||
GLSMEnable.glDisable(GL11.GL_LIGHT2);
|
||||
GLSMEnable.glDisable(GL11.GL_COLOR_MATERIAL);
|
||||
GLSMCurrentAttribs.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
if (sm.isActive()) sm.deactivate();
|
||||
GLSMVertexInput.glBindVertexArray(0);
|
||||
@@ -126,7 +129,7 @@ class FFPVertexLightingGLTest {
|
||||
+ " light1=" + GLSMLighting.getLightStates()[1].isEnabled();
|
||||
}
|
||||
|
||||
private void buildTriangle() {
|
||||
private void buildTriangle(boolean withColor) {
|
||||
vao = GLSMVertexInput.glGenVertexArrays();
|
||||
GLSMVertexInput.glBindVertexArray(vao);
|
||||
|
||||
@@ -141,9 +144,13 @@ class FFPVertexLightingGLTest {
|
||||
GLSMBuffers.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STATIC_DRAW);
|
||||
GLSMVertexInput.glEnableVertexAttribArray(POSITION_LOC);
|
||||
GLSMVertexInput.glVertexAttribPointer(POSITION_LOC, 3, GL11.GL_FLOAT, false, STRIDE, 0L);
|
||||
GLSMVertexInput.glEnableVertexAttribArray(COLOR_LOC);
|
||||
GLSMVertexInput.glVertexAttribPointer(COLOR_LOC, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, 12L);
|
||||
VAOManager.setCurrentVertexFlags(COLOR_BIT);
|
||||
if (withColor) {
|
||||
GLSMVertexInput.glEnableVertexAttribArray(COLOR_LOC);
|
||||
GLSMVertexInput.glVertexAttribPointer(COLOR_LOC, 4, GL11.GL_UNSIGNED_BYTE, true, STRIDE, 12L);
|
||||
VAOManager.setCurrentVertexFlags(COLOR_BIT);
|
||||
} else {
|
||||
VAOManager.setCurrentVertexFlags(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void putVertex(ByteBuffer buf, float x, float y) {
|
||||
@@ -154,7 +161,11 @@ class FFPVertexLightingGLTest {
|
||||
}
|
||||
|
||||
private float[] drawAndReadCenter() {
|
||||
buildTriangle();
|
||||
return drawAndReadCenter(true);
|
||||
}
|
||||
|
||||
private float[] drawAndReadCenter(boolean withColor) {
|
||||
buildTriangle(withColor);
|
||||
final ShaderManager sm = ShaderManager.getInstance();
|
||||
ShaderManager.enable();
|
||||
sm.activate();
|
||||
@@ -233,6 +244,64 @@ class FFPVertexLightingGLTest {
|
||||
state.run();
|
||||
assertFalse(FFPVertexLighting.modulatesVertexColor(factor), reason);
|
||||
assertEquals(IDENTITY_FACTOR, factor, "a rejected solve must leave an identity factor");
|
||||
assertBakeRejected(reason);
|
||||
}
|
||||
|
||||
private static void assertBakeRejected(String reason) {
|
||||
final int[] raw = new int[8];
|
||||
Arrays.fill(raw, 0x5A3C1E0F);
|
||||
final int[] before = raw.clone();
|
||||
assertFalse(FFPVertexLighting.bakeColors(raw, 1, true, true), reason + ": bake must be rejected");
|
||||
assertArrayEquals(before, raw, reason + ": a rejected bake must leave the buffer untouched");
|
||||
}
|
||||
|
||||
@Test
|
||||
void positionalLightIsNotBaked() {
|
||||
GLSMLighting.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, floats(0.0f, 0.0f, 1.0f, 1.0f));
|
||||
assertBakeRejected("a positional light has a per-vertex direction");
|
||||
assertTrue(FFPVertexLighting.modulatesVertexColor(factor), "text keeps the object-origin approximation for positional lights");
|
||||
}
|
||||
|
||||
static Stream<Arguments> bakeCases() {
|
||||
return Stream.of(
|
||||
Arguments.of("flattened face-on", -1.0e-4f, 0.0f, 0.0f, 1.0f, true, true),
|
||||
Arguments.of("flattened top", -1.0e-4f, 0.0f, 1.0f, 0.0f, true, true),
|
||||
Arguments.of("flattened side", -1.0e-4f, 1.0f, 0.0f, 0.0f, true, true),
|
||||
Arguments.of("unflattened tilted", 1.0f, 0.3f, 0.8f, 0.5f, true, true),
|
||||
Arguments.of("current color", -1.0e-4f, 0.0f, 1.0f, 0.0f, true, false),
|
||||
Arguments.of("current normal", -1.0e-4f, 0.0f, 1.0f, 0.0f, false, true));
|
||||
}
|
||||
|
||||
private static int packNormal(float x, float y, float z) {
|
||||
return ((byte) (x * 127.0f) & 0xFF) | (((byte) (y * 127.0f) & 0xFF) << 8) | (((byte) (z * 127.0f) & 0xFF) << 16);
|
||||
}
|
||||
|
||||
private static float unpackNormal(int packed, int shift) {
|
||||
return (byte) (packed >> shift) / 127.0f;
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("bakeCases")
|
||||
void bakedColorMatchesGeneratedShader(String name, float zScale, float nx, float ny, float nz, boolean hasNormals, boolean hasColor) {
|
||||
GLSMMatrices.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLSMMatrices.glLoadIdentity();
|
||||
GLSMMatrices.glScalef(1.0f, 1.0f, zScale);
|
||||
standardItemLighting();
|
||||
if (!hasColor) GLSMCurrentAttribs.glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
|
||||
final int packedNormal = packNormal(nx, ny, nz);
|
||||
final int[] raw = new int[8];
|
||||
raw[5] = VERTEX_COLOR;
|
||||
if (hasNormals) raw[6] = packedNormal;
|
||||
GLSMCurrentAttribs.glNormal3f(unpackNormal(packedNormal, 0), unpackNormal(packedNormal, 8), unpackNormal(packedNormal, 16));
|
||||
assertTrue(FFPVertexLighting.bakeColors(raw, 1, hasNormals, hasColor), name + ": expected a bakeable state, " + gateState());
|
||||
|
||||
final float[] actual = drawAndReadCenter(hasColor);
|
||||
for (int c = 0; c < 3; c++) {
|
||||
final int baked = (raw[5] >>> (c * 8)) & 0xFF;
|
||||
final int shader = Math.round(actual[c] * 255.0f);
|
||||
assertEquals(baked, shader, name + ": channel " + c + " baked=" + baked + " shader=" + shader);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+8
-10
@@ -3,21 +3,21 @@ package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
import com.gtnewhorizons.angelica.glsm.GLCoreTest;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMDepthStencilAlpha;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMFog;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMLighting;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMTexEnvGen;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -32,12 +32,6 @@ class FfpUniformBlockDedupTest {
|
||||
return GL30.glGetInteger(GL31.GL_UNIFORM_BUFFER_START, FFPUniformBlock.BINDING_POINT);
|
||||
}
|
||||
|
||||
private static DoubleBuffer doubles(double... values) {
|
||||
final DoubleBuffer buf = BufferUtils.createDoubleBuffer(values.length);
|
||||
buf.put(values).flip();
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static void resetState() {
|
||||
GLSMMatrices.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLSMMatrices.glLoadIdentity();
|
||||
@@ -58,7 +52,8 @@ class FfpUniformBlockDedupTest {
|
||||
GLSMLighting.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, floats(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
GLSMLighting.glMaterialf(GL11.GL_FRONT, GL11.GL_SHININESS, 0.0f);
|
||||
GLSMTexEnvGen.glTexGen(GL11.GL_S, GL11.GL_OBJECT_PLANE, floats(1.0f, 0.0f, 0.0f, 0.0f));
|
||||
GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE0, doubles(0.0, 0.0, 0.0, 0.0));
|
||||
GLSMEnable.glDisable(GL11.GL_CLIP_PLANE0);
|
||||
ClipTestSupport.setPlane(0, 0.0, 0.0, 0.0, 0.0);
|
||||
GLSMRasterizer.glLineStipple(1, (short) 0xFFFF);
|
||||
GLSMRasterizer.glViewport(0, 0, 854, 480);
|
||||
}
|
||||
@@ -149,7 +144,10 @@ class FfpUniformBlockDedupTest {
|
||||
addCategory(tests, "currentTexCoord", () -> GLSMCurrentAttribs.glTexCoord2f(0.125f, 0.375f));
|
||||
addCategory(tests, "lightmapCoord", () -> GLStateManager.ctx().lastBrightnessX = 96.0f);
|
||||
addCategory(tests, "texGenPlane", () -> GLSMTexEnvGen.glTexGen(GL11.GL_S, GL11.GL_OBJECT_PLANE, floats(0.5f, 0.0f, 0.0f, 0.25f)));
|
||||
addCategory(tests, "clipPlane", () -> GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE0, doubles(0.0, 1.0, 0.0, 7.0)));
|
||||
addCategory(tests, "clipPlane", () -> {
|
||||
GLSMEnable.glEnable(GL11.GL_CLIP_PLANE0);
|
||||
ClipTestSupport.setPlane(0, 0.0, 1.0, 0.0, 7.0);
|
||||
});
|
||||
addCategory(tests, "alphaRef", () -> GLSMDepthStencilAlpha.glAlphaFunc(GL11.GL_GREATER, 0.35f));
|
||||
addCategory(tests, "fogColor", () -> GLSMFog.glFog(GL11.GL_FOG_COLOR, floats(0.1f, 0.2f, 0.3f, 0.4f)));
|
||||
addCategory(tests, "fogDensity", () -> GLSMFog.glFogf(GL11.GL_FOG_DENSITY, 0.75f));
|
||||
|
||||
@@ -100,6 +100,7 @@ class InstancedFFPDrawGLTest {
|
||||
CubeParityFixture.disableDirectionalLight();
|
||||
CubeParityFixture.deleteResources();
|
||||
ParticleParityFixture.deleteResources();
|
||||
WeatherParityFixture.deleteResources();
|
||||
if (templateVbo != 0) { GLSMBuffers.glDeleteBuffers(templateVbo); templateVbo = 0; }
|
||||
if (instanceVbo != 0) { GLSMBuffers.glDeleteBuffers(instanceVbo); instanceVbo = 0; }
|
||||
if (vao != 0) { GLSMVertexInput.glDeleteVertexArrays(vao); vao = 0; }
|
||||
@@ -196,6 +197,27 @@ class InstancedFFPDrawGLTest {
|
||||
ParticleParityFixture.assertPixelParity(reference, instanced, PARITY_SIZE, 0xFF000000, pixel -> "0x" + Integer.toHexString(pixel));
|
||||
}
|
||||
|
||||
@Test
|
||||
void weatherColumnsMatchTessellatedReference() {
|
||||
GLSMRasterizer.glViewport(0, 0, PARITY_SIZE, PARITY_SIZE);
|
||||
WeatherParityFixture.setupScene();
|
||||
|
||||
FfpFixture.activateFfp();
|
||||
|
||||
final WeatherParityFixture.Spec[] specs = WeatherParityFixture.specs();
|
||||
|
||||
FfpFixture.clear();
|
||||
WeatherParityFixture.drawReference(specs);
|
||||
final int[] reference = FfpFixture.readRegion(PARITY_SIZE);
|
||||
|
||||
FfpFixture.clear();
|
||||
WeatherParityFixture.drawInstanced(specs);
|
||||
final int[] instanced = FfpFixture.readRegion(PARITY_SIZE);
|
||||
|
||||
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "parity draws must not raise a GL error");
|
||||
WeatherParityFixture.assertPixelParity(reference, instanced, PARITY_SIZE, 0xFF000000, pixel -> "0x" + Integer.toHexString(pixel));
|
||||
}
|
||||
|
||||
@Test
|
||||
void entityAttribSelectsColorPerInstance() {
|
||||
GLSMRasterizer.glViewport(0, 0, INTEGER_WIDTH, INTEGER_HEIGHT);
|
||||
|
||||
+192
-1
@@ -1,10 +1,14 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet;
|
||||
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
|
||||
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.Random;
|
||||
|
||||
@@ -15,6 +19,7 @@ public class VAOManagerEboIndexTest {
|
||||
|
||||
private static final int DEFAULT_VAO = 0;
|
||||
private static final int NO_VAO = -1;
|
||||
private static final VAOManager.ClientArrays[] NO_SAVED = new VAOManager.ClientArrays[0];
|
||||
|
||||
private final VAOManager vaos = new VAOManager();
|
||||
private final Int2IntOpenHashMap expectedEbo = new Int2IntOpenHashMap();
|
||||
@@ -62,7 +67,7 @@ public class VAOManagerEboIndexTest {
|
||||
}
|
||||
|
||||
private void deleteBuffer(int buffer) {
|
||||
vaos.onDeleteBuffer(buffer);
|
||||
vaos.onDeleteBuffer(buffer, NO_SAVED);
|
||||
for (int vao : expectedEbo.keySet().toIntArray()) {
|
||||
if (expectedEbo.get(vao) == buffer) expectedEbo.put(vao, 0);
|
||||
}
|
||||
@@ -126,4 +131,190 @@ public class VAOManagerEboIndexTest {
|
||||
}
|
||||
assertTrue(sharedEboDeletes > 20, "op stream never freed a multi-owner EBO (hits: " + sharedEboDeletes + ")");
|
||||
}
|
||||
|
||||
private static final int PRUNE_INTERVAL = 4;
|
||||
private static final int OPS = 40_000;
|
||||
private static final int SLOTS = 4;
|
||||
private static final int[] CONVS = {VAOManager.CONV_POSITION, VAOManager.CONV_COLOR, VAOManager.CONV_SECONDARY_COLOR, VAOManager.CONV_TEX0};
|
||||
private static final String[] CURRENT_FIELDS = {"currentAttribs", "currentGeneric", "currentConventional"};
|
||||
private static final int STACK_DEPTH = 16;
|
||||
private static final int MAX_VAOS = 8;
|
||||
private static final int MAX_LIVE_BUFFERS = 24;
|
||||
|
||||
private final VAOManager pruned = new VAOManager(PRUNE_INTERVAL);
|
||||
private final VAOManager oracle = new VAOManager(OPS + 1);
|
||||
private final VAOManager[] pair = {pruned, oracle};
|
||||
private final VAOManager.ClientArrays[][] stacks = new VAOManager.ClientArrays[pair.length][STACK_DEPTH];
|
||||
private int deadSlotChecks;
|
||||
|
||||
private static int pick(Random random, IntArrayList list) {
|
||||
return list.getInt(random.nextInt(list.size()));
|
||||
}
|
||||
|
||||
private int staleName(Random random, IntArrayList vaoIds, IntLinkedOpenHashSet freeNames) {
|
||||
final VAOManager.VAOData data = oracle.vaoMap.get(pick(random, vaoIds));
|
||||
final VAOManager.Attrib[] slots = random.nextBoolean() ? data.attribs : data.conventional;
|
||||
if (slots == null) return 0;
|
||||
final VAOManager.Attrib a = slots[random.nextInt(slots.length)];
|
||||
return a != null && a.vboId != 0 && freeNames.remove(a.vboId) ? a.vboId : 0;
|
||||
}
|
||||
|
||||
private void bindBoth(int vao) {
|
||||
if (vao == currentVao) return;
|
||||
for (VAOManager m : pair) m.onBindVertexArrayPre(vao);
|
||||
currentVao = vao;
|
||||
}
|
||||
|
||||
private static void restore(VAOManager m, VAOManager.ClientArrays saved) {
|
||||
if (m.isSavedVboLive(saved)) m.restoreClientArrays(saved);
|
||||
if (m.boundEBO != saved.boundEBO && m.isSavedEboLive(saved)) m.onBindEBO(saved.boundEBO);
|
||||
}
|
||||
|
||||
private static String where(int op, String kind, int id, int slot) {
|
||||
return "op " + op + ": " + kind + " " + id + " slot " + slot;
|
||||
}
|
||||
|
||||
private void assertSameSlot(int op, String kind, int id, int slot, VAOManager.Attrib p, VAOManager.Attrib o, boolean countDead) {
|
||||
assertEquals(o == null, p == null, () -> where(op, kind, id, slot) + " presence");
|
||||
if (o == null) return;
|
||||
assertEquals(o.vboId, p.vboId, () -> where(op, kind, id, slot) + " vbo");
|
||||
final boolean live = oracle.isBufferLive(o);
|
||||
assertEquals(live, pruned.isBufferLive(p), () -> where(op, kind, id, slot) + " liveness of buffer " + o.vboId);
|
||||
if (countDead && !live && o.vboId != 0) deadSlotChecks++;
|
||||
}
|
||||
|
||||
private void assertSameSlots(int op, String kind, int id, VAOManager.Attrib[] p, VAOManager.Attrib[] o, boolean countDead) {
|
||||
assertEquals(o == null, p == null, () -> where(op, kind, id, -1) + " array presence");
|
||||
if (o == null) return;
|
||||
for (int i = 0; i < o.length; i++) assertSameSlot(op, kind, id, i, p[i], o[i], countDead);
|
||||
}
|
||||
|
||||
private void assertSameLiveness(int op, int sp) {
|
||||
assertEquals(oracle.vaoMap.keySet(), pruned.vaoMap.keySet(), () -> "op " + op + ": vao sets differ");
|
||||
for (var entry : oracle.vaoMap.int2ObjectEntrySet()) {
|
||||
final int vao = entry.getIntKey();
|
||||
final VAOManager.VAOData o = entry.getValue();
|
||||
final VAOManager.VAOData p = pruned.vaoMap.get(vao);
|
||||
final boolean countDead = vao != currentVao;
|
||||
assertSameSlots(op, "vao attribs", vao, p.attribs, o.attribs, countDead);
|
||||
assertSameSlots(op, "vao generic", vao, p.generic, o.generic, countDead);
|
||||
assertSameSlots(op, "vao conventional", vao, p.conventional, o.conventional, countDead);
|
||||
}
|
||||
for (String field : CURRENT_FIELDS) {
|
||||
assertSameSlots(op, field, currentVao, Reflect.get(pruned, field), Reflect.get(oracle, field), false);
|
||||
}
|
||||
for (int s = 0; s < sp; s++) {
|
||||
final VAOManager.ClientArrays p = stacks[0][s];
|
||||
final VAOManager.ClientArrays o = stacks[1][s];
|
||||
final int depth = s;
|
||||
assertEquals(oracle.isSavedVboLive(o), pruned.isSavedVboLive(p), () -> "op " + op + ": saved " + depth + " vbo liveness");
|
||||
assertEquals(oracle.isSavedEboLive(o), pruned.isSavedEboLive(p), () -> "op " + op + ": saved " + depth + " ebo liveness");
|
||||
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
|
||||
if ((o.genericPresent >> i & 1) != 0) assertSameSlot(op, "saved generic", s, i, p.generic[i], o.generic[i], true);
|
||||
}
|
||||
for (int i = 0; i < VAOManager.CONV_COUNT; i++) {
|
||||
if ((o.conventionalPresent >> i & 1) != 0) assertSameSlot(op, "saved conventional", s, i, p.conventional[i], o.conventional[i], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void prunedDeletedNamesAnswerLivenessLikeAnUnprunedReference() {
|
||||
final Random random = new Random(0x5EED);
|
||||
for (VAOManager m : pair) m.init(DEFAULT_VAO);
|
||||
for (VAOManager.ClientArrays[] stack : stacks) {
|
||||
for (int s = 0; s < STACK_DEPTH; s++) stack[s] = new VAOManager.ClientArrays();
|
||||
}
|
||||
final IntArrayList vaoIds = IntArrayList.of(DEFAULT_VAO);
|
||||
final IntArrayList liveBuffers = new IntArrayList();
|
||||
final IntLinkedOpenHashSet freeNames = new IntLinkedOpenHashSet();
|
||||
final IntOpenHashSet everDeleted = new IntOpenHashSet();
|
||||
int sp = 0;
|
||||
int nextVao = 1;
|
||||
int nextBuffer = 1;
|
||||
int prunes = 0;
|
||||
int reusedDeletes = 0;
|
||||
|
||||
for (int op = 0; op < OPS; op++) {
|
||||
final int epochBefore = Reflect.get(pruned, "bufferEpoch");
|
||||
switch (random.nextInt(11)) {
|
||||
case 0, 1 -> {
|
||||
if (liveBuffers.size() >= MAX_LIVE_BUFFERS) break;
|
||||
final int stale = random.nextInt(3) == 0 ? staleName(random, vaoIds, freeNames) : 0;
|
||||
liveBuffers.add(stale != 0 ? stale : !freeNames.isEmpty() && random.nextInt(3) == 0 ? freeNames.removeLastInt() : nextBuffer++);
|
||||
}
|
||||
case 2, 3 -> {
|
||||
if (liveBuffers.isEmpty()) break;
|
||||
final int buffer = liveBuffers.removeInt(random.nextInt(liveBuffers.size()));
|
||||
if (!everDeleted.add(buffer)) reusedDeletes++;
|
||||
for (int m = 0; m < pair.length; m++) {
|
||||
if (pair[m].boundEBO == buffer) pair[m].onBindEBO(0);
|
||||
pair[m].onDeleteBuffer(buffer, stacks[m]);
|
||||
}
|
||||
freeNames.add(buffer);
|
||||
}
|
||||
case 4 -> {
|
||||
final int index = random.nextInt(SLOTS);
|
||||
final int buffer = liveBuffers.isEmpty() || random.nextInt(8) == 0 ? 0 : pick(random, liveBuffers);
|
||||
for (VAOManager m : pair) m.setAttribute(index, 4, GL11.GL_FLOAT, false, 0, 0L, buffer);
|
||||
}
|
||||
case 5 -> {
|
||||
final int conv = CONVS[random.nextInt(CONVS.length)];
|
||||
final int buffer = liveBuffers.isEmpty() || random.nextInt(8) == 0 ? 0 : pick(random, liveBuffers);
|
||||
for (VAOManager m : pair) m.setConventional(conv, 4, GL11.GL_FLOAT, false, 0, 0L, buffer);
|
||||
}
|
||||
case 6 -> {
|
||||
final int vao;
|
||||
if (vaoIds.size() < MAX_VAOS && random.nextBoolean()) {
|
||||
vao = nextVao++;
|
||||
vaoIds.add(vao);
|
||||
} else {
|
||||
vao = pick(random, vaoIds);
|
||||
}
|
||||
bindBoth(vao);
|
||||
}
|
||||
case 7 -> {
|
||||
final int ebo = liveBuffers.isEmpty() || random.nextInt(4) == 0 ? 0 : pick(random, liveBuffers);
|
||||
for (VAOManager m : pair) m.onBindEBO(ebo);
|
||||
}
|
||||
case 8 -> {
|
||||
if (sp == STACK_DEPTH) break;
|
||||
final int vbo = liveBuffers.isEmpty() || random.nextInt(4) == 0 ? 0 : pick(random, liveBuffers);
|
||||
for (int m = 0; m < pair.length; m++) {
|
||||
final VAOManager.ClientArrays saved = stacks[m][sp];
|
||||
saved.boundVAO = currentVao;
|
||||
saved.boundVBO = vbo;
|
||||
saved.boundEBO = pair[m].boundEBO;
|
||||
pair[m].saveClientArrays(saved);
|
||||
}
|
||||
sp++;
|
||||
}
|
||||
case 9 -> {
|
||||
if (sp == 0) break;
|
||||
sp--;
|
||||
final int vao = stacks[1][sp].boundVAO;
|
||||
if (!oracle.vaoMap.containsKey(vao)) break;
|
||||
bindBoth(vao);
|
||||
for (int m = 0; m < pair.length; m++) restore(pair[m], stacks[m][sp]);
|
||||
}
|
||||
case 10 -> {
|
||||
if (vaoIds.size() == 1) break;
|
||||
final int vao = vaoIds.removeInt(1 + random.nextInt(vaoIds.size() - 1));
|
||||
for (VAOManager m : pair) m.onDeleteVertexArray(vao);
|
||||
if (vao == currentVao) {
|
||||
currentVao = NO_VAO;
|
||||
bindBoth(DEFAULT_VAO);
|
||||
}
|
||||
}
|
||||
}
|
||||
final int epoch = Reflect.get(pruned, "bufferEpoch");
|
||||
final int at = op;
|
||||
assertTrue(epoch < PRUNE_INTERVAL, () -> "op " + at + ": epoch " + epoch + " escaped the prune interval");
|
||||
if (epoch < epochBefore) prunes++;
|
||||
assertSameLiveness(op, sp);
|
||||
}
|
||||
assertTrue(prunes >= 800, "op stream pruned only " + prunes + " times");
|
||||
assertTrue(deadSlotChecks >= 400_000, "op stream compared only " + deadSlotChecks + " dead slots");
|
||||
assertTrue(reusedDeletes >= 1_000, "op stream deleted only " + reusedDeletes + " reused names");
|
||||
}
|
||||
}
|
||||
+4
-2
@@ -13,6 +13,7 @@ import com.gtnewhorizons.angelica.glsm.recording.support.DisplayListTestFixture;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
@@ -112,7 +113,7 @@ class IndexedDrawReplayTest extends DisplayListTestFixture {
|
||||
}
|
||||
|
||||
@Test
|
||||
void execute_doesNotRestoreVAO_backToBackDraws() {
|
||||
void execute_restoresRecordedVao_backToBackDraws() {
|
||||
final int vbo = defaultQuadVbo();
|
||||
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
|
||||
final int vao = setupPosColorVao(vbo);
|
||||
@@ -137,7 +138,8 @@ class IndexedDrawReplayTest extends DisplayListTestFixture {
|
||||
assertSame(batchOf(cmds.getFirst()), batchOf(cmds.get(1)));
|
||||
final int sharedVAO = vaoOf(batchOf(cmds.getFirst()));
|
||||
assertNotEquals(0, sharedVAO);
|
||||
assertEquals(sharedVAO, GLSMVertexInput.getBoundVAO());
|
||||
assertEquals(vao, GLSMVertexInput.getBoundVAO());
|
||||
assertEquals(vao, GL11.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING));
|
||||
GLSMVertexInput.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
|
||||
+33
@@ -277,6 +277,39 @@ class SpirvShaderTranslatorTest {
|
||||
assertFalse(es.contains("!= 0u"), "no bool use-site should still be comparing against 0u");
|
||||
}
|
||||
|
||||
@Test
|
||||
void uniformBlock_staysABlockUnderGLES() {
|
||||
final String src = """
|
||||
#version 330 core
|
||||
layout(std140) uniform Blk {
|
||||
mat4 u_M;
|
||||
vec4 u_C;
|
||||
};
|
||||
layout(std140) uniform Named {
|
||||
vec4 u_N;
|
||||
} named;
|
||||
uniform float u_Loose;
|
||||
struct L { vec4 c; };
|
||||
uniform L u_Light;
|
||||
in vec4 color;
|
||||
out vec4 fragColor;
|
||||
void main() {
|
||||
fragColor = u_M * color * u_C * u_Loose + u_Light.c + named.u_N;
|
||||
}
|
||||
""";
|
||||
final String es = SpirvShaderTranslator.glslToGlslEs(src, GL20.GL_FRAGMENT_SHADER, "uniform_block.frag");
|
||||
dumpOnFailure("uniform_block.frag", src, es);
|
||||
|
||||
assertNotNull(es);
|
||||
assertTrue(Pattern.compile("layout\\s*\\(\\s*std140\\s*\\)\\s*uniform\\s+Blk\\s*\\{").matcher(es).find(), "a source uniform block must reach GLSL ES as a block, so glUniformBlockBinding finds it by name\n\n" + es);
|
||||
assertFalse(Pattern.compile("uniform\\s+Blk\\s+_").matcher(es).find(), "the block must not be left as a plain struct uniform\n\n" + es);
|
||||
assertFalse(Pattern.compile("_[0-9]+\\.u_M").matcher(es).find(), "an unnamed source block's members must be accessed bare\n\n" + es);
|
||||
assertTrue(Pattern.compile("layout\\s*\\(\\s*std140\\s*\\)\\s*uniform\\s+Named\\s*\\{[^}]*\\}\\s*named\\s*;").matcher(es).find(), "an instance-named block keeps its instance name\n\n" + es);
|
||||
assertTrue(es.contains("named.u_N"), "members of an instance-named block stay qualified\n\n" + es);
|
||||
assertContainsDecl(es, "float", "u_Loose");
|
||||
assertTrue(Pattern.compile("uniform\\s+L\\s+u_Light\\s*;").matcher(es).find(), "a struct uniform is not a block and must stay a plain uniform\n\n" + es);
|
||||
}
|
||||
|
||||
@Test
|
||||
void readonlyImage3D_staysAnImageUnderGLES() {
|
||||
final String src = """
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.glsm;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
@@ -22,6 +23,35 @@ public final class GlTestSupport {
|
||||
while (GL11.glGetError() != GL11.GL_NO_ERROR) {}
|
||||
}
|
||||
|
||||
public static void fillQuadrants(int width, int height) {
|
||||
GLSMRasterizer.glViewport(0, 0, width, height);
|
||||
GLSMEnable.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
clearRect(0, 0, width / 2, height / 2, 1f, 0f, 0f);
|
||||
clearRect(width / 2, 0, width / 2, height / 2, 0f, 1f, 0f);
|
||||
clearRect(0, height / 2, width / 2, height / 2, 0f, 0f, 1f);
|
||||
clearRect(width / 2, height / 2, width / 2, height / 2, 1f, 1f, 1f);
|
||||
GLSMEnable.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
public static void assertQuadrantsBottomUp(long rgba, int width, int height) {
|
||||
assertRgba(rgba, width, width / 4, height / 4, 0xFF0000FF, "bottom-left");
|
||||
assertRgba(rgba, width, width * 3 / 4, height / 4, 0x00FF00FF, "bottom-right");
|
||||
assertRgba(rgba, width, width / 4, height * 3 / 4, 0x0000FFFF, "top-left");
|
||||
assertRgba(rgba, width, width * 3 / 4, height * 3 / 4, 0xFFFFFFFF, "top-right");
|
||||
}
|
||||
|
||||
private static void clearRect(int x, int y, int width, int height, float r, float g, float b) {
|
||||
GLSMRasterizer.glScissor(x, y, width, height);
|
||||
GLSMBlend.glClearColor(r, g, b, 1f);
|
||||
GLSMFramebuffer.glClear(GL11.GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
private static void assertRgba(long rgba, int width, int x, int y, int expected, String where) {
|
||||
final long at = rgba + ((long) y * width + x) * 4;
|
||||
final int actual = (MemoryUtilities.memGetByte(at) & 0xFF) << 24 | (MemoryUtilities.memGetByte(at + 1) & 0xFF) << 16 | (MemoryUtilities.memGetByte(at + 2) & 0xFF) << 8 | (MemoryUtilities.memGetByte(at + 3) & 0xFF);
|
||||
assertEquals(Integer.toHexString(expected), Integer.toHexString(actual), where + " quadrant at row " + y + " (rows bottom-up)");
|
||||
}
|
||||
|
||||
public static int compile(int type, String source) {
|
||||
final int shader = GLSMPrograms.glCreateShader(type);
|
||||
GLSMPrograms.glShaderSource(shader, source);
|
||||
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
package com.gtnewhorizons.angelica.glsm.ffp;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMBlend;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMMatrices;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMPrograms;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMTextures;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
public final class WeatherParityFixture {
|
||||
|
||||
public record Spec(float x, float z, float bottom, float top, float uBase, float uDrift, float vBase, float vDrift,
|
||||
int brightness, int scrollOffset, float scrollScale, float source,
|
||||
float r, float g, float b, float a, int period, float uDriftScale, float vDriftScale, float fadeScale) {}
|
||||
|
||||
private static final int TICK = 2650;
|
||||
private static final float PARTIAL_TICKS = 0.375f;
|
||||
private static final float TRANSLATE_X = 0.37f;
|
||||
private static final float TRANSLATE_Y = -62.25f;
|
||||
private static final float TRANSLATE_Z = -0.61f;
|
||||
private static final float INV_RADIUS = 1.0f / 6.0f;
|
||||
private static final float CAMERA_FRAC_X = 0.3f;
|
||||
private static final float CAMERA_FRAC_Z = 0.7f;
|
||||
private static final float STRENGTH = 0.8f;
|
||||
private static final int PROVIDER_TICK = 1777;
|
||||
private static final float PROVIDER_INV_RADIUS = 1.0f / 9.0f;
|
||||
|
||||
private static final float[] CORNER_X = { 0.0f, 1.0f, 1.0f, 0.0f };
|
||||
private static final float[] CORNER_Y = { 0.0f, 0.0f, 1.0f, 1.0f };
|
||||
|
||||
private static final int REF_STRIDE = 44;
|
||||
|
||||
private static int referenceVao;
|
||||
private static int referenceVbo;
|
||||
private static int instanceVbo;
|
||||
private static int weatherTexture;
|
||||
private static int lightmapTexture;
|
||||
|
||||
private WeatherParityFixture() {}
|
||||
|
||||
private static Spec rain(float x, float z, float bottom, float top, float j0, int hash, int brightness) {
|
||||
return new Spec(x, z, bottom, top, 0f, 0f, 0f, 0f, brightness, hash, WeatherInstancedAttribs.RAIN_SCROLL_BASE + j0, WeatherInstancedAttribs.SOURCE_VANILLA,
|
||||
1f, 1f, 1f, 1f, WeatherInstancedAttribs.RAIN_PERIOD, 0f, 0f, WeatherInstancedAttribs.RAIN_FADE);
|
||||
}
|
||||
|
||||
private static Spec snow(float x, float z, float bottom, float top, float j0, float j1, float j2, float j3, int brightness) {
|
||||
return new Spec(x, z, bottom, top, j0, j1, j2, j3, brightness, 0, 1f, WeatherInstancedAttribs.SOURCE_VANILLA,
|
||||
1f, 1f, 1f, 1f, WeatherInstancedAttribs.SNOW_PERIOD, WeatherInstancedAttribs.SNOW_U_DRIFT, WeatherInstancedAttribs.SNOW_V_DRIFT,
|
||||
WeatherInstancedAttribs.SNOW_FADE);
|
||||
}
|
||||
|
||||
private static Spec provider(float x, float z, float bottom, float top, float uBase, float uDrift, float vBase, float vDrift,
|
||||
int brightness, int scrollOffset, float scrollScale, float r, float g, float b, float a,
|
||||
int period, float uDriftScale, float vDriftScale, float fadeScale) {
|
||||
return new Spec(x, z, bottom, top, uBase, uDrift, vBase, vDrift, brightness, scrollOffset, scrollScale, WeatherInstancedAttribs.SOURCE_PROVIDER, r, g, b, a,
|
||||
period, uDriftScale, vDriftScale, fadeScale);
|
||||
}
|
||||
|
||||
public static Spec[] specs() {
|
||||
return new Spec[] {
|
||||
rain(-4f, -3f, 58f, 66f, 0.31f, 5, 0x00F00030),
|
||||
rain(2f, -4f, 60f, 67f, 0.77f, 19, 0x00A000D0),
|
||||
rain(-2f, 3f, 57f, 65f, 0.05f, 62, 0x0060F0A0),
|
||||
rain(4f, 2f, 59f, 64f, 0.58f, 11, 0x00D01090),
|
||||
rain(1f, 4f, 56f, 66f, 0.93f, 24, 0x0080C0F0),
|
||||
snow(-5f, 1f, 58f, 66f, 0.12f, 0.47f, 0.83f, 0.29f, 0x00E02070),
|
||||
snow(3f, -2f, 60f, 68f, 0.66f, 0.91f, 0.18f, 0.74f, 0x00B0B0B0),
|
||||
snow(-1f, -5f, 57f, 63f, 0.39f, 0.25f, 0.52f, 0.96f, 0x0040E0C0),
|
||||
snow(5f, 4f, 59f, 67f, 0.84f, 0.63f, 0.07f, 0.41f, 0x00F0F0F0),
|
||||
snow(-3f, 5f, 55f, 65f, 0.22f, 0.78f, 0.35f, 0.58f, 0x00109050),
|
||||
provider(-5f, -4f, 58f, 66f, 0.14f, 0.52f, 0.71f, 0.33f, 0x00C06040, 0, 1f, 1f, 1f, 1f, 1f, 512, 0.03f, 0.001f, 0.3f),
|
||||
provider(4f, -3f, 59f, 67f, 0.61f, 0.27f, 0.09f, 0.88f, 0x00F020B0, 0, 1f, 1f, 1f, 1f, 1f, 512, 0.03f, 0.001f, 0.3f),
|
||||
provider(-4f, 2f, 57f, 65f, 0.42f, 0.36f, 0.64f, 0.19f, 0x0090D070, 0, 0f, 0.12f, 0.27f, 0.18f, 2.0f, 512, 0.03f, 0.003f, 0f),
|
||||
provider(2f, 5f, 60f, 66f, 0.83f, 0.74f, 0.25f, 0.57f, 0x0050A0E0, 0, 0f, 0.29f, 0.14f, 0.21f, 2.0f, 512, 0.03f, 0.003f, 0f),
|
||||
provider(-2f, -5f, 56f, 64f, 0.07f, 0.45f, 0.93f, 0.62f, 0x00B0F040, 0, -1f, 0.83f, 0.92f, 0.88f, 1f, 1024, 0.001f, 0.001f, 0.3f),
|
||||
provider(5f, 1f, 58f, 68f, 0.55f, 0.16f, 0.38f, 0.81f, 0x007070F0, 0, -1f, 0.97f, 0.86f, 0.81f, 1f, 1024, 0.001f, 0.001f, 0.3f),
|
||||
provider(-5f, 4f, 59f, 66f, 0f, 0.68f, 0f, 0.23f, 0x00E0E050, 0, -1f, 1f, 1f, 1f, 0.35f, 512, 0f, 0.01f, 0.3f),
|
||||
provider(3f, 3f, 57f, 67f, 0f, 0.49f, 0f, 0.91f, 0x0030C0A0, 0, -1f, 1f, 1f, 1f, 0.82f, 512, 0f, 0.01f, 0.3f),
|
||||
provider(0f, -4f, 58f, 65f, 0.26f, 0.12f, 0.67f, 0.44f, 0x00A0A0A0, 21, 3.46f, 1f, 1f, 1f, 1f, 32, 0f, 0f, 0.3f),
|
||||
provider(1f, 3f, 60f, 66f, 0.89f, 0.31f, 0.15f, 0.76f, 0x00F0A040, 7, 3.81f, 1f, 1f, 1f, 1f, 32, 0f, 0f, 0.3f),
|
||||
provider(-3f, 0f, 56f, 67f, 0.33f, 0.85f, 0.48f, 0.13f, 0x00D0D0D0, 0, 1f, 1f, 1f, 1f, 0.24f, 512, 0.01f, 0.01f, 0.3f),
|
||||
provider(0f, 5f, 58f, 64f, 0.72f, 0.04f, 0.59f, 0.97f, 0x0010F0F0, 0, 1f, 1f, 1f, 1f, 0.67f, 512, 0.01f, 0.01f, 0.3f),
|
||||
};
|
||||
}
|
||||
|
||||
private static float mod(float x, float y) {
|
||||
return x - y * (float) Math.floor(x / y);
|
||||
}
|
||||
|
||||
public static void setupScene() {
|
||||
GLSMEnable.glDisable(GL11.GL_FOG);
|
||||
GLSMEnable.glDisable(GL11.GL_ALPHA_TEST);
|
||||
GLSMEnable.glDisable(GL11.GL_DEPTH_TEST);
|
||||
GLSMEnable.glDisable(GL11.GL_CULL_FACE);
|
||||
GLSMEnable.glDisable(GL11.GL_LIGHTING);
|
||||
GLSMEnable.glEnable(GL11.GL_BLEND);
|
||||
GLSMBlend.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
GLSMMatrices.glMatrixMode(GL11.GL_PROJECTION);
|
||||
GLSMMatrices.glLoadIdentity();
|
||||
GLSMMatrices.glOrtho(-8.0, 8.0, -8.0, 8.0, -64.0, 64.0);
|
||||
GLSMMatrices.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
GLSMMatrices.glLoadIdentity();
|
||||
GLSMMatrices.glRotatef(30.0f, 1.0f, 0.0f, 0.0f);
|
||||
GLSMMatrices.glRotatef(35.0f, 0.0f, 1.0f, 0.0f);
|
||||
|
||||
lightmapTexture = createTexture(GL13.GL_TEXTURE1, 16, GL11.GL_LINEAR, GL11.GL_CLAMP);
|
||||
GLSMMatrices.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLSMMatrices.glLoadIdentity();
|
||||
GLSMMatrices.glScalef(0.00390625f, 0.00390625f, 0.00390625f);
|
||||
GLSMMatrices.glTranslatef(8.0f, 8.0f, 8.0f);
|
||||
GLSMMatrices.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
|
||||
weatherTexture = createTexture(GL13.GL_TEXTURE0, 4, GL11.GL_NEAREST, GL11.GL_REPEAT);
|
||||
GLSMMatrices.glMatrixMode(GL11.GL_TEXTURE);
|
||||
GLSMMatrices.glLoadIdentity();
|
||||
GLSMMatrices.glMatrixMode(GL11.GL_MODELVIEW);
|
||||
}
|
||||
|
||||
private static int createTexture(int unit, int size, int filter, int wrap) {
|
||||
final int id = GLSMTextures.glGenTextures();
|
||||
GLSMTextures.glActiveTexture(unit);
|
||||
GLSMTextures.glBindTexture(GL11.GL_TEXTURE_2D, id);
|
||||
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filter);
|
||||
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, filter);
|
||||
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, wrap);
|
||||
GLSMTextures.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, wrap);
|
||||
final ByteBuffer texels = BufferUtils.createByteBuffer(size * size * 4);
|
||||
for (int y = 0; y < size; y++) {
|
||||
for (int x = 0; x < size; x++) {
|
||||
texels.put((byte) (40 + x * (200 / size)));
|
||||
texels.put((byte) (40 + y * (200 / size)));
|
||||
texels.put((byte) (255 - (x + y) * (100 / size)));
|
||||
texels.put((byte) 255);
|
||||
}
|
||||
}
|
||||
texels.flip();
|
||||
GLSMTextures.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, size, size, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, texels);
|
||||
GLSMEnable.glEnable(GL11.GL_TEXTURE_2D);
|
||||
return id;
|
||||
}
|
||||
|
||||
private static void putVertex(ByteBuffer data, float px, float py, float pz, float r, float g, float b, float a,
|
||||
float u, float v, float lmX, float lmY) {
|
||||
final int base = data.position();
|
||||
data.putFloat(base, px);
|
||||
data.putFloat(base + 4, py);
|
||||
data.putFloat(base + 8, pz);
|
||||
data.putFloat(base + 12, r);
|
||||
data.putFloat(base + 16, g);
|
||||
data.putFloat(base + 20, b);
|
||||
data.putFloat(base + 24, a);
|
||||
data.putFloat(base + 28, u);
|
||||
data.putFloat(base + 32, v);
|
||||
data.putFloat(base + 36, lmX);
|
||||
data.putFloat(base + 40, lmY);
|
||||
data.position(base + REF_STRIDE);
|
||||
}
|
||||
|
||||
private static float tessellatorByte(float channel) {
|
||||
final int value = (int) (channel * 255.0F);
|
||||
return Math.max(0, Math.min(255, value)) / 255.0f;
|
||||
}
|
||||
|
||||
private static void addColumn(ByteBuffer data, Spec s) {
|
||||
final boolean provider = s.source() == WeatherInstancedAttribs.SOURCE_PROVIDER;
|
||||
final int tick = provider ? PROVIDER_TICK : TICK;
|
||||
final float age = tick + PARTIAL_TICKS;
|
||||
final float invRadius = provider ? PROVIDER_INV_RADIUS : INV_RADIUS;
|
||||
final float strength = provider ? 1.0f : STRENGTH;
|
||||
|
||||
final float halfScale = 0.5f / (float) Math.sqrt(s.x() * s.x() + s.z() * s.z());
|
||||
final float halfX = -s.z() * halfScale;
|
||||
final float halfZ = s.x() * halfScale;
|
||||
|
||||
final float dx = s.x() + 0.5f - CAMERA_FRAC_X;
|
||||
final float dz = s.z() + 0.5f - CAMERA_FRAC_Z;
|
||||
final float distance = (float) Math.sqrt(dx * dx + dz * dz) * invRadius;
|
||||
final float fade = 1.0f - distance * distance;
|
||||
|
||||
final float scroll = (mod((float) tick + s.scrollOffset(), s.period()) + PARTIAL_TICKS) / s.period() * s.scrollScale();
|
||||
final float driftU = s.uBase() + age * s.uDriftScale() * s.uDrift();
|
||||
final float driftV = s.vBase() + age * s.vDriftScale() * s.vDrift();
|
||||
final float alpha = fade * s.fadeScale() + 0.5f;
|
||||
|
||||
final float r = tessellatorByte(s.r());
|
||||
final float g = tessellatorByte(s.g());
|
||||
final float b = tessellatorByte(s.b());
|
||||
final float a = tessellatorByte(alpha * s.a() * strength);
|
||||
final float lmX = s.brightness() & 0xFFFF;
|
||||
final float lmY = s.brightness() >> 16 & 0xFFFF;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
final float side = CORNER_X[i] * 2.0f - 1.0f;
|
||||
final float cornerX = s.x() + 0.5f + side * halfX;
|
||||
final float cornerZ = s.z() + 0.5f + side * halfZ;
|
||||
final float cornerY = s.bottom() * (1.0f - CORNER_Y[i]) + s.top() * CORNER_Y[i];
|
||||
putVertex(data, cornerX + TRANSLATE_X, cornerY + TRANSLATE_Y, cornerZ + TRANSLATE_Z, r, g, b, a,
|
||||
CORNER_X[i] + driftU, cornerY * 0.25f + scroll + driftV, lmX, lmY);
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuffer buildReferenceData(Spec[] specs) {
|
||||
final ByteBuffer data = BufferUtils.createByteBuffer(specs.length * 4 * REF_STRIDE);
|
||||
for (Spec s : specs) {
|
||||
addColumn(data, s);
|
||||
}
|
||||
data.flip();
|
||||
return data;
|
||||
}
|
||||
|
||||
public static void drawReference(Spec[] specs) {
|
||||
final ByteBuffer data = buildReferenceData(specs);
|
||||
if (referenceVao == 0) referenceVao = GLSMVertexInput.glGenVertexArrays();
|
||||
GLSMVertexInput.glBindVertexArray(referenceVao);
|
||||
if (referenceVbo == 0) referenceVbo = GLSMBuffers.glGenBuffers();
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, referenceVbo);
|
||||
GLSMBuffers.glBufferData(GL15.GL_ARRAY_BUFFER, data, GL15.GL_STREAM_DRAW);
|
||||
FfpFixture.attrib(0, 3, GL11.GL_FLOAT, false, REF_STRIDE, 0);
|
||||
FfpFixture.attrib(1, 4, GL11.GL_FLOAT, false, REF_STRIDE, 12);
|
||||
FfpFixture.attrib(2, 2, GL11.GL_FLOAT, false, REF_STRIDE, 28);
|
||||
FfpFixture.attrib(3, 2, GL11.GL_FLOAT, false, REF_STRIDE, 36);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
VAOManager.setCurrentVertexFlags(VertexFlags.COLOR_BIT | VertexFlags.TEXTURE_BIT | VertexFlags.BRIGHTNESS_BIT);
|
||||
|
||||
GLSMDraws.glDrawArrays(GL11.GL_QUADS, 0, specs.length * 4);
|
||||
GLSMVertexInput.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
public static void drawInstanced(Spec[] specs) {
|
||||
final ByteBuffer records = BufferUtils.createByteBuffer(specs.length * WeatherInstancedAttribs.STRIDE);
|
||||
for (int i = 0; i < specs.length; i++) {
|
||||
final Spec s = specs[i];
|
||||
WeatherInstancedAttribs.putColumn(records, i, s.x(), s.z(), s.bottom(), s.top(), s.uBase(), s.uDrift(), s.vBase(), s.vDrift(),
|
||||
s.brightness(), s.scrollOffset(), s.scrollScale(), s.source(), s.r(), s.g(), s.b(), s.a(),
|
||||
s.period(), s.uDriftScale(), s.vDriftScale(), s.fadeScale());
|
||||
}
|
||||
|
||||
WeatherParams.set(TRANSLATE_X, TRANSLATE_Y, TRANSLATE_Z, INV_RADIUS, CAMERA_FRAC_X, CAMERA_FRAC_Z,
|
||||
PARTIAL_TICKS, TICK + PARTIAL_TICKS, TICK, STRENGTH, PROVIDER_TICK, PROVIDER_TICK + PARTIAL_TICKS, PROVIDER_INV_RADIUS);
|
||||
|
||||
GLSMVertexInput.glBindVertexArray(WeatherQuadMesh.vao());
|
||||
if (instanceVbo == 0) instanceVbo = GLSMBuffers.glGenBuffers();
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, instanceVbo);
|
||||
GLSMBuffers.glBufferData(GL15.GL_ARRAY_BUFFER, records, GL15.GL_STREAM_DRAW);
|
||||
WeatherInstancedAttribs.pointInstanceAttribs(0L);
|
||||
GLSMBuffers.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
|
||||
VAOManager.setCurrentVertexFlags(WeatherQuadMesh.VERTEX_FLAGS);
|
||||
GLSMPrograms.setFfpInstancing(Instancing.WEATHER);
|
||||
GLSMDraws.glDrawArraysInstanced(GL11.GL_TRIANGLE_FAN, 0, WeatherQuadMesh.VERTEX_COUNT, specs.length);
|
||||
GLSMPrograms.setFfpInstancing(Instancing.NONE);
|
||||
GLSMVertexInput.glBindVertexArray(0);
|
||||
}
|
||||
|
||||
public static void assertPixelParity(int[] reference, int[] instanced, int size, int background, IntFunction<String> describe) {
|
||||
FfpFixture.assertPixelParity("instanced weather columns", reference, instanced, size, background, 16, 0, describe);
|
||||
}
|
||||
|
||||
public static void deleteResources() {
|
||||
if (instanceVbo != 0) {
|
||||
GLSMBuffers.glDeleteBuffers(instanceVbo);
|
||||
instanceVbo = 0;
|
||||
}
|
||||
if (referenceVbo != 0) {
|
||||
GLSMBuffers.glDeleteBuffers(referenceVbo);
|
||||
referenceVbo = 0;
|
||||
}
|
||||
if (referenceVao != 0) {
|
||||
GLSMVertexInput.glDeleteVertexArrays(referenceVao);
|
||||
referenceVao = 0;
|
||||
}
|
||||
if (weatherTexture != 0) {
|
||||
GLSMTextures.glDeleteTextures(weatherTexture);
|
||||
weatherTexture = 0;
|
||||
}
|
||||
if (lightmapTexture != 0) {
|
||||
GLSMTextures.glDeleteTextures(lightmapTexture);
|
||||
lightmapTexture = 0;
|
||||
}
|
||||
WeatherQuadMesh.delete();
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.gtnewhorizons.angelica.glsm.testutil;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import java.nio.DoubleBuffer;
|
||||
|
||||
public final class ClipTestSupport {
|
||||
|
||||
private ClipTestSupport() {}
|
||||
|
||||
public static void setPlane(int index, double a, double b, double c, double d) {
|
||||
final DoubleBuffer eq = BufferUtils.createDoubleBuffer(4);
|
||||
eq.put(a).put(b).put(c).put(d).flip();
|
||||
GLSMRasterizer.glClipPlane(GL11.GL_CLIP_PLANE0 + index, eq);
|
||||
}
|
||||
|
||||
public static Emulation emulation(boolean emulated) {
|
||||
return new Emulation(emulated);
|
||||
}
|
||||
|
||||
public static final class Emulation implements AutoCloseable {
|
||||
private final boolean previous;
|
||||
|
||||
private Emulation(boolean emulated) {
|
||||
previous = Reflect.getStatic(RenderSystem.class, "clipDistanceEmulated");
|
||||
Reflect.setStatic(RenderSystem.class, "clipDistanceEmulated", emulated);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
Reflect.setStatic(RenderSystem.class, "clipDistanceEmulated", previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configur
|
||||
for (name in forwardedRunProperties) {
|
||||
jvmArgs("-D$name=${System.getProperty(name)}")
|
||||
}
|
||||
jvmArgs("-Dangelica.buildVersion=${project.version}")
|
||||
}
|
||||
|
||||
val isMacOs = org.gradle.internal.os.OperatingSystem.current().isMacOsX
|
||||
|
||||
@@ -12,7 +12,7 @@ lwjgl3 = "3.4.2"
|
||||
lwjgl3Release = "3.4.2"
|
||||
lwjgl3ify = "3.0.31"
|
||||
|
||||
celeritas = "2.5.13-GTNH"
|
||||
celeritas = "2.5.14-GTNH"
|
||||
gtnhlib = "0.11.36"
|
||||
eventbus = "7.0-beta.15"
|
||||
glslTransformLib = "0.2.0-33.g3ec381e"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import java.time.Duration
|
||||
|
||||
plugins {
|
||||
`java-library`
|
||||
`maven-publish`
|
||||
@@ -75,6 +77,80 @@ dependencies {
|
||||
compileOnly(libs.annotations)
|
||||
}
|
||||
|
||||
val glesTag = "gles"
|
||||
|
||||
val glsmRedirectAgent: Configuration by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
val glsmLwjgl2Stubs: Configuration by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
glsmRedirectAgent(project(":glsm", "redirectAgent"))
|
||||
glsmLwjgl2Stubs(project(":glsm", "lwjgl2Stubs"))
|
||||
|
||||
testImplementation(platform(libs.junit.bom))
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testImplementation(testFixtures(project(":glsm")))
|
||||
testImplementation(project(":glsm")) {
|
||||
capabilities { requireCapability("${project.group}:glsm-stubs") }
|
||||
}
|
||||
listOf(libs.lwjgl3.sdl, libs.lwjgl3.shaderc, libs.lwjgl3.spvc).forEach {
|
||||
testImplementation(it)
|
||||
testRuntimeOnly(it) { artifact { classifier = lwjglNatives } }
|
||||
}
|
||||
testRuntimeOnly(libs.junit.platform.launcher)
|
||||
testRuntimeOnly(libs.log4j.core)
|
||||
testRuntimeOnly(libs.fastutil)
|
||||
testRuntimeOnly(libs.joml)
|
||||
testRuntimeOnly(libs.commons.lang3)
|
||||
testRuntimeOnly(libs.guava)
|
||||
testRuntimeOnly(libs.celeritas.common) { isTransitive = false }
|
||||
testRuntimeOnly(libs.asm.tree.test)
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform {
|
||||
excludeTags(glesTag)
|
||||
}
|
||||
}
|
||||
|
||||
val unpackLwjgl3Natives = registerLwjgl3NativesUnpack()
|
||||
|
||||
val glesTest by tasks.registering(Test::class) {
|
||||
description = "Boots GLSM on the LWJGL3 GL backend in a real OpenGL ES 3.2 context."
|
||||
group = "verification"
|
||||
testClassesDirs = sourceSets["test"].output.classesDirs
|
||||
classpath = glsmLwjgl2Stubs.plus(
|
||||
sourceSets["test"].runtimeClasspath.filter { file ->
|
||||
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
|
||||
}
|
||||
)
|
||||
useJUnitPlatform {
|
||||
includeTags(glesTag)
|
||||
}
|
||||
systemProperty("glsm.stubs.capabilities", "lwjgl3")
|
||||
javaAgent(glsmRedirectAgent)
|
||||
loadLwjgl3NativesFrom(unpackLwjgl3Natives)
|
||||
outputs.cacheIf { false }
|
||||
timeout = Duration.ofMinutes(5)
|
||||
systemProperty("junit.jupiter.execution.timeout.default", "60s")
|
||||
systemProperty("junit.jupiter.execution.timeout.threaddump.enabled", "true")
|
||||
testLogging {
|
||||
events("passed", "skipped", "failed")
|
||||
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
|
||||
}
|
||||
mustRunAfter(tasks.test)
|
||||
}
|
||||
|
||||
if (!providers.environmentVariable("CI").isPresent) {
|
||||
tasks.check { dependsOn(glesTest) }
|
||||
}
|
||||
|
||||
tasks.named<JavaCompile>("compileJava") {
|
||||
val classesDir = destinationDirectory
|
||||
doLast { injectLwjgl3Aware(classesDir.get().asFile) }
|
||||
|
||||
+67
-10
@@ -6,8 +6,13 @@ import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.DebugMessageHandler;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.MainThreadPump;
|
||||
import com.gtnewhorizons.retrofuturabootstrap.MainStartOnFirstThread;
|
||||
import com.gtnewhorizons.angelica.glsm.GLScreenCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.ProxyTexture;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
|
||||
import me.eigenraven.lwjgl3ify.client.MainThreadExec;
|
||||
import org.lwjgl.PointerBuffer;
|
||||
@@ -46,6 +51,7 @@ import org.lwjgl.sdl.SDLError;
|
||||
import org.lwjgl.sdl.SDLEvents;
|
||||
import org.lwjgl.sdl.SDLTimer;
|
||||
import org.lwjgl.sdl.SDLVideo;
|
||||
import org.lwjgl.system.FunctionProvider;
|
||||
import org.lwjgl.system.JNI;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
@@ -95,14 +101,45 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|| normalizedVendor.startsWith("advanced micro devices");
|
||||
}
|
||||
}
|
||||
final FunctionProvider functions = GL.getFunctionProvider();
|
||||
if (RenderSystem.isGLES()) {
|
||||
pfnClearDepthf = GL.getFunctionProvider().getFunctionAddress("glClearDepthf");
|
||||
pfnDepthRangef = GL.getFunctionProvider().getFunctionAddress("glDepthRangef");
|
||||
pfnClearDepthf = functions.getFunctionAddress("glClearDepthf");
|
||||
pfnDepthRangef = functions.getFunctionAddress("glDepthRangef");
|
||||
proxyTexture = new ProxyTexture(GL11C.glGetInteger(GL11C.GL_MAX_TEXTURE_SIZE), GL11C.glGetInteger(GL12C.GL_MAX_3D_TEXTURE_SIZE),
|
||||
GL11C.glGetInteger(GL13C.GL_MAX_CUBE_MAP_TEXTURE_SIZE), GL11C.glGetInteger(GL30C.GL_MAX_ARRAY_TEXTURE_LAYERS));
|
||||
}
|
||||
pfnGenSamplers = requireFunction(functions, "glGenSamplers");
|
||||
pfnDeleteSamplers = requireFunction(functions, "glDeleteSamplers");
|
||||
pfnBindSampler = requireFunction(functions, "glBindSampler");
|
||||
pfnSamplerParameteri = requireFunction(functions, "glSamplerParameteri");
|
||||
pfnSamplerParameterf = requireFunction(functions, "glSamplerParameterf");
|
||||
pfnIsSampler = requireFunction(functions, "glIsSampler");
|
||||
pfnVertexAttribDivisor = requireFunction(functions, "glVertexAttribDivisor");
|
||||
}
|
||||
|
||||
private static long requireFunction(FunctionProvider functions, String name) {
|
||||
final long address = functions.getFunctionAddress(name);
|
||||
if (address == 0L) throw new IllegalStateException(name + " is not exported by the GL driver");
|
||||
return address;
|
||||
}
|
||||
|
||||
private long pfnClearDepthf;
|
||||
private long pfnDepthRangef;
|
||||
private @Nullable ProxyTexture proxyTexture;
|
||||
// GL33C slots stay unloaded on an ES 3.2 context: LWJGL builds desktop caps only up to the reported 3.2.
|
||||
private long pfnGenSamplers;
|
||||
private long pfnDeleteSamplers;
|
||||
private long pfnBindSampler;
|
||||
private long pfnSamplerParameteri;
|
||||
private long pfnSamplerParameterf;
|
||||
private long pfnIsSampler;
|
||||
private long pfnVertexAttribDivisor;
|
||||
|
||||
private boolean recordProxy(int target, int level, int internalFormat, int width, int height, int depth) {
|
||||
if (proxyTexture == null || !TextureInfoCache.isProxyTarget(target)) return false;
|
||||
proxyTexture.record(target, level, internalFormat, width, height, depth, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
@@ -251,7 +288,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
public void finish() {GL11C.glFinish();}
|
||||
|
||||
private static boolean capAllowed(int cap) {
|
||||
return GLESCaps.isCapAllowed(cap, RenderSystem.isGLES(), RenderSystem.hasClipCullDistance());
|
||||
return GLESCaps.isCapAllowed(cap, RenderSystem.isGLES());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -563,26 +600,31 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
|
||||
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
|
||||
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, DoubleBuffer pixels) {
|
||||
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
|
||||
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, FloatBuffer pixels) {
|
||||
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
|
||||
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, IntBuffer pixels) {
|
||||
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
|
||||
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, long pixelBufferOffset) {
|
||||
if (recordProxy(target, level, internalFormat, width, height, 1)) return;
|
||||
GL11C.glTexImage2D(target, level, internalFormat, width, height, border, format, type, pixelBufferOffset);
|
||||
}
|
||||
|
||||
@@ -603,16 +645,19 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
public void texImage1D(int target, int level, int internalFormat, int width, int border, int format, int type, ByteBuffer pixels) {
|
||||
if (recordProxy(target, level, internalFormat, width, 1, 1)) return;
|
||||
GL11C.glTexImage1D(target, level, internalFormat, width, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
|
||||
if (recordProxy(target, level, internalFormat, width, height, depth)) return;
|
||||
GL12C.glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void texImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, IntBuffer pixels) {
|
||||
if (recordProxy(target, level, internalFormat, width, height, depth)) return;
|
||||
GL12C.glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, pixels);
|
||||
}
|
||||
|
||||
@@ -684,6 +729,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
public int getTexLevelParameteri(int target, int level, int pname) {
|
||||
if (proxyTexture != null && TextureInfoCache.isProxyTarget(target)) return proxyTexture.levelParameter(target, level, pname);
|
||||
return GL11C.glGetTexLevelParameteri(target, level, pname);
|
||||
}
|
||||
|
||||
@@ -704,27 +750,33 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
public int genSamplers() {
|
||||
return GL33C.glGenSamplers();
|
||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||
final IntBuffer ids = stack.mallocInt(1);
|
||||
JNI.callPV(1, MemoryUtil.memAddress(ids), pfnGenSamplers);
|
||||
return ids.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSamplers(int sampler) {
|
||||
GL33C.glDeleteSamplers(sampler);
|
||||
try (MemoryStack stack = MemoryStack.stackPush()) {
|
||||
JNI.callPV(1, MemoryUtil.memAddress(stack.ints(sampler)), pfnDeleteSamplers);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindSampler(int unit, int sampler) {
|
||||
GL33C.glBindSampler(unit, sampler);
|
||||
JNI.callV(unit, sampler, pfnBindSampler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void samplerParameteri(int sampler, int pname, int param) {
|
||||
GL33C.glSamplerParameteri(sampler, pname, param);
|
||||
JNI.callV(sampler, pname, param, pfnSamplerParameteri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void samplerParameterf(int sampler, int pname, float param) {
|
||||
GL33C.glSamplerParameterf(sampler, pname, param);
|
||||
JNI.callV(sampler, pname, param, pfnSamplerParameterf);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -792,6 +844,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
GL11C.glReadBuffer(mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScreenCapture createScreenCapture(int width, int height) {
|
||||
return new GLScreenCapture(width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readPixels(int x, int y, int width, int height, int format, int type, ByteBuffer pixels) {
|
||||
GL11C.glReadPixels(x, y, width, height, format, type, pixels);
|
||||
@@ -1364,7 +1421,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override public boolean isFramebuffer(int framebuffer) { return GL30C.glIsFramebuffer(framebuffer); }
|
||||
@Override public boolean isRenderbuffer(int renderbuffer) { return GL30C.glIsRenderbuffer(renderbuffer); }
|
||||
@Override public boolean isSampler(int sampler) { return GL33C.glIsSampler(sampler); }
|
||||
@Override public boolean isSampler(int sampler) { return JNI.callZ(sampler, pfnIsSampler); }
|
||||
@Override public boolean isQuery(int query) { return GL15C.glIsQuery(query); }
|
||||
|
||||
@Override
|
||||
@@ -1434,7 +1491,7 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
|
||||
|
||||
@Override
|
||||
public void vertexAttribDivisor(int index, int divisor) {
|
||||
GL33C.glVertexAttribDivisor(index, divisor);
|
||||
JNI.callV(index, divisor, pfnVertexAttribDivisor);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
package com.gtnewhorizons.angelica.lwjgl3;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMCurrentAttribs;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMBlend;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMEnable;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMFramebuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMRasterizer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMTextures;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.RenderSystem;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.ClipTestSupport;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.opengl.GL11C;
|
||||
import org.lwjgl.opengl.GL12C;
|
||||
import org.lwjgl.opengl.GL20C;
|
||||
import org.lwjgl.opengl.GL30C;
|
||||
import org.lwjgl.opengl.GL33C;
|
||||
import org.lwjgl.sdl.SDLError;
|
||||
import org.lwjgl.sdl.SDLInit;
|
||||
import org.lwjgl.sdl.SDLVideo;
|
||||
import org.lwjgl.system.Configuration;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Tag("gles")
|
||||
@DisabledOnOs(OS.MAC)
|
||||
class GlesSmokeTest {
|
||||
|
||||
private static final int SIZE = 64;
|
||||
private static int fbo;
|
||||
|
||||
@BeforeAll
|
||||
static void boot() {
|
||||
Configuration.OPENGL_EXPLICIT_INIT.set(true);
|
||||
assertTrue(SDLInit.SDL_Init(SDLInit.SDL_INIT_VIDEO), () -> "SDL_Init failed: " + SDLError.SDL_GetError());
|
||||
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_CONTEXT_PROFILE_MASK, SDLVideo.SDL_GL_CONTEXT_PROFILE_ES);
|
||||
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_DEPTH_SIZE, 24);
|
||||
SDLVideo.SDL_GL_SetAttribute(SDLVideo.SDL_GL_STENCIL_SIZE, 8);
|
||||
final long window = SDLVideo.SDL_CreateWindow("glesSmoke", SIZE, SIZE, SDLVideo.SDL_WINDOW_OPENGL | SDLVideo.SDL_WINDOW_HIDDEN);
|
||||
assertNotEquals(0L, window, () -> "SDL_CreateWindow failed: " + SDLError.SDL_GetError());
|
||||
final long context = SDLVideo.SDL_GL_CreateContext(window);
|
||||
assertNotEquals(0L, context, () -> "SDL_GL_CreateContext failed: " + SDLError.SDL_GetError());
|
||||
assertTrue(SDLVideo.SDL_GL_MakeCurrent(window, context), () -> "SDL_GL_MakeCurrent failed: " + SDLError.SDL_GetError());
|
||||
|
||||
GL.create(SDLVideo::SDL_GL_GetProcAddress);
|
||||
GL.createCapabilities();
|
||||
// LWJGL's GL_CONTEXT_PROFILE_MASK probe in createCapabilities leaves INVALID_ENUM on an ES context.
|
||||
while (GL11C.glGetError() != GL11C.GL_NO_ERROR) {}
|
||||
|
||||
final String version = GL11C.glGetString(GL11C.GL_VERSION);
|
||||
assertTrue(version != null && version.startsWith("OpenGL ES 3."), () -> "not an ES 3.x context: " + version);
|
||||
assertTrue(BackendManager.RENDER_BACKEND instanceof Lwjgl3GLRenderBackend, () -> "selected backend is " + BackendManager.RENDER_BACKEND.getName());
|
||||
|
||||
GLStateManager.initialize(GLSMInitConfig.builder().displaySize(SIZE, SIZE).build());
|
||||
GLStateManager.setRunningSplash(false);
|
||||
GLStateManager.markSplashComplete("glesSmoke");
|
||||
|
||||
assertTrue(RenderSystem.isGLES());
|
||||
assertTrue(RenderSystem.supportsSamplerObjects());
|
||||
fbo = createTarget();
|
||||
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
|
||||
}
|
||||
|
||||
@Test
|
||||
void samplerLifecycle() {
|
||||
final int sampler = RenderSystem.genSampler();
|
||||
assertNotEquals(0, sampler);
|
||||
RenderSystem.samplerParameteri(sampler, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_LINEAR);
|
||||
RenderSystem.samplerParameterf(sampler, GL12C.GL_TEXTURE_MAX_LOD, 4.0f);
|
||||
RenderSystem.bindSamplerToUnit(0, sampler);
|
||||
assertEquals(sampler, GL11C.glGetInteger(GL33C.GL_SAMPLER_BINDING));
|
||||
assertTrue(BackendManager.RENDER_BACKEND.isSampler(sampler));
|
||||
|
||||
RenderSystem.destroySampler(sampler);
|
||||
assertFalse(BackendManager.RENDER_BACKEND.isSampler(sampler));
|
||||
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
|
||||
}
|
||||
|
||||
@Test
|
||||
void vertexAttribDivisor() {
|
||||
final int vao = GLSMVertexInput.glGenVertexArrays();
|
||||
GLSMVertexInput.glBindVertexArray(vao);
|
||||
GLSMVertexInput.glVertexAttribDivisor(5, 1);
|
||||
assertEquals(1, GL20C.glGetVertexAttribi(5, GL33C.GL_VERTEX_ATTRIB_ARRAY_DIVISOR));
|
||||
GLSMVertexInput.glBindVertexArray(0);
|
||||
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
|
||||
}
|
||||
|
||||
@Test
|
||||
void ffpQuadRenders() {
|
||||
bindClearedTarget();
|
||||
GLSMDraws.glBegin(GL11C.GL_QUADS);
|
||||
GLSMCurrentAttribs.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
GLSMDraws.glVertex3f(-1.0f, -1.0f, 0.0f);
|
||||
GLSMDraws.glVertex3f(1.0f, -1.0f, 0.0f);
|
||||
GLSMDraws.glVertex3f(1.0f, 1.0f, 0.0f);
|
||||
GLSMDraws.glVertex3f(-1.0f, 1.0f, 0.0f);
|
||||
GLSMDraws.glEnd();
|
||||
|
||||
assertEquals(0xFF00FF00, readCenterPixel());
|
||||
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
|
||||
}
|
||||
|
||||
@Test
|
||||
void ffpTexturedQuadRenders() {
|
||||
final int texel = 0xFF2060A0;
|
||||
final int texture = GLSMTextures.glGenTextures();
|
||||
GLSMTextures.glBindTexture(GL11C.GL_TEXTURE_2D, texture);
|
||||
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_NEAREST);
|
||||
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_FILTER, GL11C.GL_NEAREST);
|
||||
final IntBuffer pixels = MemoryUtil.memAllocInt(16);
|
||||
try {
|
||||
for (int i = 0; i < 16; i++) pixels.put(i, texel);
|
||||
GLSMTextures.glTexImage2D(GL11C.GL_TEXTURE_2D, 0, GL11C.GL_RGBA, 4, 4, 0, GL12C.GL_BGRA, GL12C.GL_UNSIGNED_INT_8_8_8_8_REV, pixels);
|
||||
} finally {
|
||||
MemoryUtil.memFree(pixels);
|
||||
}
|
||||
|
||||
bindClearedTarget();
|
||||
GLSMTextures.glBindTexture(GL11C.GL_TEXTURE_2D, texture);
|
||||
GLSMEnable.glEnable(GL11C.GL_TEXTURE_2D);
|
||||
GLSMDraws.glBegin(GL11C.GL_QUADS);
|
||||
GLSMCurrentAttribs.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
GLSMCurrentAttribs.glTexCoord2f(0.0f, 0.0f);
|
||||
GLSMDraws.glVertex3f(-1.0f, -1.0f, 0.0f);
|
||||
GLSMCurrentAttribs.glTexCoord2f(1.0f, 0.0f);
|
||||
GLSMDraws.glVertex3f(1.0f, -1.0f, 0.0f);
|
||||
GLSMCurrentAttribs.glTexCoord2f(1.0f, 1.0f);
|
||||
GLSMDraws.glVertex3f(1.0f, 1.0f, 0.0f);
|
||||
GLSMCurrentAttribs.glTexCoord2f(0.0f, 1.0f);
|
||||
GLSMDraws.glVertex3f(-1.0f, 1.0f, 0.0f);
|
||||
GLSMDraws.glEnd();
|
||||
GLSMEnable.glDisable(GL11C.GL_TEXTURE_2D);
|
||||
|
||||
assertEquals(texel, readCenterPixel());
|
||||
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "emulated={0}")
|
||||
@ValueSource(booleans = {false, true})
|
||||
void clipPlaneKeepsOnlyTheEnabledHalfSpace(boolean emulated) {
|
||||
try (var ignored = ClipTestSupport.emulation(emulated)) {
|
||||
try {
|
||||
bindClearedTarget();
|
||||
ClipTestSupport.setPlane(1, 0.0, 0.0, 0.0, -1.0);
|
||||
ClipTestSupport.setPlane(0, 1.0, 0.0, 0.0, 0.0);
|
||||
GLSMEnable.glEnable(GL30C.GL_CLIP_DISTANCE0);
|
||||
GLSMDraws.glBegin(GL11C.GL_QUADS);
|
||||
GLSMCurrentAttribs.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
GLSMDraws.glVertex3f(-1.0f, -1.0f, 0.0f);
|
||||
GLSMDraws.glVertex3f(1.0f, -1.0f, 0.0f);
|
||||
GLSMDraws.glVertex3f(1.0f, 1.0f, 0.0f);
|
||||
GLSMDraws.glVertex3f(-1.0f, 1.0f, 0.0f);
|
||||
GLSMDraws.glEnd();
|
||||
|
||||
assertEquals(0xFF000000, readPixel(SIZE / 4, SIZE / 2), "x < 0 must be clipped by plane 0");
|
||||
assertEquals(0xFF00FF00, readPixel(3 * SIZE / 4, SIZE / 2), "x >= 0 must survive; plane 1 set but not enabled must not clip");
|
||||
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
|
||||
} finally {
|
||||
GLSMEnable.glDisable(GL30C.GL_CLIP_DISTANCE0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void proxyTextureProbeFindsTheMaxTextureSize() {
|
||||
final int maxSize = Integer.highestOneBit(GL11C.glGetInteger(GL11C.GL_MAX_TEXTURE_SIZE));
|
||||
int found = -1;
|
||||
for (int size = maxSize * 2; size > 0 && found < 0; size >>= 1) {
|
||||
GLSMTextures.glTexImage2D(GL11C.GL_PROXY_TEXTURE_2D, 0, GL11C.GL_RGBA, size, size, 0, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
if (GLSMTextures.glGetTexLevelParameteri(GL11C.GL_PROXY_TEXTURE_2D, 0, GL11C.GL_TEXTURE_WIDTH) != 0) found = size;
|
||||
}
|
||||
assertEquals(maxSize, found);
|
||||
assertEquals(GL11C.GL_NO_ERROR, GL11C.glGetError());
|
||||
}
|
||||
|
||||
private static void bindClearedTarget() {
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30C.GL_FRAMEBUFFER, fbo);
|
||||
GLSMRasterizer.glViewport(0, 0, SIZE, SIZE);
|
||||
GLSMBlend.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
GLSMFramebuffer.glClear(GL11C.GL_COLOR_BUFFER_BIT | GL11C.GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
private static int readCenterPixel() {
|
||||
return readPixel(SIZE / 2, SIZE / 2);
|
||||
}
|
||||
|
||||
private static int readPixel(int x, int y) {
|
||||
final ByteBuffer pixel = MemoryUtil.memAlloc(4);
|
||||
try {
|
||||
GLSMFramebuffer.glReadPixels(x, y, 1, 1, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, pixel);
|
||||
return (pixel.get(3) & 0xFF) << 24 | (pixel.get(0) & 0xFF) << 16 | (pixel.get(1) & 0xFF) << 8 | pixel.get(2) & 0xFF;
|
||||
} finally {
|
||||
MemoryUtil.memFree(pixel);
|
||||
}
|
||||
}
|
||||
|
||||
private static int createTarget() {
|
||||
final int color = GLSMTextures.glGenTextures();
|
||||
GLSMTextures.glBindTexture(GL11C.GL_TEXTURE_2D, color);
|
||||
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MIN_FILTER, GL11C.GL_NEAREST);
|
||||
GLSMTextures.glTexParameteri(GL11C.GL_TEXTURE_2D, GL11C.GL_TEXTURE_MAG_FILTER, GL11C.GL_NEAREST);
|
||||
GLSMTextures.glTexImage2D(GL11C.GL_TEXTURE_2D, 0, GL11C.GL_RGBA8, SIZE, SIZE, 0, GL11C.GL_RGBA, GL11C.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
final int depth = GLSMFramebuffer.glGenRenderbuffers();
|
||||
GLSMFramebuffer.glBindRenderbuffer(GL30C.GL_RENDERBUFFER, depth);
|
||||
GLSMFramebuffer.glRenderbufferStorage(GL30C.GL_RENDERBUFFER, GL30C.GL_DEPTH24_STENCIL8, SIZE, SIZE);
|
||||
final int target = GLSMFramebuffer.glGenFramebuffers();
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30C.GL_FRAMEBUFFER, target);
|
||||
GLSMFramebuffer.glFramebufferTexture2D(GL30C.GL_FRAMEBUFFER, GL30C.GL_COLOR_ATTACHMENT0, GL11C.GL_TEXTURE_2D, color, 0);
|
||||
GLSMFramebuffer.glFramebufferRenderbuffer(GL30C.GL_FRAMEBUFFER, GL30C.GL_DEPTH_STENCIL_ATTACHMENT, GL30C.GL_RENDERBUFFER, depth);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30C.GL_FRAMEBUFFER, 0);
|
||||
return target;
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@
|
||||
# extra - passed through to gradle; -Dangelica.* reaches the client (e.g. -Dangelica.flyby.timeOfDay=1500).
|
||||
# -Dangelica.flyby.camera=FIRST_PERSON|THIRD_PERSON_BACK|THIRD_PERSON_FRONT (default FIRST_PERSON).
|
||||
# -Dangelica.flyby.dimension=<id> moves the player there first (1 = End), after world saving is off.
|
||||
# -Dangelica.flyby.biome=<name> pins the origin to the 44x44-block area of that biome nearest to 0,0 (BiomeGenBase.biomeName), searched in the target dimension.
|
||||
# -Dangelica.flyby.survival=true runs the flight in survival with flying and no damage, restoring the original game mode afterwards.
|
||||
# -PclientJvmVendor=adoptium (macOS) picks the JVM vendor for runClient17/21/25.
|
||||
#
|
||||
# FLYBY_SPEED - blocks per tick. (0.5 ~= creative flight).
|
||||
|
||||
@@ -14,9 +14,11 @@ PLOTS = [
|
||||
"items.bail.ineligible", "items.bail.material", "items.bail.isbrh", "items.bail.blockState", "items.bail.notAllowed", "items.bail.template", "items.bail.queue",
|
||||
"particles.direct", "particles.captured", "particles.spilled", "particles.undecodable", "particles.draws", "sdl.renderPasses", "sdl.submits", "sdl.gpuWaitUs",
|
||||
"sdl.acquireWaitUs",
|
||||
"glsm.bufferDeletes", "draw.batchRebuilds.translucent", "draw.batchRebuilds", "terrain.regionsDrawn",
|
||||
"sdl.bufferCreates", "sdl.bufferDeletes", "sdl.bufferPoolHits",
|
||||
"weather.strength", "weather.quads", "weather.drawCalls", "weather.micros",
|
||||
]
|
||||
ZONES = ["entityModelParts", "tesrBatch", "entityLayerLoop", "entityDeferred", "tesrOpaque", "particlePass", "cl:weather", "streamingEndFrame"]
|
||||
ZONES = ["entityModelParts", "tesrBatch", "entityLayerLoop", "entityDeferred", "tesrOpaque", "particlePass", "cl:weather", "streamingEndFrame", "chunkAssembleRegion"]
|
||||
CLOCK = "gl.drawCalls"
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Clip-plane scene for -Dangelica.debug.testBlocks=true. ~ is the flight start, yaw 0 faces south (+z).
|
||||
# Each angelica:test_clip_plane TESR keeps only the lower half of a gold cube (plane 0) and leaves a reject-all plane 1 disabled.
|
||||
|
||||
gamerule doMobSpawning false
|
||||
gamerule doDaylightCycle false
|
||||
time set 6000
|
||||
|
||||
fill ~-2 ~-1 ~4 ~2 ~0 ~4 angelica:test_clip_plane
|
||||
@@ -20,7 +20,8 @@ BAD_LINE = re.compile(
|
||||
r"Debug screen entry \S+ failed and is disabled|needs the batching font renderer|"
|
||||
r"Flyby debugEntries:|Flyby: debug entry list is not available|Flyby: unknown debugProfile|"
|
||||
r"Debug screen compat entry for \S+ failed to register|"
|
||||
r"Failed to (?:load|save|set aside the unreadable) debug screen profile|toggle listeners kept changing"
|
||||
r"Failed to (?:load|save|set aside the unreadable) debug screen profile|toggle listeners kept changing|"
|
||||
r"Flyby heap check: (?:FAIL|SKIPPED)"
|
||||
)
|
||||
OVERLAY_RE = re.compile(r"Flyby debug overlay shot=(\d+) mode=(\w+)(?:.* renderers=(\S*))?")
|
||||
LINE_RE = re.compile(r"Flyby debug line shot=(\d+) id=(\S+) text=(.*)")
|
||||
@@ -125,7 +126,7 @@ def check(variant_dir, exit_status, new_crashes, opts=None):
|
||||
else:
|
||||
bad = [l for l in lines if BAD_LINE.search(l)]
|
||||
if bad:
|
||||
reasons.append(str(len(bad)) + " mixin/injection/scene failure line(s)")
|
||||
reasons.append(str(len(bad)) + " mixin/injection/scene/heap-check failure line(s)")
|
||||
details.extend([" " + l.strip()[:200] for l in bad[:3]])
|
||||
|
||||
offending = []
|
||||
@@ -209,6 +210,7 @@ def self_test():
|
||||
("mixin-error", GOOD_LOG + ["[10:00:05] [main/ERROR]: InvalidInjectionException: bad"], {}, 1),
|
||||
("small-shot", GOOD_LOG, {"shot_size": 100}, 1),
|
||||
("bad-entries", GOOD_LOG + ["[10:00:06] [Client thread/ERROR]: Flyby debugEntries: unknown entry id 'test:nope'"], {}, 1),
|
||||
("heap-check-fail", GOOD_LOG + ["[10:00:06] [Client thread/WARN] [Angelica/Flyby/]: Flyby heap check: FAIL WorldClient=1"], {}, 1),
|
||||
]
|
||||
for name, log, kw, want in cases:
|
||||
d = make_variant(root, name, log, **kw)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# SMOKE_PITCH - camera pitch in degrees, positive looks down (default: 15).
|
||||
# SMOKE_WARMUP_TICKS - ticks at the first pose before the pan (default: 100).
|
||||
# SMOKE_ARGS - extra -D flags for the client, word-split (default: none).
|
||||
# World-leak check: "-Dangelica.flyby.visits=3 -Dangelica.flyby.heapCheck=true" (FAIL/SKIPPED fails the variant).
|
||||
# SMOKE_CHECK_ARGS - extra flyby-smoke-check.py flags, word-split (default: none).
|
||||
# SMOKE_FULLSCREEN - 0 keeps options.txt's window mode (default: 1, fullscreen for the run).
|
||||
#
|
||||
|
||||
@@ -106,7 +106,13 @@ val glsmSdlAgent = sourceSets.create("glsmSdlAgent") {
|
||||
java.srcDir("src/glsmSdlAgent/java")
|
||||
}
|
||||
|
||||
val glsmLwjgl2Stubs: Configuration by configurations.creating {
|
||||
isCanBeConsumed = false
|
||||
isCanBeResolved = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
glsmLwjgl2Stubs(project(":glsm", "lwjgl2Stubs"))
|
||||
"glsmSdlAgentCompileOnly"(project(":glsm"))
|
||||
"glsmSdlAgentCompileOnly"(testFixtures(project(":glsm")))
|
||||
"glsmSdlAgentCompileOnly"(libs.asm.tree.test)
|
||||
@@ -148,7 +154,7 @@ val glsmSdlTest by tasks.registering(Test::class) {
|
||||
description = "Runs the headless GLSM-on-SDL FFP rig in its own JVM, with the SDL GPU gate engaged."
|
||||
group = "verification"
|
||||
testClassesDirs = sourceSets["test"].output.classesDirs
|
||||
classpath = glsmSdlStubs.output.classesDirs.plus(
|
||||
classpath = glsmSdlStubs.output.classesDirs.plus(glsmLwjgl2Stubs).plus(
|
||||
sourceSets["test"].runtimeClasspath.filter { file ->
|
||||
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
package org.lwjgl.opengl;
|
||||
|
||||
public final class GLContext {
|
||||
|
||||
private static final ContextCapabilities CAPABILITIES = new ContextCapabilities();
|
||||
|
||||
private GLContext() {}
|
||||
|
||||
public static ContextCapabilities getCapabilities() {
|
||||
return CAPABILITIES;
|
||||
}
|
||||
}
|
||||
+181
-105
@@ -18,7 +18,9 @@ import me.eigenraven.lwjgl3ify.client.MainThreadExec;
|
||||
import org.lwjglx.Lwjgl3ifyEventLoop;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureStaging;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.compute.ComputeBinder;
|
||||
@@ -42,6 +44,7 @@ import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineStore;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.VertexAttribs;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.BufferParams;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ByteRegionCopy;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ColorSource;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.CopyRectClip;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
@@ -111,6 +114,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.lwjgl.util.spvc.Spvc;
|
||||
import org.lwjgl.sdl.SDLGPU;
|
||||
import org.lwjgl.sdl.SDL_GPUBlitInfo;
|
||||
import org.lwjgl.sdl.SDLTimer;
|
||||
@@ -168,7 +172,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
new VoxelizationDispatcher(computeBinder);
|
||||
private final VertexAttribs vertexAttribs = new VertexAttribs();
|
||||
private final TextureOps textureOps = new TextureOps(device, frameManager, resourceManager, fboClearTracker);
|
||||
private final ReadbackShadows readbackShadows = new ReadbackShadows(device);
|
||||
private final ReadbackShadows readbackShadows = new ReadbackShadows(device, resourceManager, fenceTracker);
|
||||
private final PersistentBufferSync persistentSync = new PersistentBufferSync(frameManager, resourceManager,
|
||||
new PersistentBufferSync.UploadSink() {
|
||||
@Override public void enqueue(TransferThread.DeferredUpload upload) { enqueueUpload(upload); }
|
||||
@@ -1378,8 +1382,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
resourceManager.dropArrayShadow(dstGlId);
|
||||
resourceManager.dropEboShadow(dstGlId);
|
||||
resourceManager.markBufferContentsDefined(dstGlId);
|
||||
readbackShadows.invalidate(dstGlId, writeOffset);
|
||||
onBufferContentsWritten(dstGlId);
|
||||
|
||||
final ByteBuffer dstUboShadow = resourceManager.getUboShadow(dstGlId);
|
||||
if (dstUboShadow != null) {
|
||||
@@ -1460,9 +1463,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
final long cp = frameManager.ensureCopyPass();
|
||||
if (cp == 0) return;
|
||||
if (resourceManager.getBufferGlUsage(dstGlId) == GL15.GL_STREAM_READ
|
||||
&& readbackShadows.scheduleDownload(cp, srcHandle, readOffset, dstGlId, writeOffset, size)) {
|
||||
return;
|
||||
if (resourceManager.getBufferGlUsage(dstGlId) == GL15.GL_STREAM_READ && !isBoundAsSsbo(s(), dstGlId)) {
|
||||
readbackShadows.scheduleDownload(cp, srcHandle, readOffset, dstGlId, writeOffset, size);
|
||||
}
|
||||
try (var stack = MemoryStack.stackPush()) {
|
||||
final SDL_GPUBufferLocation src = SDL_GPUBufferLocation.calloc(stack).buffer(srcHandle).offset((int) readOffset);
|
||||
@@ -1507,52 +1509,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
return TextureInfoCache.isProxyTarget(target);
|
||||
}
|
||||
|
||||
private static boolean proxyLayersInHeight(int target) {
|
||||
return target == GL30.GL_PROXY_TEXTURE_1D_ARRAY;
|
||||
}
|
||||
|
||||
private static boolean proxyLayersInDepth(int target) {
|
||||
return isProxyArrayTarget(target) && !proxyLayersInHeight(target);
|
||||
}
|
||||
|
||||
private static boolean isProxyArrayTarget(int target) {
|
||||
return target == GL30.GL_PROXY_TEXTURE_1D_ARRAY || target == GL30.GL_PROXY_TEXTURE_2D_ARRAY || target == GL32.GL_PROXY_TEXTURE_2D_MULTISAMPLE_ARRAY || target == GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY;
|
||||
}
|
||||
|
||||
public static int proxyLevelSize(int base, int level) {
|
||||
return PixelOps.mipLevelSize(base, level);
|
||||
}
|
||||
|
||||
static int proxyDimensionLimit(int target) {
|
||||
return switch (target) {
|
||||
case GL12.GL_PROXY_TEXTURE_3D -> MAX_3D_TEXTURE_SIZE;
|
||||
case GL13.GL_PROXY_TEXTURE_CUBE_MAP, GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY -> MAX_CUBE_MAP_TEXTURE_SIZE;
|
||||
default -> MAX_TEXTURE_SIZE;
|
||||
};
|
||||
}
|
||||
|
||||
static int levelZeroEquivalentSize(int size, int level) {
|
||||
if (size <= 0 || level < 0 || level >= Integer.SIZE - 1) return 0;
|
||||
final long scaled = (long) size << level;
|
||||
return scaled > Integer.MAX_VALUE ? 0 : (int) scaled;
|
||||
}
|
||||
|
||||
private void recordProxyTexImage(ContextState st, int target, int level, int internalFormat, int width, int height, int depth) {
|
||||
final boolean layersInHeight = proxyLayersInHeight(target);
|
||||
final boolean layersInDepth = proxyLayersInDepth(target);
|
||||
final int dimLimit = proxyDimensionLimit(target);
|
||||
final int w = levelZeroEquivalentSize(width, level);
|
||||
final int h = layersInHeight ? height : levelZeroEquivalentSize(height, level);
|
||||
final int d = layersInDepth ? depth : levelZeroEquivalentSize(depth, level);
|
||||
final int hLimit = layersInHeight ? MAX_ARRAY_TEXTURE_LAYERS : dimLimit;
|
||||
final int dLimit = layersInDepth ? MAX_ARRAY_TEXTURE_LAYERS : dimLimit;
|
||||
final boolean fits = w >= 1 && w <= dimLimit && h >= 1 && h <= hLimit && d >= 1 && d <= dLimit
|
||||
&& resourceManager.isMappableTextureFormat(internalFormat);
|
||||
if (fits) {
|
||||
st.proxyTexture.accept(target, w, h, d, internalFormat);
|
||||
} else {
|
||||
st.proxyTexture.reject(target);
|
||||
}
|
||||
st.proxyTexture.record(target, level, internalFormat, width, height, depth, resourceManager.isMappableTextureFormat(internalFormat));
|
||||
}
|
||||
|
||||
private static int boundTextureFor(ContextState st, int target) {
|
||||
@@ -1816,23 +1774,13 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
@Override public int getTexLevelParameteri(int target, int level, int pname) {
|
||||
final ContextState cs = s();
|
||||
if (isProxyTarget(target)) {
|
||||
final ContextState.ProxyTextureState proxy = cs.proxyTexture;
|
||||
if (proxy.target != target) return 0;
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> proxyLevelSize(proxy.width, level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> proxyLayersInHeight(target) ? proxy.height : proxyLevelSize(proxy.height, level);
|
||||
case GL12.GL_TEXTURE_DEPTH -> proxyLayersInDepth(target) ? proxy.depth : proxyLevelSize(proxy.depth, level);
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> proxy.internalFormat;
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
if (isProxyTarget(target)) return cs.proxyTexture.levelParameter(target, level, pname);
|
||||
final int glId = cs.boundTextures[cs.activeTextureUnit];
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
|
||||
if (meta == null) return 0;
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> PixelOps.mipLevelSize(meta.width(), level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> PixelOps.mipLevelSize(meta.height(), level);
|
||||
case GL11.GL_TEXTURE_WIDTH -> TextureInfo.mipLevelSize(meta.width(), level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> TextureInfo.mipLevelSize(meta.height(), level);
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
|
||||
final int stored = meta.glFormat();
|
||||
yield TextureInfoCache.isGenericCompressedInternalFormat(stored) ? GL11.GL_RGBA8 : stored;
|
||||
@@ -1987,8 +1935,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (meta != null) {
|
||||
fbo.colorFormats[colorIdx] = meta.sdlFormat();
|
||||
if (colorIdx == 0) {
|
||||
fbo.width = PixelOps.mipLevelSize(meta.width(), level);
|
||||
fbo.height = PixelOps.mipLevelSize(meta.height(), level);
|
||||
fbo.width = TextureInfo.mipLevelSize(meta.width(), level);
|
||||
fbo.height = TextureInfo.mipLevelSize(meta.height(), level);
|
||||
}
|
||||
}
|
||||
fbo.colorAttachmentCount = Math.max(fbo.colorAttachmentCount, colorIdx + 1);
|
||||
@@ -2195,6 +2143,32 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (capacity < needed) throw new IllegalArgumentException("readback of " + width + "x" + height + " format 0x" + Integer.toHexString(format) + " type 0x" + Integer.toHexString(type) + " needs " + needed + " bytes, destination has " + capacity);
|
||||
}
|
||||
|
||||
@Override public ScreenCapture createScreenCapture(int width, int height) {
|
||||
return new SDLScreenCapture(this, device, frameManager, resourceManager, textureOps, width, height);
|
||||
}
|
||||
|
||||
ColorSource colorSourceForRead(int fboId) {
|
||||
final FboState fbo = fboId != 0 ? resourceManager.getFbo(fboId) : null;
|
||||
if (fbo == null) return null;
|
||||
final ContextState cs = s();
|
||||
final ColorSource source = resolveColorAttachment(fbo, cs.readColor);
|
||||
if (source.texture == 0L || source.width <= 0 || source.height <= 0) return null;
|
||||
fboClearTracker.materializePendingClearForRead(cs, frameManager.frame(), source.texture);
|
||||
return source;
|
||||
}
|
||||
|
||||
private ColorSource resolveColorAttachment(FboState fbo, ColorSource out) {
|
||||
final int colorGlId = fbo.colorGlIds[fbo.readBufferIndex];
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(colorGlId);
|
||||
out.texture = resourceManager.getTextureHandle(colorGlId);
|
||||
out.level = fbo.colorLevels[fbo.readBufferIndex];
|
||||
out.sdlFormat = meta != null ? meta.sdlFormat() : SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
out.glBaseFormat = meta != null ? PixelPack.glBaseFormat(meta.glFormat()) : GL11.GL_RGBA;
|
||||
out.width = meta != null ? TextureInfo.mipLevelSize(meta.width(), out.level) : fbo.width;
|
||||
out.height = meta != null ? TextureInfo.mipLevelSize(meta.height(), out.level) : fbo.height;
|
||||
return out;
|
||||
}
|
||||
|
||||
private void readPixelsInto(int x, int y, int width, int height, int format, int type, long dst, long capacity, int pbo, long pboOffset) {
|
||||
PixelPack.validate(format, type);
|
||||
if (width <= 0 || height <= 0) return;
|
||||
@@ -2224,14 +2198,13 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final FboState fbo = resourceManager.getFbo(cs.boundReadFboId);
|
||||
if (fbo == null) return;
|
||||
if (kind == READBACK_COLOR) {
|
||||
final int colorGlId = fbo.colorGlIds[fbo.readBufferIndex];
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(colorGlId);
|
||||
colorTex = resourceManager.getTextureHandle(colorGlId);
|
||||
colorFormat = meta != null ? meta.sdlFormat() : SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
if (meta != null) colorBase = PixelPack.glBaseFormat(meta.glFormat());
|
||||
srcLevel = fbo.colorLevels[fbo.readBufferIndex];
|
||||
srcWidth = meta != null ? PixelOps.mipLevelSize(meta.width(), srcLevel) : fbo.width;
|
||||
srcHeight = meta != null ? PixelOps.mipLevelSize(meta.height(), srcLevel) : fbo.height;
|
||||
final ColorSource source = resolveColorAttachment(fbo, cs.readColor);
|
||||
colorTex = source.texture;
|
||||
colorFormat = source.sdlFormat;
|
||||
colorBase = source.glBaseFormat;
|
||||
srcLevel = source.level;
|
||||
srcWidth = source.width;
|
||||
srcHeight = source.height;
|
||||
} else {
|
||||
if (fbo.depthLevel != 0) throw new UnsupportedOperationException("glReadPixels of a depth/stencil attachment at level " + fbo.depthLevel + " is not supported on SDL-GPU");
|
||||
depthTex = fbo.depthTexture;
|
||||
@@ -2258,8 +2231,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int glId = boundTextureFor(cs, target);
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(glId);
|
||||
if (meta == null || level < 0 || level >= meta.levels()) return;
|
||||
final int width = PixelOps.mipLevelSize(meta.width(), level);
|
||||
final int height = PixelOps.mipLevelSize(meta.height(), level);
|
||||
final int width = TextureInfo.mipLevelSize(meta.width(), level);
|
||||
final int height = TextureInfo.mipLevelSize(meta.height(), level);
|
||||
if (width <= 0 || height <= 0) return;
|
||||
checkReadbackCapacity(format, type, width, height, loadPackState(cs), capacity);
|
||||
final int kind = readbackKind(format);
|
||||
@@ -2348,7 +2321,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
} finally {
|
||||
region.clear();
|
||||
}
|
||||
resourceManager.markBufferContentsDefined(pbo);
|
||||
onBufferContentsWritten(pbo);
|
||||
flushAndSubmitMidFrame();
|
||||
}
|
||||
|
||||
@@ -2575,17 +2548,23 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (length != null && length.remaining() > 0) length.put(0, 0);
|
||||
return;
|
||||
}
|
||||
if (s == null) return;
|
||||
final byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
|
||||
final int n = Math.min(bytes.length, out.remaining());
|
||||
out.put(bytes, 0, n);
|
||||
final byte[] bytes = (s == null ? "" : s).getBytes(StandardCharsets.UTF_8);
|
||||
final int pos = out.position();
|
||||
final int max = out.remaining();
|
||||
final int n = Math.max(0, Math.min(bytes.length, max - 1));
|
||||
for (int i = 0; i < n; i++) out.put(pos + i, bytes[i]);
|
||||
if (max > 0) out.put(pos + n, (byte) 0);
|
||||
if (length != null && length.remaining() > 0) length.put(0, n);
|
||||
}
|
||||
private static int glStringLength(String s) {
|
||||
return s == null || s.isEmpty() ? 0 : s.getBytes(StandardCharsets.UTF_8).length + 1;
|
||||
}
|
||||
@Override public int getShaderi(int shader, int pname) {
|
||||
return switch (pname) {
|
||||
case GL20.GL_COMPILE_STATUS -> shaderManager.isCompiled(shader) ? 1 : 0;
|
||||
case GL20.GL_SHADER_TYPE -> shaderManager.getShaderType(shader);
|
||||
case GL20.GL_INFO_LOG_LENGTH -> shaderManager.getShaderInfoLog(shader).length();
|
||||
case GL20.GL_INFO_LOG_LENGTH -> glStringLength(shaderManager.getShaderInfoLog(shader));
|
||||
case GL20.GL_SHADER_SOURCE_LENGTH -> glStringLength(shaderManager.getShaderSource(shader));
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
@@ -2593,7 +2572,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
return switch (pname) {
|
||||
case GL20.GL_LINK_STATUS -> shaderManager.isLinked(program) ? 1 : 0;
|
||||
case GL20.GL_VALIDATE_STATUS -> shaderManager.isLinked(program) ? 1 : 0;
|
||||
case GL20.GL_INFO_LOG_LENGTH -> shaderManager.getProgramInfoLog(program).length();
|
||||
case GL20.GL_INFO_LOG_LENGTH -> glStringLength(shaderManager.getProgramInfoLog(program));
|
||||
case GL20.GL_ATTACHED_SHADERS -> shaderManager.getAttachedShaderCount(program);
|
||||
case GL20.GL_ACTIVE_UNIFORMS -> {
|
||||
final var entries = getOrBuildUniformEntries(program);
|
||||
@@ -2633,6 +2612,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int loc = e.getValue();
|
||||
int glType;
|
||||
int arrayLen = 1;
|
||||
boolean array = false;
|
||||
if (prog.allSamplerNames.contains(name)) {
|
||||
glType = GL20.GL_SAMPLER_2D;
|
||||
} else {
|
||||
@@ -2641,6 +2621,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (umi != null) {
|
||||
glType = FormatMap.mapMemberInfoToGlType(umi);
|
||||
arrayLen = Math.max(1, umi.arrayLen());
|
||||
array = umi.arrayStride() > 0;
|
||||
} else {
|
||||
glType = GL20.GL_FLOAT_VEC4;
|
||||
}
|
||||
@@ -2648,7 +2629,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
glType = FormatMap.remapUnsignedIntToBool(glType);
|
||||
}
|
||||
}
|
||||
result.add(new UniformEntry(name, glType, arrayLen));
|
||||
result.add(new UniformEntry(array ? name + "[0]" : name, glType, arrayLen));
|
||||
}
|
||||
result.sort(Comparator.comparing(UniformEntry::name));
|
||||
final List<UniformEntry> immutable = Collections.unmodifiableList(result);
|
||||
@@ -2707,6 +2688,78 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
@Override public int getUniformLocation(int program, ByteBuffer name) {
|
||||
return shaderManager.getUniformLocation(program, MemoryUtil.memASCII(name));
|
||||
}
|
||||
@Override public void getUniformfv(int program, int location, FloatBuffer params) {
|
||||
final Readback rb = readback(program, location);
|
||||
if (rb == null) return;
|
||||
final int pos = params.position();
|
||||
final int count = Math.min(rb.count(), params.remaining());
|
||||
for (int i = 0; i < count; i++) {
|
||||
final float raw = rb.raw(i);
|
||||
final int bits = Float.floatToRawIntBits(raw);
|
||||
params.put(pos + i, switch (rb.type()) {
|
||||
case FLOAT -> raw;
|
||||
case INT -> (float) bits;
|
||||
case UINT -> (float) (bits & 0xFFFFFFFFL);
|
||||
case BOOL -> bits != 0 ? 1f : 0f;
|
||||
case OPAQUE -> rb.opaqueValue();
|
||||
});
|
||||
}
|
||||
}
|
||||
@Override public void getUniformiv(int program, int location, IntBuffer params) {
|
||||
final Readback rb = readback(program, location);
|
||||
if (rb == null) return;
|
||||
final int pos = params.position();
|
||||
final int count = Math.min(rb.count(), params.remaining());
|
||||
for (int i = 0; i < count; i++) {
|
||||
final float raw = rb.raw(i);
|
||||
final int bits = Float.floatToRawIntBits(raw);
|
||||
params.put(pos + i, switch (rb.type()) {
|
||||
case FLOAT -> roundHalfAwayFromZero(raw);
|
||||
case BOOL -> bits != 0 ? 1 : 0;
|
||||
case INT, UINT -> bits;
|
||||
case OPAQUE -> rb.opaqueValue();
|
||||
});
|
||||
}
|
||||
}
|
||||
private enum ReadType { FLOAT, INT, UINT, BOOL, OPAQUE }
|
||||
private record Readback(float[] storage, int start, int count, ReadType type, int opaqueValue) {
|
||||
float raw(int i) {
|
||||
return storage != null && start + i < storage.length ? storage[start + i] : 0f;
|
||||
}
|
||||
}
|
||||
private Readback readback(int program, int location) {
|
||||
final ShaderManager.ProgramObject prog = shaderManager.getProgram(program);
|
||||
if (prog == null || !prog.linked || location < 0 || location >= prog.uniformSlotCount) return null;
|
||||
final int kind = prog.locationKind[location];
|
||||
if (kind != ShaderManager.ProgramObject.LOCATION_KIND_PLAIN) {
|
||||
return new Readback(null, 0, 1, ReadType.OPAQUE, opaqueUnit(prog, location, kind));
|
||||
}
|
||||
final ShaderManager.UniformMemberInfo info = prog.memberInfo(location);
|
||||
if (info == null) return null;
|
||||
final int perElement = info.vectorSize() * info.columns();
|
||||
final int index = prog.arrayIndexBySlot[location];
|
||||
final int base = location - index;
|
||||
final float[] storage = s().uniformStaging(prog).uniformDataBySlot[base];
|
||||
return new Readback(storage, index * perElement, perElement, readType(prog, base, info), 0);
|
||||
}
|
||||
private static ReadType readType(ShaderManager.ProgramObject prog, int base, ShaderManager.UniformMemberInfo info) {
|
||||
if (info.baseType() == Spvc.SPVC_BASETYPE_FP32) return ReadType.FLOAT;
|
||||
if (info.baseType() != Spvc.SPVC_BASETYPE_UINT32) return ReadType.INT;
|
||||
final String name = prog.locationName[base];
|
||||
return name != null && prog.boolUniforms.contains(name) ? ReadType.BOOL : ReadType.UINT;
|
||||
}
|
||||
private static int roundHalfAwayFromZero(float f) {
|
||||
final double r = Math.floor(Math.abs((double) f) + 0.5);
|
||||
return (int) (f < 0 ? -r : r);
|
||||
}
|
||||
private static int opaqueUnit(ShaderManager.ProgramObject prog, int location, int kind) {
|
||||
final String name = prog.locationName[location];
|
||||
if (name == null) return 0;
|
||||
final int unit = kind == ShaderManager.ProgramObject.LOCATION_KIND_SAMPLER
|
||||
? prog.samplerTextureUnits.getInt(name)
|
||||
: prog.imageTextureUnits.getInt(name);
|
||||
return Math.max(unit, 0);
|
||||
}
|
||||
@Override public void getShaderSource(int shader, IntBuffer length, ByteBuffer source) {
|
||||
writeUtf8(shaderManager.getShaderSource(shader), length, source);
|
||||
}
|
||||
@@ -2955,7 +3008,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
case GL31.GL_COPY_WRITE_BUFFER -> st.boundCopyWriteBuffer = buffer;
|
||||
case GL43.GL_SHADER_STORAGE_BUFFER -> {
|
||||
st.boundSSBO = buffer;
|
||||
resourceManager.markBufferContentsDefined(buffer);
|
||||
onBufferContentsWritten(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3044,8 +3097,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
|
||||
private void respecifyStore(ContextState cs, int target, int glId, long size, int usage) {
|
||||
freePlainMappingFor(glId, true);
|
||||
resourceManager.recordBufferGlParams(glId, usage, BufferParams.MUTABLE_STORE);
|
||||
if (target == GL31.GL_UNIFORM_BUFFER) {
|
||||
resourceManager.recordBufferGlParams(glId, usage, BufferParams.MUTABLE_STORE);
|
||||
if (resourceManager.getBufferSize(glId) == size) return;
|
||||
if (size > 0) {
|
||||
resourceManager.recordBufferSizeOnly(glId, size);
|
||||
@@ -3060,6 +3113,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
readbackShadows.release(glId);
|
||||
resourceManager.deleteBuffer(glId);
|
||||
resourceManager.createBuffer(glId, sdlUsage, size);
|
||||
resourceManager.recordBufferGlParams(glId, usage, BufferParams.MUTABLE_STORE);
|
||||
if (target == GL15.GL_ARRAY_BUFFER) cs.bumpAttribStateGen();
|
||||
}
|
||||
|
||||
@@ -3099,8 +3153,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
private void respecifyStore(ContextState cs, int target, int glId, ByteBuffer data, int usage) {
|
||||
final long dataSize = data.remaining();
|
||||
freePlainMappingFor(glId, true);
|
||||
resourceManager.recordBufferGlParams(glId, usage, BufferParams.MUTABLE_STORE);
|
||||
if (target == GL31.GL_UNIFORM_BUFFER) {
|
||||
resourceManager.recordBufferGlParams(glId, usage, BufferParams.MUTABLE_STORE);
|
||||
if (dataSize > 0) {
|
||||
resourceManager.recordBufferSizeOnly(glId, dataSize);
|
||||
final ByteBuffer shadow = resourceManager.getOrAllocUboShadow(glId, (int) dataSize);
|
||||
@@ -3116,6 +3170,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
readbackShadows.release(glId);
|
||||
resourceManager.deleteBuffer(glId);
|
||||
final long handle = resourceManager.createBuffer(glId, sdlUsage, dataSize);
|
||||
resourceManager.recordBufferGlParams(glId, usage, BufferParams.MUTABLE_STORE);
|
||||
if (target == GL15.GL_ARRAY_BUFFER) cs.bumpAttribStateGen();
|
||||
if (target == GL15.GL_ELEMENT_ARRAY_BUFFER && dataSize > 0
|
||||
&& (cs.primitiveRestartEnabled || resourceManager.hasEboShadow(glId))) {
|
||||
@@ -3130,7 +3185,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
} else if (frameManager.getCommandBuffer() != 0) {
|
||||
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), data, handle, 0, false);
|
||||
}
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
onBufferContentsWritten(glId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3220,7 +3275,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
FrameManager.warnDroppedOutsideFrame(droppedSsboWriteWarned, "bufferSubData(GL_SHADER_STORAGE_BUFFER)");
|
||||
droppedSsboWriteWarned = true;
|
||||
}
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
onBufferContentsWritten(glId);
|
||||
return;
|
||||
}
|
||||
final boolean cycle = target == GL40.GL_DRAW_INDIRECT_BUFFER;
|
||||
@@ -3229,7 +3284,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
} else if (frameManager.getCommandBuffer() != 0) {
|
||||
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), data, handle, offset, cycle);
|
||||
}
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
onBufferContentsWritten(glId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3294,7 +3349,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
} else if (frameManager.getCommandBuffer() != 0) {
|
||||
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), staging, gpuHandle, m.offset, false);
|
||||
}
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
onBufferContentsWritten(glId);
|
||||
}
|
||||
final ByteBuffer old = resourceManager.putPboStaging(glId, staging);
|
||||
if (old != null) MemoryUtil.memFree(old);
|
||||
@@ -3308,7 +3363,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
shadow.position((int) m.offset).limit((int) (m.offset + m.length));
|
||||
shadow.put(staging);
|
||||
shadow.position(0).limit(shadow.capacity());
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
onBufferContentsWritten(glId);
|
||||
cs.uboRangeGen++;
|
||||
}
|
||||
MemoryUtil.memFree(staging);
|
||||
@@ -3353,7 +3408,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
} else if (frameManager.getCommandBuffer() != 0) {
|
||||
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), staging, gpuHandle, m.offset, false);
|
||||
}
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
onBufferContentsWritten(glId);
|
||||
}
|
||||
|
||||
MemoryUtil.memFree(staging);
|
||||
@@ -3408,6 +3463,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
MemoryUtil.memSet(staging, 0);
|
||||
return;
|
||||
}
|
||||
if (readbackShadows.serve(glId, offset, staging, len)) {
|
||||
staging.position(0).limit(len);
|
||||
return;
|
||||
}
|
||||
|
||||
final ByteBuffer direct = switch (target) {
|
||||
case GL30.GL_PIXEL_UNPACK_BUFFER -> resourceManager.getPboStaging(glId);
|
||||
@@ -3524,6 +3583,20 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
downloadBufferBlocking(handle, (int) offset, rem, data);
|
||||
}
|
||||
|
||||
private void onBufferContentsWritten(int glId) {
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
readbackShadows.invalidateAll(glId);
|
||||
}
|
||||
|
||||
// An SSBO-bound buffer can be written by any later GPU pass, so it is never shadowed.
|
||||
private static boolean isBoundAsSsbo(ContextState st, int glId) {
|
||||
if (st.boundSSBO == glId) return true;
|
||||
for (int i = 0; i < ContextState.MAX_INDEXED_BUFFERS; i++) {
|
||||
if (st.boundSsboByIndex[i] == glId) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean readUboStore(int glId, long offset, ByteBuffer dst, int len) {
|
||||
final ByteBuffer shadow = resourceManager.getUboShadow(glId);
|
||||
if (shadow == null) return false;
|
||||
@@ -4042,6 +4115,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (refuseImmutable(buffer, "glNamedBufferData on immutable storage")) return;
|
||||
final int sdlUsage = FormatMap.mapBufferUsage(GL15.GL_ARRAY_BUFFER);
|
||||
freePlainMappingFor(buffer, true);
|
||||
readbackShadows.release(buffer);
|
||||
resourceManager.deleteBuffer(buffer);
|
||||
resourceManager.createBuffer(buffer, sdlUsage, size);
|
||||
resourceManager.recordBufferGlParams(buffer, usage, BufferParams.MUTABLE_STORE);
|
||||
@@ -4051,12 +4125,13 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (refuseImmutable(buffer, "glNamedBufferData on immutable storage")) return;
|
||||
final int sdlUsage = FormatMap.mapBufferUsage(GL15.GL_ARRAY_BUFFER);
|
||||
freePlainMappingFor(buffer, true);
|
||||
readbackShadows.release(buffer);
|
||||
resourceManager.deleteBuffer(buffer);
|
||||
final long handle = resourceManager.createBuffer(buffer, sdlUsage, data.remaining());
|
||||
resourceManager.recordBufferGlParams(buffer, usage, BufferParams.MUTABLE_STORE);
|
||||
if (handle != 0 && frameManager.getCommandBuffer() != 0) {
|
||||
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), data, handle, 0, false);
|
||||
resourceManager.markBufferContentsDefined(buffer);
|
||||
onBufferContentsWritten(buffer);
|
||||
}
|
||||
}
|
||||
@Override public void namedBufferData(int buffer, FloatBuffer data, int usage) {
|
||||
@@ -4067,7 +4142,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final long handle = resourceManager.getBufferHandle(buffer);
|
||||
if (handle != 0 && frameManager.getCommandBuffer() != 0) {
|
||||
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), data, handle, offset, false);
|
||||
resourceManager.markBufferContentsDefined(buffer);
|
||||
onBufferContentsWritten(buffer);
|
||||
}
|
||||
}
|
||||
@Override public void copyTextureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
|
||||
@@ -4119,8 +4194,8 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
|
||||
if (meta == null) return 0;
|
||||
return switch (pname) {
|
||||
case GL11.GL_TEXTURE_WIDTH -> PixelOps.mipLevelSize(meta.width(), level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> PixelOps.mipLevelSize(meta.height(), level);
|
||||
case GL11.GL_TEXTURE_WIDTH -> TextureInfo.mipLevelSize(meta.width(), level);
|
||||
case GL11.GL_TEXTURE_HEIGHT -> TextureInfo.mipLevelSize(meta.height(), level);
|
||||
case GL11.GL_TEXTURE_INTERNAL_FORMAT -> {
|
||||
final int stored = meta.glFormat();
|
||||
yield TextureInfoCache.isGenericCompressedInternalFormat(stored) ? GL11.GL_RGBA8 : stored;
|
||||
@@ -4137,6 +4212,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
case GL20.GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS -> 32;
|
||||
case GL20.GL_MAX_VERTEX_ATTRIBS -> 16;
|
||||
case GL20.GL_MAX_DRAW_BUFFERS -> 8;
|
||||
case GL30.GL_MAX_CLIP_DISTANCES -> device.hasClipDistance() ? GLStateManager.MAX_CLIP_PLANES : 0;
|
||||
case GL30.GL_MAX_COLOR_ATTACHMENTS -> 8;
|
||||
case GL15.GL_ARRAY_BUFFER_BINDING -> cs.boundArrayBuffer;
|
||||
case GL15.GL_ELEMENT_ARRAY_BUFFER_BINDING -> cs.currentVao.elementBuffer;
|
||||
@@ -4199,10 +4275,10 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
}
|
||||
|
||||
static final int MAX_UNIFORM_COMPONENTS_PER_STAGE = 4096 / Float.BYTES;
|
||||
static final int MAX_ARRAY_TEXTURE_LAYERS = 256;
|
||||
static final int MAX_TEXTURE_SIZE = 16384;
|
||||
static final int MAX_CUBE_MAP_TEXTURE_SIZE = 16384;
|
||||
static final int MAX_3D_TEXTURE_SIZE = 2048;
|
||||
public static final int MAX_ARRAY_TEXTURE_LAYERS = 256;
|
||||
public static final int MAX_TEXTURE_SIZE = 16384;
|
||||
public static final int MAX_CUBE_MAP_TEXTURE_SIZE = 16384;
|
||||
public static final int MAX_3D_TEXTURE_SIZE = 2048;
|
||||
/** D3D12 and Metal both cap anisotropy at 16, and every desktop Vulkan driver reports 16. */
|
||||
static final float MAX_ANISOTROPY = 16.0f;
|
||||
|
||||
@@ -4331,7 +4407,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
final int chunkBytes = (int) Math.min(size, defer ? CLEAR_CHUNK_BYTES : FILL_CHUNK_BYTES);
|
||||
final int alignedChunk = (chunkBytes / elemBytes) * elemBytes;
|
||||
if (alignedChunk == 0) return;
|
||||
resourceManager.markBufferContentsDefined(glId);
|
||||
onBufferContentsWritten(glId);
|
||||
final boolean zero = data == null || isAllZero(data, elemBytes);
|
||||
final ByteBuffer chunk = zero ? MemoryUtil.memCalloc(alignedChunk) : MemoryUtil.memAlloc(alignedChunk);
|
||||
try {
|
||||
@@ -4365,9 +4441,9 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
if (texHandle == 0 || frameManager.getCommandBuffer() == 0) return;
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(texture);
|
||||
if (meta == null || level >= meta.levels()) return;
|
||||
final int mipW = PixelOps.mipLevelSize(meta.width(), level);
|
||||
final int mipH = PixelOps.mipLevelSize(meta.height(), level);
|
||||
final int mipD = PixelOps.mipLevelSize(meta.depth(), level);
|
||||
final int mipW = TextureInfo.mipLevelSize(meta.width(), level);
|
||||
final int mipH = TextureInfo.mipLevelSize(meta.height(), level);
|
||||
final int mipD = TextureInfo.mipLevelSize(meta.depth(), level);
|
||||
final int bpp = PixelOps.sdlFormatTexelBytes(meta.sdlFormat());
|
||||
if (mipW <= 0 || mipH <= 0 || bpp <= 0) return;
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ColorSource;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.TextureOps;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import static org.lwjgl.sdl.SDLGPU.*;
|
||||
import static org.lwjgl.sdl.SDLSurface.SDL_FLIP_NONE;
|
||||
|
||||
final class SDLScreenCapture extends ScreenCapture {
|
||||
|
||||
private final SDLGPURenderBackend backend;
|
||||
private final Device device;
|
||||
private final FrameManager frameManager;
|
||||
private final ResourceManager resourceManager;
|
||||
private final TextureOps textureOps;
|
||||
|
||||
private final long[] textures = new long[RING];
|
||||
private final long[] transferBuffers = new long[RING];
|
||||
|
||||
SDLScreenCapture(SDLGPURenderBackend backend, Device device, FrameManager frameManager, ResourceManager resourceManager, TextureOps textureOps, int width, int height) {
|
||||
super(width, height);
|
||||
this.backend = backend;
|
||||
this.device = device;
|
||||
this.frameManager = frameManager;
|
||||
this.resourceManager = resourceManager;
|
||||
this.textureOps = textureOps;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean start(int slot, int srcFramebuffer, int srcWidth, int srcHeight) {
|
||||
if (frameManager.getCommandBuffer() == 0) return false;
|
||||
final ColorSource source = backend.colorSourceForRead(srcFramebuffer);
|
||||
if (source == null) return false;
|
||||
|
||||
final int w = width();
|
||||
final int h = height();
|
||||
if (textures[slot] == 0) {
|
||||
textures[slot] = resourceManager.createScratchTexture(SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, w, h, SDL_GPU_TEXTUREUSAGE_COLOR_TARGET);
|
||||
}
|
||||
if (transferBuffers[slot] == 0) {
|
||||
transferBuffers[slot] = resourceManager.createTransferBuffer((long) w * h * 4, SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD);
|
||||
if (transferBuffers[slot] == 0) return false;
|
||||
}
|
||||
|
||||
textureOps.blitTexture(source.texture, source.level, 0, 0, Math.min(srcWidth, source.width), Math.min(srcHeight, source.height), textures[slot], 0, 0, 0, w, h, GL11.GL_LINEAR, SDL_FLIP_NONE);
|
||||
final long copyPass = frameManager.ensureCopyPass();
|
||||
if (copyPass == 0) return false;
|
||||
resourceManager.recordTextureDownload(copyPass, textures[slot], 0, 0, 0, 0, w, h, 1, transferBuffers[slot]);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long map(int slot) {
|
||||
return nSDL_MapGPUTransferBuffer(device.getDevice(), transferBuffers[slot], false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void unmap(int slot) {
|
||||
resourceManager.unmapTransferBuffer(transferBuffers[slot]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteSlot(int slot) {
|
||||
resourceManager.releaseTextureDeferred(textures[slot]);
|
||||
resourceManager.releaseTransferBuffer(transferBuffers[slot]);
|
||||
textures[slot] = 0L;
|
||||
transferBuffers[slot] = 0L;
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ public final class Device {
|
||||
|
||||
private long device;
|
||||
private boolean claimed;
|
||||
private boolean clipDistance = true;
|
||||
private int supportedShaderFormats;
|
||||
private String driverName;
|
||||
private boolean fencePollEnabled;
|
||||
@@ -116,6 +117,10 @@ public final class Device {
|
||||
}
|
||||
|
||||
public boolean createDevice() {
|
||||
return createDevice(!SystemProperties.EMULATE_CLIP_DISTANCES);
|
||||
}
|
||||
|
||||
boolean createDevice(boolean requestClipDistance) {
|
||||
if (device != 0) return true;
|
||||
|
||||
installLogCallback();
|
||||
@@ -130,9 +135,9 @@ public final class Device {
|
||||
}
|
||||
|
||||
final boolean gpuDebug = SystemProperties.LWJGL_DEBUG || SystemProperties.SDL_GPU_DEBUG;
|
||||
device = createGPUDevice(gpuDebug, candidates);
|
||||
device = createGPUDevice(gpuDebug, candidates, requestClipDistance);
|
||||
if (device == 0 && VideoDriverRecovery.shouldRetryOnX11(SDL_GetCurrentVideoDriver(), vulkanLoaderAvailable(), System.getenv("DISPLAY"))) {
|
||||
device = VideoDriverRecovery.retryUnderX11(() -> createGPUDevice(gpuDebug, candidates));
|
||||
device = VideoDriverRecovery.retryUnderX11(() -> createGPUDevice(gpuDebug, candidates, requestClipDistance));
|
||||
}
|
||||
if (device == 0) {
|
||||
logDeviceCreationFailure(REQUESTED_SHADER_FORMATS);
|
||||
@@ -206,13 +211,22 @@ public final class Device {
|
||||
}
|
||||
}
|
||||
|
||||
private long createGPUDevice(boolean gpuDebug, String[] candidates) {
|
||||
private long createGPUDevice(boolean gpuDebug, String[] candidates, boolean requestClipDistance) {
|
||||
final boolean macos = Platform.get() == Platform.MACOSX;
|
||||
for (final String driver : candidates) {
|
||||
final boolean macVulkan = macos && isVulkan(driver);
|
||||
if (macVulkan) useMoltenVk();
|
||||
final long dev = SDL_CreateGPUDevice(REQUESTED_SHADER_FORMATS, gpuDebug, driver);
|
||||
if (dev != 0) return dev;
|
||||
long dev = createWithProperties(gpuDebug, driver, requestClipDistance);
|
||||
boolean clip = requestClipDistance;
|
||||
if (dev == 0 && clip && isVulkan(driver)) {
|
||||
LOG.info("SDL GPU Vulkan device creation failed with shaderClipDistance ({}); the device may lack it, retrying without it, clip distances will be emulated", SDLError.SDL_GetError());
|
||||
dev = createWithProperties(gpuDebug, driver, false);
|
||||
clip = false;
|
||||
}
|
||||
if (dev != 0) {
|
||||
clipDistance = clip;
|
||||
return dev;
|
||||
}
|
||||
LOG.warn("Failed to create a '{}' SDL GPU device: {}", driver, SDLError.SDL_GetError());
|
||||
if (macVulkan && MoltenVK.locate() == null) {
|
||||
LOG.warn("Vulkan on macOS needs MoltenVK. Add {} to the mods folder or install MoltenVK.", MoltenVK.JAR_NAME);
|
||||
@@ -221,6 +235,22 @@ public final class Device {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static long createWithProperties(boolean gpuDebug, String driver, boolean clipDistance) {
|
||||
final int props = SDLProperties.SDL_CreateProperties();
|
||||
try {
|
||||
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_SPIRV) != 0);
|
||||
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXBC_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_DXBC) != 0);
|
||||
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_DXIL_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_DXIL) != 0);
|
||||
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN, (REQUESTED_SHADER_FORMATS & SDL_GPU_SHADERFORMAT_MSL) != 0);
|
||||
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, gpuDebug);
|
||||
SDLProperties.SDL_SetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, driver);
|
||||
SDLProperties.SDL_SetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_CLIP_DISTANCE_BOOLEAN, clipDistance);
|
||||
return SDL_CreateGPUDeviceWithProperties(props);
|
||||
} finally {
|
||||
SDLProperties.SDL_DestroyProperties(props);
|
||||
}
|
||||
}
|
||||
|
||||
public void claimWindow(long window) {
|
||||
if (device == 0) {
|
||||
throw new IllegalStateException("SDL GPU device not created");
|
||||
@@ -298,6 +328,7 @@ public final class Device {
|
||||
driverVersion = null;
|
||||
driverInfo = null;
|
||||
supportedShaderFormats = 0;
|
||||
clipDistance = true;
|
||||
}
|
||||
|
||||
private void logWindowDiagnostics(long window) {
|
||||
@@ -351,6 +382,10 @@ public final class Device {
|
||||
return device;
|
||||
}
|
||||
|
||||
public boolean hasClipDistance() {
|
||||
return clipDistance;
|
||||
}
|
||||
|
||||
public String getDriverName() {
|
||||
return driverName;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.frame;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.ProxyTexture;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ColorSource;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.MappedRange;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PackState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpVariant;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.UniformStaging;
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import it.unimi.dsi.fastutil.longs.Long2FloatOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
@@ -64,28 +67,10 @@ public final class ContextState {
|
||||
|
||||
public final PixelStoreState pixelStore = new PixelStoreState();
|
||||
public final PackState packState = new PackState();
|
||||
public final ColorSource readColor = new ColorSource();
|
||||
|
||||
public static final class ProxyTextureState {
|
||||
public int target;
|
||||
public int width;
|
||||
public int height;
|
||||
public int depth;
|
||||
public int internalFormat;
|
||||
|
||||
public void accept(int target, int width, int height, int depth, int internalFormat) {
|
||||
this.target = target;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.depth = depth;
|
||||
this.internalFormat = internalFormat;
|
||||
}
|
||||
|
||||
public void reject(int target) {
|
||||
accept(target, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public final ProxyTextureState proxyTexture = new ProxyTextureState();
|
||||
public final ProxyTexture proxyTexture = new ProxyTexture(SDLGPURenderBackend.MAX_TEXTURE_SIZE, SDLGPURenderBackend.MAX_3D_TEXTURE_SIZE,
|
||||
SDLGPURenderBackend.MAX_CUBE_MAP_TEXTURE_SIZE, SDLGPURenderBackend.MAX_ARRAY_TEXTURE_LAYERS);
|
||||
|
||||
public static final class VAOState {
|
||||
public final boolean[] attribEnabled = new boolean[MAX_VERTEX_ATTRIBS];
|
||||
@@ -199,6 +184,7 @@ public final class ContextState {
|
||||
private final Reference2ObjectOpenHashMap<ShaderManager.ProgramObject, UniformStaging> uniformStagingByProgram = new Reference2ObjectOpenHashMap<>();
|
||||
private ShaderManager.ProgramObject stagingProg;
|
||||
private UniformStaging currentUniformStaging;
|
||||
public final Int2ObjectOpenHashMap<float[]> uniformArrayScratch = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
public UniformStaging uniformStaging(ShaderManager.ProgramObject prog) {
|
||||
if (prog == stagingProg) return currentUniformStaging;
|
||||
|
||||
@@ -64,16 +64,20 @@ public final class FenceTracker {
|
||||
if (pendingFenceSyncs.isEmpty()) return;
|
||||
final long fence = frameManager.frame().lastAcquiredFence;
|
||||
frameManager.frame().lastAcquiredFence = 0;
|
||||
boolean fenceTaken = false;
|
||||
for (int i = 0; i < pendingFenceSyncs.size(); i++) {
|
||||
final long id = pendingFenceSyncs.getLong(i);
|
||||
if (!fenceMap.containsKey(id)) continue;
|
||||
if (fence != 0) {
|
||||
fenceMap.put(id, fence);
|
||||
fenceRefcounts.addTo(fence, 1);
|
||||
fenceTaken = true;
|
||||
} else {
|
||||
fenceMap.remove(id);
|
||||
}
|
||||
}
|
||||
pendingFenceSyncs.clear();
|
||||
if (fence != 0 && !fenceTaken && device.getDevice() != 0) device.fenceReleaser().release(fence);
|
||||
}
|
||||
|
||||
public int clientWaitSync(long sync, int flags, long timeout) {
|
||||
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.pipeline;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FrameManager.FrameState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PixelOps;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ResourceManager;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.TextureOps;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpFormats;
|
||||
@@ -121,13 +121,13 @@ public final class LogicOpEmulator {
|
||||
if (fbo == null) return frameManager.getFbo0Width();
|
||||
final int db = drawBuffer(fbo, loc);
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(fbo.colorGlIds[db]);
|
||||
return meta != null ? PixelOps.mipLevelSize(meta.width(), fbo.colorLevels[db]) : fbo.width;
|
||||
return meta != null ? TextureInfo.mipLevelSize(meta.width(), fbo.colorLevels[db]) : fbo.width;
|
||||
}
|
||||
|
||||
private int targetHeight(FboState fbo, int loc) {
|
||||
if (fbo == null) return frameManager.getFbo0Height();
|
||||
final int db = drawBuffer(fbo, loc);
|
||||
final ResourceManager.TextureMeta meta = resourceManager.getTextureMeta(fbo.colorGlIds[db]);
|
||||
return meta != null ? PixelOps.mipLevelSize(meta.height(), fbo.colorLevels[db]) : fbo.height;
|
||||
return meta != null ? TextureInfo.mipLevelSize(meta.height(), fbo.colorLevels[db]) : fbo.height;
|
||||
}
|
||||
}
|
||||
+92
-6
@@ -754,16 +754,32 @@ public final class PipelineApplier {
|
||||
public float[] reuseOrAlloc(ContextState st, int location, int length) {
|
||||
final ShaderManager.ProgramObject prog = st.boundProgramObj;
|
||||
if (prog == null || location < 0 || location >= prog.uniformSlotCount) return null;
|
||||
if (prog.floatLimitBySlot[location] < 0) return arrayScratch(st, length);
|
||||
final float[] existing = st.uniformStaging(prog).uniformDataBySlot[location];
|
||||
if (existing != null && existing.length == length) return existing;
|
||||
return new float[length];
|
||||
}
|
||||
|
||||
private static float[] arrayScratch(ContextState st, int length) {
|
||||
float[] scratch = st.uniformArrayScratch.get(length);
|
||||
if (scratch == null) {
|
||||
scratch = new float[length];
|
||||
st.uniformArrayScratch.put(length, scratch);
|
||||
}
|
||||
return scratch;
|
||||
}
|
||||
|
||||
public void putUniformFv(ContextState st, int location, FloatBuffer value) {
|
||||
final ShaderManager.ProgramObject prog = st.boundProgramObj;
|
||||
if (prog == null || location < 0 || location >= prog.uniformSlotCount) return;
|
||||
final int n = value.remaining();
|
||||
final int pos = value.position();
|
||||
if (prog.floatLimitBySlot[location] < 0) {
|
||||
final float[] v = arrayScratch(st, n);
|
||||
value.get(pos, v);
|
||||
putUniform(st, location, v);
|
||||
return;
|
||||
}
|
||||
final float[] existing = st.uniformStaging(prog).uniformDataBySlot[location];
|
||||
if (existing != null && existing.length == n) {
|
||||
if (contentEquals(existing, value, pos, n)) {
|
||||
@@ -824,6 +840,7 @@ public final class PipelineApplier {
|
||||
if (Tracy.ENABLED) frameManager.noteUniformBlockFlush(b, size);
|
||||
}
|
||||
|
||||
private static final long ARRAY_HASH_SEED = 0x9E3779B97F4A7C15L;
|
||||
private static final boolean VERIFY_PER_FRAME_BLOCK = SystemProperties.SDL_VERIFY_PER_FRAME_UNIFORM_BLOCK;
|
||||
private final HashSet<String> perFrameBlockDriftWarned = new HashSet<>();
|
||||
|
||||
@@ -837,6 +854,12 @@ public final class PipelineApplier {
|
||||
public void putUniform(ContextState st, int location, float[] data) {
|
||||
final ShaderManager.ProgramObject prog = st.boundProgramObj;
|
||||
if (prog == null || location < 0 || location >= prog.uniformSlotCount) return;
|
||||
final int limit = prog.floatLimitBySlot[location];
|
||||
if (limit < 0) {
|
||||
putArrayElement(st, prog, st.uniformStaging(prog), location - prog.arrayIndexBySlot[location], location, data);
|
||||
return;
|
||||
}
|
||||
if (data.length > limit) return;
|
||||
final UniformStaging us = st.uniformStaging(prog);
|
||||
final int len = data.length;
|
||||
final long valueKey;
|
||||
@@ -884,6 +907,62 @@ public final class PipelineApplier {
|
||||
}
|
||||
return;
|
||||
}
|
||||
writeStages(st, prog, us, location, data);
|
||||
}
|
||||
|
||||
private void putArrayElement(ContextState st, ShaderManager.ProgramObject prog, UniformStaging us, int base, int location, float[] data) {
|
||||
final ShaderManager.UniformMemberInfo baseInfo = prog.memberInfo(base);
|
||||
if (baseInfo == null) return;
|
||||
final int len = prog.arrayLenBySlot[location];
|
||||
final int index = prog.arrayIndexBySlot[location];
|
||||
final int count = Math.min(elementCount(baseInfo, data), len - index);
|
||||
if (count <= 0) return;
|
||||
final int floatsPerElement = baseInfo.vectorSize() * baseInfo.columns();
|
||||
if (Tracy.ENABLED) st.slotWrites++;
|
||||
final int start = index * floatsPerElement;
|
||||
final int floats = count * floatsPerElement;
|
||||
float[] storage = us.uniformDataBySlot[base];
|
||||
final boolean fresh = storage == null;
|
||||
long storageHash;
|
||||
if (fresh) {
|
||||
storage = us.uniformDataBySlot[base] = new float[len * floatsPerElement];
|
||||
storageHash = ARRAY_HASH_SEED;
|
||||
} else {
|
||||
storageHash = us.uniformValueHashBySlot[base];
|
||||
}
|
||||
boolean changed = fresh;
|
||||
for (int i = 0; i < floats; i++) {
|
||||
final int oldBits = Float.floatToRawIntBits(storage[start + i]);
|
||||
final int newBits = Float.floatToRawIntBits(data[i]);
|
||||
if (oldBits == newBits) continue;
|
||||
storageHash ^= arrayHashTerm(start + i, oldBits) ^ arrayHashTerm(start + i, newBits);
|
||||
storage[start + i] = data[i];
|
||||
changed = true;
|
||||
}
|
||||
if (!changed) {
|
||||
if (Tracy.ENABLED) st.slotWritesElided++;
|
||||
return;
|
||||
}
|
||||
us.uniformValueHashBySlot[base] = storageHash;
|
||||
for (int b = 0; b < ShaderManager.BLOCK_COUNT; b++) {
|
||||
final ShaderManager.UniformMemberInfo info = prog.blockInfoBySlot[b][location];
|
||||
if (info == null) continue;
|
||||
final ContextState.UniformBlockState block = st.uniformBlocks[b];
|
||||
if (prog.blockSize[b] > block.size) block.size = prog.blockSize[b];
|
||||
final FloatBuffer blockFb = block.staging(prog.blockSize[b]);
|
||||
final int hashSlot = block.hashSlot(prog.blockInfoBySlot[b][base].offset());
|
||||
if (hashSlot >= 0) block.setHash(hashSlot, storageHash);
|
||||
writeMember(st, prog, blockFb, prog.blockSize[b], info, data, location, false);
|
||||
block.dirty = true;
|
||||
if (VERIFY_PER_FRAME_BLOCK && b == ShaderManager.BLOCK_PER_FRAME && block.flushedThisFrame) {
|
||||
notePerFrameBlockDrift(prog, base);
|
||||
}
|
||||
return;
|
||||
}
|
||||
writeStages(st, prog, us, location, data);
|
||||
}
|
||||
|
||||
private void writeStages(ContextState st, ShaderManager.ProgramObject prog, UniformStaging us, int location, float[] data) {
|
||||
final ShaderManager.UniformMemberInfo vs = prog.vsInfoBySlot[location];
|
||||
if (vs != null && us.vsUniformFb != null) {
|
||||
writeMember(st, prog, us.vsUniformFb, prog.vertexUboSize, vs, data, location, true);
|
||||
@@ -896,6 +975,10 @@ public final class PipelineApplier {
|
||||
}
|
||||
}
|
||||
|
||||
private static long arrayHashTerm(int index, int bits) {
|
||||
return bits == 0 ? 0L : Hashing.fmix64(ARRAY_HASH_SEED, Hashing.packHiLo(index, bits));
|
||||
}
|
||||
|
||||
public void storeMatrix(ContextState st, int location, boolean transpose, FloatBuffer value, int size) {
|
||||
if (location < 0) return;
|
||||
final int n = value.remaining();
|
||||
@@ -914,16 +997,19 @@ public final class PipelineApplier {
|
||||
putUniform(st, location, out);
|
||||
}
|
||||
|
||||
private void writeMember(ContextState st, ShaderManager.ProgramObject prog, FloatBuffer ubo, int uboSize, ShaderManager.UniformMemberInfo info, float[] data, int loc, boolean isVs) {
|
||||
private static int elementCount(ShaderManager.UniformMemberInfo info, float[] data) {
|
||||
final int floatsPerElement = info.vectorSize() * info.columns();
|
||||
if (floatsPerElement <= 0) return;
|
||||
final int srcCount = data.length / floatsPerElement;
|
||||
if (srcCount <= 0) return;
|
||||
final int dataBytes = srcCount * floatsPerElement * 4;
|
||||
return floatsPerElement <= 0 ? 0 : data.length / floatsPerElement;
|
||||
}
|
||||
|
||||
private void writeMember(ContextState st, ShaderManager.ProgramObject prog, FloatBuffer ubo, int uboSize, ShaderManager.UniformMemberInfo info, float[] data, int loc, boolean isVs) {
|
||||
final int count = Math.min(elementCount(info, data), info.arrayLen());
|
||||
if (count <= 0) return;
|
||||
final int dataBytes = count * info.vectorSize() * info.columns() * 4;
|
||||
final int avail = uboSize - info.offset();
|
||||
if (dataBytes > avail) {
|
||||
throw new IllegalStateException("Uniform overflows UBO: prog=" + st.boundProgram + " loc=" + loc + " name='" + prog.locationToName.getOrDefault(loc, "?") + "' dataBytes=" + dataBytes + " availFromOffset=" + avail + " stage=" + (isVs ? "VS" : "FS"));
|
||||
}
|
||||
Std140Writer.write(ubo, info, data, srcCount);
|
||||
Std140Writer.write(ubo, info, data, count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
public final class ColorSource {
|
||||
public long texture;
|
||||
public int level;
|
||||
public int width;
|
||||
public int height;
|
||||
public int sdlFormat;
|
||||
public int glBaseFormat;
|
||||
}
|
||||
@@ -14,12 +14,6 @@ import static org.lwjgl.sdl.SDLGPU.*;
|
||||
public final class PixelOps {
|
||||
private PixelOps() {}
|
||||
|
||||
public static int mipLevelSize(int base, int level) {
|
||||
if (base <= 0 || level < 0) return 0;
|
||||
if (level >= Integer.SIZE - 1) return 1;
|
||||
return Math.max(1, base >> level);
|
||||
}
|
||||
|
||||
public static int defaultMipLevels(int width, int height) {
|
||||
final int max = Math.max(1, Math.max(width, height));
|
||||
return Math.max(1, 32 - Integer.numberOfLeadingZeros(max));
|
||||
|
||||
+62
-45
@@ -1,16 +1,16 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FenceTracker;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectIterator;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.lwjgl.sdl.SDLError;
|
||||
import org.lwjgl.sdl.SDL_GPUBufferRegion;
|
||||
import org.lwjgl.sdl.SDL_GPUTransferBufferCreateInfo;
|
||||
import org.lwjgl.sdl.SDL_GPUTransferBufferLocation;
|
||||
import org.lwjgl.system.MemoryStack;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.system.MemoryUtil;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -25,25 +25,27 @@ public final class ReadbackShadows {
|
||||
private static final class Slot {
|
||||
long transferBuffer;
|
||||
int capacity;
|
||||
int validLen;
|
||||
boolean valid;
|
||||
long sync;
|
||||
}
|
||||
|
||||
private final Device device;
|
||||
private final Long2ObjectOpenHashMap<Slot> slots = new Long2ObjectOpenHashMap<>();
|
||||
private final ResourceManager resourceManager;
|
||||
private final FenceTracker fenceTracker;
|
||||
private final Int2ObjectOpenHashMap<Long2ObjectOpenHashMap<Slot>> byBuffer = new Int2ObjectOpenHashMap<>();
|
||||
|
||||
public ReadbackShadows(Device device) {
|
||||
public ReadbackShadows(Device device, ResourceManager resourceManager, FenceTracker fenceTracker) {
|
||||
this.device = device;
|
||||
this.resourceManager = resourceManager;
|
||||
this.fenceTracker = fenceTracker;
|
||||
}
|
||||
|
||||
private static long key(int glId, long offset) {
|
||||
return ((long) glId << 32) | (offset & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
public boolean scheduleDownload(long copyPass, long srcHandle, long srcOffset, int dstGlId, long dstOffset, long size) {
|
||||
if (copyPass == 0 || srcHandle == 0 || size <= 0 || size > Integer.MAX_VALUE) return false;
|
||||
public void scheduleDownload(long copyPass, long srcHandle, long srcOffset, int dstGlId, long dstOffset, long size) {
|
||||
if (copyPass == 0 || srcHandle == 0 || size <= 0 || size > Integer.MAX_VALUE) return;
|
||||
|
||||
final Slot slot = ensureSlot(dstGlId, dstOffset, (int) size);
|
||||
if (slot == null) return false;
|
||||
if (slot == null) return;
|
||||
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final SDL_GPUBufferRegion src = SDL_GPUBufferRegion.calloc(stack)
|
||||
@@ -55,18 +57,32 @@ public final class ReadbackShadows {
|
||||
.offset(0);
|
||||
SDL_DownloadFromGPUBuffer(copyPass, src, dst);
|
||||
}
|
||||
dropSync(slot);
|
||||
slot.sync = fenceTracker.fenceSync();
|
||||
slot.validLen = (int) size;
|
||||
slot.valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void invalidate(int glId, long offset) {
|
||||
final Slot slot = slots.get(key(glId, offset));
|
||||
if (slot != null) slot.valid = false;
|
||||
public void invalidateAll(int glId) {
|
||||
if (glId == 0) return;
|
||||
final Long2ObjectOpenHashMap<Slot> slots = byBuffer.get(glId);
|
||||
if (slots == null) return;
|
||||
for (Slot slot : slots.values()) {
|
||||
slot.valid = false;
|
||||
dropSync(slot);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean serve(int glId, long offset, ByteBuffer out, int len) {
|
||||
final Slot slot = slots.get(key(glId, offset));
|
||||
if (slot == null || !slot.valid || slot.transferBuffer == 0 || slot.capacity < len) return false;
|
||||
final Long2ObjectOpenHashMap<Slot> slots = byBuffer.get(glId);
|
||||
final Slot slot = slots != null ? slots.get(offset) : null;
|
||||
if (slot == null || !slot.valid || slot.transferBuffer == 0 || slot.validLen < len) return false;
|
||||
|
||||
if (slot.sync != 0) {
|
||||
final int wait = fenceTracker.clientWaitSync(slot.sync, GL32.GL_SYNC_FLUSH_COMMANDS_BIT, Long.MAX_VALUE);
|
||||
if (wait != GL32.GL_ALREADY_SIGNALED && wait != GL32.GL_CONDITION_SATISFIED) return false;
|
||||
dropSync(slot);
|
||||
}
|
||||
|
||||
final long mapped = nSDL_MapGPUTransferBuffer(device.getDevice(), slot.transferBuffer, false);
|
||||
if (mapped == 0) {
|
||||
@@ -80,62 +96,63 @@ public final class ReadbackShadows {
|
||||
}
|
||||
|
||||
public boolean hasSlotsFor(int glId) {
|
||||
for (long k : slots.keySet()) {
|
||||
if ((int) (k >>> 32) == glId) return true;
|
||||
}
|
||||
return false;
|
||||
return byBuffer.containsKey(glId);
|
||||
}
|
||||
|
||||
public void release(int glId) {
|
||||
if (slots.isEmpty()) return;
|
||||
final ObjectIterator<Long2ObjectMap.Entry<Slot>> it = slots.long2ObjectEntrySet().fastIterator();
|
||||
while (it.hasNext()) {
|
||||
final Long2ObjectMap.Entry<Slot> entry = it.next();
|
||||
if ((int) (entry.getLongKey() >>> 32) == glId) {
|
||||
releaseSlot(entry.getValue());
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
final Long2ObjectOpenHashMap<Slot> slots = byBuffer.remove(glId);
|
||||
if (slots == null) return;
|
||||
for (Slot slot : slots.values()) releaseSlot(slot);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
for (Slot slot : slots.values()) releaseSlot(slot);
|
||||
slots.clear();
|
||||
for (Long2ObjectOpenHashMap<Slot> slots : byBuffer.values()) {
|
||||
for (Slot slot : slots.values()) releaseSlot(slot);
|
||||
}
|
||||
byBuffer.clear();
|
||||
}
|
||||
|
||||
private Slot ensureSlot(int glId, long offset, int size) {
|
||||
final long k = key(glId, offset);
|
||||
Slot slot = slots.get(k);
|
||||
Long2ObjectOpenHashMap<Slot> slots = byBuffer.get(glId);
|
||||
Slot slot = slots != null ? slots.get(offset) : null;
|
||||
if (slot != null && slot.capacity >= size) return slot;
|
||||
|
||||
if (slot == null) {
|
||||
if (slots == null) {
|
||||
slots = new Long2ObjectOpenHashMap<>();
|
||||
byBuffer.put(glId, slots);
|
||||
}
|
||||
slot = new Slot();
|
||||
slots.put(k, slot);
|
||||
slots.put(offset, slot);
|
||||
} else {
|
||||
releaseSlot(slot);
|
||||
}
|
||||
|
||||
try (MemoryStack stack = stackPush()) {
|
||||
final SDL_GPUTransferBufferCreateInfo ci = SDL_GPUTransferBufferCreateInfo.calloc(stack)
|
||||
.usage(SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD)
|
||||
.size(size);
|
||||
slot.transferBuffer = SDL_CreateGPUTransferBuffer(device.getDevice(), ci);
|
||||
}
|
||||
slot.transferBuffer = resourceManager.createTransferBuffer(size, SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD);
|
||||
if (slot.transferBuffer == 0) {
|
||||
LOG.error("Failed to create readback transfer buffer size={}: {}", size, SDLError.SDL_GetError());
|
||||
slots.remove(k);
|
||||
slots.remove(offset);
|
||||
if (slots.isEmpty()) byBuffer.remove(glId);
|
||||
return null;
|
||||
}
|
||||
slot.capacity = size;
|
||||
slot.validLen = 0;
|
||||
slot.valid = false;
|
||||
return slot;
|
||||
}
|
||||
|
||||
private void dropSync(Slot slot) {
|
||||
if (slot.sync == 0) return;
|
||||
fenceTracker.deleteSync(slot.sync);
|
||||
slot.sync = 0;
|
||||
}
|
||||
|
||||
private void releaseSlot(Slot slot) {
|
||||
dropSync(slot);
|
||||
if (slot.transferBuffer != 0 && device.getDevice() != 0) {
|
||||
SDL_ReleaseGPUTransferBuffer(device.getDevice(), slot.transferBuffer);
|
||||
resourceManager.releaseTransferBuffer(slot.transferBuffer);
|
||||
}
|
||||
slot.transferBuffer = 0;
|
||||
slot.capacity = 0;
|
||||
slot.validLen = 0;
|
||||
}
|
||||
}
|
||||
+50
-51
@@ -807,6 +807,7 @@ public final class ResourceManager {
|
||||
final ByteBuffer droppedPboStaging;
|
||||
final ByteBuffer droppedPlainStaging;
|
||||
final PersistentMapping droppedPm;
|
||||
final boolean hadEboShadow;
|
||||
wLock.lock();
|
||||
try {
|
||||
final long handle = bufferHandles.remove(glId);
|
||||
@@ -820,7 +821,7 @@ public final class ResourceManager {
|
||||
droppedPlainStaging = removePlainMappingLocked(glId);
|
||||
droppedPboStaging = pboStagingData.remove(glId);
|
||||
deleteUboShadow(glId);
|
||||
deleteEboShadow(glId);
|
||||
hadEboShadow = deleteEboShadow(glId);
|
||||
deleteArrayShadow(glId);
|
||||
if (handle != 0) {
|
||||
returnBufferToPool(handle, usage, size);
|
||||
@@ -829,6 +830,7 @@ public final class ResourceManager {
|
||||
} finally {
|
||||
wLock.unlock();
|
||||
}
|
||||
if (hadEboShadow) invalidateSplitCacheFor(glId);
|
||||
if (droppedPboStaging != null) {
|
||||
memFree(droppedPboStaging);
|
||||
}
|
||||
@@ -1277,11 +1279,12 @@ public final class ResourceManager {
|
||||
try { return eboShadowVersions.get(bufferGlId); } finally { rLock.unlock(); }
|
||||
}
|
||||
|
||||
private void deleteEboShadow(int bufferGlId) {
|
||||
private boolean deleteEboShadow(int bufferGlId) {
|
||||
final ByteBuffer existing = eboShadows.remove(bufferGlId);
|
||||
if (existing != null) memFree(existing);
|
||||
eboShadowVersions.remove(bufferGlId);
|
||||
eboShadowWanted.remove(bufferGlId);
|
||||
return existing != null;
|
||||
}
|
||||
|
||||
public boolean canShadowArrayBuffer(int size) {
|
||||
@@ -1569,9 +1572,13 @@ public final class ResourceManager {
|
||||
}
|
||||
|
||||
public long createTransferBuffer(long size) {
|
||||
return createTransferBuffer(size, SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD);
|
||||
}
|
||||
|
||||
public long createTransferBuffer(long size, int usage) {
|
||||
try (var stack = stackPush()) {
|
||||
final SDL_GPUTransferBufferCreateInfo ci = SDL_GPUTransferBufferCreateInfo.calloc(stack)
|
||||
.usage(SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD)
|
||||
.usage(usage)
|
||||
.size((int) size);
|
||||
|
||||
final long handle = SDL_CreateGPUTransferBuffer(device.getDevice(), ci);
|
||||
@@ -2064,23 +2071,42 @@ public final class ResourceManager {
|
||||
public void downloadFromTexture(long commandBuffer, long gpuTexture, int x, int y, int z, int w, int h, int d, int level, ByteBuffer output) {
|
||||
final int size = output.remaining();
|
||||
|
||||
final long xfer = createTransferBuffer(size, SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD);
|
||||
if (xfer == 0) return;
|
||||
|
||||
final long copyPass = SDL_BeginGPUCopyPass(commandBuffer);
|
||||
if (copyPass == 0) {
|
||||
LOG.error("Failed to begin copy pass for texture download: {}", SDLError.SDL_GetError());
|
||||
releaseTransferBuffer(xfer);
|
||||
return;
|
||||
}
|
||||
|
||||
recordTextureDownload(copyPass, gpuTexture, level, x, y, z, w, h, d, xfer);
|
||||
SDL_EndGPUCopyPass(copyPass);
|
||||
|
||||
final long fence = Submits.submitAndAcquireFence(commandBuffer);
|
||||
if (fence == 0) {
|
||||
LOG.error("Failed to submit download command buffer: {}", SDLError.SDL_GetError());
|
||||
releaseTransferBuffer(xfer);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FenceWait.await(device, fence, FenceWait.FOREVER);
|
||||
} finally {
|
||||
device.fenceReleaser().release(fence);
|
||||
}
|
||||
|
||||
final long mappedPtr = nSDL_MapGPUTransferBuffer(device.getDevice(), xfer, false);
|
||||
if (mappedPtr != 0) {
|
||||
final ByteBuffer mapped = MemoryUtil.memByteBuffer(mappedPtr, size);
|
||||
output.put(mapped);
|
||||
}
|
||||
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
|
||||
releaseTransferBuffer(xfer);
|
||||
}
|
||||
|
||||
public void recordTextureDownload(long copyPass, long gpuTexture, int level, int x, int y, int z, int w, int h, int d, long xfer) {
|
||||
try (var stack = stackPush()) {
|
||||
final SDL_GPUTransferBufferCreateInfo xferCi = SDL_GPUTransferBufferCreateInfo.calloc(stack)
|
||||
.usage(SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD)
|
||||
.size(size);
|
||||
final long xfer = SDL_CreateGPUTransferBuffer(device.getDevice(), xferCi);
|
||||
if (xfer == 0) {
|
||||
LOG.error("Failed to create download transfer buffer: {}", SDLError.SDL_GetError());
|
||||
return;
|
||||
}
|
||||
|
||||
final long copyPass = SDL_BeginGPUCopyPass(commandBuffer);
|
||||
if (copyPass == 0) {
|
||||
LOG.error("Failed to begin copy pass for texture download: {}", SDLError.SDL_GetError());
|
||||
SDL_ReleaseGPUTransferBuffer(device.getDevice(), xfer);
|
||||
return;
|
||||
}
|
||||
|
||||
final SDL_GPUTextureRegion src = SDL_GPUTextureRegion.calloc(stack)
|
||||
.texture(gpuTexture)
|
||||
.mip_level(level)
|
||||
@@ -2092,45 +2118,18 @@ public final class ResourceManager {
|
||||
.offset(0);
|
||||
|
||||
SDL_DownloadFromGPUTexture(copyPass, src, dst);
|
||||
SDL_EndGPUCopyPass(copyPass);
|
||||
|
||||
final long fence = Submits.submitAndAcquireFence(commandBuffer);
|
||||
if (fence == 0) {
|
||||
LOG.error("Failed to submit download command buffer: {}", SDLError.SDL_GetError());
|
||||
SDL_ReleaseGPUTransferBuffer(device.getDevice(), xfer);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
FenceWait.await(device, fence, FenceWait.FOREVER);
|
||||
} finally {
|
||||
device.fenceReleaser().release(fence);
|
||||
}
|
||||
|
||||
final long mappedPtr = nSDL_MapGPUTransferBuffer(device.getDevice(), xfer, false);
|
||||
if (mappedPtr != 0) {
|
||||
final ByteBuffer mapped = MemoryUtil.memByteBuffer(mappedPtr, size);
|
||||
output.put(mapped);
|
||||
}
|
||||
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
|
||||
SDL_ReleaseGPUTransferBuffer(device.getDevice(), xfer);
|
||||
}
|
||||
}
|
||||
|
||||
public void downloadFromBuffer(long commandBuffer, long gpuBuffer, int offset, int size, ByteBuffer output) {
|
||||
try (var stack = stackPush()) {
|
||||
final SDL_GPUTransferBufferCreateInfo xferCi = SDL_GPUTransferBufferCreateInfo.calloc(stack)
|
||||
.usage(SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD)
|
||||
.size(size);
|
||||
final long xfer = SDL_CreateGPUTransferBuffer(device.getDevice(), xferCi);
|
||||
if (xfer == 0) {
|
||||
LOG.error("Failed to create download transfer buffer for buffer readback: {}", SDLError.SDL_GetError());
|
||||
return;
|
||||
}
|
||||
final long xfer = createTransferBuffer(size, SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD);
|
||||
if (xfer == 0) return;
|
||||
|
||||
final long copyPass = SDL_BeginGPUCopyPass(commandBuffer);
|
||||
if (copyPass == 0) {
|
||||
LOG.error("Failed to begin copy pass for buffer download: {}", SDLError.SDL_GetError());
|
||||
SDL_ReleaseGPUTransferBuffer(device.getDevice(), xfer);
|
||||
releaseTransferBuffer(xfer);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2149,7 +2148,7 @@ public final class ResourceManager {
|
||||
final long fence = Submits.submitAndAcquireFence(commandBuffer);
|
||||
if (fence == 0) {
|
||||
LOG.error("Failed to submit buffer download command buffer: {}", SDLError.SDL_GetError());
|
||||
SDL_ReleaseGPUTransferBuffer(device.getDevice(), xfer);
|
||||
releaseTransferBuffer(xfer);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -2164,7 +2163,7 @@ public final class ResourceManager {
|
||||
output.put(mapped);
|
||||
}
|
||||
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
|
||||
SDL_ReleaseGPUTransferBuffer(device.getDevice(), xfer);
|
||||
releaseTransferBuffer(xfer);
|
||||
}
|
||||
}
|
||||
private long defaultSampler;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Device;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.device.Submits;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
@@ -156,11 +157,11 @@ public final class TextureOps {
|
||||
}
|
||||
|
||||
public static int levelWidth(ResourceManager.TextureMeta meta, int level) {
|
||||
return meta != null ? PixelOps.mipLevelSize(meta.width(), level) : 0;
|
||||
return meta != null ? TextureInfo.mipLevelSize(meta.width(), level) : 0;
|
||||
}
|
||||
|
||||
public static int levelHeight(ResourceManager.TextureMeta meta, int level) {
|
||||
return meta != null ? PixelOps.mipLevelSize(meta.height(), level) : 0;
|
||||
return meta != null ? TextureInfo.mipLevelSize(meta.height(), level) : 0;
|
||||
}
|
||||
|
||||
private void copyFromDefaultFramebuffer(ContextState st, int destGlId, long destTex, int level, int xoffset, int yoffset, int x, int y, int width, int height) {
|
||||
|
||||
@@ -777,7 +777,7 @@ public final class ShaderManager {
|
||||
else prog.fragmentUboSize = refl.uboSize();
|
||||
}
|
||||
for (UboMember m : refl.uboMembers()) {
|
||||
final int loc = ensureLocation(prog, m.name());
|
||||
final int loc = ensureLocation(prog, m.name(), m.arrayLen());
|
||||
final UniformMemberInfo info = new UniformMemberInfo(
|
||||
m.offset(), m.size(), m.arrayStride(), isVertex,
|
||||
m.vectorSize(), m.columns(), m.baseType(), m.arrayLen()
|
||||
@@ -795,7 +795,7 @@ public final class ShaderManager {
|
||||
prog.blockSize[b] = Math.max(prog.blockSize[b], block.size());
|
||||
prog.blockBinding[b] = block.binding();
|
||||
for (UboMember m : block.members()) {
|
||||
final int loc = ensureLocation(prog, m.name());
|
||||
final int loc = ensureLocation(prog, m.name(), m.arrayLen());
|
||||
if (prog.blockMemberInfo[b].containsKey(loc)) continue;
|
||||
prog.blockMemberInfo[b].put(loc, new UniformMemberInfo(
|
||||
m.offset(), m.size(), m.arrayStride(), false,
|
||||
@@ -809,14 +809,34 @@ public final class ShaderManager {
|
||||
}
|
||||
|
||||
private static int ensureLocation(ProgramObject prog, String name) {
|
||||
return ensureLocation(prog, name, 1);
|
||||
}
|
||||
|
||||
private static int ensureLocation(ProgramObject prog, String name, int arrayLen) {
|
||||
final int existing = prog.nameToLocation.getInt(name);
|
||||
if (existing != -1) return existing;
|
||||
final int loc = prog.nextUniformLocation++;
|
||||
if (existing != -1) {
|
||||
final int registered = Math.max(1, prog.arrayLenByLocation.get(existing));
|
||||
if (registered == arrayLen) return existing;
|
||||
retireLocations(prog, existing, registered);
|
||||
}
|
||||
final int loc = prog.nextUniformLocation;
|
||||
prog.nextUniformLocation += arrayLen;
|
||||
prog.nameToLocation.put(name, loc);
|
||||
prog.locationToName.put(loc, name);
|
||||
for (int i = 1; i < arrayLen; i++) prog.locationToName.put(loc + i, name + "[" + i + "]");
|
||||
if (arrayLen > 1) prog.arrayLenByLocation.put(loc, arrayLen);
|
||||
return loc;
|
||||
}
|
||||
|
||||
private static void retireLocations(ProgramObject prog, int base, int len) {
|
||||
prog.arrayLenByLocation.remove(base);
|
||||
for (int i = 0; i < len; i++) prog.locationToName.remove(base + i);
|
||||
prog.locationToMemberInfo.remove(base);
|
||||
prog.vertexMemberInfo.remove(base);
|
||||
prog.fragmentMemberInfo.remove(base);
|
||||
for (int b = 0; b < BLOCK_COUNT; b++) prog.blockMemberInfo[b].remove(base);
|
||||
}
|
||||
|
||||
private static void applyVaryingMatch(ByteBuffer fragmentSpirv, StageReflection vs, StageReflection fs) {
|
||||
if (vs.vsOutputs().isEmpty() || fs.fsInputs().isEmpty() || fragmentSpirv == null) return;
|
||||
final HashMap<String, Integer> vsOutLocs = new HashMap<>(vs.vsOutputs().size() * 2);
|
||||
@@ -1268,7 +1288,13 @@ public final class ShaderManager {
|
||||
public int getUniformLocation(int program, String name) {
|
||||
final ProgramObject prog = programObjects.get(program);
|
||||
if (prog == null || !prog.linked) return -1;
|
||||
return prog.nameToLocation.getInt(name);
|
||||
final int exact = prog.nameToLocation.getInt(name);
|
||||
if (exact != -1) return exact;
|
||||
final int index = UniformNames.arrayIndex(name);
|
||||
if (index < 0) return -1;
|
||||
final int base = prog.nameToLocation.getInt(name.substring(0, name.lastIndexOf('[')));
|
||||
if (base < 0 || base >= prog.uniformSlotCount || index >= prog.arrayLenBySlot[base]) return -1;
|
||||
return base + index;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1645,7 +1671,12 @@ public final class ShaderManager {
|
||||
public record UniformMemberInfo(
|
||||
int offset, int size, int arrayStride, boolean isVertex,
|
||||
int vectorSize, int columns, int baseType, int arrayLen
|
||||
) {}
|
||||
) {
|
||||
public UniformMemberInfo element(int i) {
|
||||
return new UniformMemberInfo(offset + i * arrayStride, size - i * arrayStride, arrayStride, isVertex,
|
||||
vectorSize, columns, baseType, arrayLen - i);
|
||||
}
|
||||
}
|
||||
|
||||
public record UboMember(
|
||||
String name, int offset, int size, int arrayStride,
|
||||
@@ -2069,11 +2100,24 @@ public final class ShaderManager {
|
||||
return m != null ? m : blockMemberInfo[BLOCK_PER_PASS].get(loc);
|
||||
}
|
||||
|
||||
public UniformMemberInfo memberInfo(int slot) {
|
||||
for (int b = 0; b < BLOCK_COUNT; b++) {
|
||||
final UniformMemberInfo info = blockInfoBySlot[b][slot];
|
||||
if (info != null) return info;
|
||||
}
|
||||
final UniformMemberInfo vs = vsInfoBySlot[slot];
|
||||
return vs != null ? vs : fsInfoBySlot[slot];
|
||||
}
|
||||
|
||||
private static final UniformMemberInfo[] EMPTY_INFO = new UniformMemberInfo[0];
|
||||
|
||||
public UniformMemberInfo[] vsInfoBySlot;
|
||||
public UniformMemberInfo[] fsInfoBySlot;
|
||||
public int uniformSlotCount;
|
||||
public final Int2IntOpenHashMap arrayLenByLocation = new Int2IntOpenHashMap();
|
||||
public int[] arrayIndexBySlot = EMPTY_UNITS;
|
||||
public int[] arrayLenBySlot = EMPTY_UNITS;
|
||||
public int[] floatLimitBySlot = EMPTY_UNITS;
|
||||
public final int[] vertexInputBaseType = new int[16];
|
||||
public final int[] vertexInputVecSize = new int[16];
|
||||
public final String[] vertexInputName = new String[16];
|
||||
@@ -2200,24 +2244,51 @@ public final class ShaderManager {
|
||||
locationKind[loc] = LOCATION_KIND_PLAIN;
|
||||
}
|
||||
}
|
||||
arrayIndexBySlot = new int[n];
|
||||
arrayLenBySlot = new int[n];
|
||||
Arrays.fill(arrayLenBySlot, 1);
|
||||
for (var it = arrayLenByLocation.int2IntEntrySet().fastIterator(); it.hasNext(); ) {
|
||||
final var e = it.next();
|
||||
final int base = e.getIntKey();
|
||||
final int len = e.getIntValue();
|
||||
for (int i = 0; i < len; i++) {
|
||||
arrayLenBySlot[base + i] = len;
|
||||
arrayIndexBySlot[base + i] = i;
|
||||
}
|
||||
}
|
||||
for (int b = 0; b < BLOCK_COUNT; b++) {
|
||||
blockInfoBySlot[b] = new UniformMemberInfo[n];
|
||||
for (var it = blockMemberInfo[b].int2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
|
||||
final var e = it.next();
|
||||
final int k = e.getIntKey();
|
||||
if (k >= 0 && k < n) blockInfoBySlot[b][k] = e.getValue();
|
||||
fillSlots(blockInfoBySlot[b], e.getIntKey(), e.getValue());
|
||||
}
|
||||
}
|
||||
for (var it = vertexMemberInfo.int2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
|
||||
final var e = it.next();
|
||||
final int k = e.getIntKey();
|
||||
if (k >= 0 && k < n) vsInfoBySlot[k] = e.getValue();
|
||||
fillSlots(vsInfoBySlot, e.getIntKey(), e.getValue());
|
||||
}
|
||||
for (var it = fragmentMemberInfo.int2ObjectEntrySet().fastIterator(); it.hasNext(); ) {
|
||||
final var e = it.next();
|
||||
final int k = e.getIntKey();
|
||||
if (k >= 0 && k < n) fsInfoBySlot[k] = e.getValue();
|
||||
fillSlots(fsInfoBySlot, e.getIntKey(), e.getValue());
|
||||
}
|
||||
floatLimitBySlot = new int[n];
|
||||
for (int slot = 0; slot < n; slot++) {
|
||||
if (arrayLenBySlot[slot] > 1) {
|
||||
floatLimitBySlot[slot] = -1;
|
||||
continue;
|
||||
}
|
||||
final UniformMemberInfo info = memberInfo(slot);
|
||||
final int floats = info == null || info.arrayStride() > 0 ? 0 : info.vectorSize() * info.columns();
|
||||
floatLimitBySlot[slot] = floats > 0 ? floats : Integer.MAX_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
private void fillSlots(UniformMemberInfo[] bySlot, int base, UniformMemberInfo info) {
|
||||
final int n = bySlot.length;
|
||||
if (base < 0 || base >= n) return;
|
||||
bySlot[base] = info;
|
||||
final int len = arrayLenBySlot[base];
|
||||
for (int i = 1; i < len; i++) bySlot[base + i] = info.element(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.shader;
|
||||
|
||||
public final class UniformNames {
|
||||
|
||||
private UniformNames() {}
|
||||
|
||||
public static int arrayIndex(String name) {
|
||||
final int len = name.length();
|
||||
if (len == 0 || name.charAt(len - 1) != ']') return -1;
|
||||
int i = len - 1;
|
||||
while (i > 0 && isDigit(name.charAt(i - 1))) i--;
|
||||
if (i == 0 || name.charAt(i - 1) != '[') return -1;
|
||||
if (name.charAt(i) == '0' && i + 1 != len - 1) return -1;
|
||||
long index = 0;
|
||||
for (int c = i; c < len - 1; c++) {
|
||||
index = index * 10 + (name.charAt(c) - '0');
|
||||
if (index > Integer.MAX_VALUE) return Integer.MAX_VALUE;
|
||||
}
|
||||
return (int) index;
|
||||
}
|
||||
|
||||
private static boolean isDigit(char c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
}
|
||||
+13
-12
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.texture.ProxyTexture;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -47,11 +48,11 @@ class ProxyTextureLimitsTest {
|
||||
assertEquals(2048, SDLGPURenderBackend.deviceLimit(GL12.GL_MAX_3D_TEXTURE_SIZE));
|
||||
assertEquals(16384, SDLGPURenderBackend.deviceLimit(GL13.GL_MAX_CUBE_MAP_TEXTURE_SIZE));
|
||||
|
||||
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL11.GL_PROXY_TEXTURE_2D));
|
||||
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL11.GL_PROXY_TEXTURE_1D));
|
||||
assertEquals(2048, SDLGPURenderBackend.proxyDimensionLimit(GL12.GL_PROXY_TEXTURE_3D));
|
||||
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL13.GL_PROXY_TEXTURE_CUBE_MAP));
|
||||
assertEquals(16384, SDLGPURenderBackend.proxyDimensionLimit(GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY));
|
||||
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL11.GL_PROXY_TEXTURE_2D));
|
||||
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL11.GL_PROXY_TEXTURE_1D));
|
||||
assertEquals(2048, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL12.GL_PROXY_TEXTURE_3D));
|
||||
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL13.GL_PROXY_TEXTURE_CUBE_MAP));
|
||||
assertEquals(16384, SdlTestRig.contextState().proxyTexture.dimensionLimit(GL40.GL_PROXY_TEXTURE_CUBE_MAP_ARRAY));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -99,13 +100,13 @@ class ProxyTextureLimitsTest {
|
||||
|
||||
@Test
|
||||
void levelZeroEquivalentSizeRejectsOverflowAndNonsense() {
|
||||
assertEquals(4, SDLGPURenderBackend.levelZeroEquivalentSize(4, 0));
|
||||
assertEquals(32, SDLGPURenderBackend.levelZeroEquivalentSize(4, 3));
|
||||
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, -1));
|
||||
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(0, 2));
|
||||
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, 31));
|
||||
assertEquals(0, SDLGPURenderBackend.levelZeroEquivalentSize(4, 30));
|
||||
assertEquals(1 << 30, SDLGPURenderBackend.levelZeroEquivalentSize(1, 30));
|
||||
assertEquals(4, ProxyTexture.levelZeroEquivalentSize(4, 0));
|
||||
assertEquals(32, ProxyTexture.levelZeroEquivalentSize(4, 3));
|
||||
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(4, -1));
|
||||
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(0, 2));
|
||||
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(4, 31));
|
||||
assertEquals(0, ProxyTexture.levelZeroEquivalentSize(4, 30));
|
||||
assertEquals(1 << 30, ProxyTexture.levelZeroEquivalentSize(1, 30));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureInfo;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
@@ -81,7 +82,7 @@ class ProxyTextureTargetTest {
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("levelSizeCases")
|
||||
void levelSizeMatchesExpectation(String label, int base, int level, int expected) {
|
||||
assertEquals(expected, SDLGPURenderBackend.proxyLevelSize(base, level), label);
|
||||
assertEquals(expected, TextureInfo.mipLevelSize(base, level), label);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Loaded 100 of 182 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user