From 03fa272b6b18c540b66e0b37bf8198a1b480f01b Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Sun, 24 Aug 2025 12:16:34 +0200 Subject: [PATCH 01/11] executing, not workin^g --- .../computenode/cyfra/spirv/SpirvTypes.scala | 1 + .../cyfra/runtime/ExecutionHandler.scala | 26 +++--- .../cyfra/runtime/PendingExecution.scala | 89 +++++++++++++++++++ .../cyfra/runtime/VkAllocation.scala | 50 ++++++----- .../computenode/cyfra/runtime/VkBinding.scala | 69 ++++++++++++++ .../computenode/cyfra/runtime/VkBuffer.scala | 23 ----- .../computenode/cyfra/runtime/VkUniform.scala | 21 ----- .../cyfra/vulkan/command/CommandPool.scala | 37 +++----- .../cyfra/vulkan/command/Semaphore.scala | 2 +- .../cyfra/vulkan/memory/Buffer.scala | 21 ++++- .../cyfra/vulkan/util/VulkanObject.scala | 1 + 11 files changed, 226 insertions(+), 114 deletions(-) create mode 100644 cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala create mode 100644 cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala delete mode 100644 cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBuffer.scala delete mode 100644 cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala diff --git a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvTypes.scala b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvTypes.scala index 9fe1b386..7adeb972 100644 --- a/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvTypes.scala +++ b/cyfra-compiler/src/main/scala/io/computenode/cyfra/spirv/SpirvTypes.scala @@ -54,6 +54,7 @@ private[cyfra] object SpirvTypes: case LGBooleanTag => 4 case v if v <:< LVecTag => vecSize(v) * typeStride(v.typeArgs.head) + case _ => 4 def typeStride(tag: Tag[?]): Int = typeStride(tag.tag) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index 782b2a85..a20efbbb 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -51,7 +51,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte .zip(layout) .map: case (set, bindings) => - set.update(bindings.map(x => VkAllocation.getUnderlying(x.binding))) + set.update(bindings.map(x => VkAllocation.getUnderlying(x.binding).buffer)) set val dispatches: Seq[Dispatch] = shaderCalls @@ -67,19 +67,15 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte else (steps.appended(step), dirty ++ bindings) val commandBuffer = recordCommandBuffer(executeSteps) - pushStack: stack => - val pCommandBuffer = stack.callocPointer(1).put(0, commandBuffer) - val submitInfo = VkSubmitInfo - .calloc(stack) - .sType$Default() - .pCommandBuffers(pCommandBuffer) - - val fence = new Fence() - timed("Vulkan render command"): - check(vkQueueSubmit(commandPool.queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") - fence.block().destroy() - commandPool.freeCommandBuffer(commandBuffer) - descriptorSets.flatten.foreach(dsManager.free) + val cleanup = () => + descriptorSets.flatten.foreach(dsManager.free) + commandPool.freeCommandBuffer(commandBuffer) + + val externalBindings = (summon[LayoutBinding[EL]].toBindings(layout) ++ summon[LayoutBinding[RL]].toBindings(result)) + .map(VkAllocation.getUnderlying) + val deps = externalBindings.flatMap(_.execution.fold(Seq(_), _.toSeq)) + val pe = new PendingExecution(commandBuffer, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, deps, cleanup) + externalBindings.foreach(_.execution = Left(pe)) result private def interpret[Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding]( @@ -228,7 +224,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte dispatch match case Direct(x, y, z) => vkCmdDispatch(commandBuffer, x, y, z) - case Indirect(buffer, offset) => vkCmdDispatchIndirect(commandBuffer, VkAllocation.getUnderlying(buffer).get, offset) + case Indirect(buffer, offset) => vkCmdDispatchIndirect(commandBuffer, VkAllocation.getUnderlying(buffer).buffer.get, offset) check(vkEndCommandBuffer(commandBuffer), "Failed to finish recording command buffer") commandBuffer diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala new file mode 100644 index 00000000..b213c0c9 --- /dev/null +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala @@ -0,0 +1,89 @@ +package io.computenode.cyfra.runtime + +import io.computenode.cyfra.vulkan.command.{CommandPool, Fence, Semaphore} +import io.computenode.cyfra.vulkan.core.{Device, Queue} +import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} +import io.computenode.cyfra.vulkan.util.VulkanObject +import org.lwjgl.vulkan.VK13.{VK_PIPELINE_STAGE_2_COPY_BIT, vkQueueSubmit2} +import org.lwjgl.vulkan.{VkCommandBuffer, VkCommandBufferSubmitInfo, VkSemaphoreSubmitInfo, VkSubmitInfo2} + +import scala.collection.mutable + +class PendingExecution(protected val handle: VkCommandBuffer, val waitStage: Long, val dependencies: Seq[PendingExecution], cleanup: () => Unit)( + using Device, +) extends VulkanObject[VkCommandBuffer]: + private val semaphore: Semaphore = Semaphore(waitStage) + private var fence: Option[Fence] = None + + override protected def close(): Unit = cleanup() + + private def setFence(otherFence: Fence): Unit = + if fence.isDefined then return + fence = Some(otherFence) + dependencies.foreach(_.setFence(otherFence)) + + private def gatherForSubmission: Seq[((VkCommandBuffer, Semaphore), Set[Semaphore])] = { + if fence.isDefined then return Seq.empty + val mySubmission = ((handle, semaphore), dependencies.map(_.semaphore).toSet) + dependencies.flatMap(_.gatherForSubmission).appended(mySubmission) + } + + def block(): Unit = + fence match + case Some(f) => f.block() + case None => throw new IllegalStateException("No fence set for this execution") + +object PendingExecution: + def executeAll(executions: Seq[PendingExecution], queue: Queue)(using Device): Fence = pushStack: stack => + val gathered = executions.flatMap(_.gatherForSubmission).toSet.groupMap(_._2)(_._1).toSeq + + val submitInfos = VkSubmitInfo2.calloc(gathered.size, stack) + gathered.foreach: (semaphores, executions) => + val pCommandBuffersSI = VkCommandBufferSubmitInfo.calloc(executions.size, stack) + val signalSemaphoreSI = VkSemaphoreSubmitInfo.calloc(executions.size, stack) + executions.foreach: (cb, s) => + pCommandBuffersSI + .get() + .sType$Default() + .commandBuffer(cb) + .deviceMask(0) + signalSemaphoreSI + .get() + .sType$Default() + .semaphore(s.get) + .stageMask(s.stage) + + pCommandBuffersSI.flip() + signalSemaphoreSI.flip() + + val waitSemaphoreSI = VkSemaphoreSubmitInfo.calloc(semaphores.size, stack) + semaphores.foreach: s => + waitSemaphoreSI + .get() + .sType$Default() + .semaphore(s.get) + .stageMask(s.stage) + + waitSemaphoreSI.flip() + + submitInfos + .get() + .sType$Default() + .flags(0) + .pCommandBufferInfos(pCommandBuffersSI) + .pSignalSemaphoreInfos(signalSemaphoreSI) + .pWaitSemaphoreInfos(waitSemaphoreSI) + + submitInfos.flip() + + val fence = Fence() + executions.foreach(_.setFence(fence)) + check(vkQueueSubmit2(queue.get, submitInfos, fence.get), "Failed to submit command buffer to queue") + fence + + def cleanupAll(executions: Seq[PendingExecution]): Unit = + def cleanupRec(ex: PendingExecution): Unit = + if !ex.isAlive then return + ex.destroy() + ex.dependencies.foreach(cleanupRec) + executions.foreach(cleanupRec) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala index a038ac7b..a851389e 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala @@ -14,37 +14,48 @@ import io.computenode.cyfra.vulkan.command.CommandPool import io.computenode.cyfra.vulkan.memory.{Allocator, Buffer} import io.computenode.cyfra.vulkan.util.Util.pushStack import io.computenode.cyfra.dsl.Value.Int32 +import io.computenode.cyfra.vulkan.core.Device import izumi.reflect.Tag import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil import org.lwjgl.vulkan.VK10 +import org.lwjgl.vulkan.VK13.VK_PIPELINE_STAGE_2_COPY_BIT import org.lwjgl.vulkan.VK10.{VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_BUFFER_USAGE_TRANSFER_SRC_BIT} import java.nio.ByteBuffer import scala.collection.mutable import scala.util.chaining.* -class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler)(using Allocator) extends Allocation: +class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler)(using Allocator, Device) extends Allocation: given VkAllocation = this extension (buffer: GBinding[?]) def read(bb: ByteBuffer, offset: Int = 0): Unit = val size = bb.remaining() - getUnderlying(buffer) match - case buffer: Buffer.HostBuffer => buffer.copyTo(bb, offset) - case buffer: Buffer.DeviceBuffer => + buffer match + case VkBinding(buffer: Buffer.HostBuffer) => buffer.copyTo(bb, offset) + case binding: VkBinding[?] => + binding.materialise(commandPool.queue) val stagingBuffer = getStagingBuffer(size) - Buffer.copyBuffer(buffer, stagingBuffer, offset, 0, size, commandPool) + Buffer.copyBuffer(binding.buffer, stagingBuffer, offset, 0, size, commandPool) stagingBuffer.copyTo(bb, 0) + case _ => throw new IllegalArgumentException(s"Tried to read from non-VkBinding $buffer") def write(bb: ByteBuffer, offset: Int = 0): Unit = val size = bb.remaining() - getUnderlying(buffer) match - case buffer: Buffer.HostBuffer => buffer.copyFrom(bb, offset) - case buffer: Buffer.DeviceBuffer => + buffer match + case VkBinding(buffer: Buffer.HostBuffer) => buffer.copyFrom(bb, offset) + case binding: VkBinding[?] => + binding.materialise(commandPool.queue) val stagingBuffer = getStagingBuffer(size) - stagingBuffer.copyFrom(bb, offset) - Buffer.copyBuffer(stagingBuffer, buffer, 0, offset, size, commandPool) + stagingBuffer.copyFrom(bb, 0) + val cb = Buffer.copyBufferCommandBuffer(stagingBuffer, binding.buffer, 0, offset, size, commandPool) + val cleanup = () => + commandPool.freeCommandBuffer(cb) + stagingBuffer.destroy() + val pe = new PendingExecution(cb, VK_PIPELINE_STAGE_2_COPY_BIT, binding.execution.fold(Seq(_), _.toSeq), cleanup) + binding.execution = Left(pe) + case _ => throw new IllegalArgumentException(s"Tried to write to non-VkBinding $buffer") extension (buffers: GBuffer.type) def apply[T <: Value: {Tag, FromExpr}](length: Int): GBuffer[T] = @@ -80,22 +91,13 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) private val bindings = mutable.Buffer[VkUniform[?] | VkBuffer[?]]() private[cyfra] def close(): Unit = - bindings.map(getUnderlying).foreach(_.destroy()) - stagingBuffer.foreach(_.destroy()) + bindings.map(getUnderlying).foreach(_.buffer.destroy()) - private var stagingBuffer: Option[Buffer.HostBuffer] = None private def getStagingBuffer(size: Int): Buffer.HostBuffer = - stagingBuffer match - case Some(buffer) if buffer.size >= size => buffer - case _ => - stagingBuffer.foreach(_.destroy()) - val newBuffer = Buffer.HostBuffer(size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT) - stagingBuffer = Some(newBuffer) - newBuffer + Buffer.HostBuffer(size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT) object VkAllocation: - private[runtime] def getUnderlying(buffer: GBinding[?]): Buffer = + private[runtime] def getUnderlying(buffer: GBinding[?]): VkBinding[?] = buffer match - case buffer: VkBuffer[?] => buffer.underlying - case uniform: VkUniform[?] => uniform.underlying - case _ => throw new IllegalArgumentException(s"Tried to get underlying of non-VkBinding $buffer") + case buffer: VkBinding[?] => buffer + case _ => throw new IllegalArgumentException(s"Tried to get underlying of non-VkBinding $buffer") diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala new file mode 100644 index 00000000..f83f39fb --- /dev/null +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala @@ -0,0 +1,69 @@ +package io.computenode.cyfra.runtime + +import io.computenode.cyfra.dsl.Value +import io.computenode.cyfra.dsl.Value.FromExpr +import io.computenode.cyfra.spirv.SpirvTypes.typeStride +import izumi.reflect.Tag +import io.computenode.cyfra.dsl.Value +import io.computenode.cyfra.dsl.Value.FromExpr +import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer} +import io.computenode.cyfra.vulkan.memory.{Allocator, Buffer} +import io.computenode.cyfra.vulkan.core.Queue +import io.computenode.cyfra.vulkan.core.Device +import izumi.reflect.Tag +import io.computenode.cyfra.spirv.SpirvTypes.typeStride +import org.lwjgl.vulkan.VK10 +import org.lwjgl.vulkan.VK10.{VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_BUFFER_USAGE_TRANSFER_SRC_BIT} +import io.computenode.cyfra.dsl.Value +import io.computenode.cyfra.dsl.Value.FromExpr +import io.computenode.cyfra.dsl.binding.GUniform +import io.computenode.cyfra.vulkan.memory.{Allocator, Buffer} +import izumi.reflect.Tag +import org.lwjgl.vulkan.VK10 +import org.lwjgl.vulkan.VK10.* + +import scala.collection.mutable + +sealed abstract class VkBinding[T <: Value: {Tag, FromExpr}](val buffer: Buffer): + val sizeOfT: Int = typeStride(summon[Tag[T]]) + + /** Holds either: + * 1. a single execution that writes to this buffer + * 1. multiple executions that read from this buffer + */ + var execution: Either[PendingExecution, mutable.Buffer[PendingExecution]] = Right(mutable.Buffer.empty) + + def materialise(queue: Queue)(using Device): Unit = execution match + case Left(exec) if exec.isAlive => + PendingExecution.executeAll(Seq(exec), queue) + exec.block() + PendingExecution.cleanupAll(Seq(exec)) + case _ => () + +object VkBinding: + def unapply(binding: GBinding[?]): Option[Buffer] = binding match + case b: VkBinding[?] => Some(b.buffer) + case _ => None + +class VkBuffer[T <: Value: {Tag, FromExpr}] private (val length: Int, underlying: Buffer) extends VkBinding(underlying) with GBuffer[T] + +object VkBuffer: + private final val Padding = 64 + private final val UsageFlags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT + + def apply[T <: Value: {Tag, FromExpr}](length: Int)(using Allocator): VkBuffer[T] = + val sizeOfT = typeStride(summon[Tag[T]]) + val size = (length * sizeOfT + Padding - 1) / Padding * Padding + val buffer = new Buffer.DeviceBuffer(size, UsageFlags) + new VkBuffer[T](length, buffer) + +class VkUniform[T <: Value: {Tag, FromExpr}] private (underlying: Buffer) extends VkBinding[T](underlying) with GUniform[T] + +object VkUniform: + private final val UsageFlags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT + + def apply[T <: Value: {Tag, FromExpr}]()(using Allocator): VkUniform[T] = + val sizeOfT = typeStride(summon[Tag[T]]) + val buffer = new Buffer.DeviceBuffer(sizeOfT, UsageFlags) + new VkUniform[T](buffer) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBuffer.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBuffer.scala deleted file mode 100644 index cda73868..00000000 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBuffer.scala +++ /dev/null @@ -1,23 +0,0 @@ -package io.computenode.cyfra.runtime - -import io.computenode.cyfra.dsl.Value -import io.computenode.cyfra.dsl.Value.FromExpr -import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer} -import io.computenode.cyfra.vulkan.memory.{Allocator, Buffer} -import izumi.reflect.Tag -import io.computenode.cyfra.spirv.SpirvTypes.typeStride -import org.lwjgl.vulkan.VK10 -import org.lwjgl.vulkan.VK10.{VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_BUFFER_USAGE_TRANSFER_SRC_BIT} - -class VkBuffer[T <: Value: {Tag, FromExpr}] private (var length: Int, val underlying: Buffer) extends GBuffer[T]: - val sizeOfT: Int = typeStride(summon[Tag[T]]) - -object VkBuffer: - private final val Padding = 64 - private final val UsageFlags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT - - def apply[T <: Value: {Tag, FromExpr}](length: Int)(using Allocator): VkBuffer[T] = - val sizeOfT = typeStride(summon[Tag[T]]) - val size = (length * sizeOfT + Padding - 1) / Padding * Padding - val buffer = new Buffer.DeviceBuffer(size, UsageFlags) - new VkBuffer[T](length, buffer) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala deleted file mode 100644 index f8c75da7..00000000 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala +++ /dev/null @@ -1,21 +0,0 @@ -package io.computenode.cyfra.runtime - -import io.computenode.cyfra.dsl.Value -import io.computenode.cyfra.dsl.Value.FromExpr -import io.computenode.cyfra.dsl.binding.GUniform -import io.computenode.cyfra.vulkan.memory.{Allocator, Buffer} -import izumi.reflect.Tag -import org.lwjgl.vulkan.VK10 -import org.lwjgl.vulkan.VK10.* - -class VkUniform[T <: Value: {Tag, FromExpr}] private (val underlying: Buffer) extends GUniform[T]: - val sizeOfT: Int = 4 - -object VkUniform: - private final val UsageFlags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | - VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT - - def apply[T <: Value: {Tag, FromExpr}]()(using Allocator): VkUniform[T] = - val sizeOfT = 4 // typeStride(summon[Tag[T]]) - val buffer = new Buffer.DeviceBuffer(sizeOfT, UsageFlags) - new VkUniform[T](buffer) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala index 3db65668..11d21e1a 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala @@ -39,35 +39,18 @@ private[cyfra] abstract class CommandPool private (flags: Int, val queue: Queue) check(vkAllocateCommandBuffers(device.get, allocateInfo, pointerBuffer), "Failed to allocate command buffers") 0 until n map (i => pointerBuffer.get(i)) map (new VkCommandBuffer(_, device.get)) - def executeCommand(block: VkCommandBuffer => Unit): Unit = - val commandBuffer = beginSingleTimeCommands() - block(commandBuffer) - endSingleTimeCommands(commandBuffer).block().destroy() - freeCommandBuffer(commandBuffer) - - private def beginSingleTimeCommands(): VkCommandBuffer = - pushStack: stack => - val commandBuffer = this.createCommandBuffer() - - val beginInfo = VkCommandBufferBeginInfo - .calloc(stack) - .sType$Default() - .flags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT) + def recordSingleTimeCommand(block: VkCommandBuffer => Unit): VkCommandBuffer = pushStack: stack => + val commandBuffer = createCommandBuffer() - check(vkBeginCommandBuffer(commandBuffer, beginInfo), "Failed to begin single time command buffer") - commandBuffer + val beginInfo = VkCommandBufferBeginInfo + .calloc(stack) + .sType$Default() + .flags(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT) - private def endSingleTimeCommands(commandBuffer: VkCommandBuffer): Fence = - pushStack: stack => - vkEndCommandBuffer(commandBuffer) - val pointerBuffer = stack.callocPointer(1).put(0, commandBuffer) - val submitInfo = VkSubmitInfo - .calloc(stack) - .sType$Default() - .pCommandBuffers(pointerBuffer) - val fence = Fence() - check(vkQueueSubmit(queue.get, submitInfo, fence.get), "Failed to submit single time command buffer") - fence + check(vkBeginCommandBuffer(commandBuffer, beginInfo), "Failed to begin single time command buffer") + block(commandBuffer) + check(vkEndCommandBuffer(commandBuffer), "Failed to end single time command buffer") + commandBuffer def freeCommandBuffer(commandBuffer: VkCommandBuffer*): Unit = pushStack: stack => diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala index 04034b1c..88386710 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala @@ -9,7 +9,7 @@ import org.lwjgl.vulkan.VkSemaphoreCreateInfo /** @author * MarconZet Created 30.10.2019 */ -private[cyfra] class Semaphore(using device: Device) extends VulkanObjectHandle: +private[cyfra] class Semaphore(val stage: Long)(using device: Device) extends VulkanObjectHandle: protected val handle: Long = pushStack: stack => val semaphoreCreateInfo = VkSemaphoreCreateInfo .calloc(stack) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala index 963aa1cd..1f677f04 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala @@ -1,13 +1,14 @@ package io.computenode.cyfra.vulkan.memory import io.computenode.cyfra.vulkan.command.{CommandPool, Fence} +import io.computenode.cyfra.vulkan.core.Device import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import io.computenode.cyfra.vulkan.util.VulkanObjectHandle import org.lwjgl.system.MemoryUtil.* import org.lwjgl.util.vma.Vma.* import org.lwjgl.util.vma.VmaAllocationCreateInfo import org.lwjgl.vulkan.VK10.* -import org.lwjgl.vulkan.{VkBufferCopy, VkBufferCreateInfo} +import org.lwjgl.vulkan.{VkBufferCopy, VkBufferCreateInfo, VkCommandBuffer, VkSubmitInfo} import java.nio.ByteBuffer @@ -61,8 +62,22 @@ object Buffer: def copyFrom(src: ByteBuffer, dstOffset: Int): Unit = pushStack: stack => vmaCopyMemoryToAllocation(allocator.get, src, allocation, dstOffset) - def copyBuffer(src: Buffer, dst: Buffer, srcOffset: Int, dstOffset: Int, bytes: Int, commandPool: CommandPool): Unit = - commandPool.executeCommand: commandBuffer => + def copyBuffer(src: Buffer, dst: Buffer, srcOffset: Int, dstOffset: Int, bytes: Int, commandPool: CommandPool)(using Device): Unit = pushStack: + stack => + val cb = copyBufferCommandBuffer(src, dst, srcOffset, dstOffset, bytes, commandPool) + + val pCB = stack.callocPointer(1).put(0, cb) + val submitInfo = VkSubmitInfo + .calloc(stack) + .sType$Default() + .pCommandBuffers(pCB) + + val fence = Fence() + check(vkQueueSubmit(commandPool.queue.get, submitInfo, fence.get), "Failed to submit single time command buffer") + fence.block() + + def copyBufferCommandBuffer(src: Buffer, dst: Buffer, srcOffset: Int, dstOffset: Int, bytes: Int, commandPool: CommandPool): VkCommandBuffer = + commandPool.recordSingleTimeCommand: commandBuffer => pushStack: stack => val copyRegion = VkBufferCopy .calloc(1, stack) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObject.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObject.scala index 3ec34726..50d3baf7 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObject.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObject.scala @@ -6,6 +6,7 @@ package io.computenode.cyfra.vulkan.util private[cyfra] abstract class VulkanObject[T]: protected val handle: T private var alive: Boolean = true + def isAlive: Boolean = alive def get: T = if !alive then throw new IllegalStateException() From 40aa8e2892055cc09e5672985f1cb83baa57aa91 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Wed, 3 Sep 2025 20:14:31 +0200 Subject: [PATCH 02/11] more^ --- .../cyfra/runtime/ExecutionHandler.scala | 2 +- .../cyfra/runtime/PendingExecution.scala | 22 ++++++++++--------- .../cyfra/runtime/VkAllocation.scala | 2 +- .../cyfra/vulkan/command/Semaphore.scala | 2 +- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index a20efbbb..89473f8c 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -74,7 +74,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte val externalBindings = (summon[LayoutBinding[EL]].toBindings(layout) ++ summon[LayoutBinding[RL]].toBindings(result)) .map(VkAllocation.getUnderlying) val deps = externalBindings.flatMap(_.execution.fold(Seq(_), _.toSeq)) - val pe = new PendingExecution(commandBuffer, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, deps, cleanup) + val pe = new PendingExecution(commandBuffer, deps, cleanup) externalBindings.foreach(_.execution = Left(pe)) result diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala index b213c0c9..63cf516f 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala @@ -5,14 +5,13 @@ import io.computenode.cyfra.vulkan.core.{Device, Queue} import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import io.computenode.cyfra.vulkan.util.VulkanObject import org.lwjgl.vulkan.VK13.{VK_PIPELINE_STAGE_2_COPY_BIT, vkQueueSubmit2} -import org.lwjgl.vulkan.{VkCommandBuffer, VkCommandBufferSubmitInfo, VkSemaphoreSubmitInfo, VkSubmitInfo2} +import org.lwjgl.vulkan.{VK13, VkCommandBuffer, VkCommandBufferSubmitInfo, VkSemaphoreSubmitInfo, VkSubmitInfo2} import scala.collection.mutable -class PendingExecution(protected val handle: VkCommandBuffer, val waitStage: Long, val dependencies: Seq[PendingExecution], cleanup: () => Unit)( - using Device, -) extends VulkanObject[VkCommandBuffer]: - private val semaphore: Semaphore = Semaphore(waitStage) +class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: Seq[PendingExecution], cleanup: () => Unit)(using Device) + extends VulkanObject[VkCommandBuffer]: + private val semaphore: Semaphore = Semaphore() private var fence: Option[Fence] = None override protected def close(): Unit = cleanup() @@ -35,10 +34,13 @@ class PendingExecution(protected val handle: VkCommandBuffer, val waitStage: Lon object PendingExecution: def executeAll(executions: Seq[PendingExecution], queue: Queue)(using Device): Fence = pushStack: stack => - val gathered = executions.flatMap(_.gatherForSubmission).toSet.groupMap(_._2)(_._1).toSeq + val exec = + val gathered = executions.flatMap(_.gatherForSubmission) + val ordering = gathered.zipWithIndex.map(x => (x._1._1._1, x._2)).toMap + gathered.toSet.groupMap(_._2)(_._1).toSeq.sortBy(x => x._2.map(_._1).map(ordering).min) - val submitInfos = VkSubmitInfo2.calloc(gathered.size, stack) - gathered.foreach: (semaphores, executions) => + val submitInfos = VkSubmitInfo2.calloc(exec.size, stack) + exec.foreach: (semaphores, executions) => val pCommandBuffersSI = VkCommandBufferSubmitInfo.calloc(executions.size, stack) val signalSemaphoreSI = VkSemaphoreSubmitInfo.calloc(executions.size, stack) executions.foreach: (cb, s) => @@ -51,7 +53,7 @@ object PendingExecution: .get() .sType$Default() .semaphore(s.get) - .stageMask(s.stage) + .stageMask(VK13.VK_PIPELINE_STAGE_2_COPY_BIT | VK13.VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT) pCommandBuffersSI.flip() signalSemaphoreSI.flip() @@ -62,7 +64,7 @@ object PendingExecution: .get() .sType$Default() .semaphore(s.get) - .stageMask(s.stage) + .stageMask(VK13.VK_PIPELINE_STAGE_2_COPY_BIT | VK13.VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT) waitSemaphoreSI.flip() diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala index a851389e..4ce9692c 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala @@ -53,7 +53,7 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) val cleanup = () => commandPool.freeCommandBuffer(cb) stagingBuffer.destroy() - val pe = new PendingExecution(cb, VK_PIPELINE_STAGE_2_COPY_BIT, binding.execution.fold(Seq(_), _.toSeq), cleanup) + val pe = new PendingExecution(cb, binding.execution.fold(Seq(_), _.toSeq), cleanup) binding.execution = Left(pe) case _ => throw new IllegalArgumentException(s"Tried to write to non-VkBinding $buffer") diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala index 88386710..2e86ef68 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Semaphore.scala @@ -9,7 +9,7 @@ import org.lwjgl.vulkan.VkSemaphoreCreateInfo /** @author * MarconZet Created 30.10.2019 */ -private[cyfra] class Semaphore(val stage: Long)(using device: Device) extends VulkanObjectHandle: +private[cyfra] class Semaphore()(using device: Device) extends VulkanObjectHandle: protected val handle: Long = pushStack: stack => val semaphoreCreateInfo = VkSemaphoreCreateInfo .calloc(stack) From 62c383c7b9b84e4256d21122ff13d8dc188e493e Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Mon, 8 Sep 2025 22:38:41 +0200 Subject: [PATCH 03/11] curret std^ --- .../io/computenode/cyfra/core/GBufferRegion.scala | 10 +++++----- .../cyfra/runtime/ExecutionHandler.scala | 13 ++----------- .../io/computenode/cyfra/vulkan/VulkanContext.scala | 1 - 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala index cfd3ad8d..d0c02fbb 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala @@ -1,6 +1,7 @@ package io.computenode.cyfra.core import io.computenode.cyfra.core.Allocation +import io.computenode.cyfra.core.GBufferRegion.MapRegion import io.computenode.cyfra.core.GProgram.BufferLengthSpec import io.computenode.cyfra.core.layout.{Layout, LayoutBinding} import io.computenode.cyfra.dsl.Value @@ -10,8 +11,10 @@ import izumi.reflect.Tag import java.nio.ByteBuffer -sealed trait GBufferRegion[ReqAlloc <: Layout: LayoutBinding, ResAlloc <: Layout: LayoutBinding] - +sealed trait GBufferRegion[ReqAlloc <: Layout: LayoutBinding, ResAlloc <: Layout: LayoutBinding]: + def map[NewAlloc <: Layout: LayoutBinding](f: Allocation ?=> ResAlloc => NewAlloc): GBufferRegion[ReqAlloc, NewAlloc] = + MapRegion(this, (alloc: Allocation) => (resAlloc: ResAlloc) => f(using alloc)(resAlloc)) + object GBufferRegion: def allocate[Alloc <: Layout: LayoutBinding]: GBufferRegion[Alloc, Alloc] = AllocRegion() @@ -24,9 +27,6 @@ object GBufferRegion: ) extends GBufferRegion[ReqAlloc, ResAlloc] extension [ReqAlloc <: Layout: LayoutBinding, ResAlloc <: Layout: LayoutBinding](region: GBufferRegion[ReqAlloc, ResAlloc]) - def map[NewAlloc <: Layout: LayoutBinding](f: Allocation ?=> ResAlloc => NewAlloc): GBufferRegion[ReqAlloc, NewAlloc] = - MapRegion(region, (alloc: Allocation) => (resAlloc: ResAlloc) => f(using alloc)(resAlloc)) - def runUnsafe(init: Allocation ?=> ReqAlloc, onDone: Allocation ?=> ResAlloc => Unit)(using cyfraRuntime: CyfraRuntime): Unit = cyfraRuntime.withAllocation: allocation => diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index 89473f8c..b15fa95a 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -8,15 +8,7 @@ import io.computenode.cyfra.core.layout.{Layout, LayoutBinding, LayoutStruct} import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} -import io.computenode.cyfra.runtime.ExecutionHandler.{ - BindingLogicError, - Dispatch, - DispatchType, - ExecutionBinding, - ExecutionStep, - PipelineBarrier, - ShaderCall, -} +import io.computenode.cyfra.runtime.ExecutionHandler.{BindingLogicError, Dispatch, DispatchType, ExecutionBinding, ExecutionStep, PipelineBarrier, ShaderCall} import io.computenode.cyfra.runtime.ExecutionHandler.DispatchType.* import io.computenode.cyfra.runtime.ExecutionHandler.ExecutionBinding.{BufferBinding, UniformBinding} import io.computenode.cyfra.utility.Utility.timed @@ -29,7 +21,7 @@ import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import izumi.reflect.Tag import org.lwjgl.vulkan.VK10.* import org.lwjgl.vulkan.VK13.{VK_ACCESS_2_SHADER_READ_BIT, VK_ACCESS_2_SHADER_WRITE_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, vkCmdPipelineBarrier2} -import org.lwjgl.vulkan.{VkCommandBuffer, VkCommandBufferBeginInfo, VkDependencyInfo, VkMemoryBarrier2, VkSubmitInfo} +import org.lwjgl.vulkan.{VK13, VkCommandBuffer, VkCommandBufferBeginInfo, VkDependencyInfo, VkMemoryBarrier2, VkSubmitInfo} import scala.collection.mutable @@ -198,7 +190,6 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte .flags(0) check(vkBeginCommandBuffer(commandBuffer, commandBufferBeginInfo), "Failed to begin recording command buffer") - steps.foreach: case PipelineBarrier => val memoryBarrier = VkMemoryBarrier2 // TODO don't synchronise everything diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala index 9c8c99c6..67d612fe 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala @@ -17,7 +17,6 @@ import scala.jdk.CollectionConverters.* private[cyfra] object VulkanContext: val ValidationLayer: String = "VK_LAYER_KHRONOS_validation" private val ValidationLayers: Boolean = System.getProperty("io.computenode.cyfra.vulkan.validation", "false").toBoolean - if Configuration.STACK_SIZE.get() < 100 then logger.warn(s"Small stack size. Increase with org.lwjgl.system.stackSize") private[cyfra] class VulkanContext: private val instance: Instance = new Instance(ValidationLayers) From 6993f85e3840d4d016ab379f3536bf9da0b8a505 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Tue, 9 Sep 2025 00:16:35 +0200 Subject: [PATCH 04/11] working^ --- .../cyfra/samples/TestingStuff.scala | 15 ++++++++++++-- .../cyfra/runtime/ExecutionHandler.scala | 20 ++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala index 8b9a5014..a389efc4 100644 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala +++ b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala @@ -12,9 +12,16 @@ import io.computenode.cyfra.runtime.VkCyfraRuntime import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil +import java.nio.ByteBuffer import java.util.concurrent.atomic.AtomicInteger import scala.collection.parallel.CollectionConverters.given +def printBuffer(bb: ByteBuffer): Unit = { + val l = bb.asIntBuffer() + val s = (0 until l.remaining()).map(l.get).toList + println(s.mkString(" ")) +} + object TestingStuff: given GContext = GContext() @@ -111,10 +118,12 @@ object TestingStuff: emitBuffer = GBuffer[Int32](data.length * 2), filterBuffer = GBuffer[GBoolean](data.length * 2), ), - onDone = layout => layout.filterBuffer.read(rbb), + onDone = layout => + layout.filterBuffer.read(rbb) ) runtime.close() + printBuffer(rbb) val actual = (0 until 2 * 1024).map(i => result.get(i * 1) != 0) val expected = (0 until 1024).flatMap(x => Seq.fill(emitFilterParams.emitN)(x)).map(_ == emitFilterParams.filterValue) expected @@ -191,7 +200,7 @@ object TestingStuff: def testAddProgram10Times = given runtime: VkCyfraRuntime = VkCyfraRuntime() val bufferSize = 1280 - val params = AddProgramParams(bufferSize, addA = 0, addB = 1) + val params = AddProgramParams(bufferSize, addA = 5, addB = 10) val region = GBufferRegion .allocate[AddProgramExecLayout] .map: region => @@ -226,6 +235,8 @@ object TestingStuff: }, ) runtime.close() + + printBuffer(rbbList(0)) val expected = inData.map(_ + 11 * (params.addA + params.addB)) outBuffers.foreach { buf => (0 until bufferSize).foreach { i => diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index b15fa95a..cca151fb 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -8,7 +8,15 @@ import io.computenode.cyfra.core.layout.{Layout, LayoutBinding, LayoutStruct} import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} -import io.computenode.cyfra.runtime.ExecutionHandler.{BindingLogicError, Dispatch, DispatchType, ExecutionBinding, ExecutionStep, PipelineBarrier, ShaderCall} +import io.computenode.cyfra.runtime.ExecutionHandler.{ + BindingLogicError, + Dispatch, + DispatchType, + ExecutionBinding, + ExecutionStep, + PipelineBarrier, + ShaderCall, +} import io.computenode.cyfra.runtime.ExecutionHandler.DispatchType.* import io.computenode.cyfra.runtime.ExecutionHandler.ExecutionBinding.{BufferBinding, UniformBinding} import io.computenode.cyfra.utility.Utility.timed @@ -63,8 +71,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte descriptorSets.flatten.foreach(dsManager.free) commandPool.freeCommandBuffer(commandBuffer) - val externalBindings = (summon[LayoutBinding[EL]].toBindings(layout) ++ summon[LayoutBinding[RL]].toBindings(result)) - .map(VkAllocation.getUnderlying) + val externalBindings = getAllBindings(executeSteps).map(VkAllocation.getUnderlying) val deps = externalBindings.flatMap(_.execution.fold(Seq(_), _.toSeq)) val pe = new PendingExecution(commandBuffer, deps, cleanup) externalBindings.foreach(_.execution = Left(pe)) @@ -220,6 +227,13 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte check(vkEndCommandBuffer(commandBuffer), "Failed to finish recording command buffer") commandBuffer + private def getAllBindings(steps: Seq[ExecutionStep]): Seq[GBinding[?]] = + steps + .flatMap: + case Dispatch(_, layout, _, _) => layout.flatten.map(_.binding) + case PipelineBarrier => Seq.empty + .distinct + object ExecutionHandler: case class ShaderCall(pipeline: ComputePipeline, layout: ShaderLayout, dispatch: DispatchType) From 226c1215e37d196be1c7768dcc8510bdd4cac5f8 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Tue, 9 Sep 2025 00:30:33 +0200 Subject: [PATCH 05/11] todo^ --- .../io/computenode/cyfra/runtime/ExecutionHandler.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index cca151fb..f8624b27 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -74,7 +74,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte val externalBindings = getAllBindings(executeSteps).map(VkAllocation.getUnderlying) val deps = externalBindings.flatMap(_.execution.fold(Seq(_), _.toSeq)) val pe = new PendingExecution(commandBuffer, deps, cleanup) - externalBindings.foreach(_.execution = Left(pe)) + externalBindings.foreach(_.execution = Left(pe)) // TODO we assume all accesses are read-write result private def interpret[Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding]( @@ -231,8 +231,8 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte steps .flatMap: case Dispatch(_, layout, _, _) => layout.flatten.map(_.binding) - case PipelineBarrier => Seq.empty - .distinct + case PipelineBarrier => Seq.empty + .distinct object ExecutionHandler: case class ShaderCall(pipeline: ComputePipeline, layout: ShaderLayout, dispatch: DispatchType) From fdc9ee2134581ebe2f4168998b92f70a5186111b Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Tue, 9 Sep 2025 00:34:23 +0200 Subject: [PATCH 06/11] foramt^^ --- .../main/scala/io/computenode/cyfra/core/GBufferRegion.scala | 2 +- .../main/scala/io/computenode/cyfra/samples/TestingStuff.scala | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala index d0c02fbb..633b3e1b 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala @@ -14,7 +14,7 @@ import java.nio.ByteBuffer sealed trait GBufferRegion[ReqAlloc <: Layout: LayoutBinding, ResAlloc <: Layout: LayoutBinding]: def map[NewAlloc <: Layout: LayoutBinding](f: Allocation ?=> ResAlloc => NewAlloc): GBufferRegion[ReqAlloc, NewAlloc] = MapRegion(this, (alloc: Allocation) => (resAlloc: ResAlloc) => f(using alloc)(resAlloc)) - + object GBufferRegion: def allocate[Alloc <: Layout: LayoutBinding]: GBufferRegion[Alloc, Alloc] = AllocRegion() diff --git a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala index a389efc4..c45680ea 100644 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala +++ b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala @@ -118,8 +118,7 @@ object TestingStuff: emitBuffer = GBuffer[Int32](data.length * 2), filterBuffer = GBuffer[GBoolean](data.length * 2), ), - onDone = layout => - layout.filterBuffer.read(rbb) + onDone = layout => layout.filterBuffer.read(rbb), ) runtime.close() From 1ab927aea92c9023b5bf816cc73d8c040110ecf6 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Tue, 9 Sep 2025 21:44:31 +0200 Subject: [PATCH 07/11] not working destroy^ --- .../computenode/cyfra/core/Allocation.scala | 2 + .../cyfra/core/GBufferRegion.scala | 16 +++-- .../cyfra/runtime/ExecutionHandler.scala | 1 + .../cyfra/runtime/PendingExecution.scala | 58 ++++++++++++------- .../cyfra/runtime/VkAllocation.scala | 16 +++++ .../computenode/cyfra/runtime/VkBinding.scala | 15 +++-- 6 files changed, 76 insertions(+), 32 deletions(-) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala index bdc1d5a7..d279bb88 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala @@ -10,6 +10,8 @@ import izumi.reflect.Tag import java.nio.ByteBuffer trait Allocation: + def reportLayout[L <: Layout: LayoutBinding](layout: L): Unit + extension (buffer: GBinding[?]) def read(bb: ByteBuffer, offset: Int = 0): Unit diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala index 633b3e1b..65ad0b41 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala @@ -9,9 +9,13 @@ import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.GBuffer import izumi.reflect.Tag +import scala.util.chaining.given import java.nio.ByteBuffer sealed trait GBufferRegion[ReqAlloc <: Layout: LayoutBinding, ResAlloc <: Layout: LayoutBinding]: + def reqAllocBinding: LayoutBinding[ReqAlloc] = summon[LayoutBinding[ReqAlloc]] + def resAllocBinding: LayoutBinding[ResAlloc] = summon[LayoutBinding[ResAlloc]] + def map[NewAlloc <: Layout: LayoutBinding](f: Allocation ?=> ResAlloc => NewAlloc): GBufferRegion[ReqAlloc, NewAlloc] = MapRegion(this, (alloc: Allocation) => (resAlloc: ResAlloc) => f(using alloc)(resAlloc)) @@ -31,13 +35,13 @@ object GBufferRegion: cyfraRuntime.withAllocation: allocation => // noinspection ScalaRedundantCast - val steps: Seq[Allocation => Layout => Layout] = Seq.unfold(region: GBufferRegion[?, ?]): - case _: AllocRegion[?] => None - case MapRegion(req, f) => - Some((f.asInstanceOf[Allocation => Layout => Layout], req)) + val steps: Seq[(Allocation => Layout => Layout, LayoutBinding[Layout])] = Seq.unfold(region: GBufferRegion[?, ?]): + case _: AllocRegion[?] => None + case m @ MapRegion(req, f) => + Some(((f.asInstanceOf[Allocation => Layout => Layout], req.resAllocBinding.asInstanceOf[LayoutBinding[Layout]]), req)) - val initAlloc = init(using allocation) + val initAlloc = init(using allocation).tap(allocation.reportLayout) val bodyAlloc = steps.foldLeft[Layout](initAlloc): (acc, step) => - step(allocation)(acc) + step._1(allocation)(acc).tap(allocation.reportLayout(_)(using step._2)) onDone(using allocation)(bodyAlloc.asInstanceOf[ResAlloc]) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala index f8624b27..9b9b385d 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionHandler.scala @@ -74,6 +74,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte val externalBindings = getAllBindings(executeSteps).map(VkAllocation.getUnderlying) val deps = externalBindings.flatMap(_.execution.fold(Seq(_), _.toSeq)) val pe = new PendingExecution(commandBuffer, deps, cleanup) + summon[VkAllocation].addExecution(pe) externalBindings.foreach(_.execution = Left(pe)) // TODO we assume all accesses are read-write result diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala index 63cf516f..15e595f1 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala @@ -9,32 +9,50 @@ import org.lwjgl.vulkan.{VK13, VkCommandBuffer, VkCommandBufferSubmitInfo, VkSem import scala.collection.mutable -class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: Seq[PendingExecution], cleanup: () => Unit)(using Device) - extends VulkanObject[VkCommandBuffer]: +class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: Seq[PendingExecution], cleanup: () => Unit)(using Device): private val semaphore: Semaphore = Semaphore() private var fence: Option[Fence] = None - override protected def close(): Unit = cleanup() - - private def setFence(otherFence: Fence): Unit = - if fence.isDefined then return - fence = Some(otherFence) - dependencies.foreach(_.setFence(otherFence)) + def isPending: Boolean = fence.isEmpty + def isRunning: Boolean = fence.exists(!_.isSignaled) + def isFinished: Boolean = fence.exists(_.isSignaled) + + def block(): Unit = fence.foreach(_.block()) + + private var closed = false + def isClosed: Boolean = closed + private def close(): Unit = + assert(!closed, "PendingExecution already closed") + assert(isFinished, "Cannot close a PendingExecution that is not finished") + cleanup() + closed = true + + private var destroyed = false + def destroy(): Unit = + assert(!destroyed, "PendingExecution already destroyed") + assert(isFinished, "Cannot destroy a PendingExecution that is not finished") + if !closed then close() + semaphore.destroy() + fence.foreach(x => if x.isAlive then x.destroy()) + destroyed = true + + private def setFence(f: Fence): Unit = { + if !isPending then return + fence = Some(f) + dependencies.foreach(_.setFence(f)) + } - private def gatherForSubmission: Seq[((VkCommandBuffer, Semaphore), Set[Semaphore])] = { - if fence.isDefined then return Seq.empty + private def gatherForSubmission: Seq[((VkCommandBuffer, Semaphore), Set[Semaphore])] = + if !isPending then return Seq.empty val mySubmission = ((handle, semaphore), dependencies.map(_.semaphore).toSet) dependencies.flatMap(_.gatherForSubmission).appended(mySubmission) - } - - def block(): Unit = - fence match - case Some(f) => f.block() - case None => throw new IllegalStateException("No fence set for this execution") object PendingExecution: def executeAll(executions: Seq[PendingExecution], queue: Queue)(using Device): Fence = pushStack: stack => - val exec = + assert(executions.forall(_.isPending), "All executions must be pending") + assert(executions.nonEmpty, "At least one execution must be provided") + + val exec: Seq[(Set[Semaphore], Set[(VkCommandBuffer, Semaphore)])] = val gathered = executions.flatMap(_.gatherForSubmission) val ordering = gathered.zipWithIndex.map(x => (x._1._1._1, x._2)).toMap gathered.toSet.groupMap(_._2)(_._1).toSeq.sortBy(x => x._2.map(_._1).map(ordering).min) @@ -79,13 +97,13 @@ object PendingExecution: submitInfos.flip() val fence = Fence() - executions.foreach(_.setFence(fence)) check(vkQueueSubmit2(queue.get, submitInfos, fence.get), "Failed to submit command buffer to queue") + executions.foreach(_.setFence(fence)) fence def cleanupAll(executions: Seq[PendingExecution]): Unit = def cleanupRec(ex: PendingExecution): Unit = - if !ex.isAlive then return - ex.destroy() + if !ex.isClosed then return + ex.close() ex.dependencies.foreach(cleanupRec) executions.foreach(cleanupRec) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala index 4ce9692c..0db6a108 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala @@ -29,6 +29,15 @@ import scala.util.chaining.* class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler)(using Allocator, Device) extends Allocation: given VkAllocation = this + override def reportLayout[L <: Layout: LayoutBinding](layout: L): Unit = + val executions = summon[LayoutBinding[L]] + .toBindings(layout) + .map(getUnderlying) + .flatMap(_.execution.fold(Seq(_), _.toSeq)) + .filter(_.isPending) + + PendingExecution.executeAll(executions, commandPool.queue) + extension (buffer: GBinding[?]) def read(bb: ByteBuffer, offset: Int = 0): Unit = val size = bb.remaining() @@ -54,6 +63,7 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) commandPool.freeCommandBuffer(cb) stagingBuffer.destroy() val pe = new PendingExecution(cb, binding.execution.fold(Seq(_), _.toSeq), cleanup) + addExecution(pe) binding.execution = Left(pe) case _ => throw new IllegalArgumentException(s"Tried to write to non-VkBinding $buffer") @@ -89,8 +99,14 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) case _ => ??? direct(bb) + private val executions = mutable.Buffer[PendingExecution]() + + def addExecution(pe: PendingExecution): Unit = + executions += pe + private val bindings = mutable.Buffer[VkUniform[?] | VkBuffer[?]]() private[cyfra] def close(): Unit = + executions.foreach(_.destroy()) bindings.map(getUnderlying).foreach(_.buffer.destroy()) private def getStagingBuffer(size: Int): Buffer.HostBuffer = diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala index f83f39fb..acda99f3 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala @@ -33,12 +33,15 @@ sealed abstract class VkBinding[T <: Value: {Tag, FromExpr}](val buffer: Buffer) */ var execution: Either[PendingExecution, mutable.Buffer[PendingExecution]] = Right(mutable.Buffer.empty) - def materialise(queue: Queue)(using Device): Unit = execution match - case Left(exec) if exec.isAlive => - PendingExecution.executeAll(Seq(exec), queue) - exec.block() - PendingExecution.cleanupAll(Seq(exec)) - case _ => () + def materialise(queue: Queue)(using Device): Unit = + val (pendingExecs, runningExecs) = execution.fold(Seq(_), _.toSeq).partition(_.isPending) // TODO better handle read only executions + if pendingExecs.nonEmpty then + val fence = PendingExecution.executeAll(pendingExecs, queue) + fence.block() + PendingExecution.cleanupAll(pendingExecs) + + runningExecs.foreach(_.block()) + PendingExecution.cleanupAll(runningExecs) object VkBinding: def unapply(binding: GBinding[?]): Option[Buffer] = binding match From 2d75933e926e4a3f11c70bb9aed58986ecf3b546 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Wed, 10 Sep 2025 00:04:59 +0200 Subject: [PATCH 08/11] working intermediete submission^ --- .../io/computenode/cyfra/runtime/PendingExecution.scala | 9 ++++----- .../io/computenode/cyfra/runtime/VkAllocation.scala | 1 + .../io/computenode/cyfra/vulkan/memory/Buffer.scala | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala index 15e595f1..52aea0f2 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala @@ -4,6 +4,7 @@ import io.computenode.cyfra.vulkan.command.{CommandPool, Fence, Semaphore} import io.computenode.cyfra.vulkan.core.{Device, Queue} import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import io.computenode.cyfra.vulkan.util.VulkanObject +import org.lwjgl.vulkan.VK10.VK_TRUE import org.lwjgl.vulkan.VK13.{VK_PIPELINE_STAGE_2_COPY_BIT, vkQueueSubmit2} import org.lwjgl.vulkan.{VK13, VkCommandBuffer, VkCommandBufferSubmitInfo, VkSemaphoreSubmitInfo, VkSubmitInfo2} @@ -22,16 +23,14 @@ class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: private var closed = false def isClosed: Boolean = closed private def close(): Unit = - assert(!closed, "PendingExecution already closed") - assert(isFinished, "Cannot close a PendingExecution that is not finished") + if closed then return cleanup() closed = true private var destroyed = false def destroy(): Unit = - assert(!destroyed, "PendingExecution already destroyed") - assert(isFinished, "Cannot destroy a PendingExecution that is not finished") - if !closed then close() + if destroyed then return + close() semaphore.destroy() fence.foreach(x => if x.isAlive then x.destroy()) destroyed = true diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala index 0db6a108..a799f98c 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala @@ -48,6 +48,7 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) val stagingBuffer = getStagingBuffer(size) Buffer.copyBuffer(binding.buffer, stagingBuffer, offset, 0, size, commandPool) stagingBuffer.copyTo(bb, 0) + stagingBuffer.destroy() case _ => throw new IllegalArgumentException(s"Tried to read from non-VkBinding $buffer") def write(bb: ByteBuffer, offset: Int = 0): Unit = diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala index 1f677f04..c1f34b40 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Buffer.scala @@ -74,7 +74,7 @@ object Buffer: val fence = Fence() check(vkQueueSubmit(commandPool.queue.get, submitInfo, fence.get), "Failed to submit single time command buffer") - fence.block() + fence.block().destroy() def copyBufferCommandBuffer(src: Buffer, dst: Buffer, srcOffset: Int, dstOffset: Int, bytes: Int, commandPool: CommandPool): VkCommandBuffer = commandPool.recordSingleTimeCommand: commandBuffer => From 92d021907fc3b0d7bb7911154d5f3226a146fcaf Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Wed, 10 Sep 2025 11:43:13 +0200 Subject: [PATCH 09/11] fix --- .../main/scala/io/computenode/cyfra/core/Allocation.scala | 2 +- .../scala/io/computenode/cyfra/samples/TestingStuff.scala | 8 ++++---- .../scala/io/computenode/cyfra/runtime/VkBinding.scala | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala index d279bb88..908c452e 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala @@ -10,7 +10,7 @@ import izumi.reflect.Tag import java.nio.ByteBuffer trait Allocation: - def reportLayout[L <: Layout: LayoutBinding](layout: L): Unit + def reportLayout[L <: Layout: LayoutBinding](layout: L): Unit extension (buffer: GBinding[?]) def read(bb: ByteBuffer, offset: Int = 0): Unit diff --git a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala index c45680ea..2f1b2799 100644 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala +++ b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala @@ -16,11 +16,11 @@ import java.nio.ByteBuffer import java.util.concurrent.atomic.AtomicInteger import scala.collection.parallel.CollectionConverters.given -def printBuffer(bb: ByteBuffer): Unit = { +def printBuffer(bb: ByteBuffer): Unit = val l = bb.asIntBuffer() - val s = (0 until l.remaining()).map(l.get).toList - println(s.mkString(" ")) -} + val a = new Array[Int](l.remaining()) + l.get(a) + println(a.mkString(" ")) object TestingStuff: diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala index acda99f3..6283ad78 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBinding.scala @@ -39,7 +39,7 @@ sealed abstract class VkBinding[T <: Value: {Tag, FromExpr}](val buffer: Buffer) val fence = PendingExecution.executeAll(pendingExecs, queue) fence.block() PendingExecution.cleanupAll(pendingExecs) - + runningExecs.foreach(_.block()) PendingExecution.cleanupAll(runningExecs) From cdb7900c2fbb0a8ffbe2f0b7e4ba0eecb5772534 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Thu, 11 Sep 2025 13:16:45 +0200 Subject: [PATCH 10/11] fixes^ --- .../computenode/cyfra/core/Allocation.scala | 2 +- .../cyfra/core/GBufferRegion.scala | 8 ++--- .../cyfra/runtime/PendingExecution.scala | 32 ++++++++++++------- .../cyfra/runtime/VkAllocation.scala | 2 +- 4 files changed, 27 insertions(+), 17 deletions(-) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala index 908c452e..493b6a6e 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/Allocation.scala @@ -10,7 +10,7 @@ import izumi.reflect.Tag import java.nio.ByteBuffer trait Allocation: - def reportLayout[L <: Layout: LayoutBinding](layout: L): Unit + def submitLayout[L <: Layout: LayoutBinding](layout: L): Unit extension (buffer: GBinding[?]) def read(bb: ByteBuffer, offset: Int = 0): Unit diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala index 65ad0b41..b80bc679 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala @@ -36,12 +36,12 @@ object GBufferRegion: // noinspection ScalaRedundantCast val steps: Seq[(Allocation => Layout => Layout, LayoutBinding[Layout])] = Seq.unfold(region: GBufferRegion[?, ?]): - case _: AllocRegion[?] => None - case m @ MapRegion(req, f) => + case AllocRegion => None + case MapRegion(req, f) => Some(((f.asInstanceOf[Allocation => Layout => Layout], req.resAllocBinding.asInstanceOf[LayoutBinding[Layout]]), req)) - val initAlloc = init(using allocation).tap(allocation.reportLayout) + val initAlloc = init(using allocation).tap(allocation.submitLayout) val bodyAlloc = steps.foldLeft[Layout](initAlloc): (acc, step) => - step._1(allocation)(acc).tap(allocation.reportLayout(_)(using step._2)) + step._1(allocation)(acc).tap(allocation.submitLayout(_)(using step._2)) onDone(using allocation)(bodyAlloc.asInstanceOf[ResAlloc]) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala index 52aea0f2..1fe09725 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala @@ -10,6 +10,12 @@ import org.lwjgl.vulkan.{VK13, VkCommandBuffer, VkCommandBufferSubmitInfo, VkSem import scala.collection.mutable +/** A command buffer that is pending execution, along with its dependencies and cleanup actions. + * + * You can call `close()` only when `isFinished || isPending` is true + * + * You can call `destroy()` only when all dependants are `isClosed` + */ class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: Seq[PendingExecution], cleanup: () => Unit)(using Device): private val semaphore: Semaphore = Semaphore() private var fence: Option[Fence] = None @@ -23,6 +29,7 @@ class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: private var closed = false def isClosed: Boolean = closed private def close(): Unit = + assert(isFinished || isPending, "Cannot close a PendingExecution that is not finished or pending") if closed then return cleanup() closed = true @@ -35,24 +42,29 @@ class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: fence.foreach(x => if x.isAlive then x.destroy()) destroyed = true - private def setFence(f: Fence): Unit = { - if !isPending then return - fence = Some(f) - dependencies.foreach(_.setFence(f)) - } - - private def gatherForSubmission: Seq[((VkCommandBuffer, Semaphore), Set[Semaphore])] = + /** Gathers all command buffers and their semaphores for submission to the queue, in the correct order. + * + * When you call this method, you are expected to submit the command buffers to the queue, and signal the provided fence when done. + * @param f + * The fence to signal when the command buffers are done executing. + * @return + * A sequence of tuples, each containing a command buffer, semaphore to signal, and a set of semaphores to wait on. + */ + private def gatherForSubmission(f: Fence): Seq[((VkCommandBuffer, Semaphore), Set[Semaphore])] = if !isPending then return Seq.empty val mySubmission = ((handle, semaphore), dependencies.map(_.semaphore).toSet) - dependencies.flatMap(_.gatherForSubmission).appended(mySubmission) + fence = Some(f) + dependencies.flatMap(_.gatherForSubmission(f)).appended(mySubmission) object PendingExecution: def executeAll(executions: Seq[PendingExecution], queue: Queue)(using Device): Fence = pushStack: stack => assert(executions.forall(_.isPending), "All executions must be pending") assert(executions.nonEmpty, "At least one execution must be provided") + val fence = Fence() + val exec: Seq[(Set[Semaphore], Set[(VkCommandBuffer, Semaphore)])] = - val gathered = executions.flatMap(_.gatherForSubmission) + val gathered = executions.flatMap(_.gatherForSubmission(fence)) val ordering = gathered.zipWithIndex.map(x => (x._1._1._1, x._2)).toMap gathered.toSet.groupMap(_._2)(_._1).toSeq.sortBy(x => x._2.map(_._1).map(ordering).min) @@ -95,9 +107,7 @@ object PendingExecution: submitInfos.flip() - val fence = Fence() check(vkQueueSubmit2(queue.get, submitInfos, fence.get), "Failed to submit command buffer to queue") - executions.foreach(_.setFence(fence)) fence def cleanupAll(executions: Seq[PendingExecution]): Unit = diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala index a799f98c..ea80f7c6 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkAllocation.scala @@ -29,7 +29,7 @@ import scala.util.chaining.* class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler)(using Allocator, Device) extends Allocation: given VkAllocation = this - override def reportLayout[L <: Layout: LayoutBinding](layout: L): Unit = + override def submitLayout[L <: Layout: LayoutBinding](layout: L): Unit = val executions = summon[LayoutBinding[L]] .toBindings(layout) .map(getUnderlying) From 409e75deb4213cd885b1a72ebc818bdfbb332b79 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Thu, 11 Sep 2025 22:09:26 +0200 Subject: [PATCH 11/11] fix^ --- .../main/scala/io/computenode/cyfra/core/GBufferRegion.scala | 2 +- .../scala/io/computenode/cyfra/runtime/PendingExecution.scala | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala index b80bc679..cfc041cf 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GBufferRegion.scala @@ -36,7 +36,7 @@ object GBufferRegion: // noinspection ScalaRedundantCast val steps: Seq[(Allocation => Layout => Layout, LayoutBinding[Layout])] = Seq.unfold(region: GBufferRegion[?, ?]): - case AllocRegion => None + case AllocRegion() => None case MapRegion(req, f) => Some(((f.asInstanceOf[Allocation => Layout => Layout], req.resAllocBinding.asInstanceOf[LayoutBinding[Layout]]), req)) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala index 1fe09725..9ed42d7d 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/PendingExecution.scala @@ -21,8 +21,8 @@ class PendingExecution(protected val handle: VkCommandBuffer, val dependencies: private var fence: Option[Fence] = None def isPending: Boolean = fence.isEmpty - def isRunning: Boolean = fence.exists(!_.isSignaled) - def isFinished: Boolean = fence.exists(_.isSignaled) + def isRunning: Boolean = fence.exists(f => f.isAlive && !f.isSignaled) + def isFinished: Boolean = fence.exists(f => !f.isAlive || f.isSignaled) def block(): Unit = fence.foreach(_.block())