diff --git a/src/main/java/net/vulkanmod/render/chunk/buffer/UploadManager.java b/src/main/java/net/vulkanmod/render/chunk/buffer/UploadManager.java index 5ab8bee72..2980bf604 100644 --- a/src/main/java/net/vulkanmod/render/chunk/buffer/UploadManager.java +++ b/src/main/java/net/vulkanmod/render/chunk/buffer/UploadManager.java @@ -7,14 +7,17 @@ import net.vulkanmod.vulkan.memory.buffer.Buffer; import net.vulkanmod.vulkan.memory.buffer.StagingBuffer; import net.vulkanmod.vulkan.queue.CommandPool; -import net.vulkanmod.vulkan.queue.Queue; import net.vulkanmod.vulkan.queue.TransferQueue; import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkBufferCopy; import org.lwjgl.vulkan.VkBufferMemoryBarrier; import org.lwjgl.vulkan.VkCommandBuffer; import org.lwjgl.vulkan.VkMemoryBarrier; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import static org.lwjgl.vulkan.VK10.*; @@ -25,49 +28,124 @@ public static void createInstance() { INSTANCE = new UploadManager(); } - Queue queue = DeviceManager.getTransferQueue(); + TransferQueue queue = DeviceManager.getTransferQueue(); CommandPool.CommandBuffer commandBuffer; LongOpenHashSet dstBuffers = new LongOpenHashSet(); + private final List pendingCopies = new ArrayList<>(); + private long lastFence; + public void submitUploads() { - if (this.commandBuffer == null) - return; + flush(); + } + + public void recordUpload(Buffer buffer, long dstOffset, long bufferSize, ByteBuffer src) { + StagingBuffer stagingBuffer = Vulkan.getStagingBuffer(); + boolean copied = stagingBuffer.copyBuffer((int) bufferSize, src, buffer, dstOffset); + + if (copied) { + pendingCopies.add(new PendingCopy(buffer, dstOffset, bufferSize, stagingBuffer.getOffset())); + } + } + + public boolean hasPendingWork() { + return !pendingCopies.isEmpty() || this.commandBuffer != null; + } + + public long flush() { + if (pendingCopies.isEmpty() && this.commandBuffer == null) { + return 0L; + } + + beginCommands(); + VkCommandBuffer vkCmdBuffer = this.commandBuffer.getHandle(); + + List consolidated = consolidateCopies(); + + for (PendingCopy copy : consolidated) { + if (!this.dstBuffers.add(copy.dstBuffer.getId())) { + try (MemoryStack stack = MemoryStack.stackPush()) { + VkMemoryBarrier.Buffer barrier = VkMemoryBarrier.calloc(1, stack); + barrier.sType$Default(); + barrier.srcAccessMask(VK_ACCESS_TRANSFER_WRITE_BIT); + barrier.dstAccessMask(VK_ACCESS_TRANSFER_WRITE_BIT); + + vkCmdPipelineBarrier(vkCmdBuffer, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, + barrier, + null, + null); + } + + this.dstBuffers.clear(); + } + + StagingBuffer stagingBuffer = Vulkan.getStagingBuffer(); + TransferQueue.uploadBufferCmd(vkCmdBuffer, + stagingBuffer.getId(), copy.srcOffset, + copy.dstBuffer.getId(), copy.dstOffset, + copy.bufferSize); + } - this.queue.submitCommands(this.commandBuffer); + long fence = this.queue.submitCommands(this.commandBuffer); Synchronization.INSTANCE.addCommandBuffer(this.commandBuffer); + this.lastFence = fence; this.commandBuffer = null; this.dstBuffers.clear(); + this.pendingCopies.clear(); + + return fence; } - public void recordUpload(Buffer buffer, long dstOffset, long bufferSize, ByteBuffer src) { - StagingBuffer stagingBuffer = Vulkan.getStagingBuffer(); - stagingBuffer.copyBuffer((int) bufferSize, src); + public long getLastFence() { + return lastFence; + } - beginCommands(); - VkCommandBuffer commandBuffer = this.commandBuffer.getHandle(); - - if (!this.dstBuffers.add(buffer.getId())) { - try (MemoryStack stack = MemoryStack.stackPush()) { - VkMemoryBarrier.Buffer barrier = VkMemoryBarrier.calloc(1, stack); - barrier.sType$Default(); - barrier.srcAccessMask(VK_ACCESS_TRANSFER_WRITE_BIT); - barrier.dstAccessMask(VK_ACCESS_TRANSFER_WRITE_BIT); - - vkCmdPipelineBarrier(commandBuffer, - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, - barrier, - null, - null); - } + public void recordUploadFallback(Buffer dst, long dstOffset, long bufferSize, ByteBuffer src) { + StagingBuffer tempStaging = new StagingBuffer(bufferSize); + tempStaging.copyBuffer((int) bufferSize, src); + + TransferQueue transferQueue = DeviceManager.getTransferQueue(); + transferQueue.uploadBufferImmediate( + tempStaging.getId(), 0L, + dst.getId(), dstOffset, + bufferSize); + + tempStaging.scheduleFree(); + } - this.dstBuffers.clear(); + private List consolidateCopies() { + if (pendingCopies.size() <= 1) { + return new ArrayList<>(pendingCopies); } - TransferQueue.uploadBufferCmd(commandBuffer, stagingBuffer.getId(), stagingBuffer.getOffset(), buffer.getId(), dstOffset, bufferSize); + List sorted = new ArrayList<>(pendingCopies); + sorted.sort(Comparator.comparingLong((PendingCopy c) -> c.dstBuffer.getId()) + .thenComparingLong(c -> c.dstOffset)); + + List merged = new ArrayList<>(); + PendingCopy current = sorted.get(0); + + for (int i = 1; i < sorted.size(); i++) { + PendingCopy next = sorted.get(i); + + if (current.dstBuffer == next.dstBuffer + && current.dstOffset + current.bufferSize == next.dstOffset + && current.srcOffset + current.bufferSize == next.srcOffset) { + current = new PendingCopy(current.dstBuffer, current.dstOffset, + current.bufferSize + next.bufferSize, current.srcOffset); + } else { + merged.add(current); + current = next; + } + } + merged.add(current); + + return merged; } public void copyBuffer(Buffer src, Buffer dst) { @@ -75,9 +153,13 @@ public void copyBuffer(Buffer src, Buffer dst) { } public void copyBuffer(Buffer src, long srcOffset, Buffer dst, long dstOffset, long size) { + if (!this.pendingCopies.isEmpty()) { + flush(); + } + beginCommands(); - VkCommandBuffer commandBuffer = this.commandBuffer.getHandle(); + VkCommandBuffer vkCmdBuffer = this.commandBuffer.getHandle(); try (MemoryStack stack = MemoryStack.stackPush()) { VkMemoryBarrier.Buffer barrier = VkMemoryBarrier.calloc(1, stack); @@ -91,7 +173,7 @@ public void copyBuffer(Buffer src, long srcOffset, Buffer dst, long dstOffset, l bufferMemoryBarrier.dstAccessMask(VK_ACCESS_TRANSFER_READ_BIT); bufferMemoryBarrier.size(VK_WHOLE_SIZE); - vkCmdPipelineBarrier(commandBuffer, + vkCmdPipelineBarrier(vkCmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, barrier, @@ -101,7 +183,7 @@ public void copyBuffer(Buffer src, long srcOffset, Buffer dst, long dstOffset, l this.dstBuffers.add(dst.getId()); - TransferQueue.uploadBufferCmd(commandBuffer, src.getId(), srcOffset, dst.getId(), dstOffset, size); + TransferQueue.uploadBufferCmd(vkCmdBuffer, src.getId(), srcOffset, dst.getId(), dstOffset, size); } public void syncUploads() { @@ -115,4 +197,6 @@ private void beginCommands() { this.commandBuffer = queue.beginCommands(); } + record PendingCopy(Buffer dstBuffer, long dstOffset, long bufferSize, long srcOffset) { + } } diff --git a/src/main/java/net/vulkanmod/render/texture/ImageUploadHelper.java b/src/main/java/net/vulkanmod/render/texture/ImageUploadHelper.java index 8554c36c6..e65ba2c90 100644 --- a/src/main/java/net/vulkanmod/render/texture/ImageUploadHelper.java +++ b/src/main/java/net/vulkanmod/render/texture/ImageUploadHelper.java @@ -16,17 +16,18 @@ public ImageUploadHelper() { queue = DeviceManager.getGraphicsQueue(); } - public void submitCommands() { + public long submitCommands() { if (this.currentCmdBuffer == null) { - return; + return 0L; } SpriteUpdateUtil.transitionLayouts(); - queue.submitCommands(this.currentCmdBuffer, true); + long fence = queue.submitCommands(this.currentCmdBuffer, true); Synchronization.INSTANCE.addCommandBuffer(this.currentCmdBuffer, true); this.currentCmdBuffer = null; + return fence; } public CommandPool.CommandBuffer getOrStartCommandBuffer() { diff --git a/src/main/java/net/vulkanmod/vulkan/memory/buffer/StagingBuffer.java b/src/main/java/net/vulkanmod/vulkan/memory/buffer/StagingBuffer.java index a6a72228f..cb95a2864 100644 --- a/src/main/java/net/vulkanmod/vulkan/memory/buffer/StagingBuffer.java +++ b/src/main/java/net/vulkanmod/vulkan/memory/buffer/StagingBuffer.java @@ -8,13 +8,19 @@ import org.lwjgl.system.MemoryUtil; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; import static org.lwjgl.system.libc.LibCString.nmemcpy; -import static org.lwjgl.vulkan.VK10.*; +import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_TRANSFER_SRC_BIT; public class StagingBuffer extends Buffer { private static final long DEFAULT_SIZE = 64 * 1024 * 1024; + private final List fencedRegions = new ArrayList<>(); + private int flushStartOffset; + public StagingBuffer() { this(DEFAULT_SIZE); } @@ -33,8 +39,13 @@ public void copyBuffer(int size, long scrPtr) { throw new IllegalArgumentException("Upload size is greater than staging buffer size."); } - if (size > this.bufferSize - this.usedBytes) { - submitUploads(); + if (size > getRemaining()) { + flush(); + reclaimCompleted(); + } + + if (this.usedBytes + size > this.bufferSize) { + this.usedBytes = 0; } nmemcpy(this.dataPtr + this.usedBytes, scrPtr, size); @@ -43,23 +54,112 @@ public void copyBuffer(int size, long scrPtr) { this.usedBytes += size; } + public boolean copyBuffer(int size, ByteBuffer src, Buffer dstBuffer, long dstOffset) { + long srcPtr = MemoryUtil.memAddress(src); + + if (size > this.bufferSize) { + throw new IllegalArgumentException("Upload size is greater than staging buffer size."); + } + + if (size > getRemaining()) { + flush(); + reclaimCompleted(); + + if (size > getRemaining()) { + UploadManager.INSTANCE.recordUploadFallback(dstBuffer, dstOffset, size, src); + return false; + } + } + + if (this.usedBytes + size > this.bufferSize) { + this.usedBytes = 0; + } + + nmemcpy(this.dataPtr + this.usedBytes, srcPtr, size); + + this.offset = this.usedBytes; + this.usedBytes += size; + return true; + } + public void align(int alignment) { long alignedOffset = Util.align(usedBytes, alignment); - if (alignedOffset > this.bufferSize) { - submitUploads(); + if (alignedOffset + alignment > this.bufferSize) { + flush(); + reclaimCompleted(); alignedOffset = 0; } this.usedBytes = alignedOffset; } - private void submitUploads() { - // Submit and wait all recorded uploads before resetting the buffer - UploadManager.INSTANCE.submitUploads(); - ImageUploadHelper.INSTANCE.submitCommands(); - Synchronization.INSTANCE.waitFences(); + public void flush() { + long uploadFence = UploadManager.INSTANCE.flush(); + long imageFence = ImageUploadHelper.INSTANCE.submitCommands(); + + int end = (int) this.usedBytes; + + if (end > flushStartOffset) { + if (uploadFence != 0) { + fencedRegions.add(new FencedRegion(uploadFence, flushStartOffset, end)); + } + if (imageFence != 0) { + fencedRegions.add(new FencedRegion(imageFence, flushStartOffset, end)); + } + } else if (end < flushStartOffset) { + if (uploadFence != 0) { + fencedRegions.add(new FencedRegion(uploadFence, flushStartOffset, (int) this.bufferSize)); + fencedRegions.add(new FencedRegion(uploadFence, 0, end)); + } + if (imageFence != 0) { + fencedRegions.add(new FencedRegion(imageFence, flushStartOffset, (int) this.bufferSize)); + fencedRegions.add(new FencedRegion(imageFence, 0, end)); + } + } + + flushStartOffset = end; + } + + public void reclaimCompleted() { + Iterator it = fencedRegions.iterator(); + while (it.hasNext()) { + FencedRegion region = it.next(); + if (Synchronization.checkFenceStatus(region.fence)) { + it.remove(); + } + } + } + + public long getRemaining() { + long remaining = this.bufferSize - this.usedBytes; - this.reset(); + for (FencedRegion region : fencedRegions) { + if (region.startOffset <= this.usedBytes && this.usedBytes < region.endOffset) { + return 0L; + } + if (this.usedBytes < region.startOffset && region.startOffset - this.usedBytes < remaining) { + remaining = region.startOffset - this.usedBytes; + } + } + + return remaining; + } + + void submitUploads() { + flush(); + reclaimCompleted(); + } + + static class FencedRegion { + final long fence; + final int startOffset; + final int endOffset; + + FencedRegion(long fence, int startOffset, int endOffset) { + this.fence = fence; + this.startOffset = startOffset; + this.endOffset = endOffset; + } } }