mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-11-15 01:34:48 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e46eb46343 | ||
|
|
5da38c9180 | ||
|
|
85bde5827f | ||
|
|
ea2d34589e | ||
|
|
2b46b4f602 | ||
|
|
8e099e9529 | ||
|
|
bbbdc12b2a |
No files matched your search
@@ -45,6 +45,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");
|
||||
@@ -77,6 +79,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,13 @@ 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 CaptureGate() {}
|
||||
|
||||
public static void refresh() {
|
||||
markersThisFrame = gateValue(FORCED, TOOL_ATTACHED, Tracy.isConnected());
|
||||
tracyConnected = Tracy.isConnected();
|
||||
markersThisFrame = gateValue(FORCED, TOOL_ATTACHED, tracyConnected);
|
||||
}
|
||||
|
||||
static boolean gateValue(boolean forced, boolean toolAttached, boolean tracyConnected) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
@@ -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",
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
+7
@@ -6,7 +6,9 @@ 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 me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
|
||||
import me.eigenraven.lwjgl3ify.client.MainThreadExec;
|
||||
@@ -792,6 +794,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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -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).
|
||||
#
|
||||
|
||||
@@ -18,6 +18,7 @@ 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.TextureInfoCache;
|
||||
import com.gtnewhorizons.angelica.glsm.texture.TextureStaging;
|
||||
@@ -42,6 +43,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;
|
||||
@@ -168,7 +170,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 +1380,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 +1461,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);
|
||||
@@ -2195,6 +2195,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 ? PixelOps.mipLevelSize(meta.width(), out.level) : fbo.width;
|
||||
out.height = meta != null ? PixelOps.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 +2250,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;
|
||||
@@ -2348,7 +2373,7 @@ public class SDLGPURenderBackend extends RenderBackend {
|
||||
} finally {
|
||||
region.clear();
|
||||
}
|
||||
resourceManager.markBufferContentsDefined(pbo);
|
||||
onBufferContentsWritten(pbo);
|
||||
flushAndSubmitMidFrame();
|
||||
}
|
||||
|
||||
@@ -2955,7 +2980,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 +3069,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 +3085,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 +3125,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 +3142,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 +3157,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 +3247,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 +3256,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 +3321,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 +3335,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 +3380,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 +3435,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 +3555,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 +4087,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 +4097,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 +4114,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) {
|
||||
@@ -4331,7 +4378,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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.sdlgpu.frame;
|
||||
import com.gtnewhorizons.angelica.glsm.GLContextState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SDLGPURenderBackend;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.pipeline.PipelineCache;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ColorSource;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.MappedRange;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.PackState;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpVariant;
|
||||
@@ -64,6 +65,7 @@ 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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+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;
|
||||
|
||||
+215
@@ -3,6 +3,15 @@ package com.gtnewhorizons.angelica.sdlgpu.glsm;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMFramebuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMTextures;
|
||||
import com.gtnewhorizons.angelica.glsm.GlTestSupport;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.FenceTracker;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.ReadbackShadows;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.resource.SdlReflect;
|
||||
import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
@@ -10,11 +19,19 @@ import org.lwjgl.BufferUtils;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL15;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
import org.lwjgl.opengl.GL31;
|
||||
import org.lwjgl.opengl.GL32;
|
||||
import org.lwjgl.opengl.GL43;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Tag("glsm-sdl")
|
||||
@ExtendWith(GlsmSdlFrameExtension.class)
|
||||
@@ -26,6 +43,14 @@ class GlsmSdlReadbackTest {
|
||||
private static final int B = 0x56;
|
||||
private static final int A = 0xFF;
|
||||
|
||||
private static final int SRC_W = 64;
|
||||
private static final int SRC_H = 32;
|
||||
private static final int CAPTURE_W = 16;
|
||||
private static final int CAPTURE_H = 8;
|
||||
private static final int COPY_BYTES = 64;
|
||||
|
||||
private record StreamReadCopy(int src, int dst, ByteBuffer data) {}
|
||||
|
||||
@Test
|
||||
void getTexImageSeesDeferredTextureUpload() {
|
||||
final int texture = GLSMTextures.glGenTextures();
|
||||
@@ -84,6 +109,196 @@ class GlsmSdlReadbackTest {
|
||||
assertEquals(0xFF, pixel.get(3) & 0xFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
void screenCaptureSeesUserFramebuffer() {
|
||||
final int texture = GlsmSdlHeadlessRig.newNearestTexture();
|
||||
GLSMTextures.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SRC_W, SRC_H, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
final int framebuffer = GlsmSdlHeadlessRig.fboWithColor(texture, 0);
|
||||
GlTestSupport.fillQuadrants(SRC_W, SRC_H);
|
||||
final ScreenCapture capture = GLSMFramebuffer.createScreenCapture(CAPTURE_W, CAPTURE_H);
|
||||
try {
|
||||
assertTrue(capture.begin(framebuffer, SRC_W, SRC_H, 7L), "capture was not recorded");
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
awaitPoll(capture);
|
||||
assertEquals(7L, capture.tag());
|
||||
GlTestSupport.assertQuadrantsBottomUp(capture.address(), CAPTURE_W, CAPTURE_H);
|
||||
capture.release();
|
||||
|
||||
final long texture0 = Reflect.<long[]>get(capture, "textures")[0];
|
||||
final long transferBuffer0 = Reflect.<long[]>get(capture, "transferBuffers")[0];
|
||||
assertNotEquals(0L, texture0, "slot 0 was never created");
|
||||
assertNotEquals(0L, transferBuffer0, "slot 0 was never created");
|
||||
capture.delete();
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
assertNotLive(texture0, "liveTextureHandles");
|
||||
assertNotLive(transferBuffer0, "liveTransferBufferHandles");
|
||||
} finally {
|
||||
capture.delete();
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLSMFramebuffer.glDeleteFramebuffers(framebuffer);
|
||||
GLSMTextures.glDeleteTextures(texture);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertNotLive(long handle, String field) {
|
||||
final LongOpenHashSet live = Reflect.get(GlsmSdlHeadlessRig.resourceManager(), field);
|
||||
assertFalse(live.contains(handle), field + " still holds capture handle " + handle + " after delete");
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteSyncBeforeSubmitIsNotResurrected() {
|
||||
final long sync = GLSMBuffers.glFenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
GLSMBuffers.glDeleteSync(sync);
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
GlsmSdlHeadlessRig.beginFrame();
|
||||
|
||||
final FenceTracker fences = Reflect.get(BackendManager.RENDER_BACKEND, "fenceTracker");
|
||||
final Long2LongOpenHashMap fenceMap = Reflect.get(fences, "fenceMap");
|
||||
assertFalse(fenceMap.containsKey(sync), "a sync deleted before its submit must not be bound to the frame fence afterwards");
|
||||
}
|
||||
|
||||
private static void awaitPoll(ScreenCapture capture) {
|
||||
final long deadline = System.nanoTime() + 5_000_000_000L;
|
||||
while (!capture.poll()) {
|
||||
assertTrue(System.nanoTime() < deadline, "capture did not complete within 5 s");
|
||||
LockSupport.parkNanos(1_000_000L);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamReadCopyServesWithoutAppFence() {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
final StreamReadCopy copy = streamReadCopy(false);
|
||||
assertTrue(hasShadow(copy.dst()), "copy into a GL_STREAM_READ buffer must take the shadow path");
|
||||
final ReadbackShadows shadows = Reflect.get(BackendManager.RENDER_BACKEND, "readbackShadows");
|
||||
assertNotEquals(0L, SdlReflect.readbackSlotSync(shadows, copy.dst(), 0), "the shadow download must carry a fence");
|
||||
|
||||
final ByteBuffer read = BufferUtils.createByteBuffer(COPY_BYTES);
|
||||
GLSMBuffers.glGetBufferSubData(GL31.GL_COPY_WRITE_BUFFER, 0, read);
|
||||
|
||||
assertBytes(copy.data(), read, "glGetBufferSubData right after the copy");
|
||||
assertEquals(0L, SdlReflect.readbackSlotSync(shadows, copy.dst(), 0), "the read must be served from the shadow, which consumes its fence");
|
||||
GLSMBuffers.glDeleteBuffers(copy.src());
|
||||
GLSMBuffers.glDeleteBuffers(copy.dst());
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamReadShadowSkippedWhileBoundAsSsbo() {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
final StreamReadCopy copy = streamReadCopy(true);
|
||||
assertFalse(hasShadow(copy.dst()), "a destination bound as an SSBO can be written by the GPU, so it must not be shadowed");
|
||||
|
||||
final ByteBuffer read = BufferUtils.createByteBuffer(COPY_BYTES);
|
||||
GLSMBuffers.glGetBufferSubData(GL31.GL_COPY_WRITE_BUFFER, 0, read);
|
||||
assertBytes(copy.data(), read, "glGetBufferSubData of an SSBO-bound copy destination");
|
||||
GLSMBuffers.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, 3, 0);
|
||||
GLSMBuffers.glDeleteBuffers(copy.src());
|
||||
GLSMBuffers.glDeleteBuffers(copy.dst());
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamReadCopyVisibleToMapAndGpu() {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
final StreamReadCopy copy = streamReadCopy(false);
|
||||
|
||||
final ByteBuffer mapped = GLSMBuffers.glMapBufferRange(GL31.GL_COPY_WRITE_BUFFER, 0, COPY_BYTES, GL30.GL_MAP_READ_BIT);
|
||||
assertNotNull(mapped, "glMapBufferRange returned null");
|
||||
assertBytes(copy.data(), mapped, "glMapBufferRange READ of the copy destination");
|
||||
GLSMBuffers.glUnmapBuffer(GL31.GL_COPY_WRITE_BUFFER);
|
||||
|
||||
final int third = GLSMBuffers.glGenBuffers();
|
||||
GLSMBuffers.glBindBuffer(GL31.GL_COPY_READ_BUFFER, copy.dst());
|
||||
GLSMBuffers.glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, third);
|
||||
GLSMBuffers.glBufferData(GL31.GL_COPY_WRITE_BUFFER, COPY_BYTES, GL15.GL_DYNAMIC_DRAW);
|
||||
GLSMBuffers.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, 0, 0, COPY_BYTES);
|
||||
|
||||
final ByteBuffer read = BufferUtils.createByteBuffer(COPY_BYTES);
|
||||
GLSMBuffers.glGetBufferSubData(GL31.GL_COPY_WRITE_BUFFER, 0, read);
|
||||
assertBytes(copy.data(), read, "GPU copy out of the copy destination");
|
||||
|
||||
GLSMBuffers.glDeleteBuffers(third);
|
||||
GLSMBuffers.glDeleteBuffers(copy.src());
|
||||
GLSMBuffers.glDeleteBuffers(copy.dst());
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamReadShadowDroppedOnSubData() {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
final StreamReadCopy copy = streamReadCopy(false);
|
||||
|
||||
final ByteBuffer other = BufferUtils.createByteBuffer(COPY_BYTES);
|
||||
for (int i = 0; i < COPY_BYTES; i++) other.put(i, (byte) (0xA0 ^ i));
|
||||
GLSMBuffers.glBufferSubData(GL31.GL_COPY_WRITE_BUFFER, 0, other);
|
||||
|
||||
final ByteBuffer read = BufferUtils.createByteBuffer(COPY_BYTES);
|
||||
GLSMBuffers.glGetBufferSubData(GL31.GL_COPY_WRITE_BUFFER, 0, read);
|
||||
|
||||
assertBytes(other, read, "glGetBufferSubData after glBufferSubData over the copy");
|
||||
GLSMBuffers.glDeleteBuffers(copy.src());
|
||||
GLSMBuffers.glDeleteBuffers(copy.dst());
|
||||
}
|
||||
|
||||
@Test
|
||||
void streamReadShadowServesOnlyDownloadedBytes() {
|
||||
GlsmSdlHeadlessRig.endFrame();
|
||||
final StreamReadCopy copy = streamReadCopy(false);
|
||||
final int half = COPY_BYTES / 2;
|
||||
final int quarter = COPY_BYTES / 4;
|
||||
|
||||
final ByteBuffer tail = BufferUtils.createByteBuffer(half);
|
||||
for (int i = 0; i < half; i++) tail.put(i, (byte) (0x50 + i));
|
||||
GLSMBuffers.glBufferSubData(GL31.GL_COPY_WRITE_BUFFER, half, tail);
|
||||
|
||||
final ByteBuffer head = BufferUtils.createByteBuffer(quarter);
|
||||
for (int i = 0; i < quarter; i++) head.put(i, (byte) (0xC0 + i));
|
||||
final int src2 = GLSMBuffers.glGenBuffers();
|
||||
GLSMBuffers.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, src2);
|
||||
GLSMBuffers.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, head, GL15.GL_DYNAMIC_DRAW);
|
||||
GLSMBuffers.glBindBuffer(GL31.GL_COPY_READ_BUFFER, src2);
|
||||
GLSMBuffers.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, 0, 0, quarter);
|
||||
|
||||
final ByteBuffer read = BufferUtils.createByteBuffer(COPY_BYTES);
|
||||
GLSMBuffers.glGetBufferSubData(GL31.GL_COPY_WRITE_BUFFER, 0, read);
|
||||
|
||||
for (int i = 0; i < COPY_BYTES; i++) {
|
||||
final byte expected = i < quarter ? head.get(i) : i < half ? copy.data().get(i) : tail.get(i - half);
|
||||
assertEquals(expected, read.get(i), "byte " + i + " after a shorter copy reused a larger shadow");
|
||||
}
|
||||
GLSMBuffers.glDeleteBuffers(src2);
|
||||
GLSMBuffers.glDeleteBuffers(copy.src());
|
||||
GLSMBuffers.glDeleteBuffers(copy.dst());
|
||||
}
|
||||
|
||||
private static StreamReadCopy streamReadCopy(boolean dstBoundAsSsbo) {
|
||||
final ByteBuffer data = BufferUtils.createByteBuffer(COPY_BYTES);
|
||||
for (int i = 0; i < COPY_BYTES; i++) data.put(i, (byte) (i * 7 + 3));
|
||||
|
||||
final int src = GLSMBuffers.glGenBuffers();
|
||||
GLSMBuffers.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, src);
|
||||
GLSMBuffers.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, data, GL15.GL_DYNAMIC_DRAW);
|
||||
|
||||
final int dst = GLSMBuffers.glGenBuffers();
|
||||
GLSMBuffers.glBindBuffer(GL31.GL_COPY_READ_BUFFER, src);
|
||||
GLSMBuffers.glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, dst);
|
||||
GLSMBuffers.glBufferData(GL31.GL_COPY_WRITE_BUFFER, COPY_BYTES, GL15.GL_STREAM_READ);
|
||||
if (dstBoundAsSsbo) GLSMBuffers.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, 3, dst);
|
||||
GLSMBuffers.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, 0, 0, COPY_BYTES);
|
||||
return new StreamReadCopy(src, dst, data);
|
||||
}
|
||||
|
||||
private static boolean hasShadow(int glId) {
|
||||
final ReadbackShadows shadows = Reflect.get(BackendManager.RENDER_BACKEND, "readbackShadows");
|
||||
return shadows.hasSlotsFor(glId);
|
||||
}
|
||||
|
||||
private static void assertBytes(ByteBuffer expected, ByteBuffer actual, String what) {
|
||||
for (int i = 0; i < COPY_BYTES; i++) {
|
||||
assertEquals(expected.get(i), actual.get(i), what + ": byte " + i);
|
||||
}
|
||||
}
|
||||
|
||||
private static ByteBuffer solidTexture(int width, int height) {
|
||||
final ByteBuffer pixels = BufferUtils.createByteBuffer(width * height * 4);
|
||||
for (int i = 0; i < width * height; i++) {
|
||||
|
||||
+15
-12
@@ -1,12 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.SdlAsserts;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
@@ -19,7 +17,7 @@ class ReadbackShadowsTest {
|
||||
private static final int SLOT_STRIDE = 64;
|
||||
|
||||
private static ReadbackShadows shadows() {
|
||||
return new ReadbackShadows(null);
|
||||
return new ReadbackShadows(null, null, null);
|
||||
}
|
||||
|
||||
private static ByteBuffer out(int bytes) {
|
||||
@@ -36,15 +34,24 @@ class ReadbackShadowsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidateTouchesOnlyItsOwnSlot() {
|
||||
void invalidateAllTouchesOnlyItsBuffer() {
|
||||
final ReadbackShadows shadows = shadows();
|
||||
SdlReflect.putReadbackSlot(shadows, 7, 0, SENTINEL_HANDLE, 64, true);
|
||||
SdlReflect.putReadbackSlot(shadows, 7, 64, SENTINEL_HANDLE, 64, true);
|
||||
SdlReflect.putReadbackSlot(shadows, 8, 0, SENTINEL_HANDLE, 64, true);
|
||||
|
||||
shadows.invalidate(7, 0);
|
||||
shadows.invalidateAll(7);
|
||||
|
||||
assertFalse(shadows.serve(7, 0, out(64), 64), "the invalidated slot is still being served");
|
||||
assertTrue(SdlReflect.readbackSlotValid(shadows, 7, 64), "invalidating one ring slot must not disturb the others");
|
||||
assertFalse(shadows.serve(7, 0, out(64), 64), "an invalidated slot is still being served");
|
||||
assertFalse(SdlReflect.readbackSlotValid(shadows, 7, 64), "every slot of the written buffer must be invalidated");
|
||||
assertTrue(SdlReflect.readbackSlotValid(shadows, 8, 0), "a write to one buffer must not invalidate another's slots");
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidateAllOfAnUnshadowedBufferDoesNotAllocate() {
|
||||
final ReadbackShadows shadows = shadows();
|
||||
SdlReflect.putReadbackSlot(shadows, 8, 0, SENTINEL_HANDLE, 64, true);
|
||||
SdlAsserts.assertAllocationFree(() -> shadows.invalidateAll(7), "buffer writes while another buffer is shadowed");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -60,10 +67,6 @@ class ReadbackShadowsTest {
|
||||
assertTrue(shadows.hasSlotsFor(8), "deleting one buffer must not drop another's slots");
|
||||
}
|
||||
|
||||
private static int slotCount(ReadbackShadows shadows) {
|
||||
return Reflect.<Map<Long, Object>>get(shadows, "slots").size();
|
||||
}
|
||||
|
||||
private static ReadbackShadows ringShadows(int... glIds) {
|
||||
final ReadbackShadows shadows = shadows();
|
||||
for (int glId : glIds) {
|
||||
@@ -81,7 +84,7 @@ class ReadbackShadowsTest {
|
||||
shadows.release(2);
|
||||
|
||||
assertFalse(shadows.hasSlotsFor(2));
|
||||
assertEquals(2 * SLOTS_PER_BUFFER, slotCount(shadows));
|
||||
assertEquals(2 * SLOTS_PER_BUFFER, SdlReflect.readbackSlotCount(shadows));
|
||||
for (int glId = 1; glId <= 3; glId += 2) {
|
||||
for (int i = 0; i < SLOTS_PER_BUFFER; i++) {
|
||||
assertTrue(SdlReflect.readbackSlotValid(shadows, glId, (long) i * SLOT_STRIDE));
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package com.gtnewhorizons.angelica.sdlgpu.resource;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.testutil.Reflect;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
|
||||
|
||||
public final class SdlReflect {
|
||||
@@ -30,20 +31,27 @@ public final class SdlReflect {
|
||||
final Object slot = Reflect.construct(slotClass);
|
||||
Reflect.setDeclared(slotClass, slot, "transferBuffer", handle);
|
||||
Reflect.setDeclared(slotClass, slot, "capacity", capacity);
|
||||
Reflect.setDeclared(slotClass, slot, "validLen", capacity);
|
||||
Reflect.setDeclared(slotClass, slot, "valid", valid);
|
||||
readbackSlots(shadows).put(readbackKey(glId, offset), slot);
|
||||
readbackSlots(shadows).computeIfAbsent(glId, k -> new Long2ObjectOpenHashMap<>()).put(offset, slot);
|
||||
}
|
||||
|
||||
static boolean readbackSlotValid(ReadbackShadows shadows, int glId, long offset) {
|
||||
return Reflect.get(readbackSlots(shadows).get(readbackKey(glId, offset)), "valid");
|
||||
return Reflect.get(readbackSlots(shadows).get(glId).get(offset), "valid");
|
||||
}
|
||||
|
||||
private static long readbackKey(int glId, long offset) {
|
||||
return (((long) glId) << 32) | (offset & 0xFFFFFFFFL);
|
||||
public static long readbackSlotSync(ReadbackShadows shadows, int glId, long offset) {
|
||||
return Reflect.get(readbackSlots(shadows).get(glId).get(offset), "sync");
|
||||
}
|
||||
|
||||
private static Map<Long, Object> readbackSlots(ReadbackShadows shadows) {
|
||||
return Reflect.get(shadows, "slots");
|
||||
static int readbackSlotCount(ReadbackShadows shadows) {
|
||||
int count = 0;
|
||||
for (Long2ObjectOpenHashMap<Object> slots : readbackSlots(shadows).values()) count += slots.size();
|
||||
return count;
|
||||
}
|
||||
|
||||
private static Int2ObjectOpenHashMap<Long2ObjectOpenHashMap<Object>> readbackSlots(ReadbackShadows shadows) {
|
||||
return Reflect.get(shadows, "byBuffer");
|
||||
}
|
||||
|
||||
static void recordBuffer(ResourceManager rm, int glId, long handle, long size, int usage) {
|
||||
|
||||
@@ -99,6 +99,22 @@ public class TracyOptionPages {
|
||||
.build())
|
||||
.add(new ActionOption(I18n.format("options.angelica.tracy.captureAction"), I18n.format("options.angelica.tracy.captureAction.tooltip"),
|
||||
captureActionLabel, TracyCaptureNotifier.INSTANCE::toggle, () -> Tracy.ENABLED))
|
||||
.build(),
|
||||
OptionGroup.createBuilder()
|
||||
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
|
||||
.setName(I18n.format("options.angelica.tracy.frameImages"))
|
||||
.setTooltip(overrideTooltip("options.angelica.tracy.frameImages.tooltip", "angelica.tracy.frameImages", SystemProperties.TRACY_FRAME_IMAGES_OVERRIDE != null))
|
||||
.setControl(TickBoxControl::new)
|
||||
.setBinding((opts, value) -> AngelicaConfig.tracyFrameImages = value, opts -> AngelicaConfig.tracyFrameImages)
|
||||
.setEnabled(Tracy.ENABLED && SystemProperties.TRACY_FRAME_IMAGES_OVERRIDE == null)
|
||||
.build())
|
||||
.add(OptionImpl.createBuilder(int.class, angelicaOpts)
|
||||
.setName(I18n.format("options.angelica.tracy.frameImageRate"))
|
||||
.setTooltip(overrideTooltip("options.angelica.tracy.frameImageRate.tooltip", "angelica.tracy.frameImageRate", SystemProperties.TRACY_FRAME_IMAGE_RATE_OVERRIDE != null))
|
||||
.setControl(o -> new SliderControl(o, 0, 60, 5, ControlValueFormatter.quantityOrDisabled("options.angelica.tracy.frameImageRate.value", "options.angelica.tracy.frameImageRate.everyFrame")))
|
||||
.setBinding((opts, value) -> AngelicaConfig.tracyFrameImageRate = value, opts -> AngelicaConfig.tracyFrameImageRate)
|
||||
.setEnabled(Tracy.ENABLED && SystemProperties.TRACY_FRAME_IMAGE_RATE_OVERRIDE == null)
|
||||
.build())
|
||||
.build()));
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.gtnewhorizons.angelica.glsm.GLSMVertexInput;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.glsm.ffp.FragmentKey;
|
||||
import com.gtnewhorizons.angelica.glsm.hooks.BatchStateGuard;
|
||||
import com.gtnewhorizons.angelica.glsm.states.FogState;
|
||||
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
|
||||
import net.coderbot.iris.gl.framebuffer.GlFramebuffer;
|
||||
@@ -181,6 +182,7 @@ final class PortalDrawer {
|
||||
}
|
||||
|
||||
void drawDirect(float[] faces, int count, int tunnelTexture, int fieldTexture, Frame frame, boolean fogged) {
|
||||
BatchStateGuard.beforeChange();
|
||||
final int fogMode = fogged ? FragmentKey.activeFogMode(GLStateManager.ctx()) : FragmentKey.FOG_NONE;
|
||||
final GlProgram<Uniforms> program;
|
||||
if (fogMode == FragmentKey.FOG_NONE) {
|
||||
|
||||
@@ -556,6 +556,15 @@ public class AngelicaConfig {
|
||||
@Config.RangeInt(min = 0, max = 3600)
|
||||
public static int tracyCaptureSeconds;
|
||||
|
||||
@Config.Comment("If enabled, attaches downscaled screenshots to Tracy frames. Overridden by -Dangelica.tracy.frameImages")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean tracyFrameImages;
|
||||
|
||||
@Config.Comment("Tracy frame images captured per second. 0 = every frame. Overridden by -Dangelica.tracy.frameImageRate")
|
||||
@Config.DefaultInt(10)
|
||||
@Config.RangeInt(min = 0, max = 60)
|
||||
public static int tracyFrameImageRate;
|
||||
|
||||
public static void applyGpuCullingMode() {
|
||||
GpuCulling.setMode(gpuCullingMode == null ? GpuCullingMode.CPU_ONLY : gpuCullingMode);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.gtnewhorizons.angelica.debug.flyby;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.debug.profiling.AsprofRecorder;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.AngelicaRenderSectionManager;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.DeferredMeshScheduler;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.world.WorldSlice;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.world.cloned.ClonedChunkSection;
|
||||
import com.sun.management.HotSpotDiagnosticMXBean;
|
||||
import it.unimi.dsi.fastutil.objects.Object2LongMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
|
||||
import net.minecraft.client.entity.EntityClientPlayerMP;
|
||||
import net.minecraft.client.multiplayer.WorldClient;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.RenderSection;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.compile.ChunkBuildBuffers;
|
||||
|
||||
import javax.management.ObjectName;
|
||||
import java.io.File;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
final class FlybyHeapCheck {
|
||||
private static final Logger LOGGER = LogManager.getLogger("Angelica/Flyby");
|
||||
private static final int TOP_GROWERS = 10;
|
||||
private static final int MIN_REPORTS_FOR_GROWTH = 3;
|
||||
|
||||
private final List<Object2LongOpenHashMap<String>> history = new ArrayList<>();
|
||||
private final Object2LongOpenHashMap<String> maxRetained = new Object2LongOpenHashMap<>();
|
||||
private String skipReason;
|
||||
|
||||
private static Class<?>[] mustBeZero() {
|
||||
return new Class<?>[] {AngelicaRenderSectionManager.class, WorldSlice.class, ClonedChunkSection.class, DeferredMeshScheduler.class, RenderSection.class, ChunkBuildBuffers.class, WorldClient.class};
|
||||
}
|
||||
|
||||
private static Class<?>[] watched() {
|
||||
return new Class<?>[] {Chunk.class, EntityClientPlayerMP.class};
|
||||
}
|
||||
|
||||
void report(String label) {
|
||||
final Object2LongOpenHashMap<String> counts;
|
||||
try {
|
||||
// Objects with a pending finalize() (AE2 part monitors) count as live and keep their world until finalized.
|
||||
System.gc();
|
||||
System.runFinalization();
|
||||
counts = liveHistogram();
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Flyby heap check: histogram failed", e);
|
||||
skip("histogram failed: " + e);
|
||||
return;
|
||||
}
|
||||
final StringBuilder sb = new StringBuilder("Flyby heap ").append(label).append(':');
|
||||
for (Class<?> type : mustBeZero()) {
|
||||
final long count = counts.getLong(type.getName());
|
||||
if (count > maxRetained.getLong(type.getName())) maxRetained.put(type.getName(), count);
|
||||
sb.append(' ').append(type.getSimpleName()).append('=').append(count);
|
||||
}
|
||||
sb.append(" | watched");
|
||||
for (Class<?> type : watched()) {
|
||||
sb.append(' ').append(type.getSimpleName()).append('=').append(counts.getLong(type.getName()));
|
||||
}
|
||||
LOGGER.info(sb);
|
||||
history.add(counts);
|
||||
}
|
||||
|
||||
void skip(String reason) {
|
||||
if (skipReason == null) skipReason = reason;
|
||||
}
|
||||
|
||||
void finish() {
|
||||
if (skipReason != null) {
|
||||
LOGGER.warn("Flyby heap check: SKIPPED ({})", skipReason);
|
||||
return;
|
||||
}
|
||||
if (history.size() >= MIN_REPORTS_FOR_GROWTH) logGrewEveryVisit();
|
||||
final StringBuilder retained = new StringBuilder();
|
||||
for (Class<?> type : mustBeZero()) {
|
||||
final long max = maxRetained.getLong(type.getName());
|
||||
if (max > 0) retained.append(' ').append(type.getSimpleName()).append('=').append(max);
|
||||
}
|
||||
if (retained.length() == 0) {
|
||||
LOGGER.info("Flyby heap check: PASS");
|
||||
return;
|
||||
}
|
||||
LOGGER.warn("Flyby heap check: FAIL{}", retained);
|
||||
dump();
|
||||
}
|
||||
|
||||
private void logGrewEveryVisit() {
|
||||
final Object2LongOpenHashMap<String> first = history.get(0);
|
||||
final Object2LongOpenHashMap<String> last = history.get(history.size() - 1);
|
||||
final List<Object2LongMap.Entry<String>> grew = new ArrayList<>();
|
||||
for (Object2LongMap.Entry<String> e : last.object2LongEntrySet()) {
|
||||
if (roseAtEveryReport(e.getKey())) grew.add(e);
|
||||
}
|
||||
grew.sort((a, b) -> Long.compare(b.getLongValue() - first.getLong(b.getKey()), a.getLongValue() - first.getLong(a.getKey())));
|
||||
for (int i = 0; i < Math.min(TOP_GROWERS, grew.size()); i++) {
|
||||
final Object2LongMap.Entry<String> e = grew.get(i);
|
||||
LOGGER.info("Flyby heap: grew at every visit: {} {} -> {}", e.getKey(), first.getLong(e.getKey()), e.getLongValue());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean roseAtEveryReport(String className) {
|
||||
for (int i = 1; i < history.size(); i++) {
|
||||
if (history.get(i).getLong(className) <= history.get(i - 1).getLong(className)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void dump() {
|
||||
final File file = new File(SystemProperties.PROFILE_DIR, AsprofRecorder.fileName("leak", "hprof", System.currentTimeMillis())).getAbsoluteFile();
|
||||
try {
|
||||
file.getParentFile().mkdirs();
|
||||
ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class).dumpHeap(file.getPath(), true);
|
||||
LOGGER.info("Flyby heap: dumped {}", file.getPath());
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn("Flyby heap dump to {} failed", file.getPath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Object2LongOpenHashMap<String> liveHistogram() throws Exception {
|
||||
final String histogram = (String) ManagementFactory.getPlatformMBeanServer().invoke(new ObjectName("com.sun.management:type=DiagnosticCommand"), "gcClassHistogram", new Object[] {null}, new String[] {String[].class.getName()});
|
||||
final Object2LongOpenHashMap<String> counts = new Object2LongOpenHashMap<>();
|
||||
for (String line : histogram.split("\n")) {
|
||||
final String[] cols = line.trim().split("\\s+");
|
||||
if (cols.length >= 4 && cols[0].endsWith(":")) counts.put(cols[3], Long.parseLong(cols[1]));
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
}
|
||||
@@ -13,15 +13,19 @@ import com.gtnewhorizons.angelica.glsm.profiling.DebugCounters;
|
||||
import com.gtnewhorizons.angelica.rendering.FpsReducer;
|
||||
import com.gtnewhorizons.angelica.rendering.FramePacer;
|
||||
import com.gtnewhorizons.angelica.rendering.IsbrhTestRenderer;
|
||||
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
|
||||
import com.sun.management.OperatingSystemMXBean;
|
||||
import com.sun.management.ThreadMXBean;
|
||||
import cpw.mods.fml.client.FMLClientHandler;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.common.gameevent.TickEvent;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.entity.EntityClientPlayerMP;
|
||||
import net.minecraft.client.gui.GuiMainMenu;
|
||||
import net.minecraft.client.gui.GuiSelectWorld;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.server.integrated.IntegratedServer;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.ChunkCoordinates;
|
||||
@@ -60,12 +64,17 @@ public final class FlybyRunner {
|
||||
|
||||
public static final FlybyRunner INSTANCE = new FlybyRunner();
|
||||
|
||||
private enum State { IDLE, WAITING, PREPARING, WARMUP, RUNNING, SETTLE, EXITING, DONE }
|
||||
private enum State { IDLE, WAITING, PREPARING, WARMUP, RUNNING, SETTLE, EXITING, MENU, DONE }
|
||||
|
||||
private static final int EXIT_TICKS = 20;
|
||||
private static final int SETTLE_TICKS = 40;
|
||||
private static final int MENU_SETTLE_TICKS = 20;
|
||||
|
||||
private final FlybyHeapCheck heapCheck = new FlybyHeapCheck();
|
||||
private State state = State.IDLE;
|
||||
private int visit;
|
||||
private String rejoinFolder;
|
||||
private String rejoinName;
|
||||
private FlybyRoute route;
|
||||
private int warmupTicks;
|
||||
private int runLength;
|
||||
@@ -189,6 +198,14 @@ public final class FlybyRunner {
|
||||
this.waitForFocus = SystemProperties.FLYBY_WAIT_FOR_FOCUS;
|
||||
this.exitWhenDone = SystemProperties.FLYBY_EXIT_WHEN_DONE;
|
||||
this.jfr = SystemProperties.FLYBY_JFR;
|
||||
if (this.jfr && SystemProperties.FLYBY_VISITS > 1) {
|
||||
LOGGER.error("Flyby: angelica.flyby.jfr records one file per run and cannot cover {} visits; exiting instead of profiling only the first", SystemProperties.FLYBY_VISITS);
|
||||
FMLCommonHandler.instance().exitJava(1, false);
|
||||
}
|
||||
if (!this.exitWhenDone && (SystemProperties.FLYBY_VISITS > 1 || SystemProperties.FLYBY_HEAP_CHECK)) {
|
||||
LOGGER.error("Flyby: angelica.flyby.visits and angelica.flyby.heapCheck leave and rejoin the world, which needs angelica.flyby.exitWhenDone=true; exiting instead of ignoring them");
|
||||
FMLCommonHandler.instance().exitJava(1, false);
|
||||
}
|
||||
if (!SystemProperties.FLYBY_COMMANDS.isEmpty()) {
|
||||
this.sceneCommands = FlybyScene.load(SystemProperties.FLYBY_COMMANDS);
|
||||
LOGGER.info("Flyby scene '{}': {} commands", SystemProperties.FLYBY_COMMANDS, this.sceneCommands.length);
|
||||
@@ -251,7 +268,16 @@ public final class FlybyRunner {
|
||||
if (this.state == State.EXITING) {
|
||||
if (++this.tick >= EXIT_TICKS) {
|
||||
this.state = State.DONE;
|
||||
this.exitGame(mc);
|
||||
this.leave(mc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state == State.MENU) {
|
||||
if (++this.tick >= MENU_SETTLE_TICKS) {
|
||||
this.state = State.DONE;
|
||||
this.checkHeapAtMenu();
|
||||
this.afterMenu(mc);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -892,14 +918,51 @@ public final class FlybyRunner {
|
||||
this.state = this.exitWhenDone ? State.EXITING : State.DONE;
|
||||
}
|
||||
|
||||
private void exitGame(Minecraft mc) {
|
||||
private void leave(Minecraft mc) {
|
||||
this.stopRecording(mc);
|
||||
LOGGER.info("Flyby complete, shutting down.");
|
||||
this.rejoinFolder = null;
|
||||
this.rejoinName = null;
|
||||
if (++this.visit < SystemProperties.FLYBY_VISITS) {
|
||||
final IntegratedServer server = mc.getIntegratedServer();
|
||||
if (server != null) {
|
||||
this.rejoinFolder = server.getFolderName();
|
||||
this.rejoinName = server.getWorldName();
|
||||
LOGGER.info("Flyby: visit {} of {} done, rejoining '{}'", this.visit, SystemProperties.FLYBY_VISITS, this.rejoinFolder);
|
||||
} else {
|
||||
LOGGER.error("Flyby: no singleplayer world to revisit, shutting down");
|
||||
}
|
||||
}
|
||||
if (this.rejoinFolder == null) LOGGER.info("Flyby complete, shutting down.");
|
||||
if (mc.theWorld != null) {
|
||||
mc.theWorld.sendQuittingDisconnectingPacket();
|
||||
mc.loadWorld(null);
|
||||
worldChangesDiscarded = false;
|
||||
mc.displayGuiScreen(new GuiMainMenu());
|
||||
}
|
||||
if (SystemProperties.FLYBY_HEAP_CHECK) {
|
||||
this.state = State.MENU;
|
||||
this.tick = 0;
|
||||
} else {
|
||||
this.afterMenu(mc);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkHeapAtMenu() {
|
||||
final CeleritasWorldRenderer renderer = CeleritasWorldRenderer.getInstanceOrNull();
|
||||
if (renderer != null && renderer.isActive()) this.heapCheck.skip("renderer still loaded: no unload hook");
|
||||
else this.heapCheck.report("after visit " + this.visit);
|
||||
}
|
||||
|
||||
private void afterMenu(Minecraft mc) {
|
||||
if (this.rejoinFolder != null) {
|
||||
FMLClientHandler.instance().tryLoadExistingWorld(new GuiSelectWorld(new GuiMainMenu()), this.rejoinFolder, this.rejoinName);
|
||||
if (mc.getIntegratedServer() != null) {
|
||||
this.startFromProperties();
|
||||
return;
|
||||
}
|
||||
LOGGER.error("Flyby: could not rejoin '{}', shutting down", this.rejoinFolder);
|
||||
}
|
||||
if (SystemProperties.FLYBY_HEAP_CHECK) this.heapCheck.finish();
|
||||
mc.shutdown();
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ public final class AsprofRecorder {
|
||||
return opts.isEmpty() ? "start,jfr,file=" + file : "start,jfr," + opts + ",file=" + file;
|
||||
}
|
||||
|
||||
static String fileName(String tag, String ext, long nowMillis) {
|
||||
public static String fileName(String tag, String ext, long nowMillis) {
|
||||
final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.ROOT);
|
||||
return "angelica-" + tag + "-" + format.format(new Date(nowMillis)) + "." + ext;
|
||||
}
|
||||
|
||||
@@ -514,7 +514,6 @@ public enum Mixins implements IMixins {
|
||||
, "celeritas.terrain.MixinShaderLoader"
|
||||
, "celeritas.terrain.MixinGlUniformMatrix4f"
|
||||
, "celeritas.terrain.MixinGlUniformMatrix3f"
|
||||
, "celeritas.terrain.MixinDefaultChunkRenderer"
|
||||
)
|
||||
),
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.gtnewhorizons.angelica.profiling;
|
||||
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.config.SystemProperties;
|
||||
import com.gtnewhorizons.angelica.glsm.CaptureGate;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMFramebuffer;
|
||||
import com.gtnewhorizons.angelica.glsm.backend.ScreenCapture;
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.shader.Framebuffer;
|
||||
|
||||
public final class TracyFrameImages {
|
||||
private static final int MAX_WIDTH = 320;
|
||||
private static final int MAX_HEIGHT = 180;
|
||||
private static final Tracy.ZoneId Z_CAPTURE = Tracy.zoneId("frameImageCapture", Tracy.COLOR_SWAP);
|
||||
private static final Tracy.ZoneId Z_EMIT = Tracy.zoneId("frameImageEmit", Tracy.COLOR_SWAP);
|
||||
private static final boolean HAS_ENABLED_OVERRIDE = SystemProperties.TRACY_FRAME_IMAGES_OVERRIDE != null;
|
||||
private static final boolean ENABLED_OVERRIDE = HAS_ENABLED_OVERRIDE && SystemProperties.TRACY_FRAME_IMAGES_OVERRIDE;
|
||||
private static final boolean HAS_RATE_OVERRIDE = SystemProperties.TRACY_FRAME_IMAGE_RATE_OVERRIDE != null;
|
||||
private static final int RATE_OVERRIDE = HAS_RATE_OVERRIDE ? Math.max(0, SystemProperties.TRACY_FRAME_IMAGE_RATE_OVERRIDE) : 0;
|
||||
|
||||
private static ScreenCapture capture;
|
||||
private static long frameIndex;
|
||||
private static long lastCaptureNs;
|
||||
private static int srcW;
|
||||
private static int srcH;
|
||||
|
||||
private TracyFrameImages() {}
|
||||
|
||||
public static void onPostSwap(Minecraft mc) {
|
||||
if (!enabled() || !CaptureGate.tracyConnected) {
|
||||
drop();
|
||||
return;
|
||||
}
|
||||
emitReady();
|
||||
final long now = System.nanoTime();
|
||||
final int rate = rate();
|
||||
if (rate != 0 && now - lastCaptureNs < 1_000_000_000L / rate) return;
|
||||
final Framebuffer source = mc.getFramebuffer();
|
||||
final int w = source.framebufferWidth;
|
||||
final int h = source.framebufferHeight;
|
||||
if (w <= 0 || h <= 0) return;
|
||||
if (capture == null || w != srcW || h != srcH) {
|
||||
srcW = w;
|
||||
srcH = h;
|
||||
final long size = fitCaptureSize(w, h);
|
||||
final int captureW = (int) (size >>> 32);
|
||||
final int captureH = (int) size;
|
||||
if (capture == null || capture.width() != captureW || capture.height() != captureH) {
|
||||
if (capture != null) capture.delete();
|
||||
capture = GLSMFramebuffer.createScreenCapture(captureW, captureH);
|
||||
}
|
||||
return;
|
||||
}
|
||||
Tracy.beginZone(Z_CAPTURE);
|
||||
if (capture.begin(source.framebufferObject, w, h, frameIndex)) lastCaptureNs = now;
|
||||
Tracy.endZone();
|
||||
}
|
||||
|
||||
static long fitCaptureSize(int srcWidth, int srcHeight) {
|
||||
final float aspect = (float) srcWidth / srcHeight;
|
||||
int width = srcWidth;
|
||||
int height = srcHeight;
|
||||
if (width > MAX_WIDTH) {
|
||||
width = MAX_WIDTH;
|
||||
height = (int) (MAX_WIDTH / aspect);
|
||||
}
|
||||
if (height > MAX_HEIGHT) {
|
||||
width = (int) (MAX_HEIGHT * aspect);
|
||||
height = MAX_HEIGHT;
|
||||
}
|
||||
return ((long) Math.max(4, width & ~3) << 32) | Math.max(4, height & ~3);
|
||||
}
|
||||
|
||||
public static void onFrameMark() {
|
||||
frameIndex++;
|
||||
}
|
||||
|
||||
private static void emitReady() {
|
||||
if (capture == null || capture.idle()) return;
|
||||
Tracy.beginZone(Z_EMIT);
|
||||
while (capture.poll()) {
|
||||
final long lag = frameIndex - capture.tag();
|
||||
if (lag <= 255) Tracy.frameImage(capture.address(), capture.width(), capture.height(), (int) lag, true);
|
||||
capture.release();
|
||||
}
|
||||
Tracy.endZone();
|
||||
}
|
||||
|
||||
private static void drop() {
|
||||
if (capture == null) return;
|
||||
capture.delete();
|
||||
capture = null;
|
||||
srcW = srcH = 0;
|
||||
}
|
||||
|
||||
private static boolean enabled() {
|
||||
return HAS_ENABLED_OVERRIDE ? ENABLED_OVERRIDE : AngelicaConfig.tracyFrameImages;
|
||||
}
|
||||
|
||||
private static int rate() {
|
||||
return HAS_RATE_OVERRIDE ? RATE_OVERRIDE : AngelicaConfig.tracyFrameImageRate;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
|
||||
import com.gtnewhorizons.angelica.glsm.CaptureGate;
|
||||
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMAttribStack;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMBuffers;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMDraws;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMPrograms;
|
||||
import com.gtnewhorizons.angelica.glsm.GLSMTextureLifetime;
|
||||
@@ -40,6 +41,7 @@ public final class TracyFramePlots {
|
||||
private static final PlotDelta P_GL_TEX_BIND_MISSES = new PlotDelta("gl.texBindMisses");
|
||||
private static final PlotDelta P_GL_PROGRAM_SWITCHES = new PlotDelta("gl.programSwitches");
|
||||
private static final long P_GL_TEX_AWAITING_RELEASE = Tracy.plotHandle("gl.texAwaitingRelease");
|
||||
private static final PlotDelta P_GLSM_BUFFER_DELETES = new PlotDelta("glsm.bufferDeletes");
|
||||
private static final PlotDelta P_GLSM_BRACKETS = new PlotDelta("glsm.brackets");
|
||||
private static final PlotDelta P_GLSM_COW_SLOTS = new PlotDelta("glsm.cowSlots");
|
||||
private static final PlotDelta P_GLSM_POP_RESTORES = new PlotDelta("glsm.popRestores");
|
||||
@@ -62,6 +64,7 @@ public final class TracyFramePlots {
|
||||
private static final long P_VOX_RANGES = Tracy.plotHandle("voxelization.ranges");
|
||||
private static final long P_VOX_REGIONS = Tracy.plotHandle("voxelization.regions");
|
||||
private static final long P_DRAW_BATCH_REBUILDS = Tracy.plotHandle("draw.batchRebuilds");
|
||||
private static final long P_DRAW_BATCH_REBUILDS_TRANSLUCENT = Tracy.plotHandle("draw.batchRebuilds.translucent");
|
||||
private static final PlotDelta P_DRAW_INDEX_BUFFER_GROWTHS = new PlotDelta("draw.indexBufferGrowths");
|
||||
|
||||
private static final long P_CULL_COMMANDS_SUBMITTED = Tracy.plotHandle("cull.commandsSubmitted");
|
||||
@@ -197,6 +200,7 @@ public final class TracyFramePlots {
|
||||
P_GL_TEX_BIND_MISSES.plot(GLSMTextures.texBindMisses);
|
||||
P_GL_PROGRAM_SWITCHES.plot(GLSMPrograms.programSwitches);
|
||||
Tracy.plotInt(P_GL_TEX_AWAITING_RELEASE, GLSMTextureLifetime.deferredTexturesAwaitingRelease());
|
||||
P_GLSM_BUFFER_DELETES.plot(GLSMBuffers.bufferDeletes);
|
||||
P_GLSM_BRACKETS.plot(GLSMAttribStack.attribPushes);
|
||||
P_GLSM_COW_SLOTS.plot(GLSMAttribStack.attribSlotsSaved);
|
||||
P_GLSM_POP_RESTORES.plot(GLSMAttribStack.attribValueRestores);
|
||||
@@ -214,6 +218,7 @@ public final class TracyFramePlots {
|
||||
Tracy.plotInt(P_TERRAIN_DRAW_COMMANDS, TerrainDrawStats.takeCommandsSubmitted());
|
||||
Tracy.plotInt(P_TERRAIN_REGIONS_DRAWN, TerrainDrawStats.takeRegionsDrawn());
|
||||
Tracy.plotInt(P_DRAW_BATCH_REBUILDS, TerrainDrawStats.takeBatchRebuilds());
|
||||
Tracy.plotInt(P_DRAW_BATCH_REBUILDS_TRANSLUCENT, TerrainDrawStats.takeBatchRebuildsTranslucent());
|
||||
Tracy.plotInt(P_VOX_ENCODERS, SdlShadowVoxelizationSink.takeEncoders());
|
||||
Tracy.plotInt(P_VOX_RANGES, SdlShadowVoxelizationSink.takeRanges());
|
||||
Tracy.plotInt(P_VOX_REGIONS, SdlShadowVoxelizationSink.takeRegions());
|
||||
|
||||
@@ -57,6 +57,10 @@ public class AngelicaRenderQueue {
|
||||
LockSupport.unpark(GLStateManager.getMainThread());
|
||||
}
|
||||
|
||||
public static void remove(Runnable runnable) {
|
||||
TASKS.removeIf(t -> t == runnable);
|
||||
}
|
||||
|
||||
public static int processTasks(int max) {
|
||||
int tasksRun = 0;
|
||||
while(tasksRun < max) {
|
||||
|
||||
+6
-1
@@ -305,6 +305,11 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SectionRenderDataStorage.BatchCacheParams batchCacheParams(boolean useBlockFaceCulling) {
|
||||
return SectionRenderDataStorage.BatchCacheParams.of(useBlockFaceCulling, ShadowRenderingState.areShadowsCurrentlyBeingRendered() ? 1 : 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable CachedBatch getRegionBatch(CommandList commandList, RenderRegion region,
|
||||
SectionRenderDataStorage storage, ChunkRenderList renderList,
|
||||
@@ -327,7 +332,7 @@ class AngelicaChunkRenderer extends DefaultChunkRenderer {
|
||||
try {
|
||||
final CachedBatch cached = super.getRegionBatch(commandList, region, storage, renderList, occlusionCamera,
|
||||
renderPass, useBlockFaceCulling, cacheParams);
|
||||
if (cached != before) TerrainDrawStats.recordRebuild();
|
||||
if (cached != before) TerrainDrawStats.recordRebuild(renderPass.isSorted());
|
||||
final MultiDrawBatch batch = cached != null ? cached.getBatch() : null;
|
||||
if (batch != null && !batch.isEmpty()) TerrainDrawStats.recordBatch(batch.size());
|
||||
return cached;
|
||||
|
||||
+4
-1
@@ -56,6 +56,7 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
|
||||
private static final long P_MESH_DEVICE_ALLOCATED = Tracy.plotHandle("mesh.deviceAllocated", TracyBackend.PLOT_FORMAT_MEMORY);
|
||||
private static final long P_MESH_DEFERRED_PENDING = Tracy.plotHandle("mesh.deferredPending");
|
||||
private static final long P_MESH_DEFERRED_FINISHING = Tracy.plotHandle("mesh.deferredFinishing");
|
||||
private static final long P_MESH_DEFERRED_FREE_SLICES = Tracy.plotHandle("mesh.deferredFreeSlices");
|
||||
|
||||
private static final Tracy.ZoneId Z_BIOME_REBUILDS = Tracy.zoneId("biomeRebuilds", Tracy.COLOR_TERRAIN);
|
||||
private static final Tracy.ZoneId Z_GRAPH_SEARCH = Tracy.zoneId("graphSearch", Tracy.COLOR_TERRAIN);
|
||||
@@ -73,7 +74,7 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
|
||||
this.world = world;
|
||||
this.sectionCache = new ClonedChunkSectionCache(world);
|
||||
this.taskProvider = taskProvider;
|
||||
this.deferredScheduler = new DeferredMeshScheduler(configuration, getBuilder(), () -> new WorldSlice(world), AngelicaRenderQueue::submit, ((RenderSectionManagerAccessor) this).angelica$getBuildResults());
|
||||
this.deferredScheduler = new DeferredMeshScheduler(configuration, getBuilder(), () -> new WorldSlice(world), AngelicaRenderQueue::submit, AngelicaRenderQueue::remove, ((RenderSectionManagerAccessor) this).angelica$getBuildResults());
|
||||
}
|
||||
|
||||
public static AngelicaRenderSectionManager create(ChunkVertexType vertexType, WorldClient world, int renderDistance, CommandList commandList) {
|
||||
@@ -211,6 +212,7 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
|
||||
@Override
|
||||
public void updateChunks(boolean updateImmediately) {
|
||||
this.sectionCache.cleanup();
|
||||
this.deferredScheduler.trimPools();
|
||||
this.flushBiomeRebuilds();
|
||||
if (Tracy.ENABLED) Tracy.beginZone(Z_GRAPH_SEARCH);
|
||||
try {
|
||||
@@ -353,5 +355,6 @@ public class AngelicaRenderSectionManager extends RenderSectionManager {
|
||||
Tracy.plotInt(P_DRAW_REGION_BUFFERS, mem.bufferCount);
|
||||
Tracy.plotInt(P_MESH_DEFERRED_PENDING, this.deferredScheduler.pendingCount());
|
||||
Tracy.plotInt(P_MESH_DEFERRED_FINISHING, this.deferredScheduler.finishingCount());
|
||||
Tracy.plotInt(P_MESH_DEFERRED_FREE_SLICES, this.deferredScheduler.freeSliceCount());
|
||||
}
|
||||
}
|
||||
+20
-2
@@ -28,9 +28,12 @@ public class DeferredMeshScheduler {
|
||||
private static final long FINISH_PRIORITY = -1L;
|
||||
|
||||
private static final Comparator<DeferredSectionMesh> NEAREST_FIRST = Comparator.comparingLong(m -> m.priority);
|
||||
private static final int CAPTURES_PER_WORKER = 2;
|
||||
private static final int MIN_FREE_PER_WORKER = 2;
|
||||
|
||||
private final ChunkBuilder builder;
|
||||
private final Consumer<Runnable> mainThreadQueue;
|
||||
private final Consumer<Runnable> mainThreadUnqueue;
|
||||
private final Queue<ChunkJobResult<? extends ChunkTaskOutput>> results;
|
||||
private final Consumer<ChunkJobResult<? extends ChunkTaskOutput>> resultSink;
|
||||
private final ObjectPool<ChunkBuildBuffers> buffers;
|
||||
@@ -41,17 +44,21 @@ public class DeferredMeshScheduler {
|
||||
private final Set<DeferredSectionMesh> inFlight = ConcurrentHashMap.newKeySet();
|
||||
private final Runnable runNext = this::runNext;
|
||||
private final AtomicInteger pending = new AtomicInteger();
|
||||
private final int minFree;
|
||||
|
||||
private boolean destroyed;
|
||||
|
||||
public DeferredMeshScheduler(RenderPassConfiguration<?> config, ChunkBuilder builder, Supplier<WorldSlice> sliceFactory, Consumer<Runnable> mainThreadQueue, Queue<ChunkJobResult<? extends ChunkTaskOutput>> results) {
|
||||
public DeferredMeshScheduler(RenderPassConfiguration<?> config, ChunkBuilder builder, Supplier<WorldSlice> sliceFactory, Consumer<Runnable> mainThreadQueue, Consumer<Runnable> mainThreadUnqueue, Queue<ChunkJobResult<? extends ChunkTaskOutput>> results) {
|
||||
this.builder = builder;
|
||||
this.mainThreadQueue = mainThreadQueue;
|
||||
this.mainThreadUnqueue = mainThreadUnqueue;
|
||||
this.results = results;
|
||||
this.resultSink = results::add;
|
||||
final int workers = Math.max(1, builder.getTotalThreadCount());
|
||||
this.minFree = workers * MIN_FREE_PER_WORKER;
|
||||
this.buffers = new ObjectPool<>(() -> new ChunkBuildBuffers(config), Integer.MAX_VALUE);
|
||||
this.slices = new ObjectPool<>(sliceFactory, Integer.MAX_VALUE);
|
||||
this.captures = new ObjectPool<>(DeferredCapture::new, Math.max(1, builder.getTotalThreadCount()) * 2);
|
||||
this.captures = new ObjectPool<>(DeferredCapture::new, workers * CAPTURES_PER_WORKER);
|
||||
}
|
||||
|
||||
public ChunkBuildBuffers acquireBuffers() {
|
||||
@@ -93,6 +100,7 @@ public class DeferredMeshScheduler {
|
||||
|
||||
/** Call after the builder shut down: its queued jobs were dropped without reaching their consumers. */
|
||||
public void releaseOrphans() {
|
||||
mainThreadUnqueue.accept(runNext);
|
||||
DeferredSectionMesh m;
|
||||
while ((m = important.poll()) != null) {
|
||||
release(m);
|
||||
@@ -113,6 +121,16 @@ public class DeferredMeshScheduler {
|
||||
return inFlight.size();
|
||||
}
|
||||
|
||||
public int freeSliceCount() {
|
||||
return slices.freeCount();
|
||||
}
|
||||
|
||||
public void trimPools() {
|
||||
final long now = System.nanoTime();
|
||||
slices.trim(now, minFree);
|
||||
buffers.trim(now, minFree);
|
||||
}
|
||||
|
||||
void finished(DeferredSectionMesh m, @Nullable ChunkJobResult<ChunkBuildOutput> result, @Nullable ChunkJobCollector collector) {
|
||||
final long nanos = m.workerNanos + m.mainNanos;
|
||||
ChunkJobResult<ChunkBuildOutput> out = result instanceof ChunkJobResult.Success<ChunkBuildOutput> success ? new ChunkJobResult.Success<>(success.output(), nanos + success.executionTimeNanos()) : result;
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
package com.gtnewhorizons.angelica.rendering.celeritas;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
final class ObjectPool<T> {
|
||||
static final long TRIM_WINDOW_NANOS = TimeUnit.SECONDS.toNanos(30);
|
||||
|
||||
private final ConcurrentLinkedDeque<T> free = new ConcurrentLinkedDeque<>();
|
||||
private final AtomicInteger freeCount = new AtomicInteger();
|
||||
private final AtomicInteger inUse = new AtomicInteger();
|
||||
private final Supplier<T> factory;
|
||||
private final int maxFree;
|
||||
|
||||
private boolean started;
|
||||
private long windowStart;
|
||||
private int windowPeak;
|
||||
private int previousPeak;
|
||||
|
||||
ObjectPool(Supplier<T> factory, int maxFree) {
|
||||
this.factory = factory;
|
||||
this.maxFree = maxFree;
|
||||
}
|
||||
|
||||
T acquire() {
|
||||
inUse.incrementAndGet();
|
||||
final T t = free.pollFirst();
|
||||
if (t == null) return factory.get();
|
||||
freeCount.decrementAndGet();
|
||||
@@ -23,10 +33,34 @@ final class ObjectPool<T> {
|
||||
}
|
||||
|
||||
void release(T t) {
|
||||
inUse.decrementAndGet();
|
||||
if (freeCount.incrementAndGet() > maxFree) {
|
||||
freeCount.decrementAndGet();
|
||||
return;
|
||||
}
|
||||
free.offerFirst(t);
|
||||
}
|
||||
|
||||
int freeCount() {
|
||||
return freeCount.get();
|
||||
}
|
||||
|
||||
void trim(long nowNanos, int minFree) {
|
||||
final int used = inUse.get();
|
||||
if (used > windowPeak) windowPeak = used;
|
||||
if (!started) {
|
||||
started = true;
|
||||
windowStart = nowNanos;
|
||||
return;
|
||||
}
|
||||
if (nowNanos - windowStart < TRIM_WINDOW_NANOS) return;
|
||||
final int peak = Math.max(windowPeak, previousPeak);
|
||||
previousPeak = windowPeak;
|
||||
windowPeak = used;
|
||||
windowStart = nowNanos;
|
||||
final int excess = freeCount.get() - Math.max(minFree, peak + (peak >> 1) - used);
|
||||
for (int i = (excess + 1) >> 1; i > 0 && free.pollLast() != null; i--) {
|
||||
freeCount.decrementAndGet();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ public final class TerrainDrawStats {
|
||||
private static int commandsSubmitted;
|
||||
private static int regionsDrawn;
|
||||
private static int batchRebuilds;
|
||||
private static int batchRebuildsTranslucent;
|
||||
|
||||
private static int cullCommandsSubmitted;
|
||||
private static int cullRegionsDrawn;
|
||||
@@ -35,8 +36,9 @@ public final class TerrainDrawStats {
|
||||
regionsDrawn++;
|
||||
}
|
||||
|
||||
public static void recordRebuild() {
|
||||
public static void recordRebuild(boolean translucent) {
|
||||
batchRebuilds++;
|
||||
if (translucent) batchRebuildsTranslucent++;
|
||||
}
|
||||
|
||||
public static void recordCullRegion(int commandCount) {
|
||||
@@ -76,6 +78,12 @@ public final class TerrainDrawStats {
|
||||
return n;
|
||||
}
|
||||
|
||||
public static int takeBatchRebuildsTranslucent() {
|
||||
final int n = batchRebuildsTranslucent;
|
||||
batchRebuildsTranslucent = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
public static int takeCommandsSubmitted() {
|
||||
final int n = commandsSubmitted;
|
||||
commandsSubmitted = 0;
|
||||
|
||||
@@ -34,7 +34,8 @@ public final class BatchDrawDefaults {
|
||||
}
|
||||
|
||||
public void apply() {
|
||||
if (passFramebuffer >= 0 && TesrBatchRenderer.deferredPipeline() == null) {
|
||||
final boolean pack = TesrBatchRenderer.deferredPipeline() != null;
|
||||
if (passFramebuffer >= 0 && !pack) {
|
||||
final int framebuffer = GLSMFramebuffer.getDrawFramebuffer();
|
||||
if (framebuffer != passFramebuffer) {
|
||||
callerFramebuffer = framebuffer;
|
||||
@@ -42,7 +43,7 @@ public final class BatchDrawDefaults {
|
||||
}
|
||||
}
|
||||
final int program = GLSMPrograms.getActiveProgram();
|
||||
if (program != 0 && !Program.isManagedBind(program) && !DepthColorStorage.isOwnedProgram(program)) {
|
||||
if (program != 0 && !Program.isManagedBind(program) && !(pack && DepthColorStorage.isOwnedProgram(program))) {
|
||||
callerProgram = program;
|
||||
GLSMPrograms.glUseProgram(0);
|
||||
}
|
||||
|
||||
@@ -381,6 +381,12 @@ options.angelica.tracy.captureAction.tooltip=Starts or stops a Tracy capture. Sa
|
||||
options.angelica.tracy.captureAction.start=Start capture
|
||||
options.angelica.tracy.captureAction.stop=Stop capture
|
||||
options.angelica.tracy.captureAction.saving=Saving...
|
||||
options.angelica.tracy.frameImages=Frame Images
|
||||
options.angelica.tracy.frameImages.tooltip=Attach small screenshots to Tracy frames for playback in Tracy Profiler. Only captured while a profiler is connected.
|
||||
options.angelica.tracy.frameImageRate=Frame Image Rate
|
||||
options.angelica.tracy.frameImageRate.tooltip=Frame images captured per second while connected.
|
||||
options.angelica.tracy.frameImageRate.value=%s/s
|
||||
options.angelica.tracy.frameImageRate.everyFrame=Every frame
|
||||
|
||||
pack.iris.select.title=Select
|
||||
pack.iris.configure.title=Configure
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
options.button.shader=Shadery...
|
||||
|
||||
options.chunks=%s chunków
|
||||
options.entityShadows=Cienie bytów
|
||||
options.entityShadows=Cienie mobów
|
||||
options.droppedItems=%s przedmiotów
|
||||
options.droppedItems.max=Bez limitu
|
||||
|
||||
sodium.option_impact.low=Niski
|
||||
sodium.option_impact.medium=Średni
|
||||
sodium.option_impact.high=Wysoki
|
||||
@@ -10,56 +13,107 @@ sodium.option_impact.varies=Różny
|
||||
sodium.options.pages.quality=Jakość
|
||||
sodium.options.pages.performance=Wydajność
|
||||
sodium.options.pages.advanced=Zaawansowane
|
||||
sodium.options.view_distance.tooltip=Zasięg renderowania kontroluje, jak daleko otoczenie będzie renderowane. Krótszy dystans oznacza, że mniej otoczenia będzie renderowane, zwiększając klatki na sekundę.
|
||||
sodium.options.simulation_distance.tooltip=Zasięg symulacji kontroluje jak daleko otoczenie i byty będą wczytywane i symulowane. Krótsze dystansy mogą zmniejszyć obciążenie wewnętrznego serwera i poprawić klatki na sekundę.
|
||||
sodium.options.brightness.tooltip=Kontroluje jasność (gammę) gry.
|
||||
sodium.options.pages.appearance=Wygląd
|
||||
sodium.options.pages.text=Efekty tekstu
|
||||
sodium.options.pages.fps_reducer=Ogranicznik FPS
|
||||
sodium.options.view_distance.tooltip=Zasięg renderowania określa, jak daleko otoczenie będzie renderowane. Krótszy dystans oznacza, że mniej otoczenia będzie renderowane, zwiększając klatki na sekundę.
|
||||
sodium.options.simulation_distance.tooltip=Zasięg symulacji określa, jak daleko otoczenie i byty będą wczytywane i symulowane. Krótsze dystansy mogą zmniejszyć obciążenie wewnętrznego serwera i poprawić klatki na sekundę.
|
||||
sodium.options.cc.vertical_view_distance.name=Pionowy zasięg widzenia
|
||||
sodium.options.cc.vertical_view_distance.tooltip=Pionowy zasięg widzenia określa pionowy zakres, w którym sześciany będą ładowane, wyśrodkowany względem gracza. Krótszy dystans oznacza, że mniej otoczenia będzie renderowane, zwiększając klatki na sekundę.
|
||||
sodium.options.brightness.tooltip=Określa jasność (gammę) gry.
|
||||
sodium.options.clouds.name=Chmury
|
||||
sodium.options.clouds.tooltip=Kontroluje, czy chmury będą lub nie będą widoczne.
|
||||
sodium.options.gui_scale.tooltip=Ustawia maksymalne skalowanie użyte dla interfejsu użytkownika. Jeśli opcja "auto" jest użyta, wtedy największa skala interfejsu zawsze będzie użyta.
|
||||
sodium.options.clouds.tooltip=Określa widoczność chmur.
|
||||
sodium.options.gui_scale.tooltip=Ustawia maksymalną skalę interfejsu użytkownika. Jeśli opcja "auto" jest użyta, zawsze stosowany będzie największy współczynnik skalowania.
|
||||
sodium.options.fullscreen.tooltip=Jeśli włączone, gra będzie wyświetlana w trybie pełnoekranowym (Jeśli opcja jest wspierana).
|
||||
sodium.options.v_sync.tooltip=Jeśli włączone, klatki na sekundę będą synchronizowane z częstotliwością odświeżania monitora, co zapewni płynniejsze wrażenia kosztem gorszego opóźnienia wejścia. To ustawienie może zmniejszyć wydajność, jeśli twój sprzęt jest zbyt słaby.
|
||||
sodium.options.fps_limit.tooltip=Ogranicza maksymalną liczbę klatek na sekundę. Ta opcja może pomóc zmniejszyć zużycie baterii i obciążenie systemu, gdy robisz wiele rzeczy naraz. Jeśli VSync jest włączony, limit klatek będzie zignorowany, jeśli jest mniejszy od częstotliwości odświeżania twojego monitora.
|
||||
sodium.options.view_bobbing.tooltip=Jeśli włączone, pole widzenia gracza będzie się bujać i podskakiwać. Gracze, którzy posiadają chorobę lokomocyjną lub tym podobne mogą mieć korzyści z wyłączenia tej opcji.
|
||||
sodium.options.attack_indicator.tooltip=Kontroluje, gdzie ikona ataku jest wyświetlana na ekranie.
|
||||
sodium.options.autosave_indicator.tooltip=Jeśli włączone, ikona zapisywania będzie pokazana, gdy gra zapisuje świat na dysku.
|
||||
sodium.options.graphics_quality.tooltip=Domyślna jakość grafiki kontroluje kilka starych opcji i jest potrzebna do kompatybilności modów. Jeśli opcje poniżej są ustawione na "Domyślna", wtedy użyją one tej opcji.
|
||||
sodium.options.clouds_quality.tooltip=Kontroluje jakość renderowania chmur na niebie.
|
||||
sodium.options.weather_quality.tooltip=Kontroluje jakość efektów deszczu i śniegu.
|
||||
sodium.options.v_sync.tooltip=Wył. prezentuje każdą klatkę natychmiastowo: Najniższe opóźnienie, ale występuje rozrwyanie obrazu. Wł. czeka na vblank i kolejkuje nową klatkę: Brak rozrwyania obrazu, większe opóźnienie. Mailbox czeka na vblank ale zamienia zakolejkowaną klatkę: Brak rozrwyania obrazu, opóźnienie zbliżone do trybu Wył.
|
||||
sodium.options.vsync_mode.off=Wył.
|
||||
sodium.options.vsync_mode.on=Wł.
|
||||
sodium.options.vsync_mode.mailbox=Mailbox
|
||||
sodium.options.fps_limit.tooltip=Ogranicza maksymalną liczbę klatek na sekundę. Ta opcja może pomóc zmniejszyć zużycie baterii i obciążenie systemu, gdy robisz wiele rzeczy naraz. Działa w każdym trybie VSync.
|
||||
sodium.options.view_bobbing.tooltip=Jeśli włączone, pole widzenia gracza będzie kołysać się i podskakiwać podczas poruszania się. Wyłączenie tej opcji może korzystnie wpłynąć na graczy z chorobą lokomocyjną.
|
||||
sodium.options.attack_indicator.tooltip=Określa, gdzie ikona ataku jest wyświetlana na ekranie.
|
||||
sodium.options.autosave_indicator.tooltip=Jeśli włączone, ikona zapisywania będzie widoczna, gdy gra zapisuje świat na dysku.
|
||||
sodium.options.graphics_quality.tooltip=Domyślna jakość grafiki określa kilka starszych opcji i jest niezbędna do zachowania kompatybilności z modami. Jeśli opcje poniżej są ustawione na "Domyślna", będą używać tej opcji.
|
||||
sodium.options.clouds_quality.tooltip=Określa jakość renderowania chmur na niebie.
|
||||
sodium.options.weather_quality.tooltip=Określa jakość efektów deszczu i śniegu.
|
||||
sodium.options.leaves_quality.name=Jakość liści
|
||||
sodium.options.leaves_quality.tooltip=Kontroluje jakość liści.
|
||||
sodium.options.leaves_quality.tooltip=Określa jakość liści.
|
||||
sodium.options.grass_quality.name=Jakość trawy
|
||||
sodium.options.grass_quality.tooltip=Kontroluje jakość stron bloków trawy.
|
||||
sodium.options.grass_quality.tooltip=Określa jakość stron bloków trawy.
|
||||
sodium.options.celeritas_smooth_lighting.name=Gładkie oświetlenie Celeritas
|
||||
sodium.options.celeritas_smooth_lighting.tooltip=Jeśli włączone, system oświetlenia Celeritas zostanie użyty do gładkiego oświetlenia. Działa tylko wtedy, kiedy gładkie oświetlenie jest włączone.
|
||||
sodium.options.texture_filtering.name=Filtrowanie tekstur
|
||||
sodium.options.texture_filtering.tooltip=Określa sposób próbkowania tekstur bloków w oddali.
|
||||
sodium.options.texture_filtering.rgss=RGSS
|
||||
sodium.options.texture_filtering.anisotropic=Anizotropowe
|
||||
sodium.options.texture_filtering.rgss_anisotropic=RGSS i anizotropowe
|
||||
sodium.options.texel_sampling.name=Wygładzanie tekseli
|
||||
sodium.options.texel_sampling.tooltip=Określa sposób rysowania granicy między dwoma tekselami tekstury bloku. "Najbliższe" jest ostre, "Liniowe" wygładza.
|
||||
sodium.options.texel_sampling.linear=Liniowe
|
||||
sodium.options.texel_sampling.nearest=Najbliższe
|
||||
sodium.options.anisotropic_filtering.name=Filtrowanie anizotropowe
|
||||
sodium.options.anisotropic_filtering.tooltip=Uwydatnia tekstury oglądane pod ostrym kątem.
|
||||
sodium.options.gui_block_supersampling.name=Supersampling bloków w GUI
|
||||
sodium.options.gui_block_supersampling.tooltip=Określa liczbę próbek tekstur na piksel dla trójwymiarowych ikon bloków w GUI.
|
||||
sodium.options.particle_quality.name=Jakość cząstek
|
||||
sodium.options.particle_quality.tooltip=Kontroluje maksymalną ilość cząstek, które mogą być widoczne na ekranie w tym samym czasie.
|
||||
sodium.options.smooth_lighting.tooltip=Kontroluje, czy bloki będą gładko oświetlone i cieniowane. Ta opcja może troszkę podwyższyć ilość czasu, jaką zajmuje załadowanie lub zaktualizowanie chunku, lecz nie ma wpływu na klatki na sekundę.This slightly increases the amount of time needed to re-build a chunk, but doesn't affect frame rates.
|
||||
sodium.options.particle_quality.tooltip=Określa maksymalną ilość cząstek, które mogą być widoczne na ekranie w tym samym czasie.
|
||||
sodium.options.smooth_lighting.tooltip=Określa, czy bloki będą gładko oświetlone i cieniowane. Ta opcja może troszkę podwyższyć ilość czasu, jaką zajmuje załadowanie lub zaktualizowanie chunku, lecz nie ma wpływu na klatki na sekundę.
|
||||
sodium.options.biome_blend.value=%s blok(ów)
|
||||
sodium.options.biome_blend.tooltip=Kontroluje zasięg, w którym biomy będą próbkowane dla kolorowania bloków. Wyższe wartoście znacznie zwiększy czas ładowania chunków, w zamian za znikome poprawki w jakości.
|
||||
sodium.options.entity_distance.tooltip=Kontroluje, jak daleko od gracza będą renderowane byty. Wyższe wartości zwiększają zasięg renderowania, kosztem klatek na sekundę.
|
||||
options.biomeBlendRadius=Zasięg przenikania biomów
|
||||
sodium.options.biome_blend_mode.name=Przenikanie bloków biomów
|
||||
sodium.options.biome_blend_mode.tooltip="Szybkie" stosuje jeden odcień biomu na każdy blok. "Dokładne" miesza odcień między trawą, liśćmi i powierzchnią wody używając zasięgu mieszania biomów.
|
||||
sodium.options.biome_blend.tooltip=Określa zasięg, w którym biomy będą próbkowane dla kolorowania trawy, liści, wody i nieba.
|
||||
sodium.options.entity_distance.tooltip=Określa, jak daleko od gracza będą renderowane byty. Wyższe wartości zwiększają zasięg renderowania, kosztem klatek na sekundę.
|
||||
sodium.options.entity_shadows.tooltip=Jeśli włączone, podstawowe cienie będą renderowane pod mobami i innymi bytami.
|
||||
sodium.options.vignette.name=Winieta
|
||||
sodium.options.vignette.tooltip=Jeśli włączone, efekt winiety będzie renderowany na widoku gracza. To raczej nie wpłynie na klatki na sekundę, chyba że jesteś ograniczany przez szybkość wypełniania.
|
||||
sodium.options.mipmap_levels.tooltip=Kontroluje ilość mipmap, które będą użyte do tekstur modeli bloków. Wyższe wartości skutkują lepszym renderowaniem bloków w oddali, lecz mogą spowodować pogorszenie wydajności przy dużej ilości animowanych tekstur.
|
||||
sodium.options.mipmap_levels.tooltip=Określa ilość mipmap, które będą użyte do tekstur modeli bloków. Wyższe wartości skutkują lepszą jakością bloków w oddali, lecz mogą spowodować pogorszenie wydajności przy dużej ilości animowanych tekstur.
|
||||
sodium.options.mipmap_generation.name=Generowanie mipmap
|
||||
sodium.options.mipmap_generation.tooltip=Wybiera, jak mniejsze warianty tekstur są generowane.
|
||||
sodium.options.mipmap_generation.vanilla_1_7_10=Minecraft 1.7.10
|
||||
sodium.options.mipmap_generation.celeritas=Celeritas
|
||||
sodium.options.mipmap_generation.modern_vanilla=Minecraft 26.2
|
||||
sodium.options.use_vertex_objects.name=Obiekty układu wierzchołków
|
||||
sodium.options.use_vertex_objects.tooltip=Pomaga poprawić wydajność poprzez przenoszenie informacji o tym, jak dane wierzchołków powinny być renderowane do sterownika, pozwalając na lepszą optymalizację dla wielokrotnego renderowania tych samych obiektów. Generalnie, nie ma potrzebyy wyłączania tego, chyba że używasz niekompatybilnych modów.
|
||||
sodium.options.use_block_face_culling.name=Chowanie ścian bloków
|
||||
sodium.options.use_block_face_culling.tooltip=Jeśli włączone, tylko ściany bloków, które odwrócone są w stronę kamery, będą renderowane. To może wyeliminować dużą liczbę ścian bloków wcześnie w procesie renderowania, oszczędzając przepustowość pamięci i czas na GPU. Niektóre paczki zasobów mogą mieć problem z tą opcją, możesz wtedy spróbować ją wyłączyć jeśli widzisz dziury w blokach.
|
||||
sodium.options.use_vertex_objects.tooltip=Pomaga poprawić wydajność poprzez przenoszenie informacji o tym, jak dane wierzchołków powinny być renderowane do sterownika, pozwalając na lepszą optymalizację dla wielokrotnego renderowania tych samych obiektów. Generalnie, nie ma potrzeby wyłączania tego, chyba że używasz niekompatybilnych modów.
|
||||
sodium.options.use_block_face_culling.name=Culling ścian bloków
|
||||
sodium.options.use_block_face_culling.tooltip=Jeśli włączone, tylko ściany bloków które odwrócone są w stronę kamery, będą renderowane. To może wyeliminować dużą liczbę ścian bloków wcześnie w procesie renderowania, oszczędzając przepustowość pamięci i czas na GPU. Niektóre paczki zasobów mogą mieć problem z tą opcją, możesz wtedy spróbować ją wyłączyć jeśli widzisz dziury w blokach.
|
||||
sodium.options.use_compact_vertex_format.name=Kompaktowy format wierzchołków
|
||||
sodium.options.use_compact_vertex_format.tooltip=Jeśli włączone, bardziej kompaktowy format wierzchołków będzie użyty do renderowania chunków. To może znacznie zmniejszyć zużycie pamięci grafiki i wymagań przepustowości, szczególnie dla zintegrowanych układów graficznych, ale może powodować z-fighting z niektórymi paczkami zasobów przez to jak zmniejsza precyzję pozycji i atrybutuów współrzędnych tekstur.
|
||||
sodium.options.use_fog_occlusion.name=Okluzja mgły
|
||||
sodium.options.use_fog_occlusion.tooltip=Jeśli włączone, chunki, które uważane są za w pełni ukryte przez mgłę, nie będą renderowane, zwiększając wydajność. Efekt poprawy może być większy, gdy efekt mgły jest cięższy (np. pod wodą), ale może skutkować w niechcianych artefaktach graficznych pomiędzy niebem a mgłą w niektórych przypadkach.
|
||||
sodium.options.use_entity_culling.name=Chowanie bytów
|
||||
sodium.options.use_fog_occlusion.tooltip=Jeśli włączone, chunki, które uważane są za w pełni ukryte przez mgłę, nie będą renderowane, zwiększając wydajność. Poprawa wydajności może być większa, gdy efekt mgły jest większy (np. pod wodą), ale może skutkować w niechcianych artefaktach graficznych pomiędzy niebem a mgłą w niektórych przypadkach.
|
||||
sodium.options.use_entity_culling.name=Culling bytów
|
||||
sodium.options.use_entity_culling.tooltip=Jeśli włączone, byty, które nie są w żadnym widocznym chunku, będą zignorowane podczas renderowaniu.To może zwiększyć wydajność, pomijając renderowanie bytów znajdujących się pod ziemią lub za ścianami.
|
||||
sodium.options.use_particle_culling.name=Chowanie cząstek
|
||||
sodium.options.use_particle_culling.tooltip=Jeśli włączone, tylko cząstki, które uważane są za widoczne, będą renderowane. To może skutkować znaczną poprawą klatek na sekundę, gdy wiele cząstek jest w polu widzenia.
|
||||
sodium.options.section_gated_tesr_culling.name=Bramkowany sekcyjnie culling TESR
|
||||
sodium.options.section_gated_tesr_culling.tooltip=Jeśli włączone, byty-bloki są chowane algorytmem frustum po jednej sekcji na raz, zamist po jednym bytcie na raz.
|
||||
sodium.options.use_particle_culling.name=Culling cząstek
|
||||
sodium.options.use_particle_culling.tooltip=Jeśli włączone, tylko cząstki, które uważane są za widoczne, będą renderowane. To może skutkować znaczną poprawą klatek na sekundę, gdy wiele cząstek znajduje się w polu widzenia.
|
||||
sodium.options.animate_only_visible_textures.name=Animuj tylko widoczne tekstury
|
||||
sodium.options.animate_only_visible_textures.tooltip=Jeśli włączone, tylko animowane tekstury, które uważane są za widoczne, będą aktualizowane. To może pomóc z wydajnością na niektórych systemach, szczególnie z cięższymi paczkami zasobów. Jeśli zauważyłeś, że niektóre tekstury nie są animowane, spróbuj wyłączyć tę opcję. If enabled, only animated textures determined to be visible will be updated.
|
||||
sodium.options.animate_only_visible_textures.tooltip=Jeśli włączone, tylko animowane tekstury, które uważane są za widoczne, będą aktualizowane. To może pomóc z wydajnością na niektórych systemach, szczególnie z cięższymi paczkami zasobów. Jeśli zauważyłeś, że niektóre tekstury nie są animowane, spróbuj wyłączyć tę opcję.
|
||||
sodium.options.translucency_sorting.name=Sortowanie przezroczystości
|
||||
sodium.options.translucency_sorting.tooltip=Jeśli włączone, przezroczyste bloki będą sortowane poprawnie od tyłu do przodu aby zapewnić lepsze renderowanie, szczególnie jeśli wiele przezroczystych bloków jest widocznych i ułożonych na sobie. Jest to trochę kosztowna operacja.
|
||||
sodium.options.translucency_sorting.tooltip=Jeśli włączone, przezroczysty teren (woda, kolorowe szkło, lód) i przezroczyste byty-bloki będą sortowane od tyłu do przodu, aby nakładające się warstwy były poprawnie wyświetlone. Wyłączenie tej opcji zwiększa wdyajność w sytuacjach z dużą ilością przezroczystych bloków, ale powoduje artefakty. Zmienienie tej opcji przeładowuje chunki.
|
||||
sodium.options.cpu_render_ahead_limit.name=Limit renderowania klatek z wyprzedzeniem przez CPU
|
||||
sodium.options.cpu_render_ahead_limit.tooltip=Określa maksymalną liczbę klatek na które CPU może czekać, aby zostały wyrenderowane przez GPU. Bardzo niskie lub wysokie wartości mogą powodować niestabilność klatek.
|
||||
sodium.options.cpu_render_ahead_limit.tooltip=Określa maksymalną liczbę klatek na które CPU może czekać, aby zostały wyrenderowane przez GPU. Bardzo niskie lub wysokie wartości mogą powodować niestabilność klatek. 0 wyłącza tą opcję; Backend SDL-GPU ignoruje ją i stosuje własne rozwiązanie.
|
||||
sodium.options.cpu_render_ahead_limit.value=%s klat(ki/ek)
|
||||
sodium.options.fps_reducer_enabled.name=Ogranicz klatki na sekundę w tle
|
||||
sodium.options.fps_reducer_enabled.tooltip=Zmniejsza liczbę klatek na sekundę i głośność, gdy gra działa w tle.
|
||||
sodium.options.unfocused_fps_limit.name=Klatki na sekundę na drugim planie
|
||||
sodium.options.unfocused_fps_limit.tooltip=Ograniczenie liczby klatek na sekundę, gdy inne okno jest na pierwszym planie.
|
||||
sodium.options.unfocused_volume.name=Głośność na drugim planie
|
||||
sodium.options.unfocused_volume.tooltip=Ograniczenie głośności, gdy inne okno jest na pierwszym planie.
|
||||
sodium.options.minimized_volume.name=Głośność zminimalizowana
|
||||
sodium.options.minimized_volume.tooltip=Ograniczenie głośności, gdy gra jest zminimalizowana.
|
||||
sodium.options.idle_timeout.name=Limit czasu bezczynności
|
||||
sodium.options.idle_timeout.tooltip=Liczba minut bezczynności przed zastosowaniem limitów bezczynności.
|
||||
sodium.options.idle_timeout.value=%s minut
|
||||
sodium.options.idle_timeout.off=Wył.
|
||||
sodium.options.idle_fps_limit.name=Liczba klatek na sekundę w stanie bezczynności
|
||||
sodium.options.idle_fps_limit.tooltip=Ograniczenie liczby klatek na sekundę w stanie bezczynności.
|
||||
sodium.options.idle_volume.name=Głośność w stanie bezczynności
|
||||
sodium.options.idle_volume.tooltip=Głośność w stanie bezczynności.
|
||||
sodium.options.limit_menu_fps.name=Ograniczenie liczby klatek na sekundę w menu
|
||||
sodium.options.limit_menu_fps.tooltip=Ogranicza liczbę klatek na sekundę do 30, gdy żaden świat nie jest załadowany.
|
||||
sodium.options.allow_direct_memory_access.name=Zezwól na bezpośredni dostęp do pamięci
|
||||
sodium.options.allow_direct_memory_access.tooltip=Jeśli włączone, niektóre krytyczne ścieżki kodu będą miały pozwolenie na użycie bezpośredniego dostępu do pamięci dla wydajności. To często znacznie zmniejsza obciążenie CPU do renderowania chunków i bytów, lecz może utrudnić diagnozę błędów i awarii. Powinieneś to wyłączyć tylko wtedy, jeśli zostałeś o to zapytany lub wiesz co robisz.
|
||||
sodium.options.allow_direct_memory_access.tooltip=Jeśli włączone, niektóre krytyczne ścieżki kodu będą miały pozwolenie na użycie bezpośredniego dostępu do pamięci dla zwiększenia wydajności. To często znacznie zmniejsza obciążenie CPU podczas renderowania chunków i bytów, lecz może utrudnić diagnozę błędów i awarii. Powinieneś to wyłączyć tylko wtedy, jeśli zostałeś o to zapytany lub wiesz co robisz.
|
||||
sodium.options.ignore_driver_blacklist.name=Ignoruj czarną listę sterowników
|
||||
sodium.options.ignore_driver_blacklist.tooltip=Jeśli włączone, znane problemy z kompatybilnością z twoim systemem/sterownikiem będą zignorowane, pozwalając na włączenie opcji, które mogą powodować problemy z grą. Nie powinieneś dotykać tej opcji, chyba że dokładnie wiesz co robisz. Po zmienieniu tej opcji, musisz zapisać, zamknąć, a potem ponownie otworzyć ekran ustawień.
|
||||
sodium.options.enable_memory_tracing.name=Włącz śledzenie pamięci
|
||||
@@ -74,22 +128,49 @@ sodium.options.chunk_memory_allocator.swap=Swap
|
||||
sodium.options.chunk_update_threads.name=Wątki aktualizacji chunków
|
||||
sodium.options.chunk_update_threads.tooltip=Określa liczbę wątków użytych do budowania chunków. Używanie większej liczby wątków może przyspieszyć wczytywanie chunków i ich aktualizowanie, ale może negatywnie wpłynąć na klatki.
|
||||
sodium.options.always_defer_chunk_updates.name=Zawsze opóźniaj aktualizacje chunków
|
||||
sodium.options.always_defer_chunk_updates.tooltip=Jeśli włączone, renderowanie nigdy nie będzie czekać aż aktualizacje chunków się zakończą, nawet gdy są one ważne. To może znacznie poprawić wydajność klatek w niektórych przypadkach, ale może skutkować widocznym opóźnieniem na świecie.
|
||||
sodium.options.always_defer_chunk_updates.tooltip=Jeśli włączone, renderowanie nigdy nie będzie czekać aż aktualizacje chunków się zakończą, nawet gdy są one ważne. To może znacznie poprawić wydajność klatek w niektórych przypadkach, ale może skutkować widocznym opóźnieniem w świecie.
|
||||
sodium.options.use_no_error_context.name=Brak kontekstu błędu
|
||||
sodium.options.use_no_error_context.tooltip=Jeśli włączone, kontekst OpenGL zostanie stworzony z wyłączonym sprawdzaniem błędów. To może minimalnie zwiększyć wydajność, ale również zwiększa ryzyko awarii gry zamiast radzenia sobie z błędami OpenGL. Powinieneś wyłączyć tą opcję, jeśli napotykasz nagłe i niewyjaśnione awarie.
|
||||
sodium.options.async_occlusion_mode.name=Asynchroniczna okluzja
|
||||
sodium.options.async_occlusion_mode.tooltip=Określa, który przebieg renderowania używa asynchronicznej okluzji otoczenia. "Tylko cienie" (domyślnie) używa asynchronicznej okluzji tylko dla przebiegu cienia, zachowując synchroniczność głównej listy renderowania. "Wszystko" uruchamia wszystkie przebiegi w trybie asynchronicznym, co może spowodować opóźnienie widocznośći jednej klatki podczas szybkich ruchów kamery. "Żadna" całkowicie wyłącza asynchroniczną okluzję.
|
||||
sodium.options.async_occlusion_mode.only_shadow=Tylko cienie
|
||||
sodium.options.async_occlusion_mode.everything=Wszystko
|
||||
sodium.options.use_occlusion_culling.name=Culling okluzją
|
||||
sodium.options.use_occlusion_culling.tooltip=Jeśli włączone, chunki ukryte za innymi chunkami nie będą renderowane. Wyłączenie tej opcji znacznie zmniejszy wydajność, ale może być przydatne przy debugowaniu.
|
||||
sodium.options.threads.value=%s wątek(ów)
|
||||
sodium.options.default=Domyślne
|
||||
sodium.options.none=Żadne
|
||||
sodium.options.buttons.undo=Cofnij
|
||||
sodium.options.buttons.apply=Zastosuj
|
||||
sodium.options.buttons.donate=Kup kawę dla JellySquid!
|
||||
sodium.options.anaglyph.tooltip=Jeśli włączone, świat będzie renderowany dwukrotnie z czerwonymi/cyjanowymi maskami koloru.
|
||||
sodium.options.show_cape.tooltip=Jeśli włączone, twoja peleryna będzie widoczna.
|
||||
rso.search_bar_empty=Wyszukaj opcję...
|
||||
|
||||
options.dynamic_fov=Dynamiczne pole widzenia
|
||||
options.dynamic_fov.tooltip=Jeśli włączone, pole widzenia gry będzie zmieniać się na podstawie szybkości gracza.
|
||||
options.framerate=%s FPS
|
||||
|
||||
iris.shaders.toggled=Przełączono shadery na %s!
|
||||
iris.shaders.disabled=Shadery wyłączone!
|
||||
iris.shaders.reloaded=Shadery przeładowane!
|
||||
iris.shaders.reloaded.failure=Nie udało się przeładować shaderów! Powód: %s
|
||||
iris.shaders.toggled.failure=Nie udało się przełączyć shaderów! Powód: %s
|
||||
|
||||
key.category.iris.keybinds=Skróty klawiszowe Iris
|
||||
iris.keybind.reload=Przeładuj shadery
|
||||
iris.keybind.toggleShaders=Przełącz shadery
|
||||
iris.keybind.shaderPackSelection=Ekran wyboru shaderów
|
||||
key.categories.angelica=Angelica
|
||||
angelica.keybind.zoom=Zoom
|
||||
angelica.keybind.fps_reducer_force=Wymuś ogranicznik FPS
|
||||
angelica.keybind.fps_reducer_disable=Przełącz ogranicznik FPS
|
||||
angelica.keybind.tracyCapture=Zacznij/Zatrzymaj przechwytywanie Tracy
|
||||
angelica.fps_reducer.hud.forced=Ogranicznik FPS: wymuszony
|
||||
angelica.fps_reducer.hud.disabled=Ogranicznik FPS: wyłączony
|
||||
|
||||
options.iris.apply=Zastosuj
|
||||
options.iris.refresh=Odśwież
|
||||
options.iris.openShaderPackFolder=Otwórz folder paczek shaderów...
|
||||
options.iris.openShaderPackFolder=Otwórz folder z shaderami...
|
||||
options.iris.shaderPackSettings=Ustawnienia paczki shaderów...
|
||||
options.iris.shaderPackList=Lista paczek shaderów...
|
||||
options.iris.refreshShaderPacks=Odśwież paczki shaderów
|
||||
@@ -100,13 +181,21 @@ options.iris.shaderPackSelection.addedPacks=Dodano %s paczki shaderów
|
||||
options.iris.shaderPackSelection.failedAdd=Pliki nie były prawidłowymi paczkami shaderów
|
||||
options.iris.shaderPackSelection.failedAddSingle="%s" nie jest prawidłową paczką shaderów
|
||||
options.iris.shaderPackSelection.copyError=Nie można było dodać paczki shaderów "%s"
|
||||
options.iris.shaderPackSelection.copyErrorAlreadyExists="%s" już istnieje w twoim folderze Shader Packs!
|
||||
options.iris.shaderPackSelection.copyErrorAlreadyExists="%s" jest już w twoim folderze paczek shaderów!
|
||||
options.iris.shaderPackOptions.noOptions="%s" nie ma konfigurowalnych ustawień
|
||||
options.iris.shaderPackOptions.tooManyFiles=Nie można zaimportować wielu plików ustawień shadera!
|
||||
options.iris.shaderPackOptions.failedImport=Nie udało się zaimportować plik ustawień shadera "%s"
|
||||
options.iris.shaderPackOptions.failedImport=Nie udało się zaimportować pliku ustawień shadera "%s"
|
||||
options.iris.shaderPackOptions.importedSettings=Zaimportowano ustawienia shadera z "%s"
|
||||
options.iris.shaders.disabled=Shadery: Wyłączone
|
||||
options.iris.shaders.enabled=Shadery: Włączone
|
||||
options.iris.shaders.nonePresent=Shadery: Brak paczek shaderów
|
||||
options.iris.saveCompiledShaders.on=Zapisz skompilowane shadery: Wł.
|
||||
options.iris.saveCompiledShaders.off=Zapisz skompilowane shadery: Wył.
|
||||
options.iris.saveCompiledShaders.tooltip=Zapisuje skompilowane programy shaderów Iris na dysku, aby restartowanie i wchodzenie do nowych wymiarów ładowało je zamiast kompilowania na miejscu. Gdy ta opcja jest włączona, każda zmiana ustawień shadera kompiluje je dla każdego wymiaru na raz, przez co stosowanie ich dłużej trwa. Wyłączenie tej opcji usuwa zapisane programy.
|
||||
options.iris.saveCompiledShaders.unsupported=Twój sterownik graficzny nie może zapisać skompilowanych programów, więc ta opcja tylko przygotowuje shadery każdego wymiaru wcześniej.
|
||||
options.iris.saveCompiledShaders.progress=Kompilowanie shaderów
|
||||
options.iris.saveCompiledShaders.progressStep=Folder shaderów %s z %s: %s
|
||||
options.iris.saveCompiledShaders.baseFolder=(domyślne shadery)
|
||||
options.iris.back=Powrót
|
||||
options.iris.reset=Resetuj
|
||||
options.iris.reset.tooltip=Zresetować WSZYSTKIE opcje i zastosować?
|
||||
@@ -118,13 +207,334 @@ options.iris.setToDefault=Ustawić opcję na domyślną wartość?
|
||||
options.iris.profile=Profil
|
||||
options.iris.profile.custom=Niestandardowy
|
||||
options.iris.shadowDistance=Maksymalny zasięg cieni
|
||||
options.iris.shadowDistance.sodium_tooltip=Maksymalny zasięg renderowania cieni
|
||||
options.iris.shadowDistance.enabled=Pozwala zmienić maksymalny zasięg renderowania cieni. Teren i byty poza tym dystansem nie będą rzucać cieni. Zmniejszenie zasięgu renderowania cieni może znacznie zwiększyć wydajność.
|
||||
options.iris.shadowDistance.disabled=Twoja obecna paczka shaderów ustawiła już zasięg renderowania cieni; nie możesz go zmienić.
|
||||
options.iris.gui.hide=Ukryj GUI
|
||||
options.iris.gui.show=Pokaż GUI
|
||||
options.iris.search.button=Wyszukaj
|
||||
options.iris.clear.button=Wyczyść
|
||||
options.iris.search.tooltip=Ustawienia szukania shaderów (Ctrl + F)
|
||||
options.iris.clear.tooltip=Wyjdź z trybu szukania shaderów (Esc)
|
||||
|
||||
options.dynamic_lights=Dynamiczne światła
|
||||
options.dynamic_lights.tooltip=Włącza dynamiczne światła. Tryby: OFF. - wyłączone, FASTEST - opóźnienie 500ms, FAST - opóźnienie 250ms, FANCY - brak opóźnienia. Wyłączone, jeśli shadery są w użyciu.
|
||||
options.dynamic_lights.tooltip=Włącza dynamiczne światła. Tryby: OFF - wyłączone, FASTEST - opóźnienie 500ms, FAST - 250ms delay, FANCY - opóźnienie 50ms, REALTIME - brak opóźnienia.
|
||||
options.dynamic_lights_shader_force=Dynamiczne światła - Wymuszone z shaderami
|
||||
options.dynamic_lights_shader_force.tooltip=Włączone - Dynamiczne światła są widoczne nawet jeśli shader jest aktywny. Wyłączone - dynamiczne światła są wyłączone gdy shader jest aktywny.
|
||||
options.dynamic_lights_frustum_culling=Algorytm chowania frustum
|
||||
options.dynamic_lights_frustum_culling.tooltip=Przebudowuje chunki jedynie dla widocznych źródeł światła. Zmniejsza ilość pracy GPU dla świateł znajdujących się poza widokiem.
|
||||
options.dynamic_lights_adaptive_ticking=Adaptacyjne ticki
|
||||
options.dynamic_lights_adaptive_ticking.tooltip=Odświeża odległe i niewidoczne źródła światła z mniejszą częstotliwością. Działa razem z globalnym ustawieniem trybu.
|
||||
options.dynamic_lights_slow_dist=Zasięg powolnego odświeżania.
|
||||
options.dynamic_lights_slow_dist.tooltip=Zasięg w blokach gdzie światła są odświeżane z częstotliwością 1/5. Zalecane: 32 bloki (~2 chunki).
|
||||
options.dynamic_lights_slower_dist=Zasięg wolniejszego odświeżania.
|
||||
options.dynamic_lights_slower_dist.tooltip=Zasięg w blokach gdzie światła są odświeżane z częstotliwością 1/10. Zalecane: 64 bloki (~4 chunki).
|
||||
options.dynamic_lights_background_dist=Zasięg odświeżania w tle.
|
||||
options.dynamic_lights_background_dist.tooltip=Zasięg w blokach gdzie światła są odświeżane z częstotliwością 1/20 (raz na sekundę). Również używane do świateł poza widokiem.
|
||||
options.dynamic_lights_cull_timeout=Limit czasu chowania (ticki)
|
||||
options.dynamic_lights_cull_timeout.tooltip=Maksymalna ilość ticków ukryty chunk może czekać zanim zostanie zmuszony do przebudowania. Mniejsza = większa responsywność ale więcej pracy dla GPU.
|
||||
options.dynamiclights.page=Dynamiczne światła
|
||||
options.dynamiclights.entities=Konfiguruj byty...
|
||||
options.dynamiclights.entities.tooltip=Wybierz, które typy bytów mogą emitować dynamiczne światła.
|
||||
options.dynamiclights.entities.title=Dynamiczne światła - byty
|
||||
options.dynamiclights.entities.info=Przełącz które typy bytów mogą emitować dynamiczne światła. Niektóre wpisy służą zapewnieniu zgodności z modami.
|
||||
options.dynamiclights.entities.search=Wyszukaj (@mod, $on, $off)
|
||||
options.dynamiclights.entities.enableall=Włącz wszystkie
|
||||
options.dynamiclights.entities.disableall=Wyłącz wszystkie
|
||||
options.dynamiclights.entities.enableshown=Włącz widoczne
|
||||
options.dynamiclights.entities.disableshown=Wyłącz widoczne
|
||||
|
||||
options.angelica.search.no_results=Brak pasujących opcji
|
||||
options.angelica.fontconfig=Ustawienia czcionki...
|
||||
options.angelica.fontconfig.title=Ustawienia czcionki
|
||||
options.angelica.fontconfig.currentfonts=Główna czcionka: §e%s§r Zastępcza czcionka: §b%s
|
||||
options.angelica.fontconfig.searchprompt=§7§oWyszukaj czcionki...
|
||||
options.angelica.fontconfig.testareaprompt=§7§oObszar testowy...
|
||||
options.angelica.fontconfig.testareainfo=Wpisz poniżej tekst, aby sprawdzić, jak się wyświetla. Do formatowania używaj znaku &.
|
||||
options.angelica.fontconfig.testareaexample=&d&lHerobrine'a&r &ntutaj &7&m&oNIE&r było
|
||||
options.angelica.fontconfig.font_selection_info=Kliknij lewym przyciskiem myszy pozycję na liście, aby ustawić czcionkę główną. Kliknij prawym przyciskiem myszy, aby ustawić czcionkę zastępczą. Kolejność wyświetlania: czcionka główna, czcionka zastępcza, czcionka Unicode.
|
||||
options.angelica.fontconfig.disable_custom_font=Wyłącz czcionkę niestandardową
|
||||
options.angelica.fontconfig.enable_custom_font=Włącz czcionkę niestandardową
|
||||
options.angelica.fontconfig.reset_values=Zresetuj wartości
|
||||
options.angelica.fontconfig.no_fonts_warning=Niestandardowe czicionki nie są dostępne na twoim systemie. Sprawdź logi, aby uzyskać więcej informacji.
|
||||
options.angelica.fontconfig.font_quality=Jakość czcionki: %s
|
||||
options.angelica.fontconfig.shadow_offset=Przesunięcie cienia: %s
|
||||
options.angelica.fontconfig.shadow_copies=Kopie cienia: %s
|
||||
options.angelica.fontconfig.bold_copies=Pogrubione kopie: %s
|
||||
options.angelica.fontconfig.glyph_scale=Skala znaków: %s
|
||||
options.angelica.fontconfig.glyph_aspect=Aspekt znaków: %s
|
||||
options.angelica.fontconfig.whitespace_scale=Rozmiar odstępu: %s
|
||||
options.angelica.fontconfig.glyph_spacing=Skalowanie znaków: %s
|
||||
options.angelica.fontconfig.font_aa_strength=Poziom wygładzania czcionki: %s
|
||||
options.angelica.fontconfig.aamode.aa_none=Wygładzanie czcionki: OFF
|
||||
options.angelica.fontconfig.aamode.aa_4x=Wygładzanie czcionki: 4x MSAA
|
||||
options.angelica.fontconfig.aamode.aa_16x=Wygładzanie czcionki: 16x MSAA
|
||||
options.angelica.fontconfig.font_brightness=Jasność czcionki: %s
|
||||
options.angelica.fontconfig.custom_font_scale=Niestandardowa skala czcionki: %s
|
||||
options.angelica.fontconfig.shadow_offset_unicode=Przesunięcie cienia Unicode: %s
|
||||
options.angelica.fontconfig.font_quality.tooltip=Jakość w której niestandardowe czcionki są renderowane. Zmniejsza rozpikselowanie kosztem wydajnośći i pamięci.
|
||||
options.angelica.fontconfig.shadow_offset.tooltip=Zasięg w którym cienie czcionek są renderowane. Również wpływa na renderowanie §lpogrubionego§r tekstu.
|
||||
options.angelica.fontconfig.shadow_copies.tooltip=Liczba cienie do renderowania za tekstem, każdy przesunięty aby uzyskać fałszywy efekt trójwymiaru.
|
||||
options.angelica.fontconfig.bold_copies.tooltip=Liczba kopii tekstu rysowanych podczas renderowania pogrubionego tekstu. Może zwiększyć jakość pogrubionego tekstu cieńszych czcionek.
|
||||
options.angelica.fontconfig.glyph_scale.tooltip=Skala każdego pojedynczego znaku. Ekstremalne wartości mogą sprawić, że gra będzie niegrywalna.
|
||||
options.angelica.fontconfig.glyph_aspect.tooltip=Proporcje każdego pojedynczego znaku.
|
||||
options.angelica.fontconfig.whitespace_scale.tooltip=Współczynnik skalowania znaków pustych, takich jak spacje.
|
||||
options.angelica.fontconfig.glyph_spacing.tooltip=Odstęp między poszczególnymi znakami.
|
||||
options.angelica.fontconfig.font_aa_strength.tooltip=Odstępy między próbkami stosowane w wygładzaniu czcionek. Wysokie wartości mogą powodować, że czcionki będą rozmyte, natomiast niskie wartości mogą powodować efekt aliasingu. Sprawdź, jakie ustawienia najlepiej sprawdzają się w Twoim przypadku.
|
||||
options.angelica.fontconfig.aamode.tooltip=Tryb wygładzania czcionek. Może znacznie poprawić wygląd i czytelność tekstu, który jest skalowany lub oglądany pod kątem, kosztem wydajności.
|
||||
options.angelica.fontconfig.font_brightness.tooltip=Wpływa na grubość czcionek z wygładzaniem krawędzi. Może poprawić czytelność niektórych tekstów. Wartość 0,5 pozostawia współczynnik alfa bez zmian. Skrajne wartości mogą sprawić, że tekst będzie wyglądał na postrzępiony.
|
||||
options.angelica.fontconfig.custom_font_scale.tooltip=Współczynnik skalowania, który wpływa na rozmiar niestandardowych czcionek. Należy go dostosować, jeśli tekst wyświetlany czcionką, którą wybrałeś, wydaje się zbyt mały lub zbyt duży w porównaniu z tekstem wyświetlanym bez użycia czcionek niestandardowych.
|
||||
options.angelica.fontconfig.shadow_offset_unicode.tooltip=Współczynnik przesunięcia cienia stosowany wyłącznie do czcionki Unicode. Ustaw tę wartość na 1,0x, jeśli na przykład korzystasz z paczki tekstur, która zastępuje fragmenty czcionki Unicode przypominające MC.
|
||||
options.angelica.hudCaching=Buforowanie HUD
|
||||
options.angelica.hudCaching.tooltip=Zapisuje HUD do bufora klatek i ponownie wykorzystuje go w kolejnych klatkach, aby zmniejszyć obciążenie związane z renderowaniem. Wymaga włączenia funkcji buforowania HUD podczas uruchamiania. [Semi-stabilne]
|
||||
options.angelica.droppedItemLimit=Limit renderowania upuszczonych przedmiotów
|
||||
options.angelica.droppedItemLimit.tooltip=Maksymalna liczba upuszczonych przedmiotów, które zostaną wyświetlone. Niższe wartości mogą poprawić wydajność.
|
||||
options.angelica.mobSpawnerRenderDistance=Odległość renderowania spawnera mobów
|
||||
options.angelica.mobSpawnerRenderDistance.tooltip=Odległość renderowania dla wirującego moba wewnątrz spawnera mobów. Ustaw na 0, aby wyłączyć renderowanie.
|
||||
options.angelica.shadowGraphAngleDelta=Próg odbudowy wykresu cienia.
|
||||
options.angelica.shadowGraphAngleDelta.tooltip=O ile ciało niebieskie może się przemieścić, zanim mapa cieni zostanie zaktualizowana. Należy pamiętać, że ta opcja jest wyłączona, gdy pakiet shaderów korzysta z funkcji wokselizacji.
|
||||
options.angelica.shadowGraphAngleDelta.always=Zawsze
|
||||
options.angelica.shadowGraphHorizonScale=Zmniejszenie horyzontu wykresu cieni
|
||||
options.angelica.shadowGraphHorizonScale.tooltip=Reguluje częstotliwość odświeżania mapy cieni, gdy ciało niebieskie znajduje się blisko horyzontu. Częstotliwość ma przebieg sinusoidalny.
|
||||
options.angelica.shadowGraphHorizonScale.off=Wył.
|
||||
options.angelica.itemdisplaylistcount=Limit pamięci podręcznej renderera elementów
|
||||
options.angelica.itemdisplaylistcount.tooltip=Górna granica liczby siatek przedmiotów, które mają być buforowane w zoptymalizowanym rendererze przedmiotów, obejmująca VBO oraz szablony przedmiotów upuszczanych i trzymanych. Wyższe wartości znacznie poprawią wydajność, ale mogą spowodować większe zużycie pamięci i pamięci VRAM.
|
||||
options.angelica.threadedChunkBuilding=Wielowątkowe budowanie chunków
|
||||
options.angelica.threadedChunkBuilding.tooltip=Jeśli włączone, tworzenie siatki chunków będzie odbywać się równolegle na wielu wątkach, co znacznie przyspieszy proces budowania chunków. Wyłącz tę opcję, jeśli wystąpią problemy z renderowaniem z modami wykorzystującymi niestandardowe renderery bloków. Wymaga ponownego załadowania renderera.
|
||||
options.angelica.parallelAtlasDecode=Równoległe dekodowanie tekstur
|
||||
options.angelica.parallelAtlasDecode.tooltip=Dekoduje tekstury bloków i obiektów na kilku wątkach. Szybsze uruchamianie i ponowne ładowanie zasobów. Wyłącz tę opcję, jeśli brakuje tekstur lub są one uszkodzone.
|
||||
options.angelica.parallelAtlasMipmaps=Równoległe mipmapy
|
||||
options.angelica.parallelAtlasMipmaps.tooltip=Generuje mipmapy atlasu tekstur na kilku wątkach. Szybsze uruchamianie i ponowne ładowanie zasobów. Wyłącz tę opcję, jeśli brakuje tekstur lub są one uszkodzone.
|
||||
options.angelica.batchedAtlasUpload=Zbiorcze przesyłanie atlasu
|
||||
options.angelica.batchedAtlasUpload.tooltip=Przesyłaj atlas tekstur dużymi partiami, a nie po jednej teksturze na raz.
|
||||
options.angelica.workerThreads=Wątki
|
||||
options.angelica.workerThreads.tooltip=Wątki wykorzystywane do ładowania tekstur podczas uruchamiania oraz ładowania pakietów shaderów. Domyślnie wybierana jest liczba oparta na liczbie rdzeni procesora.
|
||||
options.angelica.atlasPacks=Paczki ładujace tekstury...
|
||||
options.angelica.atlasPacks.tooltip=Wybiera które paczki zasobów mogą zostać odczytane na wielu wątkach podczas ładowania tekstur.
|
||||
options.angelica.atlasPacks.title=Ładowanie tekstur paczek zasobów
|
||||
options.angelica.atlasPacks.info=Zaznaczone paczki ładują tekstury równolegle. Zmiany zostaną zastosowane przy następnym odświeżeniu zasobów (F3+T).
|
||||
options.angelica.atlasPacks.builtin=wbudowany
|
||||
options.angelica.gpuCullingMode=Tryb chowania GPU
|
||||
options.angelica.gpuCullingMode.tooltip=Wybiera przebieg chowania chunków. Opcja „Tylko procesor” wykorzystuje emiter procesora. Opcja „Compute” wykonuje frustum culling na procesorze graficznym. Wymaga GL 4.3 / ARB_compute_shader + SSBO + Indirect Multi-Draw.
|
||||
options.angelica.gpuCullingMode.cpu_only=Tylko procesor
|
||||
options.angelica.gpuCullingMode.compute=Compute
|
||||
|
||||
options.angelica.overriddenBy=Nadpisane przez -D%s
|
||||
options.angelica.status.restart=Uruchom ponownie aby zastosować
|
||||
|
||||
options.angelica.renderer.page=Silnik renderowania
|
||||
options.angelica.renderer.link=Silnik renderowania
|
||||
options.angelica.renderer.link.tooltip=Backend graficzny którego używa gra. Kliknij aby otworzyć stronę silnika renderującego.
|
||||
options.angelica.renderer.backend=Backend
|
||||
options.angelica.renderer.backend.tooltip=API graficzne używane do renderowania gry. OpenGL jest domyślne. SDL GPU renderuje przez Vulkan, Metal lub Direct3D 12 i wraca do OpenGL jeśli żadne API nie może zostać uruchomione. Zmiana zacznie obowiązywać po ponownym uruchomieniu gry.
|
||||
options.angelica.renderer.backend.experimental=SDL GPU jest eksperymentalne: oczekuj crashy i błędów renderowania.
|
||||
options.angelica.renderer.backend.unavailable=SDL GPU jest niedostępne: %s
|
||||
options.angelica.renderer.backend.opengl=OpenGL
|
||||
options.angelica.renderer.backend.sdl_gpu=SDL GPU (Eksperymentalne)
|
||||
options.angelica.renderer.driver=Sterownik SDL GPU
|
||||
options.angelica.renderer.driver.tooltip=APU graficzne używane przez backend SDL GPU. Jeśli wybrany sterownik nie może zostać uruchomiony, SDL GPU spróbuje użyć swojego domyślnego, a później OpenGL. Ma Zmiana zacznie obowiązywać po ponownym uruchomieniu gry.
|
||||
options.angelica.renderer.driver.tooltip.macos=Automatyczny używa API Metal. Vulkan wymaga MoltenVK: włóż angelica-moltenvk.jar do folderu mods lub zainstaluj MoltenVK. Metal jest użyty jeśli Vulkan nie może zostać uruchomiony.
|
||||
options.angelica.renderer.driver.tooltip.windows=Automatyczny używa API Vulkan.
|
||||
options.angelica.renderer.driver.tooltip.d3d12=API Direct3D 12 jest bardzo eksperymentalne i prawdopobonie działa gorzej niżVulkan.
|
||||
options.angelica.renderer.driver.tooltip.other=Automatyczny używa API Vulkan.
|
||||
options.angelica.renderer.driver.auto=Automatyczny
|
||||
options.angelica.renderer.driver.vulkan=Vulkan
|
||||
options.angelica.renderer.driver.metal=Metal
|
||||
options.angelica.renderer.driver.d3d12=Direct3D 12
|
||||
options.angelica.renderer.glVersion=Wersja OpenGL
|
||||
options.angelica.renderer.glVersion.tooltip=Wersja kontekstu OpenGL do zażądania. Automatyczna używa najwyższej jaką wspiera twój sprzęt. Stosowana kiedy gra używa API OpenGL, również jeśli SDL GPU nie może zostać uruchomione. Zmiana zacznie obowiązywać po ponownym uruchomieniu gry.
|
||||
options.angelica.renderer.glVersion.auto=Automatyczna
|
||||
options.angelica.renderer.status=Stan
|
||||
options.angelica.renderer.status.tooltip=Czy działający backend jest zgodny z powyższymi ustawieniami.
|
||||
options.angelica.renderer.status.active=Aktywny
|
||||
options.angelica.renderer.status.forced=Wymuszony przez flagę JVM
|
||||
options.angelica.renderer.status.failed=SDL GPU nie mogło zostać uruchomione, sprawdź logi
|
||||
options.angelica.renderer.status.reset=Zresetowano do OpenGL po nieudanym starcie
|
||||
options.angelica.renderer.active=Używane API
|
||||
options.angelica.renderer.active.tooltip=Backend którego gra używa do renderowania.
|
||||
options.angelica.renderer.moltenvk=MoltenVK
|
||||
options.angelica.renderer.moltenvk.tooltip=Gdzie znaleziono MoltenVK. Sterownik SDL GPU Vulkan potrzebuje go na macOS.
|
||||
options.angelica.renderer.moltenvk.env=SDL_VULKAN_LIBRARY
|
||||
options.angelica.renderer.moltenvk.jar=angelica-moltenvk.jar
|
||||
options.angelica.renderer.moltenvk.system=Systemowa instalacja
|
||||
options.angelica.renderer.moltenvk.none=Nie znaleziony
|
||||
|
||||
options.angelica.tracy.page=Profiler Tracy
|
||||
options.angelica.tracy.enable=Włącz Tracy
|
||||
options.angelica.tracy.enable.tooltip=Włącz moduł profilujący Tracy. Wymaga obecności pliku angelica-tracy.jar w ścieżce klasy. Zmiana zacznie obowiązywać po ponownym uruchomieniu gry.
|
||||
options.angelica.tracy.allowRemote=Zezwól na zdalne połączenia
|
||||
options.angelica.tracy.allowRemote.tooltip=Zezwól na połączenia Tracy z innych komputerów, a nie tylko z localhost. Zmiana zacznie obowiązywać po ponownym uruchomieniu gry.
|
||||
options.angelica.tracy.fineZones=Dokładne strefy
|
||||
options.angelica.tracy.fineZones.tooltip=Włącza strefy Tracy dla znacznie większej liczby punktów połączeń. Więcej szczegółów, większe obciążenie. Zmiana wchodzi w życie po ponownym uruchomieniu gry.
|
||||
options.angelica.tracy.maxSrcLocs=Maksymalna liczba lokalizacji źródeł
|
||||
options.angelica.tracy.maxSrcLocs.tooltip=Maksymalna liczba różnych stref Tracy i punktów wywołania komunikatów, które mają zostać wstępnie przydzielone. Zmiana wchodzi w życie po ponownym uruchomieniu gry.
|
||||
options.angelica.tracy.status=Stan
|
||||
options.angelica.tracy.status.tooltip=Czy Tracy jest obecnie uruchomione i czy ustawienia są zgodne z powyższymi.
|
||||
options.angelica.tracy.status.running=Uruchomione
|
||||
options.angelica.tracy.status.runningForced=Uruchomione (wymuszone przez flagę JVM)
|
||||
options.angelica.tracy.status.failed=Nie udało się uruchomić, sprawdź log
|
||||
options.angelica.tracy.status.off=Wył.
|
||||
options.angelica.tracy.captureLength=Długość ujęcia
|
||||
options.angelica.tracy.captureLength.tooltip=Domyślna długość ujęcia Tracy rozpoczętego z tej strony lub /angelica tracy start. Ujęcia są zapisywane w katalogu angelica/profiles i otwierane za pomocą Tracy Profiler 0.14.
|
||||
options.angelica.tracy.captureAction=Ujęcie
|
||||
options.angelica.tracy.captureAction.tooltip=Uruchamia lub zatrzymuje rejestrację ujęcie w Tracy. Plik jest zapisywany w katalogu angelica/profiles i otwierany za pomocą Tracy Profiler 0.14.
|
||||
options.angelica.tracy.captureAction.start=Zacznij ujęcie
|
||||
options.angelica.tracy.captureAction.stop=Zatrzymaj ujęcie
|
||||
options.angelica.tracy.captureAction.saving=Zapisywanie...
|
||||
|
||||
pack.iris.select.title=Wybierz
|
||||
pack.iris.configure.title=Konfiguruj
|
||||
pack.iris.list.label=+ Przeciągnij i upuść paczki shaderów, aby je dodać
|
||||
pack.iris.list.label.lwjgl2=Zainstaluj lwjgl3ify aby móc przeciągać i upuszczać paczki shaderów
|
||||
label.iris.true=Wł.
|
||||
label.iris.false=Wył.
|
||||
|
||||
sodium.options.use_render_pass_optimization.name=Optymalizacja przebiegu renderowania
|
||||
sodium.options.use_render_pass_optimization.tooltip=Analizuje przezroczystość sprite'ów, aby automatycznie obniżać poziom złożoności operacji renderowania (np. z półprzezroczystego na wycięty, z wyciętego na nieprzezroczysty), gdy sprite nie wymaga bardziej zasobochłonnej operacji. Zmniejsza liczbę wywołań rysowania i nadmiernego rysowania.
|
||||
|
||||
options.angelica.aggressiveChunkLoading=Agresywne ładowanie chunków
|
||||
options.angelica.aggressiveChunkLoading.tooltip=Renderuje chunki natychmiast po załadowaniu, nawet jeśli sąsiednie chunki nie są jeszcze gotowe. Poprawia odczuwalną szybkość ładowania na granicach odległości renderowania. Chunki odbudowują się automatycznie po załadowaniu sąsiednich chunków. Przydatne w przypadku serwerów o małej odległości renderowania.
|
||||
angelica.debug.verbose.on=Szczegółowe F3: widoczne
|
||||
angelica.debug.verbose.off=Szczegółowe F3: ukryte
|
||||
angelica.debug.group.rendering=Renderowanie
|
||||
angelica.debug.group.position=Pozycja
|
||||
angelica.debug.group.looking_at_block=Oglądany blok
|
||||
angelica.debug.group.memory=Pamięć
|
||||
angelica.debug.group.system_specs=Specyfikacja systemu
|
||||
angelica.debug.group.forge=Forge
|
||||
angelica.debug.group.angelica=Angelica
|
||||
angelica.debug.group.shaders=Shadery
|
||||
angelica.debug.group.other=Inne
|
||||
angelica.debug.group.help=Pomoc
|
||||
angelica.debug.options.title=Opcje debugowania
|
||||
angelica.debug.options.warning=Te opcje służą wyłącznie do celów testowych. Mogą one spowolnić działanie komputera, spowodować awarię gry lub zjeść twojego kamiennego zwierzaka.
|
||||
angelica.debug.options.search=Wyszukaj...
|
||||
angelica.debug.options.category.text=Tekst na ekranie debugowania
|
||||
angelica.debug.options.category.renderer=Renderery debugowania
|
||||
angelica.debug.options.category.layout=Układ
|
||||
angelica.debug.options.profile.default=Domyślny profil
|
||||
angelica.debug.options.profile.performance=Profil wydajności
|
||||
angelica.debug.options.failed.tooltip=Ten wpis spowodował błąd i jest wyłączony do czasu ponownego uruchomienia. Sprawdź dziennik.
|
||||
angelica.debug.options.flat_notice=Klasyczny układ F3 jest włączony (ekran zgrupowany jest wyłączony lub renderer czcionek Angelica jest wyłączony): obowiązują wyłącznie te wpisy.
|
||||
angelica.debug.options.text_size=Rozmiar tekstu F3
|
||||
angelica.debug.options.text_size.match=Dopasuj do GUI
|
||||
angelica.debug.options.text_size.auto=Automatycznie
|
||||
angelica.debug.options.text_size.tooltip=Dopasuj do GUI: taki sam rozmiar jak skala interfejsu użytkownika. Automatycznie: połowa największego rozmiaru, który mieści się w oknie. Nigdy większy niż skala GUI.
|
||||
angelica.debug.options.category.panels=Panele
|
||||
angelica.debug.options.panels.tooltip=Użyj grup=%s w regułach linii F3 (konfiguracja Angelica compat), aby przenieść linię do tego panelu.
|
||||
angelica.debug.options.status.overlay=W nakładce
|
||||
angelica.debug.options.status.always=Zawsze
|
||||
angelica.debug.entry.minecraft.game_version=Wersja gry
|
||||
angelica.debug.entry.minecraft.fps=FPS
|
||||
angelica.debug.entry.minecraft.tps=Czas ticku serwera
|
||||
angelica.debug.entry.minecraft.player_position=Pozycja gracza
|
||||
angelica.debug.entry.minecraft.player_section_position=Wybierz pozycję
|
||||
angelica.debug.entry.minecraft.player_speed=Prędkość gracza
|
||||
angelica.debug.entry.minecraft.biome=Biom
|
||||
angelica.debug.entry.minecraft.light_levels=Poziomy światła
|
||||
angelica.debug.entry.minecraft.heightmap=Mapa wysokości
|
||||
angelica.debug.entry.minecraft.chunk_render_stats=Statystyki renderowania chunków
|
||||
angelica.debug.entry.minecraft.entity_render_stats=Statystyki renderowania bytów
|
||||
angelica.debug.entry.minecraft.particle_render_stats=Statystyki cząstek
|
||||
angelica.debug.entry.minecraft.chunk_source_stats=Statystyki pamięci podręcznej chunków
|
||||
angelica.debug.entry.minecraft.post_effect=Efekty postprocesu
|
||||
angelica.debug.entry.minecraft.memory=Pamięć
|
||||
angelica.debug.entry.minecraft.system_specs=Specyfikacja systemu
|
||||
angelica.debug.entry.minecraft.looking_at_block=Oglądany blok
|
||||
angelica.debug.entry.forge.brandings=Wersje Forge i modów
|
||||
angelica.debug.entry.angelica.renderer=Renderer Angelica
|
||||
angelica.debug.entry.angelica.renderer_verbose=Szczegółowe informacje o rendererze
|
||||
angelica.debug.entry.angelica.shaders=Paczka shaderów
|
||||
angelica.debug.entry.angelica.dynamic_lights=Dynamiczne światła
|
||||
angelica.debug.entry.angelica.fog=Mgła
|
||||
angelica.debug.entry.angelica.animations=Animacje tekstur
|
||||
angelica.debug.entry.angelica.other_left=Inne mody (lewo)
|
||||
angelica.debug.entry.angelica.other_right=Inne mody (prawo)
|
||||
angelica.debug.entry.angelica.help=Pomoc dotycząca klawiszy
|
||||
angelica.debug.entry.minecraft.3d_crosshair=Celownik 3D
|
||||
angelica.debug.entry.minecraft.entity_hitboxes=Hitboxy bytów
|
||||
angelica.debug.entry.minecraft.chunk_borders=Granice chunków
|
||||
angelica.debug.entry.minecraft.chunk_section_visibility=Widoczność sekcji
|
||||
angelica.debug.entry.minecraft.visualize_block_light_levels=Poziomy światła bloków
|
||||
angelica.debug.entry.minecraft.visualize_sky_light_levels=Poziomy światła nieba
|
||||
angelica.debug.entry.minecraft.visualize_heightmap=Znaczniki mapy wysokości
|
||||
angelica.debug.entry.minecraft.visualize_collision_boxes=Prostokąty kolizji
|
||||
angelica.debug.entry.minecraft.visualize_solid_faces=Powierzchnie pełne
|
||||
angelica.debug.entry.minecraft.visualize_water_levels=Poziomy wody
|
||||
angelica.debug.entry.minecraft.visualize_entity_supporting_block=Blok wspierający byt
|
||||
angelica.debug.entry.angelica.chunk_minimap=Minimapa chunków
|
||||
angelica.debug.entry.minecraft.game_version.tooltip=Wiersz z wersją Minecrafta u góry ekranu debugowania
|
||||
angelica.debug.entry.minecraft.fps.tooltip=Liczba klatek na sekundę, aktualne ograniczenie liczby klatek oraz aktualizacje chunków – wyświetlane w lewym górnym rogu
|
||||
angelica.debug.entry.minecraft.tps.tooltip=Ticki zintegrowanego serwera w milisekundach; tylko w trybie jednoosobowym
|
||||
angelica.debug.entry.minecraft.player_position.tooltip=Twoja dokładna pozycja, blok, chunk, kierunek orientacji i wymiar
|
||||
angelica.debug.entry.minecraft.player_section_position.tooltip=Twoja pozycja w bloku względem sekcji 16x16x16, w której się znajdujesz
|
||||
angelica.debug.entry.minecraft.player_speed.tooltip=Twoja prędkość w blokach na tick, prędkość chodzenia i latania oraz to, czy znajdujesz się na ziemi
|
||||
angelica.debug.entry.minecraft.biome.tooltip=Biom w miejscu, w którym się znajdujesz, wraz z modem z którego jest
|
||||
angelica.debug.entry.minecraft.light_levels.tooltip=Poziom światła w Twojej pozycji, w podziale na światło nieba i światło bloku
|
||||
angelica.debug.entry.minecraft.heightmap.tooltip=Wysokość górnego bloku w Twojej pozycji oraz górnej wypełnionej części Twojego chunku
|
||||
angelica.debug.entry.minecraft.chunk_render_stats.tooltip=Widoczne i całkowite sekcje renderowania, zasięg renderowania oraz liczba animowanych sprite’ów
|
||||
angelica.debug.entry.minecraft.entity_render_stats.tooltip=Ile obiektów zostało wyrenderowanych, ukrytych lub odrzuconych w tej klatce spośród tych, które zostały załadowane
|
||||
angelica.debug.entry.minecraft.particle_render_stats.tooltip=Liczba cząstek z menedżera cząstek Vanilli.
|
||||
angelica.debug.entry.minecraft.chunk_source_stats.tooltip=Statystyki dotyczące pamięci podręcznej chunków klienta z dostawcy świata
|
||||
angelica.debug.entry.minecraft.post_effect.tooltip=Nazwa aktywnej grupy post-procesowej Vanilli
|
||||
angelica.debug.entry.minecraft.memory.tooltip=Wykorzystanie sterty Javy, przydzielona sterta, tempo alokacji oraz pamięć bufora bezpośredniego
|
||||
angelica.debug.entry.minecraft.system_specs.tooltip=Wersja Javy, liczba rdzeni procesora, rozdzielczość ekranu, układ graficzny i wersja OpenGL
|
||||
angelica.debug.entry.minecraft.looking_at_block.tooltip=Współrzędne, nazwa w rejestrze i metadane bloku znajdującego się pod celownikiem
|
||||
angelica.debug.entry.forge.brandings.tooltip=MCP, Wersje FML i Forge oraz liczba załadowanych i aktywnych modów
|
||||
angelica.debug.entry.angelica.renderer.tooltip=Wersja Angelica, backend renderowania i rozmiary bufora terenu
|
||||
angelica.debug.entry.angelica.renderer_verbose.tooltip=Adds detailed renderer timings to the Angelica panel, like F3+V
|
||||
angelica.debug.entry.angelica.shaders.tooltip=Aktywna paczka shaderów i szczegóły dotyczące jego potoku renderowania; pokazuje off kiedy shadery są wyłączone
|
||||
angelica.debug.entry.angelica.dynamic_lights.tooltip=Liczba dynamicznych źródeł światła, aktualizacji w tej klatce i oczekujące przebudowy chunków
|
||||
angelica.debug.entry.angelica.fog.tooltip=Stan mgły: włączony lub wyłączony, tryb, odległość początkowa i końcowa, gęstość oraz kolor
|
||||
angelica.debug.entry.angelica.animations.tooltip=Tryb animacji tekstur; ukryty, chyba że włączono przyspieszenie animacji
|
||||
angelica.debug.entry.angelica.other_left.tooltip=Wiersze dodawane przez inne modyfikacje po lewej stronie ekranu debugowania
|
||||
angelica.debug.entry.angelica.other_right.tooltip=Wiersze dodawane przez inne modyfikacje po prawej stronie ekranu debugowania
|
||||
angelica.debug.entry.angelica.help.tooltip=Przypomnienie skrótów klawiszowych F3, wyświetlane przy otwartym ekranie debugowania
|
||||
angelica.debug.entry.minecraft.3d_crosshair.tooltip=Zastępuje celownik kolorowymi liniami osi X, Y i Z, które dostosowują się do kierunku patrzenia
|
||||
angelica.debug.entry.minecraft.entity_hitboxes.tooltip=Pokazuje hitboxy bytów, tak jak F3+B
|
||||
angelica.debug.entry.minecraft.chunk_borders.tooltip=Wskazuje chunk i sekcję, w której się znajdujesz, tak jak F3+G
|
||||
angelica.debug.entry.minecraft.chunk_section_visibility.tooltip=Oznacza pobliskie sekcje, których ściany nie stykają się ze sobą, co powoduje schowanie terenu znajdującego się za nimi
|
||||
angelica.debug.entry.minecraft.visualize_block_light_levels.tooltip=Rysuje wartość jasności na każdym pobliskim bloku, który nie jest całkowicie ciemny
|
||||
angelica.debug.entry.minecraft.visualize_sky_light_levels.tooltip=Rysuje wartość światła nieba na każdym pobliskim bloku, który nie ma pełnego oświetlenia nieba
|
||||
angelica.debug.entry.minecraft.visualize_heightmap.tooltip=Oznacza górny blok każdej kolumny w sąsiednich chunkach
|
||||
angelica.debug.entry.minecraft.visualize_collision_boxes.tooltip=Wskazuje zderzające się obiekty w promieniu kilku bloków od Ciebie
|
||||
angelica.debug.entry.minecraft.visualize_solid_faces.tooltip=Barwi powierzchnie bloków pełnych, które są widoczne dla sąsiedniego, nieprzezroczystego bloku znajdującego się w pobliżu
|
||||
angelica.debug.entry.minecraft.visualize_water_levels.tooltip=Pokazuje pobliskie zbiorniki wodne wraz z ich poziomem przepływu w skali od 1 do 8
|
||||
angelica.debug.entry.minecraft.visualize_entity_supporting_block.tooltip=Pokazuje kontury bloków, na których stoją poszczególne pobliskie byty; twój jest czerwony, a pozostałe zielone
|
||||
angelica.debug.entry.angelica.chunk_minimap.tooltip=Mała mapa stanów renderowania chunków w rogu
|
||||
angelica.debug.chunk_borders.on=Granice chunków: Widoczne
|
||||
angelica.debug.chunk_borders.off=Granice chunków: ukryte
|
||||
angelica.debug.help.options=F3 + F6 = Edytuj opcje debugowania
|
||||
angelica.debug.help.hitboxes=F3 + B = Pokaż hitboxy
|
||||
angelica.debug.help.chunk_borders=F3 + G = Pokaż granice chunków
|
||||
angelica.debug.help.verbose=F3 + V = Szczegółowe informacje o rendererze
|
||||
angelica.debug.help.graphs=Alt + F3 = Wykresy FPS i TPS
|
||||
options.angelica.texteffects.rgb_colors=Kolory RGB
|
||||
options.angelica.texteffects.rgb_colors.tooltip=Włącz kolory szesnastkowe za pomocą składni %s
|
||||
options.angelica.texteffects.gradients=Gradienty
|
||||
options.angelica.texteffects.gradients.tooltip=Płynne przejścia kolorów przez
|
||||
options.angelica.texteffects.rainbow=Tęcza
|
||||
options.angelica.texteffects.rainbow.tooltip=Cykliczne kolory tęczy przez
|
||||
options.angelica.texteffects.wave_text=Tekst falowy
|
||||
options.angelica.texteffects.wave_text.tooltip=Animowany, podskakujący tekst przez
|
||||
options.angelica.texteffects.wave_amplitude=Amplituda
|
||||
options.angelica.texteffects.wave_amplitude.tooltip=Odległość, na jaką odbija się tekst falisty (domyślnie 2,0)
|
||||
options.angelica.texteffects.dinnerbone_text=Tekst Dinnerbone
|
||||
options.angelica.texteffects.dinnerbone_text.tooltip=Tekst odwrócony do góry nogami przez
|
||||
options.angelica.texteffects.drop_shadow=Rzucany cień
|
||||
options.angelica.texteffects.drop_shadow.tooltip=Cień rzucany dla poszczególnych segmentów za pomocą %s, cień kolorowy za pomocą
|
||||
options.angelica.texteffects.ampersand_conversion=Konwersja znaku „&”
|
||||
options.angelica.texteffects.ampersand_conversion.tooltip=Konwertuj kody prefiksowe %s na kody formatujące w momencie renderowania
|
||||
|
||||
sodium.options.multidraw_mode.name=Tryb Multi-Draw
|
||||
sodium.options.multidraw_mode.tooltip=Określa sposób wysyłania poleceń rysowania podczas renderowania terenu. §lDirect§r używa glMultiDrawElementsBaseVertex (domyślna, GL 3.2+). §lIndirect§r renderowanie pośrednie za pomocą procesora graficznego (GL 4.3+), co może zmniejszyć obciążenie procesora na niektórych urządzeniach, i jest wymagane do chowania GPU / Hi-Z. §lIndywidualny§r wysyła pojedyncze wywołania rysowania.
|
||||
sodium.options.multidraw_mode.direct=Bezpośredni
|
||||
sodium.options.multidraw_mode.indirect=Pośredni
|
||||
sodium.options.multidraw_mode.individual=Indywidualny
|
||||
sodium.options.upload_method.name=Metoda przesyłania strumieniowego
|
||||
sodium.options.upload_method.tooltip=Zmienia sposób przesyłania danych do procesora graficznego. Niektóre opcje mogą poprawić wydajność w przypadku niektórych sterowników.
|
||||
sodium.options.upload_method.buffer_data=BufferData
|
||||
sodium.options.upload_method.buffer_subdata=BufferSubData
|
||||
sodium.options.upload_method.map_buffer=MapBufferRange
|
||||
sodium.options.enable_deferred_batching.name=Dozowanie cząstek
|
||||
sodium.options.enable_deferred_batching.tooltip=Renderuje cząstki jako instancjonowane czworokąty lub, jeśli instancjonowanie nie jest dostępne, jako połączone obiekty. Wyłącz tę opcję, jeśli cząstki są renderowane nieprawidłowo.
|
||||
@@ -7,33 +7,77 @@ options.graphics.ultra=Ultra
|
||||
options.off=Wył.
|
||||
options.on=Wł.
|
||||
options.cloud_height=Wysokość chmur
|
||||
options.cloud_height.tooltip=Wysokość, na której chmury są renderowane w światach które same tego nie ustawiają. Światy, które ustawiają własną wartość, zachowują ją, przesuniętą o różnicę między tym ustawieniem a wartością 128.
|
||||
options.cloud_scale=Skala chmur
|
||||
options.cloud_scale.tooltip=Skala chmur
|
||||
options.gui_background=Tło
|
||||
options.gui_background.tooltip=Tło
|
||||
options.downfall_distance=Efekty pogody
|
||||
options.downfall_distance.tooltip=Efekty pogody
|
||||
options.dynamic_fov=Dynamiczne pole widzenia
|
||||
options.dynamic_fov.tooltip=Jeśli włączone, pole widzenia gry będzie zmieniać się na podstawie szybkości gracza.
|
||||
options.dynamic_fov.tooltip=W zależności od trybu, pole widzenia w grze będzie się zmieniać w zależności od prędkości gracza lub w wyniku określonych interakcji z modami (w tym powiększania za pomocą narzędzi lub celowników broni).
|
||||
options.horizon=Horyzont
|
||||
options.horizon.tooltip=Horyzont
|
||||
options.terrain_fog=Mgła terenowa
|
||||
options.terrain_fog.tooltip=Mgła terenowa (aka Mgła odległości widzenia)
|
||||
options.fog_near_distance=Odległość mgły
|
||||
options.fog_near_distance.tooltip=Odległość od gracza, przy której zacznie się pojawiać mgła ograniczająca widoczność (przezroczystość 0%, a następnie liniowy wzrost aż do odległości renderowania).
|
||||
options.bobview_mode=Tryb animacji chodzenia
|
||||
options.bobview_mode.tooltip=Zmień zachowanie animacji chodzenia tak, aby kołysała się tylko ręka lub kamera.
|
||||
options.bobview.hand=Tylko ręka
|
||||
options.bobview.camera=Tylko kamera
|
||||
options.fov.all=Włączone
|
||||
options.fov.mods=Tylko mody
|
||||
options.fov.none=Wyłączone
|
||||
options.mode_cloud_translucency=Przezroczystość chmur
|
||||
options.mode_cloud_translucency.tooltip=Przezroczystość chmur
|
||||
options.mode_clouds=Chmury
|
||||
options.mode_clouds.tooltip=Chmury
|
||||
options.mode_dropped_items=Upuszczone przedmioty
|
||||
options.mode_dropped_items.tooltip=Jakość renderowania upuszczonych przedmiotów
|
||||
options.mode_glint_inv=Efekt zaklęć w GUI
|
||||
options.mode_glint_inv.tooltip=Efekt zaklęć w GUI
|
||||
options.mode_glint_world=Efekt zaklęć na świecie
|
||||
options.mode_glint_world.tooltip=Efekt zaklęć na świecie
|
||||
options.mode_gui_background=Tła menu
|
||||
options.mode_gui_background.tooltip=Tła menu
|
||||
options.mode_leaves=Liście
|
||||
options.mode_leaves.tooltip=Liście
|
||||
options.mode_light_flicker=Migotanie światła
|
||||
options.mode_light_flicker.tooltip=Migotanie światła
|
||||
options.mode_shadows=Cienie bytów
|
||||
options.mode_shadows.tooltip=Cienie bytów
|
||||
options.mode_sky=Niebo
|
||||
options.mode_sky.tooltip=Renderuj niebo
|
||||
options.mode_sun_moon=Słońce i księżyc
|
||||
options.mode_sun_moon.tooltip=Renderuj słońce i księżyc
|
||||
options.mode_stars=Gwiazdy
|
||||
options.mode_stars.tooltip=Renderuj gwiazdy
|
||||
options.mode_water=Woda
|
||||
options.mode_water.tooltip=Woda
|
||||
options.mode_vignette=Winieta
|
||||
options.mode_vignette.tooltip=Winieta
|
||||
options.particles_enc_table=Cząstki zaklęć
|
||||
options.particles_enc_table.tooltip=Cząstki zaklęć
|
||||
options.particles_void=Cząstki próżni
|
||||
options.particles_void.tooltip=Cząstki próżni
|
||||
options.render_distance_clouds=Odległość renderowania chmur
|
||||
options.render_distance_clouds.tooltip=Odległość renderowania chmur
|
||||
options.title.detail=Ustawienia detali
|
||||
options.title.other=Inne ustawienia
|
||||
options.title.particle=Ustawienia cząstek
|
||||
options.title.sky=Ustawienia atmosfery
|
||||
options.title.video=Ustawienia graficzne
|
||||
options.total_stars=Gęstość gwiazd
|
||||
options.total_stars.tooltip=Gęstość gwiazd
|
||||
options.void_fog=Mgła próżni
|
||||
options.void_fog.tooltip=Mgła próżni
|
||||
options.hurt_shake=Drganie ekranu przy obrażeniach
|
||||
options.hurt_shake.tooltip=O ile mocno przechyla się kamera, gdy otrzymasz obrażenia.
|
||||
options.background.random=Losowe
|
||||
options.zoom_smooth=Płynne powiększanie
|
||||
options.zoom_smooth.tooltip=Płynnie dostosowuje kamerę do wybranego poziomu powiększenia, zamiast przechodzić do niego natychmiastowo.
|
||||
options.zoom_smooth_speed=Prędkość płynnego powiększania
|
||||
options.zoom_smooth_speed.tooltip=Jak szybko funkcja płynnego powiększania osiąga docelowy poziom powiększenia. Im wyższa wartość, tym szybsze działanie; im niższa, tym bardziej płynne. Nie ma wpływu, gdy funkcja płynnego powiększania jest wyłączona.
|
||||
options.zoom_cinematic=Kinowe powiększenie
|
||||
options.zoom_cinematic.tooltip=Czy włączyć płynny ruch kamery kinowej. „Wyłączone” - standardowy ruch kamery w grze Minecraft, w którym kamera ustawia się w pozycji myszy. „Włączone” - nieprzerwany ruch po początkowym ruchu myszą, w którym kamera nadal się przesuwa po zatrzymaniu ruchu myszy.
|
||||
+3
@@ -2,6 +2,7 @@ package com.gtnewhorizons.angelica.mixins.early.angelica.tracy;
|
||||
|
||||
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
|
||||
import com.gtnewhorizons.angelica.profiling.GpuFrameLagMeter;
|
||||
import com.gtnewhorizons.angelica.profiling.TracyFrameImages;
|
||||
import com.gtnewhorizons.angelica.profiling.TracyFramePlots;
|
||||
import com.gtnewhorizons.angelica.profiling.TracyUiSections;
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -21,6 +22,7 @@ public class MixinMinecraft_Tracy {
|
||||
TracyFramePlots.onFrame((Minecraft) (Object) this);
|
||||
GpuFrameLagMeter.onFrame();
|
||||
Tracy.frameMark();
|
||||
TracyFrameImages.onFrameMark();
|
||||
TracyUiSections.poll(((Minecraft) (Object) this).currentScreen);
|
||||
Tracy.gpuCollect();
|
||||
}
|
||||
@@ -43,6 +45,7 @@ public class MixinMinecraft_Tracy {
|
||||
@Inject(method = "runGameLoop", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Minecraft;func_147120_f()V", shift = At.Shift.AFTER))
|
||||
private void angelica$tracySwapEnd(CallbackInfo ci) {
|
||||
Tracy.endZone();
|
||||
TracyFrameImages.onPostSwap((Minecraft) (Object) this);
|
||||
}
|
||||
|
||||
// runGameLoop opens a second "root" section after the render phase; renaming it keeps
|
||||
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.celeritas.terrain;
|
||||
|
||||
import org.embeddedt.embeddium.impl.render.chunk.DefaultChunkRenderer;
|
||||
import org.embeddedt.embeddium.impl.render.chunk.data.SectionRenderDataStorage;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
@Mixin(value = DefaultChunkRenderer.class, remap = false)
|
||||
public abstract class MixinDefaultChunkRenderer {
|
||||
|
||||
@Unique private static final SectionRenderDataStorage.BatchCacheParams angelica$CULLED = new SectionRenderDataStorage.BatchCacheParams(true);
|
||||
@Unique private static final SectionRenderDataStorage.BatchCacheParams angelica$UNCULLED = new SectionRenderDataStorage.BatchCacheParams(false);
|
||||
|
||||
@Redirect(method = "render", at = @At(value = "NEW", target = "org/embeddedt/embeddium/impl/render/chunk/data/SectionRenderDataStorage$BatchCacheParams"))
|
||||
private SectionRenderDataStorage.BatchCacheParams angelica$sharedParams(boolean cull) {
|
||||
return cull ? angelica$CULLED : angelica$UNCULLED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.gtnewhorizons.angelica.profiling;
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class TracyFrameImagesTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"1920, 1080, 320, 180",
|
||||
"1920, 400, 320, 64",
|
||||
"800, 1200, 120, 180",
|
||||
"300, 170, 300, 168",
|
||||
"2, 2, 4, 4",
|
||||
})
|
||||
void captureSizeFitsInsideTheTracyBoxKeepingAspect(int srcWidth, int srcHeight, int width, int height) {
|
||||
final long size = TracyFrameImages.fitCaptureSize(srcWidth, srcHeight);
|
||||
assertEquals(width, (int) (size >>> 32), "width for " + srcWidth + "x" + srcHeight);
|
||||
assertEquals(height, (int) size, "height for " + srcWidth + "x" + srcHeight);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -83,7 +83,7 @@ class DeferredMeshSchedulerTest {
|
||||
scheduler = new DeferredMeshScheduler(null, builder, () -> {
|
||||
slicesCreated.incrementAndGet();
|
||||
return Mockito.mock(WorldSlice.class);
|
||||
}, queue::add, results);
|
||||
}, queue::add, r -> queue.removeIf(t -> t == r), results);
|
||||
}
|
||||
|
||||
DeferredSectionMesh submit(boolean important, long priority) {
|
||||
@@ -213,6 +213,7 @@ class DeferredMeshSchedulerTest {
|
||||
assertEquals(0, h.scheduler.pendingCount());
|
||||
assertEquals(0, h.scheduler.finishingCount());
|
||||
assertEquals(0, h.results.size());
|
||||
assertTrue(h.queue.isEmpty(), "queued runNext keeps the destroyed scheduler and its pools reachable");
|
||||
Mockito.verify(queued.slice, Mockito.times(1)).releaseContext();
|
||||
Mockito.verify(inFlight.slice, Mockito.times(1)).releaseContext();
|
||||
assertNull(inFlight.capture);
|
||||
|
||||
@@ -59,4 +59,27 @@ class ObjectPoolTest {
|
||||
pool.acquire();
|
||||
assertEquals(4, calls.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void trimKeepsRecentPeakThenHalvesExcess() {
|
||||
final long w = ObjectPool.TRIM_WINDOW_NANOS;
|
||||
final int floor = 4;
|
||||
ObjectPool<Object> pool = new ObjectPool<>(Object::new, Integer.MAX_VALUE);
|
||||
|
||||
Object[] burst = new Object[100];
|
||||
for (int i = 0; i < burst.length; i++) burst[i] = pool.acquire();
|
||||
pool.trim(0, floor);
|
||||
for (int i = 1; i < burst.length; i++) pool.release(burst[i]);
|
||||
|
||||
for (long t : new long[] {w / 2, w, 2 * w}) {
|
||||
pool.trim(t, floor);
|
||||
assertEquals(99, pool.freeCount(), "peak from the last two windows still holds at t=" + t);
|
||||
}
|
||||
|
||||
int[] expected = {51, 27, 15, 9, 6, 5, 4, 4};
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
pool.trim((3 + i) * w, floor);
|
||||
assertEquals(expected[i], pool.freeCount(), "window " + (3 + i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,32 +226,8 @@ class TesrLayerStateTest {
|
||||
GLSMMatrices.setTextureMatrix(0, new Matrix4f());
|
||||
});
|
||||
}),
|
||||
Arguments.of("foreign program and framebuffer", (Supplier<BatchScenario>) () -> {
|
||||
final int program = GlTestSupport.program(GlTestSupport.TRIVIAL_VS, GlTestSupport.TRIVIAL_FS);
|
||||
final int framebuffer = GLSMFramebuffer.glGenFramebuffers();
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
BatchDrawDefaults.capturePassFramebuffer();
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
|
||||
GLSMPrograms.glUseProgram(program);
|
||||
return new BatchScenario(
|
||||
() -> {
|
||||
assertEquals(0, GLSMPrograms.getActiveProgram(), "program during batch draw");
|
||||
assertEquals(0, GLSMFramebuffer.getDrawFramebuffer(), "framebuffer during batch draw");
|
||||
assertEquals(0, GL11.glGetInteger(GL30.GL_DRAW_FRAMEBUFFER_BINDING), "driver draw framebuffer during batch draw");
|
||||
},
|
||||
() -> {
|
||||
assertEquals(program, GLSMPrograms.getActiveProgram(), "caller program restored");
|
||||
assertEquals(framebuffer, GLSMFramebuffer.getDrawFramebuffer(), "caller framebuffer restored");
|
||||
assertEquals(program, GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM), "driver caller program restored");
|
||||
assertEquals(framebuffer, GL11.glGetInteger(GL30.GL_DRAW_FRAMEBUFFER_BINDING), "driver caller framebuffer restored");
|
||||
},
|
||||
() -> {
|
||||
GLSMPrograms.glUseProgram(0);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLSMFramebuffer.glDeleteFramebuffers(framebuffer);
|
||||
GLSMPrograms.glDeleteProgram(program);
|
||||
});
|
||||
}),
|
||||
Arguments.of("foreign program and framebuffer", (Supplier<BatchScenario>) () -> foreignProgram(false)),
|
||||
Arguments.of("angelica-owned program and framebuffer", (Supplier<BatchScenario>) () -> foreignProgram(true)),
|
||||
Arguments.of("filter from before an item renderer's blur window", (Supplier<BatchScenario>) () -> {
|
||||
final int texture = GLSMTextures.glGenTextures();
|
||||
GLSMTextures.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
@@ -281,6 +257,35 @@ class TesrLayerStateTest {
|
||||
}));
|
||||
}
|
||||
|
||||
private static BatchScenario foreignProgram(boolean owned) {
|
||||
final int program = GlTestSupport.program(GlTestSupport.TRIVIAL_VS, GlTestSupport.TRIVIAL_FS);
|
||||
if (owned) DepthColorStorage.registerOwnedProgram(program);
|
||||
final int framebuffer = GLSMFramebuffer.glGenFramebuffers();
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
BatchDrawDefaults.capturePassFramebuffer();
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer);
|
||||
GLSMPrograms.glUseProgram(program);
|
||||
return new BatchScenario(
|
||||
() -> {
|
||||
assertEquals(0, GLSMPrograms.getActiveProgram(), "program during batch draw");
|
||||
assertEquals(0, GLSMFramebuffer.getDrawFramebuffer(), "framebuffer during batch draw");
|
||||
assertEquals(0, GL11.glGetInteger(GL30.GL_DRAW_FRAMEBUFFER_BINDING), "driver draw framebuffer during batch draw");
|
||||
},
|
||||
() -> {
|
||||
assertEquals(program, GLSMPrograms.getActiveProgram(), "caller program restored");
|
||||
assertEquals(framebuffer, GLSMFramebuffer.getDrawFramebuffer(), "caller framebuffer restored");
|
||||
assertEquals(program, GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM), "driver caller program restored");
|
||||
assertEquals(framebuffer, GL11.glGetInteger(GL30.GL_DRAW_FRAMEBUFFER_BINDING), "driver caller framebuffer restored");
|
||||
},
|
||||
() -> {
|
||||
GLSMPrograms.glUseProgram(0);
|
||||
DepthColorStorage.unregisterOwnedProgram(program);
|
||||
GLSMFramebuffer.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0);
|
||||
GLSMFramebuffer.glDeleteFramebuffers(framebuffer);
|
||||
GLSMPrograms.glDeleteProgram(program);
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@MethodSource("batchDrawCases")
|
||||
void batchDrawDoesNotInheritTheInterruptedRenderersState(String name, Supplier<BatchScenario> setup) {
|
||||
|
||||
+25
-4
@@ -18,8 +18,10 @@ import org.lwjgl.system.libffi.FFICIF;
|
||||
import org.lwjgl.system.libffi.FFIType;
|
||||
import org.lwjgl.system.libffi.LibFFI;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.lwjgl.system.MemoryStack.stackPush;
|
||||
import static org.lwjgl.system.MemoryUtil.NULL;
|
||||
@@ -138,11 +140,13 @@ public final class TracyClientBackend implements TracyBackend {
|
||||
private static final class SlotArena {
|
||||
private final long base;
|
||||
private final int strideShift;
|
||||
private final AtomicReference<WeakReference<Thread>> owner;
|
||||
private int depth;
|
||||
|
||||
SlotArena(int stride) {
|
||||
SlotArena(int stride, Thread owner) {
|
||||
this.strideShift = Integer.numberOfTrailingZeros(stride);
|
||||
this.base = nmemAllocChecked((long) stride * ZoneStack.MAX_DEPTH);
|
||||
this.owner = new AtomicReference<>(new WeakReference<>(owner));
|
||||
}
|
||||
|
||||
long top() {
|
||||
@@ -155,11 +159,23 @@ public final class TracyClientBackend implements TracyBackend {
|
||||
}
|
||||
}
|
||||
|
||||
private final ThreadLocal<SlotArena> slots = ThreadLocal.withInitial(() -> {
|
||||
final SlotArena arena = new SlotArena(ctxSize);
|
||||
private final ThreadLocal<SlotArena> slots = ThreadLocal.withInitial(this::claimArena);
|
||||
|
||||
/** Arenas outlive their threads (chunk workers are recreated per world), so reuse a dead thread's arena; owners are weak because a dead thread still holds its Runnable and through it the old world. */
|
||||
private SlotArena claimArena() {
|
||||
final Thread self = Thread.currentThread();
|
||||
for (SlotArena arena : ARENAS) {
|
||||
final WeakReference<Thread> ref = arena.owner.get();
|
||||
final Thread owner = ref.get();
|
||||
if ((owner == null || !owner.isAlive()) && arena.owner.compareAndSet(ref, new WeakReference<>(self))) {
|
||||
arena.depth = 0;
|
||||
return arena;
|
||||
}
|
||||
}
|
||||
final SlotArena arena = new SlotArena(ctxSize, self);
|
||||
ARENAS.add(arena);
|
||||
return arena;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public long beginZone(long srcLoc) {
|
||||
@@ -199,6 +215,11 @@ public final class TracyClientBackend implements TracyBackend {
|
||||
JNI.invokePV(NULL, sym.frameMark);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void frameImage(long rgba, int width, int height, int offset, boolean flip) {
|
||||
JNI.invokePV(rgba, width, height, offset, flip ? 1 : 0, sym.frameImage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long internFrameName(String name) {
|
||||
return memAddress(MemoryUtil.memUTF8(name));
|
||||
|
||||
@@ -21,6 +21,7 @@ final class TracyLibrary {
|
||||
final long zoneText;
|
||||
final long zoneValue;
|
||||
final long frameMark;
|
||||
final long frameImage;
|
||||
final long plot;
|
||||
final long plotInt;
|
||||
final long plotConfig;
|
||||
@@ -58,6 +59,7 @@ final class TracyLibrary {
|
||||
zoneText = req(lib, "ang_zone_text");
|
||||
zoneValue = req(lib, "ang_zone_value");
|
||||
frameMark = req(lib, "___tracy_emit_frame_mark");
|
||||
frameImage = req(lib, "___tracy_emit_frame_image");
|
||||
plot = req(lib, "ang_plot");
|
||||
plotInt = req(lib, "___tracy_emit_plot_int");
|
||||
plotConfig = req(lib, "___tracy_emit_plot_config");
|
||||
|
||||
Reference in new issue
Block a user