Harden SDL-GPU buffer mapping lifetime (#2217)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-10-04 16:31:48 -07:00
1 parent 38af64f10c
commit 12857006c6
19 files changed
+931 -235

No files matched your search

@@ -6987,7 +6987,7 @@ public class GLStateManager {
private static void onBufferStorage(int target, int flags) {
if ((flags & MAP_PERSISTENT_BIT) != 0) {
markPersistentlyMapped(target);
if (!RENDER_BACKEND.isBufferImmutable(bufferForTarget(ctx(), target))) markPersistentlyMapped(target);
return;
}
onBufferRespec(target);
@@ -7070,14 +7070,27 @@ public class GLStateManager {
public static void nglBufferSubData(int target, long offset, long size, long data) { glBufferSubData(target, offset, MemoryUtilities.memByteBuffer(data, checkedSize(size))); }
public static ByteBuffer glMapBuffer(int target, int access) { onBufferMap(target, access); return RENDER_BACKEND.mapBuffer(target, access); }
public static ByteBuffer glMapBuffer(int target, int access) {
final ByteBuffer buf = RENDER_BACKEND.mapBuffer(target, access);
if (buf != null) onBufferMap(target, access);
return buf;
}
public static ByteBuffer glMapBuffer(int target, int access, ByteBuffer old_buffer) { return glMapBuffer(target, access); }
public static ByteBuffer glMapBuffer(int target, int access, long length, ByteBuffer old_buffer) { onBufferMap(target, access); return RENDER_BACKEND.mapBuffer(target, access, length, old_buffer); }
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access) { onBufferMapRange(target, access); return RENDER_BACKEND.mapBufferRange(target, offset, length, access); }
public static ByteBuffer glMapBuffer(int target, int access, long length, ByteBuffer old_buffer) {
final ByteBuffer buf = RENDER_BACKEND.mapBuffer(target, access, length, old_buffer);
if (buf != null) onBufferMap(target, access);
return buf;
}
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access) {
final ByteBuffer buf = RENDER_BACKEND.mapBufferRange(target, offset, length, access);
if (buf != null) onBufferMapRange(target, access);
return buf;
}
public static ByteBuffer glMapBufferRange(int target, long offset, long length, int access, ByteBuffer old_buffer) { return glMapBufferRange(target, offset, length, access); }
public static long glMapBufferRangeAddress(int target, long offset, long length, int access) {
onBufferMapRange(target, access);
return RENDER_BACKEND.mapBufferRangeAddress(target, offset, length, access);
final long address = RENDER_BACKEND.mapBufferRangeAddress(target, offset, length, access);
if (address != 0L) onBufferMapRange(target, access);
return address;
}
public static void glFlushMappedBufferRange(int target, long offset, long length) { RENDER_BACKEND.flushMappedBufferRange(target, offset, length); }
public static boolean glUnmapBuffer(int target) { onBufferUnmap(target); return RENDER_BACKEND.unmapBuffer(target); }
@@ -7466,8 +7479,8 @@ public class GLStateManager {
}
}
public static long nglMapBuffer(int target, int access) {
onBufferMap(target, access);
final ByteBuffer buf = RENDER_BACKEND.mapBuffer(target, access);
if (buf != null) onBufferMap(target, access);
return buf == null ? 0L : MemoryUtilities.memAddress0(buf);
}
public static void nglMultiDrawElementsBaseVertex(int mode, long count, int type, long indices, int primcount, long basevertex) {
@@ -59,6 +59,7 @@ public abstract class RenderBackend {
public boolean framebufferCompletenessIsMeaningful() { return true; }
public void onPersistentBufferWrite(int glId, long offset, long size) {}
public boolean isBufferImmutable(int glId) { return false; }
public boolean isAnisotropicSupported() { return true; }
@@ -41,6 +41,7 @@ import com.gtnewhorizons.angelica.sdlgpu.resource.FBOClearTracker;
import com.gtnewhorizons.angelica.sdlgpu.resource.FboState;
import com.gtnewhorizons.angelica.sdlgpu.resource.FormatMap;
import com.gtnewhorizons.angelica.sdlgpu.resource.Image3DClear;
import com.gtnewhorizons.angelica.sdlgpu.resource.MappedRange;
import com.gtnewhorizons.angelica.sdlgpu.resource.PersistentBufferSync;
import com.gtnewhorizons.angelica.sdlgpu.resource.PackState;
import com.gtnewhorizons.angelica.sdlgpu.resource.PersistentMapping;
@@ -64,6 +65,7 @@ import com.gtnewhorizons.angelica.sdlgpu.util.ThreadRegistry;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -168,6 +170,7 @@ public class SDLGPURenderBackend extends RenderBackend {
private final FFPTrace ffpTrace = SystemProperties.FFP_TRACE ? new FFPTrace(resourceManager) : null;
private final IntOpenHashSet missingProgramWarned = new IntOpenHashSet();
private final LongOpenHashSet warnedBufferOps = new LongOpenHashSet();
private boolean droppedCopyWarned;
private boolean droppedSsboWriteWarned;
@@ -248,7 +251,9 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void onPersistentBufferWrite(int glId, long offset, long size) {
persistentSync.onPersistentBufferWrite(glId, offset, size);
if (!persistentSync.onPersistentBufferWrite(glId, offset, size)) {
warnInvalidBufferOp("persistent write out of range", glId);
}
}
@Override public void onPostWindowCreate(long window) {
@@ -515,14 +520,13 @@ public class SDLGPURenderBackend extends RenderBackend {
st.appliedLogicOpKey = 0;
st.fragSamplerBindings.free();
st.vertSamplerBindings.free();
if (st.mappedStagingBuffer != null) {
MemoryUtil.memFree(st.mappedStagingBuffer);
}
clearMappedState(st);
}
registeredStates.clear();
drawDispatch.clearWarnedState();
synchronized (warnedBufferOps) {
warnedBufferOps.clear();
}
attachmentClear.shutdown();
depthStencilReadback.shutdown();
textureOps.shutdown();
@@ -1333,6 +1337,12 @@ public class SDLGPURenderBackend extends RenderBackend {
final int srcGlId = getBoundBuffer(readTarget);
final int dstGlId = getBoundBuffer(writeTarget);
if (srcGlId == 0 || dstGlId == 0 || size <= 0) return;
final long srcSize = resourceManager.getBufferSize(srcGlId);
final long dstSize = resourceManager.getBufferSize(dstGlId);
if (readOffset < 0 || writeOffset < 0 || (srcSize > 0 && (readOffset > srcSize || size > srcSize - readOffset)) || (dstSize > 0 && (writeOffset > dstSize || size > dstSize - writeOffset))) {
warnInvalidBufferOp("glCopyBufferSubData out of range", dstGlId);
return;
}
resourceManager.dropArrayShadow(dstGlId);
resourceManager.dropEboShadow(dstGlId);
resourceManager.markBufferContentsDefined(dstGlId);
@@ -1347,7 +1357,7 @@ public class SDLGPURenderBackend extends RenderBackend {
deferredCopyOp.dstHandle = dstHandle;
deferredCopyOp.writeOffset = writeOffset;
deferredCopyOp.seq = seq;
final boolean handled = resourceManager.enqueuePersistentCopyDeferred(srcGlId, dstGlId, seq, deferredCopyOp);
final boolean handled = resourceManager.enqueuePersistentCopyDeferred(srcGlId, dstGlId, seq, readOffset, size, deferredCopyOp);
if (handled) {
wakeTransferThread();
return;
@@ -2761,8 +2771,10 @@ public class SDLGPURenderBackend extends RenderBackend {
}
private void deleteBuffer(int buffer) {
dropMappingFor(buffer);
if (buffer != 0) unbindDeletedBuffer(s(), buffer);
if (buffer != 0) {
freePlainMappingFor(buffer, false);
unbindDeletedBuffer(s(), buffer);
}
readbackShadows.release(buffer);
resourceManager.deleteBuffer(buffer);
}
@@ -2809,12 +2821,6 @@ public class SDLGPURenderBackend extends RenderBackend {
if (ssboChanged) st.ssboBindGen++;
}
private static void dropMappingFor(int buffer) {
final ContextState st = s();
if (buffer == 0 || st.mappedBufferGlId != buffer || st.mappedStagingBuffer == null) return;
MemoryUtil.memFree(st.mappedStagingBuffer);
clearMappedState(st);
}
@Override public void bindBuffer(int target, int buffer) {
final ContextState st = s();
switch (target) {
@@ -2914,8 +2920,15 @@ public class SDLGPURenderBackend extends RenderBackend {
}
@Override public void bufferData(int target, long size, int usage) {
final int glId = getBoundBuffer(target);
final ContextState cs = s();
final int glId = cs.getBoundBuffer(target);
if (glId == 0) return;
if (refuseImmutable(glId, "glBufferData on immutable storage")) return;
respecifyStore(cs, target, glId, size, usage);
}
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) {
if (resourceManager.getBufferSize(glId) == size) return;
@@ -2931,14 +2944,45 @@ public class SDLGPURenderBackend extends RenderBackend {
readbackShadows.release(glId);
resourceManager.deleteBuffer(glId);
resourceManager.createBuffer(glId, sdlUsage, size);
if (target == GL15.GL_ARRAY_BUFFER) s().bumpAttribStateGen();
if (target == GL15.GL_ARRAY_BUFFER) cs.bumpAttribStateGen();
}
private void freePlainMappingFor(int glId, boolean warn) {
final ByteBuffer staging = resourceManager.takePlainStaging(glId);
if (staging == null) return;
MemoryUtil.memFree(staging);
if (warn) warnInvalidBufferOp("glBufferData on mapped buffer", glId);
}
private boolean refuseImmutable(int glId, String op) {
if (resourceManager.getBufferStorageFlags(glId) == BufferParams.MUTABLE_STORE) return false;
warnInvalidBufferOp(op, glId);
return true;
}
@Override public boolean isBufferImmutable(int glId) {
return resourceManager.getBufferStorageFlags(glId) != BufferParams.MUTABLE_STORE;
}
private void warnInvalidBufferOp(String op, int glId) {
synchronized (warnedBufferOps) {
if (warnedBufferOps.add(((long) op.hashCode() << 32) | (glId & 0xFFFFFFFFL))) {
LOG.warn("{} (buffer {})", op, glId, new Throwable());
}
}
}
@Override public void bufferData(int target, ByteBuffer data, int usage) {
final ContextState cs = s();
final int glId = getBoundBuffer(target);
final int glId = cs.getBoundBuffer(target);
if (glId == 0 || data == null) return;
if (refuseImmutable(glId, "glBufferData on immutable storage")) return;
respecifyStore(cs, target, glId, data, usage);
}
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) {
if (dataSize > 0) {
@@ -3028,11 +3072,15 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void bufferSubData(int target, long offset, ByteBuffer data) {
final ContextState cs = s();
final int glId = getBoundBuffer(target);
final int glId = cs.getBoundBuffer(target);
if (glId == 0 || data == null) return;
if (target == GL31.GL_UNIFORM_BUFFER) {
final ByteBuffer shadow = resourceManager.getUboShadow(glId);
if (shadow != null) {
if (offset < 0 || offset + data.remaining() > shadow.capacity()) {
warnInvalidBufferOp("glBufferSubData out of range on uniform buffer", glId);
return;
}
shadow.position(0);
ByteRegionCopy.copyByteRegion(data, data.position(), shadow, (int) offset, data.remaining());
}
@@ -3101,133 +3149,132 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public boolean unmapBuffer(int target) {
final ContextState cs = s();
final int glId = getBoundBuffer(target);
final int glId = cs.getBoundBuffer(target);
final PersistentMapping pm = resourceManager.removePersistentMapping(glId);
if (pm != null) {
resourceManager.releasePersistentStaging(pm);
if (resourceManager.getPersistentMapping(glId) != null) {
final PersistentMapping pm = resourceManager.removePersistentMapping(glId);
if (pm != null) resourceManager.releasePersistentStaging(pm, glId, "unmap");
return true;
}
final ContextState st = cs;
if (st.mappedStagingBuffer == null) return false;
final MappedRange m = cs.mappingScratch;
if (!resourceManager.takePlainMapping(glId, m)) {
warnInvalidBufferOp("glUnmapBuffer on unmapped buffer", glId);
return false;
}
final boolean writeMap = (st.mappedAccessFlags & GL30.GL_MAP_WRITE_BIT) != 0;
final ByteBuffer staging = m.staging;
m.staging = null;
final boolean writeMap = (m.accessFlags & GL30.GL_MAP_WRITE_BIT) != 0;
if (target == GL30.GL_PIXEL_UNPACK_BUFFER) {
st.mappedStagingBuffer.position(0).limit((int) st.mappedLength);
final long gpuHandle = resourceManager.getBufferHandle(st.mappedBufferGlId);
staging.position(0).limit((int) m.length);
final long gpuHandle = resourceManager.getBufferHandle(glId);
if (writeMap && gpuHandle != 0) {
if (st.deferUploads) {
enqueuePreCopied(st.mappedStagingBuffer, gpuHandle, st.mappedOffset, false);
if (cs.deferUploads) {
enqueuePreCopied(staging, gpuHandle, m.offset, false);
} else if (frameManager.getCommandBuffer() != 0) {
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), st.mappedStagingBuffer, gpuHandle, st.mappedOffset, false);
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), staging, gpuHandle, m.offset, false);
}
resourceManager.markBufferContentsDefined(st.mappedBufferGlId);
resourceManager.markBufferContentsDefined(glId);
}
final ByteBuffer old = resourceManager.putPboStaging(glId, st.mappedStagingBuffer);
final ByteBuffer old = resourceManager.putPboStaging(glId, staging);
if (old != null) MemoryUtil.memFree(old);
clearMappedState(st);
return true;
}
if (target == GL31.GL_UNIFORM_BUFFER) {
final ByteBuffer shadow = resourceManager.getUboShadow(st.mappedBufferGlId);
if (writeMap && shadow != null && st.mappedOffset + st.mappedLength <= shadow.capacity()) {
st.mappedStagingBuffer.position(0).limit((int) st.mappedLength);
shadow.position((int) st.mappedOffset).limit((int) (st.mappedOffset + st.mappedLength));
shadow.put(st.mappedStagingBuffer);
final ByteBuffer shadow = resourceManager.getUboShadow(glId);
if (writeMap && shadow != null && m.offset + m.length <= shadow.capacity()) {
staging.position(0).limit((int) m.length);
shadow.position((int) m.offset).limit((int) (m.offset + m.length));
shadow.put(staging);
shadow.position(0).limit(shadow.capacity());
resourceManager.markBufferContentsDefined(st.mappedBufferGlId);
resourceManager.markBufferContentsDefined(glId);
}
MemoryUtil.memFree(st.mappedStagingBuffer);
clearMappedState(st);
MemoryUtil.memFree(staging);
return true;
}
if (!writeMap) {
MemoryUtil.memFree(st.mappedStagingBuffer);
clearMappedState(st);
MemoryUtil.memFree(staging);
return true;
}
st.mappedStagingBuffer.position(0).limit((int) st.mappedLength);
staging.position(0).limit((int) m.length);
final long gpuHandle;
if (st.mappedInvalidate) {
if (m.invalidate) {
final int sdlUsage = FormatMap.mapBufferUsage(0);
final long glSize = Math.max(resourceManager.getBufferSize(st.mappedBufferGlId), st.mappedLength);
final int glUsage = resourceManager.getBufferGlUsage(st.mappedBufferGlId);
final int storageFlags = resourceManager.getBufferStorageFlags(st.mappedBufferGlId);
resourceManager.deleteBuffer(st.mappedBufferGlId);
gpuHandle = resourceManager.createBuffer(st.mappedBufferGlId, sdlUsage, glSize);
resourceManager.recordBufferGlParams(st.mappedBufferGlId, glUsage, storageFlags);
st.bumpAttribStateGen();
final long glSize = Math.max(resourceManager.getBufferSize(glId), m.length);
final int glUsage = resourceManager.getBufferGlUsage(glId);
final int storageFlags = resourceManager.getBufferStorageFlags(glId);
resourceManager.deleteBuffer(glId);
gpuHandle = resourceManager.createBuffer(glId, sdlUsage, glSize);
resourceManager.recordBufferGlParams(glId, glUsage, storageFlags);
cs.bumpAttribStateGen();
} else {
gpuHandle = resourceManager.getBufferHandle(st.mappedBufferGlId);
gpuHandle = resourceManager.getBufferHandle(glId);
}
if (target == GL15.GL_ELEMENT_ARRAY_BUFFER
&& (cs.primitiveRestartEnabled || resourceManager.hasEboShadow(st.mappedBufferGlId))) {
final int len = (int) st.mappedLength;
st.mappedStagingBuffer.position(0).limit(len);
persistentSync.mirrorEboShadow(st.mappedBufferGlId, st.mappedStagingBuffer, (int) st.mappedOffset, len);
st.mappedStagingBuffer.position(0).limit(len);
&& (cs.primitiveRestartEnabled || resourceManager.hasEboShadow(glId))) {
final int len = (int) m.length;
staging.position(0).limit(len);
persistentSync.mirrorEboShadow(glId, staging, (int) m.offset, len);
staging.position(0).limit(len);
}
if (target == GL15.GL_ARRAY_BUFFER && resourceManager.hasArrayShadow(st.mappedBufferGlId)) {
final int len = (int) st.mappedLength;
st.mappedStagingBuffer.position(0).limit(len);
resourceManager.mirrorArrayShadowRegion(st.mappedBufferGlId, st.mappedStagingBuffer, (int) st.mappedOffset);
st.mappedStagingBuffer.position(0).limit(len);
if (target == GL15.GL_ARRAY_BUFFER && resourceManager.hasArrayShadow(glId)) {
final int len = (int) m.length;
staging.position(0).limit(len);
resourceManager.mirrorArrayShadowRegion(glId, staging, (int) m.offset);
staging.position(0).limit(len);
}
if (gpuHandle != 0) {
if (st.deferUploads) {
enqueuePreCopied(st.mappedStagingBuffer, gpuHandle, st.mappedOffset, false);
if (cs.deferUploads) {
enqueuePreCopied(staging, gpuHandle, m.offset, false);
} else if (frameManager.getCommandBuffer() != 0) {
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), st.mappedStagingBuffer, gpuHandle, st.mappedOffset, false);
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), staging, gpuHandle, m.offset, false);
}
resourceManager.markBufferContentsDefined(st.mappedBufferGlId);
resourceManager.markBufferContentsDefined(glId);
}
MemoryUtil.memFree(st.mappedStagingBuffer);
clearMappedState(st);
MemoryUtil.memFree(staging);
return true;
}
private static void clearMappedState(ContextState st) {
st.mappedStagingBuffer = null;
st.mappedBufferGlId = 0;
st.mappedOffset = 0;
st.mappedLength = 0;
st.mappedInvalidate = false;
st.mappedAccessFlags = 0;
}
@Override public ByteBuffer mapBufferRange(int target, long offset, long length, int access) {
final int glId = getBoundBuffer(target);
final ContextState cs = s();
final int glId = cs.getBoundBuffer(target);
if (glId == 0 || length <= 0) return null;
final boolean persistent = (access & GL44.GL_MAP_PERSISTENT_BIT) != 0;
if (persistent) {
if (target == GL15.GL_ARRAY_BUFFER) resourceManager.dropArrayShadow(glId);
final int storageFlags = resourceManager.getBufferStorageFlags(glId);
if (storageFlags < 0 || (storageFlags & GL44.GL_MAP_PERSISTENT_BIT) == 0) {
warnInvalidBufferOp("glMapBufferRange persistent without persistent storage", glId);
return null;
}
final ByteBuffer staging = MemoryUtil.memCalloc((int) length);
final PersistentMapping fresh = new PersistentMapping(staging, offset, length, access);
final PersistentMapping prior = resourceManager.swapPersistentMapping(glId, fresh);
if (prior != null) resourceManager.releasePersistentStaging(prior);
if (!resourceManager.putPersistentMappingIfAbsent(glId, fresh)) {
MemoryUtil.memFree(staging);
warnInvalidBufferOp("glMapBufferRange on mapped buffer", glId);
return null;
}
if (target == GL15.GL_ARRAY_BUFFER) resourceManager.dropArrayShadow(glId);
return staging;
}
final ContextState st = s();
if (st.mappedStagingBuffer != null) MemoryUtil.memFree(st.mappedStagingBuffer);
st.mappedStagingBuffer = MemoryUtil.memAlloc((int) length);
st.mappedBufferGlId = glId;
st.mappedOffset = offset;
st.mappedLength = length;
st.mappedInvalidate = (access & GL30.GL_MAP_INVALIDATE_BUFFER_BIT) != 0;
st.mappedAccessFlags = access;
final ByteBuffer staging = MemoryUtil.memAlloc((int) length);
if ((access & (GL30.GL_MAP_INVALIDATE_BUFFER_BIT | GL30.GL_MAP_INVALIDATE_RANGE_BIT)) == 0) {
seedMappedStaging(target, glId, offset, length, st.mappedStagingBuffer);
seedMappedStaging(target, glId, offset, length, staging);
}
return st.mappedStagingBuffer;
if (!resourceManager.putPlainMappingIfAbsent(glId, staging, offset, length, (access & GL30.GL_MAP_INVALIDATE_BUFFER_BIT) != 0, access)) {
MemoryUtil.memFree(staging);
warnInvalidBufferOp("glMapBufferRange on mapped buffer", glId);
return null;
}
return staging;
}
private void seedMappedStaging(int target, int glId, long offset, long length, ByteBuffer staging) {
@@ -3315,22 +3362,31 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void flushMappedBufferRange(int target, long offset, long length) {
final int glId = getBoundBuffer(target);
if (glId == 0 || length <= 0) return;
persistentSync.onPersistentBufferWrite(glId, offset, length);
if (!persistentSync.onPersistentBufferWrite(glId, offset, length)) {
warnInvalidBufferOp("glFlushMappedBufferRange out of range", glId);
}
}
@Override public void bufferStorage(int target, ByteBuffer data, int flags) {
bufferData(target, data, GL15.GL_DYNAMIC_DRAW);
recordImmutableStorage(target, flags);
final ContextState cs = s();
final int glId = cs.getBoundBuffer(target);
if (glId == 0) return;
if (refuseImmutable(glId, "glBufferStorage on immutable storage")) return;
if (data != null) respecifyStore(cs, target, glId, data, GL15.GL_DYNAMIC_DRAW);
recordImmutableStorage(glId, flags);
}
@Override public void bufferStorage(int target, long size, int flags) {
bufferData(target, size, GL15.GL_DYNAMIC_DRAW);
recordImmutableStorage(target, flags);
final ContextState cs = s();
final int glId = cs.getBoundBuffer(target);
if (glId == 0) return;
if (refuseImmutable(glId, "glBufferStorage on immutable storage")) return;
respecifyStore(cs, target, glId, size, GL15.GL_DYNAMIC_DRAW);
recordImmutableStorage(glId, flags);
}
private void recordImmutableStorage(int target, int flags) {
final int glId = getBoundBuffer(target);
if (glId != 0) resourceManager.recordBufferGlParams(glId, GL15.GL_DYNAMIC_DRAW, flags);
private void recordImmutableStorage(int glId, int flags) {
resourceManager.recordBufferGlParams(glId, GL15.GL_DYNAMIC_DRAW, flags);
}
@Override public void getBufferSubData(int target, long offset, ByteBuffer data) {
final int glId = getBoundBuffer(target);
@@ -3346,23 +3402,12 @@ public class SDLGPURenderBackend extends RenderBackend {
}
private void downloadBufferBlocking(long handle, int offset, int size, ByteBuffer out) {
flushDeferredUploadsForRead();
frameManager.endCopyPassIfActive();
frameManager.endRenderPassIfActive(FrameManager.PASS_END_COPY);
flushAndSubmitMidFrame();
final long cb = SDL_AcquireGPUCommandBuffer(device.getDevice());
if (cb == 0) return;
resourceManager.downloadFromBuffer(cb, handle, offset, size, out);
}
private void flushDeferredUploadsForRead() {
final ContextState st = s();
drainDeferredPersistentRegions(st);
resourceManager.flushUploadArena();
if (transferThread != null && st.frameHighestEnqueuedSeq > transferThread.getSubmittedSeq()) {
transferThread.awaitSubmittedUpTo(st.frameHighestEnqueuedSeq);
}
}
private static void serveFromShadow(ByteBuffer shadow, int srcOffset, ByteBuffer data, int rem) {
final int dstPos = data.position();
if (shadow.isDirect() && data.isDirect()) {
@@ -3399,8 +3444,9 @@ public class SDLGPURenderBackend extends RenderBackend {
}
final PersistentMapping pm = resourceManager.getPersistentMapping(glId);
final ContextState st = s();
final boolean plainMapped = st.mappedStagingBuffer != null && st.mappedBufferGlId == glId;
final MappedRange plain = s().mappingScratch;
final boolean plainMapped = resourceManager.peekPlainMapping(glId, plain);
plain.staging = null;
final boolean mapped;
final int accessFlags;
@@ -3413,9 +3459,9 @@ public class SDLGPURenderBackend extends RenderBackend {
mapLength = pm.length;
} else if (plainMapped) {
mapped = true;
accessFlags = st.mappedAccessFlags;
mapOffset = st.mappedOffset;
mapLength = st.mappedLength;
accessFlags = plain.accessFlags;
mapOffset = plain.offset;
mapLength = plain.length;
} else {
mapped = false;
accessFlags = 0;
@@ -3853,14 +3899,18 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public int createBuffers() { return resourceManager.genBuffer(); }
@Override public void namedBufferData(int buffer, long size, int usage) {
if (buffer == 0 || size <= 0) return;
if (refuseImmutable(buffer, "glNamedBufferData on immutable storage")) return;
final int sdlUsage = FormatMap.mapBufferUsage(GL15.GL_ARRAY_BUFFER);
freePlainMappingFor(buffer, true);
resourceManager.deleteBuffer(buffer);
resourceManager.createBuffer(buffer, sdlUsage, size);
resourceManager.recordBufferGlParams(buffer, usage, BufferParams.MUTABLE_STORE);
}
@Override public void namedBufferData(int buffer, ByteBuffer data, int usage) {
if (buffer == 0 || data == null) return;
if (refuseImmutable(buffer, "glNamedBufferData on immutable storage")) return;
final int sdlUsage = FormatMap.mapBufferUsage(GL15.GL_ARRAY_BUFFER);
freePlainMappingFor(buffer, true);
resourceManager.deleteBuffer(buffer);
final long handle = resourceManager.createBuffer(buffer, sdlUsage, data.remaining());
resourceManager.recordBufferGlParams(buffer, usage, BufferParams.MUTABLE_STORE);
@@ -4121,6 +4171,11 @@ public class SDLGPURenderBackend extends RenderBackend {
@Override public void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, ByteBuffer data) {
final int glId = getBoundBuffer(target);
if (glId == 0 || size <= 0) return;
final long bufSize = resourceManager.getBufferSize(glId);
if (offset < 0 || (bufSize > 0 && (offset > bufSize || size > bufSize - offset))) {
warnInvalidBufferOp("glClearBufferSubData out of range", glId);
return;
}
final long handle = resourceManager.getBufferHandle(glId);
if (handle == 0 || frameManager.getCommandBuffer() == 0) return;
@@ -4484,8 +4539,8 @@ public class SDLGPURenderBackend extends RenderBackend {
long readOffset, size, dstHandle, writeOffset, seq;
@Override
public void accept(PersistentMapping src, PersistentMapping dst) {
enqueueUpload(TransferThread.StagingReadUpload.acquire(src.staging, readOffset, size, dstHandle, writeOffset, seq, false));
if (dst != null) PersistentBufferSync.mirrorPersistentCopy(src.staging, readOffset, dst.staging, writeOffset, size);
enqueueUpload(TransferThread.StagingReadUpload.acquire(src.staging, src.stagingIndex(readOffset), size, dstHandle, writeOffset, seq, false));
if (dst != null) PersistentBufferSync.mirrorPersistentCopy(src, readOffset, dst, writeOffset, size);
}
}
}
@@ -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.MappedRange;
import com.gtnewhorizons.angelica.sdlgpu.resource.PackState;
import com.gtnewhorizons.angelica.sdlgpu.shader.LogicOpVariant;
import com.gtnewhorizons.angelica.sdlgpu.shader.ShaderManager;
@@ -314,12 +315,7 @@ public final class ContextState {
public final long[] computeBatchRwTexs = new long[MAX_IMAGE_UNITS];
public final int[] computeBatchRwTexLevels = new int[MAX_IMAGE_UNITS];
public int mappedBufferGlId;
public ByteBuffer mappedStagingBuffer;
public long mappedOffset;
public long mappedLength;
public boolean mappedInvalidate;
public int mappedAccessFlags;
public final MappedRange mappingScratch = new MappedRange();
public final SDL_GPUTextureSamplerBinding.Buffer fragSamplerBindings = SDL_GPUTextureSamplerBinding.calloc(MAX_SAMPLERS);
public final SDL_GPUTextureSamplerBinding.Buffer vertSamplerBindings = SDL_GPUTextureSamplerBinding.calloc(MAX_SAMPLERS);
@@ -173,8 +173,9 @@ public final class FFPTrace {
sb.append(" | ffpUbo=buffer").append(glId).append(" noStaging");
return;
}
final int base = st.uboRangeOffset[binding];
sb.append(" | ffpUbo=buffer").append(glId).append(" base=").append(base);
final int rangeOffset = st.uboRangeOffset[binding];
final int base = pm != null ? (int) pm.stagingIndex(rangeOffset) : rangeOffset;
sb.append(" | ffpUbo=buffer").append(glId).append(" base=").append(rangeOffset);
appendUboFloat(buf, base, FFPUniformBlock.ALPHA_REF, " u_AlphaRef=");
appendUboMatrix(buf, base, FFPUniformBlock.MVP_MATRIX, " u_MVPMatrix=");
appendUboMatrix(buf, base, FFPUniformBlock.LIGHTMAP_TEXTURE_MATRIX, " u_LightmapTextureMatrix=");
@@ -698,8 +698,8 @@ public final class PipelineApplier {
final int offset = st.uboRangeOffset[binding];
final PersistentMapping pm = resourceManager.getPersistentMapping(glId);
if (pm != null) {
if (offset < 0 || offset + size > pm.staging.capacity()) return 0L;
return MemoryUtil.memAddress0(pm.staging) + offset;
if (!pm.covers(offset, size)) return 0L;
return MemoryUtil.memAddress0(pm.staging) + pm.stagingIndex(offset);
}
final ByteBuffer shadow = resourceManager.getUboShadow(glId);
if (shadow != null) {
@@ -8,16 +8,13 @@ public final class ByteRegionCopy {
private ByteRegionCopy() {}
public static void copyByteRegion(ByteBuffer src, int srcOff, ByteBuffer dst, int dstOff, int len) {
if ((srcOff | dstOff | len) < 0 || srcOff + (long) len > src.capacity() || dstOff + (long) len > dst.capacity()) {
throw new IllegalStateException("copyByteRegion out of bounds: len=" + len + " srcOff=" + srcOff + " src.capacity=" + src.capacity() + " dstOff=" + dstOff + " dst.capacity=" + dst.capacity());
}
if (src.isDirect() && dst.isDirect()) {
MemoryUtil.memCopy(MemoryUtil.memAddress(src, srcOff), MemoryUtil.memAddress(dst, dstOff), len);
return;
}
if (src.hasArray() && dst.hasArray()) {
System.arraycopy(src.array(), src.arrayOffset() + srcOff, dst.array(), dst.arrayOffset() + dstOff, len);
return;
}
for (int i = 0; i < len; i++) {
dst.put(dstOff + i, src.get(srcOff + i));
}
dst.put(dstOff, src, srcOff, len);
}
}
@@ -0,0 +1,12 @@
package com.gtnewhorizons.angelica.sdlgpu.resource;
import java.nio.ByteBuffer;
public final class MappedRange {
public int glId;
public ByteBuffer staging;
public long offset;
public long length;
public boolean invalidate;
public int accessFlags;
}
@@ -33,11 +33,14 @@ public final class PersistentBufferSync {
this.sink = sink;
}
public void onPersistentBufferWrite(int glId, long offset, long size) {
public boolean onPersistentBufferWrite(int glId, long offset, long size) {
final PersistentMapping pm = resourceManager.getPersistentMapping(glId);
if (pm != null && pm.markDirty(offset, size)) {
if (pm == null || size == 0) return true;
if (offset < 0 || size < 0 || offset > pm.length || size > pm.length - offset) return false;
if (pm.markDirty(offset, size)) {
resourceManager.trackPersistentDirty();
}
return true;
}
public void uploadDirtyPersistentRegion() { processDirtyPersistentRegions(false); }
@@ -59,30 +62,41 @@ public final class PersistentBufferSync {
for (int i = 0; i < n; i++) {
final PersistentMapping pm = vals.get(i);
if (!pm.isDirty()) continue;
final long gpuHandle = resourceManager.getBufferHandle(keys.getInt(i));
if (gpuHandle == 0) continue;
final long claimed = pm.claimDirty();
if (PersistentMapping.isClean(claimed)) continue;
final long off = PersistentMapping.rangeOffset(claimed);
final long size = PersistentMapping.rangeSize(claimed);
if (Tracy.ENABLED) frameManager.notePersistentDrain();
if (defer) {
final long seq = sink.nextSeq();
pm.lastEnqueuedSeq = seq;
sink.enqueue(TransferThread.StagingReadUpload.acquire(pm.staging, off, size, gpuHandle, off, seq, false));
enqueued++;
} else {
pm.staging.position((int) off).limit((int) (off + size));
resourceManager.uploadToBuffer(frameManager.ensureCopyPass(), pm.staging, gpuHandle, off, false);
pm.staging.clear();
final int glId = keys.getInt(i);
long copyPass = 0L;
if (!defer) {
if (resourceManager.getBufferHandle(glId) == 0) continue;
copyPass = frameManager.ensureCopyPass();
}
resourceManager.beginPersistentDrain();
try {
if (resourceManager.getPersistentMapping(glId) != pm) continue;
final long gpuHandle = resourceManager.getBufferHandle(glId);
if (gpuHandle == 0) continue;
final long claimed = pm.claimDirty();
if (PersistentMapping.isClean(claimed)) continue;
resourceManager.clearPersistentDirty();
final long off = PersistentMapping.rangeOffset(claimed);
final long size = PersistentMapping.rangeSize(claimed);
if (Tracy.ENABLED) frameManager.notePersistentDrain();
if (defer) {
final long seq = sink.nextSeq();
pm.lastEnqueuedSeq = seq;
sink.enqueue(TransferThread.StagingReadUpload.acquire(pm.staging, off, size, gpuHandle, pm.offset + off, seq, false));
enqueued++;
} else {
resourceManager.uploadRangeToBuffer(copyPass, pm.staging, off, size, gpuHandle, pm.offset + off, false);
}
} finally {
resourceManager.endPersistentDrain();
}
resourceManager.clearPersistentDirty();
}
return enqueued;
}
public static void mirrorPersistentCopy(ByteBuffer srcStaging, long srcOffset, ByteBuffer dstStaging, long dstOffset, long size) {
ByteRegionCopy.copyByteRegion(srcStaging, (int) srcOffset, dstStaging, (int) dstOffset, (int) size);
public static void mirrorPersistentCopy(PersistentMapping src, long readOffset, PersistentMapping dst, long writeOffset, long size) {
if (!src.covers(readOffset, size) || !dst.covers(writeOffset, size)) return;
ByteRegionCopy.copyByteRegion(src.staging, (int) src.stagingIndex(readOffset), dst.staging, (int) dst.stagingIndex(writeOffset), (int) size);
}
public void mirrorEboShadow(int glId, ByteBuffer src, int dstOffset, int len) {
@@ -24,6 +24,12 @@ public final class PersistentMapping {
public static long rangeSize(long packed) { return (packed & 0xFFFFFFFFL) - (packed >>> 32); }
static long packRange(long min, long max) { return (min << 32) | (max & 0xFFFFFFFFL); }
public long stagingIndex(long bufferOffset) { return bufferOffset - offset; }
public boolean covers(long bufferOffset, long size) {
return size >= 0 && bufferOffset >= offset && bufferOffset - offset <= length && size <= length - (bufferOffset - offset);
}
public boolean isDirty() { return dirtyRange.get() != CLEAN; }
public long claimDirty() { return dirtyRange.getAndSet(CLEAN); }
@@ -31,6 +31,7 @@ import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@@ -96,6 +97,9 @@ public final class ResourceManager {
private final Int2ObjectOpenHashMap<ContextState.VAOState> vaoStates = new Int2ObjectOpenHashMap<>();
private final Int2ObjectOpenHashMap<PersistentMapping> persistentMappings = new Int2ObjectOpenHashMap<>();
private final Int2ObjectOpenHashMap<MappedRange> plainMappings = new Int2ObjectOpenHashMap<>();
private MappedRange[] plainMappingPool = new MappedRange[4];
private int plainMappingPoolCount;
private final Int2ObjectOpenHashMap<ByteBuffer> uboShadows = new Int2ObjectOpenHashMap<>();
private final Int2ObjectOpenHashMap<ByteBuffer> eboShadows = new Int2ObjectOpenHashMap<>();
private final IntOpenHashSet eboShadowWanted = new IntOpenHashSet();
@@ -752,6 +756,7 @@ public final class ResourceManager {
public void deleteBuffer(int glId) {
final ByteBuffer droppedPboStaging;
final ByteBuffer droppedPlainStaging;
final PersistentMapping droppedPm;
wLock.lock();
try {
@@ -763,6 +768,7 @@ public final class ResourceManager {
undefinedContentBuffers.remove(glId);
droppedPm = persistentMappings.remove(glId);
if (droppedPm != null) mappingsVersion++;
droppedPlainStaging = removePlainMappingLocked(glId);
droppedPboStaging = pboStagingData.remove(glId);
deleteUboShadow(glId);
deleteEboShadow(glId);
@@ -777,8 +783,11 @@ public final class ResourceManager {
if (droppedPboStaging != null) {
memFree(droppedPboStaging);
}
if (droppedPlainStaging != null) {
memFree(droppedPlainStaging);
}
untrackPersistentDirty(droppedPm);
releasePersistentStaging(droppedPm);
releasePersistentStaging(droppedPm, glId, "delete");
}
public void flushDeferredReleases() {
@@ -953,10 +962,81 @@ public final class ResourceManager {
try { vaoStates.remove(glId); } finally { wLock.unlock(); }
}
public void putPersistentMapping(int bufferGlId, PersistentMapping m) {
public boolean putPersistentMappingIfAbsent(int glId, PersistentMapping pm) {
wLock.lock();
try { persistentMappings.put(bufferGlId, m); mappingsVersion++; } finally { wLock.unlock(); }
try {
if (plainMappings.containsKey(glId) || persistentMappings.putIfAbsent(glId, pm) != null) return false;
mappingsVersion++;
return true;
} finally { wLock.unlock(); }
}
public boolean putPlainMappingIfAbsent(int glId, ByteBuffer staging, long offset, long length, boolean invalidate, int accessFlags) {
wLock.lock();
try {
if (plainMappings.containsKey(glId) || persistentMappings.containsKey(glId)) return false;
final MappedRange m;
if (plainMappingPoolCount > 0) {
m = plainMappingPool[--plainMappingPoolCount];
plainMappingPool[plainMappingPoolCount] = null;
} else {
m = new MappedRange();
}
m.glId = glId;
m.staging = staging;
m.offset = offset;
m.length = length;
m.invalidate = invalidate;
m.accessFlags = accessFlags;
plainMappings.put(glId, m);
return true;
} finally { wLock.unlock(); }
}
public boolean takePlainMapping(int glId, MappedRange out) {
wLock.lock();
try {
final MappedRange m = plainMappings.get(glId);
if (m == null) return false;
copyMappedRange(m, out);
removePlainMappingLocked(glId);
return true;
} finally { wLock.unlock(); }
}
public ByteBuffer takePlainStaging(int glId) {
wLock.lock();
try { return removePlainMappingLocked(glId); } finally { wLock.unlock(); }
}
public boolean peekPlainMapping(int glId, MappedRange out) {
if (fastRead()) return copyMappedRange(plainMappings.get(glId), out);
rLock.lock();
try { return copyMappedRange(plainMappings.get(glId), out); } finally { rLock.unlock(); }
}
private static boolean copyMappedRange(MappedRange m, MappedRange out) {
if (m == null) return false;
out.glId = m.glId;
out.staging = m.staging;
out.offset = m.offset;
out.length = m.length;
out.invalidate = m.invalidate;
out.accessFlags = m.accessFlags;
return true;
}
private ByteBuffer removePlainMappingLocked(int glId) {
final MappedRange m = plainMappings.remove(glId);
if (m == null) return null;
final ByteBuffer staging = m.staging;
m.staging = null;
if (plainMappingPoolCount == plainMappingPool.length) plainMappingPool = Arrays.copyOf(plainMappingPool, plainMappingPoolCount * 2);
plainMappingPool[plainMappingPoolCount++] = m;
return staging;
}
public void beginPersistentDrain() { rLock.lock(); }
public void endPersistentDrain() { rLock.unlock(); }
public PersistentMapping getPersistentMapping(int bufferGlId) {
if (fastRead()) return persistentMappings.get(bufferGlId);
rLock.lock();
@@ -965,17 +1045,13 @@ public final class ResourceManager {
public PersistentMapping removePersistentMapping(int bufferGlId) {
final PersistentMapping dropped;
wLock.lock();
try { dropped = persistentMappings.remove(bufferGlId); mappingsVersion++; } finally { wLock.unlock(); }
try {
dropped = persistentMappings.remove(bufferGlId);
if (dropped != null) mappingsVersion++;
} finally { wLock.unlock(); }
untrackPersistentDirty(dropped);
return dropped;
}
public PersistentMapping swapPersistentMapping(int bufferGlId, PersistentMapping fresh) {
final PersistentMapping prior;
wLock.lock();
try { prior = persistentMappings.put(bufferGlId, fresh); mappingsVersion++; } finally { wLock.unlock(); }
untrackPersistentDirty(prior);
return prior;
}
private volatile int mappingsVersion;
@@ -989,8 +1065,9 @@ public final class ResourceManager {
if (pm != null && !PersistentMapping.isClean(pm.claimDirty())) persistentDirtyCount.decrementAndGet();
}
public void releasePersistentStaging(PersistentMapping pm) {
public void releasePersistentStaging(PersistentMapping pm, int glId, String cause) {
if (pm == null || pm.staging == null) return;
if (LOG.isDebugEnabled()) LOG.debug("Persistent mapping released: buffer {} ({} KiB) via {}", glId, pm.length >> 10, cause);
final TransferThread tt = transferThread;
final long seq = pm.lastEnqueuedSeq;
if (tt != null && seq > tt.getSubmittedSeq()) tt.freeAfterSeq(pm.staging, seq);
@@ -1001,24 +1078,20 @@ public final class ResourceManager {
rLock.lock();
try {
final PersistentMapping srcPm = persistentMappings.get(srcGlId);
if (srcPm == null) return false;
final ByteBuffer slice = srcPm.staging.duplicate();
slice.position((int) readOffset).limit((int) (readOffset + size));
uploadToBuffer(copyPass, slice, dstHandle, writeOffset, false);
if (srcPm == null || !srcPm.covers(readOffset, size)) return false;
uploadRangeToBuffer(copyPass, srcPm.staging, srcPm.stagingIndex(readOffset), size, dstHandle, writeOffset, false);
final PersistentMapping dstPm = persistentMappings.get(dstGlId);
if (dstPm != null) {
PersistentBufferSync.mirrorPersistentCopy(srcPm.staging, readOffset, dstPm.staging, writeOffset, size);
}
if (dstPm != null) PersistentBufferSync.mirrorPersistentCopy(srcPm, readOffset, dstPm, writeOffset, size);
return true;
} finally { rLock.unlock(); }
}
public boolean enqueuePersistentCopyDeferred(int srcGlId, int dstGlId, long seq,
public boolean enqueuePersistentCopyDeferred(int srcGlId, int dstGlId, long seq, long readOffset, long size,
BiConsumer<PersistentMapping, PersistentMapping> inLock) {
rLock.lock();
try {
final PersistentMapping srcPm = persistentMappings.get(srcGlId);
if (srcPm == null) return false;
if (srcPm == null || !srcPm.covers(readOffset, size)) return false;
final PersistentMapping dstPm = persistentMappings.get(dstGlId);
srcPm.lastEnqueuedSeq = seq;
inLock.accept(srcPm, dstPm);
@@ -1192,7 +1265,7 @@ public final class ResourceManager {
public void mirrorArrayShadowFull(int glId, ByteBuffer data) {
final int len = data.remaining();
final ByteBuffer shadow = getOrAllocArrayShadow(glId, len);
copyShadowRegion(data, data.position(), shadow, 0, len);
ByteRegionCopy.copyByteRegion(data, data.position(), shadow, 0, len);
shadow.position(0).limit(len);
}
@@ -1206,7 +1279,7 @@ public final class ResourceManager {
removeArrayShadowLocked(glId);
return;
}
copyShadowRegion(data, data.position(), shadow, dstOffset, len);
ByteRegionCopy.copyByteRegion(data, data.position(), shadow, dstOffset, len);
} finally { wLock.unlock(); }
}
@@ -1226,18 +1299,6 @@ public final class ResourceManager {
arrayShadowWanted.remove(glId);
}
private static void copyShadowRegion(ByteBuffer src, int srcOff, ByteBuffer dst, int dstOff, int len) {
if (src.isDirect() && dst.isDirect()) {
MemoryUtil.memCopy(MemoryUtil.memAddress(src) + srcOff, MemoryUtil.memAddress(dst) + dstOff, len);
return;
}
final ByteBuffer s = src.duplicate();
s.position(srcOff).limit(srcOff + len);
final ByteBuffer d = dst.duplicate();
d.position(dstOff);
d.put(s);
}
public EBOSplitScanner.EboSplit[] getOrScanSplits(int glId, int indexType, int sentinel) {
if (!hasEboShadow(glId)) return null;
final long version = getEboShadowVersion(glId);
@@ -1502,10 +1563,10 @@ public final class ResourceManager {
}
}
public static final int COPY_CALLSITE_UPLOAD_BUFFER = 1;
public static final int COPY_CALLSITE_UPLOAD_TEX_BATCH = 2;
public static final int COPY_CALLSITE_UPLOAD_TEX_DIRECT = 3;
public static final int COPY_CALLSITE_ARENA = 4;
public static final int COPY_CALLSITE_FILL_BUFFER = 5;
public boolean arenaUpload(ByteBuffer data, long dstHandle, long dstOffset, boolean cycle) {
final FrameManager.FrameState f = frameManager.frame();
@@ -1600,7 +1661,13 @@ public final class ResourceManager {
}
public void uploadToBuffer(long copyPass, ByteBuffer data, long gpuBuffer, long offset, boolean cycle) {
final long size = data.remaining();
uploadRangeToBuffer(copyPass, data, data.position(), data.remaining(), gpuBuffer, offset, cycle);
}
public void uploadRangeToBuffer(long copyPass, ByteBuffer src, long srcOff, long size, long gpuBuffer, long dstOff, boolean cycle) {
if (srcOff < 0 || size < 0 || srcOff + size > src.capacity()) {
throw new IllegalStateException("uploadRangeToBuffer: bad source range srcOff=" + srcOff + " size=" + size + " src.capacity=" + src.capacity());
}
final long xfer = acquireTransferBuffer(size);
if (xfer == 0) return;
@@ -1609,22 +1676,26 @@ public final class ResourceManager {
if (mapped == null || mapped.capacity() < size) {
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
returnTransferBuffer(xfer, size);
throw new IllegalStateException("uploadToBuffer: bad mapping size=" + size + " mapSize=" + mapSize + " mapped=" + (mapped == null ? "null" : "cap=" + mapped.capacity()));
throw new IllegalStateException("uploadRangeToBuffer: bad mapping size=" + size + " mapSize=" + mapSize + " mapped=" + (mapped == null ? "null" : "cap=" + mapped.capacity()));
}
if (src.isDirect()) {
MemoryUtil.memCopy(MemoryUtil.memAddress0(src) + srcOff, MemoryUtil.memAddress0(mapped), size);
} else {
mapped.put(0, src, (int) srcOff, (int) size);
}
copyMappedFromData(mapped, data, (int) size, COPY_CALLSITE_UPLOAD_BUFFER);
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
try (var stack = stackPush()) {
final SDL_GPUTransferBufferLocation src = SDL_GPUTransferBufferLocation.calloc(stack)
final SDL_GPUTransferBufferLocation srcLoc = SDL_GPUTransferBufferLocation.calloc(stack)
.transfer_buffer(xfer)
.offset(0);
final SDL_GPUBufferRegion dst = SDL_GPUBufferRegion.calloc(stack)
.buffer(gpuBuffer)
.offset((int) offset)
.offset((int) dstOff)
.size((int) size);
SDL_UploadToGPUBuffer(copyPass, src, dst, cycle);
SDL_UploadToGPUBuffer(copyPass, srcLoc, dst, cycle);
}
frameManager.recordUploadCommands(size, 1);
@@ -1647,7 +1718,7 @@ public final class ResourceManager {
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
throw new IllegalStateException("fillBuffer: bad mapping size=" + chunk + " mapped=" + (mapped == null ? "null" : "cap=" + mapped.capacity()));
}
copyMappedFromData(mapped, pattern, chunk, COPY_CALLSITE_UPLOAD_BUFFER);
copyMappedFromData(mapped, pattern, chunk, COPY_CALLSITE_FILL_BUFFER);
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
int uploads = 0;
@@ -2576,12 +2647,20 @@ public final class ResourceManager {
arrayShadowWanted.clear();
for (ByteBuffer bb : pboStagingData.values()) memFree(bb);
pboStagingData.clear();
for (PersistentMapping pm : persistentMappings.values()) {
for (var e : persistentMappings.int2ObjectEntrySet()) {
final PersistentMapping pm = e.getValue();
untrackPersistentDirty(pm);
releasePersistentStaging(pm);
releasePersistentStaging(pm, e.getIntKey(), "shutdown");
}
persistentMappings.clear();
mappingsVersion++;
for (MappedRange m : plainMappings.values()) {
if (m.staging != null) memFree(m.staging);
m.staging = null;
}
plainMappings.clear();
Arrays.fill(plainMappingPool, null);
plainMappingPoolCount = 0;
}
public record TextureMeta(int glTarget, int glFormat, int sdlFormat, int width, int height, int depth, int levels, int usage) {}
@@ -329,11 +329,15 @@ public final class TransferThread {
final ByteBuffer mapped = SDL_MapGPUTransferBuffer(device.getDevice(), xfer, true, mapSize);
if (mapped != null) {
final ByteBuffer src = sr.stagingBuffer;
final int prevPos = src.position();
final int prevLim = src.limit();
src.limit((int) (sr.srcOffset + size)).position((int) sr.srcOffset);
mapped.put(src);
src.limit(prevLim).position(prevPos);
if (sr.srcOffset < 0 || sr.srcOffset + size > src.capacity() || size > mapped.capacity()) {
LOG.error("StagingReadUpload out of bounds: srcOffset={} size={} src.capacity={} mapped.capacity={}; upload dropped", sr.srcOffset, size, src.capacity(), mapped.capacity());
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
resourceManager.returnTransferBufferThreadSafe(xfer, size);
retireSeq(sr.seq);
StagingReadUpload.release(sr);
return;
}
MemoryUtil.memCopy(MemoryUtil.memAddress0(src) + sr.srcOffset, MemoryUtil.memAddress0(mapped), size);
}
SDL_UnmapGPUTransferBuffer(device.getDevice(), xfer);
recordBufferUpload(xfer, size, sr.dstGpuBuffer, sr.dstOffset, sr.seq, sr.cycle);
@@ -0,0 +1,393 @@
package com.gtnewhorizons.angelica.sdlgpu.glsm;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.GL44;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Tag("glsm-sdl")
@ExtendWith(GlsmSdlFrameExtension.class)
class GlsmSdlBufferMappingLifetimeTest {
private static final int SIZE = 4096;
private static final int PERSISTENT_STORAGE = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT | GL44.GL_CLIENT_STORAGE_BIT;
private static final int PERSISTENT_MAP = GL44.GL_MAP_PERSISTENT_BIT | GL30.GL_MAP_WRITE_BIT | GL44.GL_MAP_COHERENT_BIT;
private static final int PLAIN_MAP = GL30.GL_MAP_WRITE_BIT | GL30.GL_MAP_INVALIDATE_BUFFER_BIT;
@BeforeAll
static void boot() {
GlsmSdlHeadlessRig.boot();
}
private static void fill(ByteBuffer buf, int seed) {
for (int i = 0; i < buf.capacity(); i++) buf.put(i, (byte) (seed * 37 + i * 7 + 1));
}
private static void assertPattern(ByteBuffer actual, int seed) {
for (int i = 0; i < actual.capacity(); i++) {
assertEquals((byte) (seed * 37 + i * 7 + 1), actual.get(i), "byte " + i + " of pattern " + seed);
}
}
private static ByteBuffer readBack(int target, int size) {
final ByteBuffer out = MemoryUtil.memAlloc(size);
GLStateManager.glGetBufferSubData(target, 0, out);
return out;
}
private static ByteBuffer mapPersistent(int target, int id) {
GLStateManager.glBindBuffer(target, id);
GLStateManager.glBufferStorage(target, SIZE, PERSISTENT_STORAGE);
return GLStateManager.glMapBufferRange(target, 0, SIZE, PERSISTENT_MAP);
}
@Test
void bufferDataOnPersistentlyMappedBufferIsRefusedAndMappingSurvives() {
final int target = GL15.GL_ARRAY_BUFFER;
final int id = GLStateManager.glGenBuffers();
ByteBuffer out = null;
try {
final ByteBuffer mapped = mapPersistent(target, id);
assertNotNull(mapped);
GLStateManager.glBufferData(target, SIZE * 2L, GL15.GL_STATIC_DRAW);
fill(mapped, 1);
GLStateManager.glFlushMappedBufferRange(target, 0, SIZE);
out = readBack(target, SIZE);
assertPattern(out, 1);
} finally {
if (out != null) MemoryUtil.memFree(out);
GLStateManager.glDeleteBuffers(id);
}
}
@Test
void persistentMapWithoutPersistentStorageReturnsNull() {
final int target = GL15.GL_ARRAY_BUFFER;
final int id = GLStateManager.glGenBuffers();
try {
GLStateManager.glBindBuffer(target, id);
GLStateManager.glBufferData(target, (long) SIZE, GL15.GL_STATIC_DRAW);
assertNull(GLStateManager.glMapBufferRange(target, 0, SIZE, PERSISTENT_MAP));
} finally {
GLStateManager.glDeleteBuffers(id);
}
}
@Test
void secondPersistentMapReturnsNullAndFirstStillRoundTrips() {
final int target = GL15.GL_ARRAY_BUFFER;
final int id = GLStateManager.glGenBuffers();
ByteBuffer out = null;
try {
final ByteBuffer first = mapPersistent(target, id);
assertNotNull(first);
assertNull(GLStateManager.glMapBufferRange(target, 0, SIZE, PERSISTENT_MAP));
fill(first, 2);
GLStateManager.glFlushMappedBufferRange(target, 0, SIZE);
out = readBack(target, SIZE);
assertPattern(out, 2);
} finally {
if (out != null) MemoryUtil.memFree(out);
GLStateManager.glDeleteBuffers(id);
}
}
@Test
void twoBuffersMappedNonPersistentlyKeepTheirOwnData() {
final int target = GL15.GL_ARRAY_BUFFER;
final int a = GLStateManager.glGenBuffers();
final int b = GLStateManager.glGenBuffers();
ByteBuffer outA = null;
ByteBuffer outB = null;
try {
GLStateManager.glBindBuffer(target, a);
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
GLStateManager.glBindBuffer(target, b);
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
GLStateManager.glBindBuffer(target, a);
final ByteBuffer mapA = GLStateManager.glMapBufferRange(target, 0, SIZE, PLAIN_MAP);
assertNotNull(mapA);
GLStateManager.glBindBuffer(target, b);
final ByteBuffer mapB = GLStateManager.glMapBufferRange(target, 0, SIZE, PLAIN_MAP);
assertNotNull(mapB);
fill(mapA, 3);
fill(mapB, 4);
GLStateManager.glBindBuffer(target, a);
assertTrue(GLStateManager.glUnmapBuffer(target));
GLStateManager.glBindBuffer(target, b);
assertTrue(GLStateManager.glUnmapBuffer(target));
GLStateManager.glBindBuffer(target, a);
outA = readBack(target, SIZE);
assertPattern(outA, 3);
GLStateManager.glBindBuffer(target, b);
outB = readBack(target, SIZE);
assertPattern(outB, 4);
} finally {
if (outA != null) MemoryUtil.memFree(outA);
if (outB != null) MemoryUtil.memFree(outB);
GLStateManager.glDeleteBuffers(a);
GLStateManager.glDeleteBuffers(b);
}
}
@Test
void unmapOfUnmappedBufferReturnsFalseAndLeavesOtherMappingIntact() {
final int target = GL15.GL_ARRAY_BUFFER;
final int mappedId = GLStateManager.glGenBuffers();
final int idle = GLStateManager.glGenBuffers();
ByteBuffer out = null;
try {
GLStateManager.glBindBuffer(target, mappedId);
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
final ByteBuffer map = GLStateManager.glMapBufferRange(target, 0, SIZE, PLAIN_MAP);
assertNotNull(map);
fill(map, 5);
GLStateManager.glBindBuffer(target, idle);
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
assertFalse(GLStateManager.glUnmapBuffer(target));
GLStateManager.glBindBuffer(target, mappedId);
assertTrue(GLStateManager.glUnmapBuffer(target));
out = readBack(target, SIZE);
assertPattern(out, 5);
} finally {
if (out != null) MemoryUtil.memFree(out);
GLStateManager.glDeleteBuffers(mappedId);
GLStateManager.glDeleteBuffers(idle);
}
}
@Test
void outOfRangeUniformSubDataIsIgnored() {
final int ubo = GLStateManager.glGenBuffers();
final ByteBuffer data = MemoryUtil.memAlloc(256);
final ByteBuffer big = MemoryUtil.memAlloc(64);
try {
fill(data, 6);
fill(big, 7);
GLStateManager.glBindBuffer(GL31.GL_UNIFORM_BUFFER, ubo);
GLStateManager.glBufferData(GL31.GL_UNIFORM_BUFFER, data, GL15.GL_DYNAMIC_DRAW);
assertDoesNotThrow(() -> {
big.clear();
GLStateManager.glBufferSubData(GL31.GL_UNIFORM_BUFFER, 1024, big);
big.clear();
GLStateManager.glBufferSubData(GL31.GL_UNIFORM_BUFFER, 224, big);
big.clear();
GLStateManager.glBufferSubData(GL31.GL_UNIFORM_BUFFER, -8, big);
});
} finally {
MemoryUtil.memFree(data);
MemoryUtil.memFree(big);
GLStateManager.glDeleteBuffers(ubo);
}
}
@Test
void outOfRangeCopyBufferSubDataIsIgnored() {
final int src = GLStateManager.glGenBuffers();
final int dst = GLStateManager.glGenBuffers();
final ByteBuffer data = MemoryUtil.memAlloc(256);
ByteBuffer outDst = null;
try {
fill(data, 6);
GLStateManager.glBindBuffer(GL31.GL_COPY_READ_BUFFER, src);
GLStateManager.glBufferData(GL31.GL_COPY_READ_BUFFER, data, GL15.GL_STATIC_DRAW);
GLStateManager.glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, dst);
data.clear();
GLStateManager.glBufferData(GL31.GL_COPY_WRITE_BUFFER, data, GL15.GL_STATIC_DRAW);
GLStateManager.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, 200, 0, 128);
GLStateManager.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, 0, 200, 128);
GLStateManager.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, -4, 0, 16);
outDst = readBack(GL31.GL_COPY_WRITE_BUFFER, 256);
assertPattern(outDst, 6);
} finally {
if (outDst != null) MemoryUtil.memFree(outDst);
MemoryUtil.memFree(data);
GLStateManager.glDeleteBuffers(src);
GLStateManager.glDeleteBuffers(dst);
}
}
@Test
void outOfRangeClearBufferSubDataIsIgnored() {
final int clearStart = 64;
final int clearEnd = 96;
final int ssbo = GLStateManager.glGenBuffers();
final ByteBuffer data = MemoryUtil.memAlloc(256);
ByteBuffer out = null;
try {
fill(data, 8);
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, ssbo);
GLStateManager.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, data, GL15.GL_DYNAMIC_DRAW);
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, clearStart, clearEnd - clearStart, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, 200, 128, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, -4, 16, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
GLStateManager.glClearBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, GL30.GL_R8, 1024, 16, GL11.GL_RED, GL11.GL_UNSIGNED_BYTE, null);
GlsmSdlHeadlessRig.endFrame();
GlsmSdlHeadlessRig.beginFrame();
out = readBack(GL43.GL_SHADER_STORAGE_BUFFER, 256);
for (int i = 0; i < 256; i++) {
final boolean cleared = i >= clearStart && i < clearEnd;
assertEquals(cleared ? 0 : data.get(i), out.get(i), cleared ? "in-range clear did not zero byte " + i : "out-of-range clear touched byte " + i);
}
} finally {
if (out != null) MemoryUtil.memFree(out);
MemoryUtil.memFree(data);
GLStateManager.glDeleteBuffers(ssbo);
}
}
@Test
void secondBufferStorageIsRefused() {
final int target = GL15.GL_ARRAY_BUFFER;
final int id = GLStateManager.glGenBuffers();
try {
GLStateManager.glBindBuffer(target, id);
GLStateManager.glBufferStorage(target, SIZE, PERSISTENT_STORAGE);
GLStateManager.glBufferStorage(target, SIZE * 2L, PERSISTENT_STORAGE);
assertEquals(GL11.GL_TRUE, GLStateManager.glGetBufferParameteri(target, GL44.GL_BUFFER_IMMUTABLE_STORAGE));
assertEquals(SIZE, GLStateManager.glGetBufferParameteri(target, GL15.GL_BUFFER_SIZE));
} finally {
GLStateManager.glDeleteBuffers(id);
}
}
@Test
void namedBufferDataOnImmutableStorageIsRefused() {
final int target = GL15.GL_ARRAY_BUFFER;
final int id = GLStateManager.glGenBuffers();
try {
GLStateManager.glBindBuffer(target, id);
GLStateManager.glBufferStorage(target, SIZE, PERSISTENT_STORAGE);
GLStateManager.glNamedBufferData(id, SIZE * 2L, GL15.GL_STATIC_DRAW);
assertEquals(SIZE, GLStateManager.glGetBufferParameteri(target, GL15.GL_BUFFER_SIZE));
} finally {
GLStateManager.glDeleteBuffers(id);
}
}
@Test
void bufferStorageWithNullDataMarksBufferImmutable() {
final int target = GL15.GL_ARRAY_BUFFER;
final int id = GLStateManager.glGenBuffers();
try {
GLStateManager.glBindBuffer(target, id);
GLStateManager.glBufferStorage(target, (ByteBuffer) null, PERSISTENT_STORAGE);
assertEquals(GL11.GL_TRUE, GLStateManager.glGetBufferParameteri(target, GL44.GL_BUFFER_IMMUTABLE_STORAGE));
} finally {
GLStateManager.glDeleteBuffers(id);
}
}
private static void assertBufferDataUnmapsPlainMapping(int target) {
final int id = GLStateManager.glGenBuffers();
try {
GLStateManager.glBindBuffer(target, id);
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
assertNotNull(GLStateManager.glMapBufferRange(target, 0, SIZE, PLAIN_MAP));
GLStateManager.glBufferData(target, SIZE, GL15.GL_DYNAMIC_DRAW);
assertNotNull(GLStateManager.glMapBufferRange(target, 0, SIZE, PLAIN_MAP));
assertTrue(GLStateManager.glUnmapBuffer(target));
} finally {
GLStateManager.glDeleteBuffers(id);
}
}
@Test
void bufferDataOnMappedArrayBufferUnmapsIt() {
assertBufferDataUnmapsPlainMapping(GL15.GL_ARRAY_BUFFER);
}
@Test
void bufferDataOnMappedUniformBufferUnmapsIt() {
assertBufferDataUnmapsPlainMapping(GL31.GL_UNIFORM_BUFFER);
}
@Test
void outOfRangeFlushOfPersistentMappingIsIgnored() {
final int target = GL15.GL_ARRAY_BUFFER;
final int id = GLStateManager.glGenBuffers();
ByteBuffer out = null;
try {
final ByteBuffer mapped = mapPersistent(target, id);
assertNotNull(mapped);
GLStateManager.glFlushMappedBufferRange(target, SIZE, 64);
GLStateManager.glFlushMappedBufferRange(target, SIZE - 16, 64);
GLStateManager.glFlushMappedBufferRange(target, -8, 16);
fill(mapped, 9);
GLStateManager.glFlushMappedBufferRange(target, 0, SIZE);
out = readBack(target, SIZE);
assertPattern(out, 9);
} finally {
if (out != null) MemoryUtil.memFree(out);
GLStateManager.glDeleteBuffers(id);
}
}
@Test
void persistentMappingAtNonZeroOffsetRoundTrips() {
final int target = GL15.GL_ARRAY_BUFFER;
final int id = GLStateManager.glGenBuffers();
final ByteBuffer out = MemoryUtil.memAlloc(SIZE);
try {
GLStateManager.glBindBuffer(target, id);
GLStateManager.glBufferStorage(target, SIZE * 2L, PERSISTENT_STORAGE);
final ByteBuffer mapped = GLStateManager.glMapBufferRange(target, SIZE, SIZE, PERSISTENT_MAP);
assertNotNull(mapped);
fill(mapped, 10);
GLStateManager.glFlushMappedBufferRange(target, 0, SIZE);
GLStateManager.glGetBufferSubData(target, SIZE, out);
assertPattern(out, 10);
} finally {
MemoryUtil.memFree(out);
GLStateManager.glDeleteBuffers(id);
}
}
@Test
void copyFromPersistentMappingShorterThanBufferReadsGpuContents() {
final int src = GLStateManager.glGenBuffers();
final int dst = GLStateManager.glGenBuffers();
final ByteBuffer data = MemoryUtil.memAlloc(SIZE * 2);
ByteBuffer out = null;
try {
fill(data, 11);
GLStateManager.glBindBuffer(GL31.GL_COPY_READ_BUFFER, src);
GLStateManager.glBufferStorage(GL31.GL_COPY_READ_BUFFER, data, PERSISTENT_STORAGE);
assertNotNull(GLStateManager.glMapBufferRange(GL31.GL_COPY_READ_BUFFER, 0, SIZE, PERSISTENT_MAP));
GLStateManager.glBindBuffer(GL31.GL_COPY_WRITE_BUFFER, dst);
GLStateManager.glBufferData(GL31.GL_COPY_WRITE_BUFFER, SIZE, GL15.GL_STATIC_DRAW);
GLStateManager.glCopyBufferSubData(GL31.GL_COPY_READ_BUFFER, GL31.GL_COPY_WRITE_BUFFER, SIZE, 0, 256);
out = readBack(GL31.GL_COPY_WRITE_BUFFER, 256);
for (int i = 0; i < 256; i++) {
assertEquals(data.get(SIZE + i), out.get(i), "byte " + i);
}
} finally {
if (out != null) MemoryUtil.memFree(out);
MemoryUtil.memFree(data);
GLStateManager.glDeleteBuffers(src);
GLStateManager.glDeleteBuffers(dst);
}
}
}
@@ -8,6 +8,8 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL43;
import java.nio.ByteBuffer;
@@ -45,6 +47,30 @@ class GlsmSdlReadbackTest {
GLStateManager.glDeleteTextures(texture);
}
@Test
void getBufferSubDataSeesSameFrameStorageBufferWrite() {
final int size = 64;
final int ssbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL43.GL_SHADER_STORAGE_BUFFER, ssbo);
final ByteBuffer initial = BufferUtils.createByteBuffer(size);
final ByteBuffer update = BufferUtils.createByteBuffer(size);
for (int i = 0; i < size; i++) {
initial.put(i, (byte) 0x11);
update.put(i, (byte) (i + 1));
}
GLStateManager.glBufferData(GL43.GL_SHADER_STORAGE_BUFFER, initial, GL15.GL_DYNAMIC_DRAW);
GLStateManager.glBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, 0, update);
final ByteBuffer dst = BufferUtils.createByteBuffer(size);
GLStateManager.glGetBufferSubData(GL43.GL_SHADER_STORAGE_BUFFER, 0, dst);
for (int i = 0; i < size; i++) {
assertEquals((byte) (i + 1), dst.get(i), "byte " + i + " should hold the same-frame sub-data write");
}
GLStateManager.glDeleteBuffers(ssbo);
}
@Test
void readPixelsSeesPriorDraw() {
GlsmSdlHeadlessRig.bindTarget();
@@ -52,7 +52,7 @@ class AbsoluteCopyRegionTest {
for (int i = 0; i < 32; i++) src.put(i, (byte) i);
src.position(7).limit(24);
dst.position(9).limit(28);
PersistentBufferSync.mirrorPersistentCopy(src, 4, dst, 16, 8);
PersistentBufferSync.mirrorPersistentCopy(new PersistentMapping(src, 0L, 32L, 0), 4, new PersistentMapping(dst, 0L, 32L, 0), 16, 8);
for (int i = 0; i < 8; i++) assertEquals((byte) (4 + i), dst.get(16 + i));
assertEquals(7, src.position());
assertEquals(24, src.limit());
@@ -40,7 +40,7 @@ class PersistentBufferSyncDirtyGateTest {
final ByteBuffer staging = MemoryUtil.memCalloc(64);
try {
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 64, 0);
assertFalse(rm.hasDirtyPersistentRegions(), "a mapping with no writes is not dirty");
@@ -70,7 +70,7 @@ class PersistentBufferSyncDirtyGateTest {
final ByteBuffer staging = MemoryUtil.memCalloc(64);
try {
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 64, 0);
sync.onPersistentBufferWrite(1, 0, 16);
@@ -95,7 +95,7 @@ class PersistentBufferSyncDirtyGateTest {
try {
final int atStart = rm.getMappingsVersion();
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
final int afterPut = rm.getMappingsVersion();
assertNotEquals(atStart, afterPut, "adding a mapping must move the version");
@@ -107,7 +107,8 @@ class PersistentBufferSyncDirtyGateTest {
sync.enqueueDirtyPersistentRegions();
assertEquals(afterPut, rm.getMappingsVersion(), "dirtying and draining is not a mapping change");
rm.swapPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
rm.removePersistentMapping(1);
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
final int afterSwap = rm.getMappingsVersion();
assertNotEquals(afterPut, afterSwap, "swapping a mapping must move the version");
@@ -129,7 +130,7 @@ class PersistentBufferSyncDirtyGateTest {
final ByteBuffer staging = MemoryUtil.memCalloc(256);
try {
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 256, 0);
for (int i = 0; i < 10; i++) {
@@ -156,13 +157,14 @@ class PersistentBufferSyncDirtyGateTest {
final ByteBuffer first = MemoryUtil.memCalloc(64);
final ByteBuffer second = MemoryUtil.memCalloc(64);
try {
rm.putPersistentMapping(1, new PersistentMapping(first, 0L, first.capacity(), 0));
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(first, 0L, first.capacity(), 0));
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 64, 0);
sync.onPersistentBufferWrite(1, 0, 16);
sync.enqueueDirtyPersistentRegions();
assertEquals(1, sink.enqueued);
rm.swapPersistentMapping(1, new PersistentMapping(second, 0L, second.capacity(), 0));
rm.removePersistentMapping(1);
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(second, 0L, second.capacity(), 0));
sync.onPersistentBufferWrite(1, 0, 16);
sync.enqueueDirtyPersistentRegions();
assertEquals(2, sink.enqueued, "the drain must see the replacement, not a stale snapshot");
@@ -184,7 +186,7 @@ class PersistentBufferSyncDirtyGateTest {
final ByteBuffer staging = MemoryUtil.memCalloc(4096);
try {
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 4096, 0);
final int writers = 6;
@@ -235,7 +237,7 @@ class PersistentBufferSyncDirtyGateTest {
final ByteBuffer staging = MemoryUtil.memCalloc(64);
try {
final PersistentMapping pm = new PersistentMapping(staging, 0L, staging.capacity(), 0);
rm.putPersistentMapping(1, pm);
rm.putPersistentMappingIfAbsent(1, pm);
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, 64, 0);
final long[] stampAtEnqueue = {-1L};
@@ -267,7 +269,7 @@ class PersistentBufferSyncDirtyGateTest {
final int seedCount = 100;
for (int i = 0; i < seedCount; i++) {
final ByteBuffer b = MemoryUtil.memAlloc(64);
rm.putPersistentMapping(i, new PersistentMapping(b, 0L, b.capacity(), 0));
rm.putPersistentMappingIfAbsent(i, new PersistentMapping(b, 0L, b.capacity(), 0));
}
final AtomicBoolean stop = new AtomicBoolean(false);
@@ -279,7 +281,7 @@ class PersistentBufferSyncDirtyGateTest {
int next = seedCount;
while (!stop.get()) {
final ByteBuffer b = MemoryUtil.memAlloc(64);
rm.putPersistentMapping(next, new PersistentMapping(b, 0L, b.capacity(), 0));
rm.putPersistentMappingIfAbsent(next, new PersistentMapping(b, 0L, b.capacity(), 0));
rm.removePersistentMapping(next - 50);
next++;
if (next > seedCount + 10000) next = seedCount;
@@ -37,13 +37,13 @@ class PersistentDrainBoundaryTest {
final ResourceManager rm;
final PersistentBufferSync sync;
final RecordingSink sink = new RecordingSink();
final ByteBuffer staging = MemoryUtil.memCalloc(4096);
final ByteBuffer staging = MemoryUtil.memCalloc(16384);
Fixture() {
final SdlTestRig rig = SdlTestRig.create();
rm = rig.resourceManager;
sync = new PersistentBufferSync(rig.frameManager, rm, sink);
rm.putPersistentMapping(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
rm.putPersistentMappingIfAbsent(1, new PersistentMapping(staging, 0L, staging.capacity(), 0));
SdlReflect.recordBuffer(rm, 1, 0xCAFEBABEL, staging.capacity(), 0);
}
@@ -18,14 +18,14 @@ class ReleasePersistentStagingTest {
@Test
void nullPmAndNullStagingNoOp() {
final ResourceManager rm = SdlTestRig.resourceManager();
rm.releasePersistentStaging(null);
rm.releasePersistentStaging(new PersistentMapping(null, 0L, 0L, 0));
rm.releasePersistentStaging(null, 7, "unmap");
rm.releasePersistentStaging(new PersistentMapping(null, 0L, 0L, 0), 7, "unmap");
}
@Test
void noTransferThreadFreesInline() {
final ResourceManager rm = SdlTestRig.resourceManager();
rm.releasePersistentStaging(new PersistentMapping(MemoryUtil.memAlloc(24), 0L, 24L, 0));
rm.releasePersistentStaging(new PersistentMapping(MemoryUtil.memAlloc(24), 0L, 24L, 0), 7, "unmap");
}
@Test
@@ -39,7 +39,7 @@ class ReleasePersistentStagingTest {
try {
final PersistentMapping pm = new PersistentMapping(MemoryUtil.memAlloc(24), 0L, 24L, 0);
pm.lastEnqueuedSeq = 0L;
rm.releasePersistentStaging(pm);
rm.releasePersistentStaging(pm, 7, "unmap");
assertEquals(0, SdlReflect.pendingFreeCount(tt));
} finally { shutdown(tt); }
}
@@ -55,7 +55,7 @@ class ReleasePersistentStagingTest {
try {
final PersistentMapping pm = new PersistentMapping(MemoryUtil.memAlloc(24), 0L, 24L, 0);
pm.lastEnqueuedSeq = 50L;
rm.releasePersistentStaging(pm);
rm.releasePersistentStaging(pm, 7, "unmap");
assertEquals(1, SdlReflect.pendingFreeCount(tt));
} finally { shutdown(tt); }
}
@@ -64,7 +64,7 @@ class ReleasePersistentStagingTest {
void deleteBufferReleasesPersistentStaging() {
final ResourceManager rm = SdlTestRig.resourceManager();
SdlReflect.recordBuffer(rm, 7, 0xDEADBEEFL, 64, 0);
rm.putPersistentMapping(7, new PersistentMapping(MemoryUtil.memCalloc(64), 0L, 64L, 0));
rm.putPersistentMappingIfAbsent(7, new PersistentMapping(MemoryUtil.memCalloc(64), 0L, 64L, 0));
rm.deleteBuffer(7);
@@ -84,7 +84,7 @@ class ReleasePersistentStagingTest {
final PersistentMapping pm = new PersistentMapping(staging, 0L, staging.capacity(), 0);
pm.lastEnqueuedSeq = 99L;
SdlReflect.recordBuffer(rm, 7, 0xDEADBEEFL, 64, 0);
rm.putPersistentMapping(7, pm);
rm.putPersistentMappingIfAbsent(7, pm);
rm.deleteBuffer(7);
@@ -3,7 +3,9 @@ package com.gtnewhorizons.angelica.sdlgpu.resource;
import com.gtnewhorizons.angelica.sdlgpu.SdlTestRig;
import com.gtnewhorizons.angelica.sdlgpu.frame.ContextState;
import org.junit.jupiter.api.Test;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -159,4 +161,99 @@ class ResourceManagerConcurrencyTest {
assertTrue(created.get() >= threads, "each thread should have created at least one FBO");
}
@Test
void putPlainMappingIfAbsent_oneWinnerAcrossThreads() throws Exception {
final ResourceManager rm = SdlTestRig.resourceManager();
final int glId = 9001;
final int n = 8;
final AtomicInteger winners = new AtomicInteger();
race(n, slot -> {
final ByteBuffer staging = MemoryUtil.memAlloc(16);
if (rm.putPlainMappingIfAbsent(glId, staging, 0L, 16L, false, slot)) winners.incrementAndGet();
else MemoryUtil.memFree(staging);
});
assertEquals(1, winners.get(), "exactly one thread may map the buffer");
final ByteBuffer staging = rm.takePlainStaging(glId);
assertTrue(staging != null, "the winning mapping must still be registered");
MemoryUtil.memFree(staging);
assertNull(rm.takePlainStaging(glId));
}
@Test
void plainMappingTakenFromAnotherThread() throws Exception {
final ResourceManager rm = SdlTestRig.resourceManager();
final int glId = 9002;
final ByteBuffer staging = MemoryUtil.memAlloc(32);
try {
assertTrue(rm.putPlainMappingIfAbsent(glId, staging, 8L, 32L, true, 0x2A));
final MappedRange peeked = new MappedRange();
final MappedRange taken = new MappedRange();
final AtomicBoolean peekedOk = new AtomicBoolean();
final AtomicBoolean takenOk = new AtomicBoolean();
final AtomicBoolean secondTake = new AtomicBoolean(true);
race(1, slot -> {
peekedOk.set(rm.peekPlainMapping(glId, peeked));
takenOk.set(rm.takePlainMapping(glId, taken));
secondTake.set(rm.takePlainMapping(glId, new MappedRange()));
});
assertTrue(peekedOk.get(), "peek from another thread must see the mapping");
assertSame(staging, peeked.staging);
assertTrue(takenOk.get(), "take from another thread must succeed");
assertSame(staging, taken.staging);
assertEquals(glId, taken.glId);
assertEquals(8L, taken.offset);
assertEquals(32L, taken.length);
assertTrue(taken.invalidate);
assertEquals(0x2A, taken.accessFlags);
assertFalse(secondTake.get(), "a taken mapping must be gone");
assertFalse(rm.peekPlainMapping(glId, new MappedRange()));
} finally {
MemoryUtil.memFree(staging);
}
}
@Test
void deleteBufferFromAnotherThreadDropsPlainMapping() throws Exception {
final ResourceManager rm = SdlTestRig.resourceManager();
final int glId = 9003;
assertTrue(rm.putPlainMappingIfAbsent(glId, MemoryUtil.memAlloc(16), 0L, 16L, false, 0));
race(1, slot -> rm.deleteBuffer(glId));
assertFalse(rm.peekPlainMapping(glId, new MappedRange()));
assertFalse(rm.takePlainMapping(glId, new MappedRange()));
final ByteBuffer again = MemoryUtil.memAlloc(16);
assertTrue(rm.putPlainMappingIfAbsent(glId, again, 0L, 16L, false, 0), "a deleted buffer's id must be mappable again");
MemoryUtil.memFree(rm.takePlainStaging(glId));
}
@Test
void persistentAndPlainMappingsExcludeEachOther() {
final ResourceManager rm = SdlTestRig.resourceManager();
final int plainFirst = 9004;
final int persistentFirst = 9005;
final ByteBuffer plainStaging = MemoryUtil.memAlloc(16);
final ByteBuffer persistentStaging = MemoryUtil.memAlloc(16);
final ByteBuffer refused = MemoryUtil.memAlloc(16);
try {
assertTrue(rm.putPlainMappingIfAbsent(plainFirst, plainStaging, 0L, 16L, false, 0));
assertFalse(rm.putPersistentMappingIfAbsent(plainFirst, new PersistentMapping(refused, 0L, 16L, 0)), "persistent map of a plain-mapped buffer must be refused");
assertNull(rm.getPersistentMapping(plainFirst));
assertTrue(rm.putPersistentMappingIfAbsent(persistentFirst, new PersistentMapping(persistentStaging, 0L, 16L, 0)));
assertFalse(rm.putPlainMappingIfAbsent(persistentFirst, refused, 0L, 16L, false, 0), "plain map of a persistently mapped buffer must be refused");
assertFalse(rm.peekPlainMapping(persistentFirst, new MappedRange()));
} finally {
rm.takePlainStaging(plainFirst);
rm.removePersistentMapping(persistentFirst);
MemoryUtil.memFree(plainStaging);
MemoryUtil.memFree(persistentStaging);
MemoryUtil.memFree(refused);
}
}
}