From 7a3b527717027db1aa8c31d250358979a893023a Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Tue, 8 Jul 2025 14:16:13 +0200 Subject: [PATCH 01/20] completed buffers^ --- .../computenode/cyfra/core/Allocation.scala | 7 +- .../computenode/cyfra/core/CyfraRuntime.scala | 2 +- .../cyfra/core/GBufferRegion.scala | 20 +++--- .../cyfra/samples/TestingStuff.scala | 4 +- .../cyfra/runtime/VkAllocation.scala | 70 ++++++++++++++++--- .../computenode/cyfra/runtime/VkBuffer.scala | 23 ++++++ .../cyfra/runtime/VkCyfraRuntime.scala | 11 ++- .../computenode/cyfra/runtime/VkUniform.scala | 22 ++++++ .../vulkan/executor/SequenceExecutor.scala | 8 +-- .../cyfra/vulkan/memory/Buffer.scala | 16 ++--- 10 files changed, 143 insertions(+), 40 deletions(-) create mode 100644 cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBuffer.scala create mode 100644 cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala 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 194b3a2f..613d532e 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 @@ -11,11 +11,12 @@ import java.nio.ByteBuffer trait Allocation: extension (buffer: GBinding[?]) - def read(bb: ByteBuffer, offset: Int = 0, length: Int = -1): Unit + def read(bb: ByteBuffer, offset: Int = 0, size: Int = -1): Unit - def write(bb: ByteBuffer, offset: Int = 0, length: Int = -1): Unit + def write(bb: ByteBuffer, offset: Int = 0, size: Int = -1): Unit - extension [Params, L <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, L, RL]) def execute(params: Params, layout: L): RL + extension [Params, L <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, L, RL]) + def execute(params: Params, layout: L): RL extension (buffers: GBuffer.type) def apply[T <: Value: {Tag, FromExpr}](size: Int): GBuffer[T] diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/CyfraRuntime.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/CyfraRuntime.scala index a7c029e4..69c9cfa3 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/CyfraRuntime.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/CyfraRuntime.scala @@ -4,4 +4,4 @@ import io.computenode.cyfra.core.Allocation trait CyfraRuntime: - def allocation(): Allocation + def withAllocation(f: Allocation => Unit): 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 2aec9159..472a61e4 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 @@ -32,16 +32,16 @@ object GBufferRegion: MapRegion(region, (alloc: Allocation) => (resAlloc: ResAlloc) => f(using alloc)(resAlloc)) def runUnsafe(init: Allocation ?=> ReqAlloc, onDone: Allocation ?=> ResAlloc => Unit)(using cyfraRuntime: CyfraRuntime): Unit = - val allocation = cyfraRuntime.allocation() - init(using allocation) + cyfraRuntime.withAllocation: allocation => + init(using 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)) + // 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 bodyAlloc = steps.foldLeft[Layout](region.initAlloc): (acc, step) => - step(allocation)(acc) + val bodyAlloc = steps.foldLeft[Layout](region.initAlloc): (acc, step) => + step(allocation)(acc) - onDone(using allocation)(bodyAlloc.asInstanceOf[ResAlloc]) + onDone(using allocation)(bodyAlloc.asInstanceOf[ResAlloc]) 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 d94c895b..c328bcd4 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 @@ -1,7 +1,7 @@ package io.computenode.cyfra.samples import io.computenode.cyfra.core.archive.GContext -import io.computenode.cyfra.core.{GBufferRegion, GExecution, GProgram} +import io.computenode.cyfra.core.{CyfraRuntime, GBufferRegion, GExecution, GProgram} import io.computenode.cyfra.core.layout.* import io.computenode.cyfra.dsl.Value.{GBoolean, Int32} import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} @@ -85,7 +85,7 @@ object TestingStuff: @main def test = - given VkCyfraRuntime = VkCyfraRuntime() + given CyfraRuntime = VkCyfraRuntime() val emitFilterParams = EmitFilterParams(inSize = 1024, emitN = 2, filterValue = 42) 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 d1474976..4f8e93a6 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 @@ -5,25 +5,77 @@ import io.computenode.cyfra.core.{Allocation, GExecution} 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.dsl.struct.GStruct +import io.computenode.cyfra.spirv.SpirvTypes.typeStride +import io.computenode.cyfra.vulkan.command.CommandPool +import io.computenode.cyfra.vulkan.memory.{Allocator, Buffer} import izumi.reflect.Tag +import org.lwjgl.vulkan.VK10 +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 extends Allocation: +class VkAllocation(commandPool: CommandPool)(using Allocator) extends Allocation: extension (buffer: GBinding[?]) - def read(bb: ByteBuffer, offset: Int = 0, length: Int = -1): Unit = ??? + def read(bb: ByteBuffer, offset: Int = 0, size: Int = -1): Unit = + val buf = getUnderlying(buffer) + val s = if size < 0 then buf.size - offset else size - def write(bb: ByteBuffer, offset: Int = 0, length: Int = -1): Unit = ??? + buf match + case buffer: Buffer.HostBuffer => Buffer.copyBuffer(buffer, bb, offset, 0, s) + case buffer: Buffer.DeviceBuffer => + val stagingBuffer = getStagingBuffer(s) + Buffer.copyBuffer(buffer, stagingBuffer, offset, 0, s, commandPool).block().destroy() + Buffer.copyBuffer(stagingBuffer, bb, 0, 0, s) - extension [Params, L <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, L, RL]) def execute(params: Params, layout: L): RL = ??? + def write(bb: ByteBuffer, offset: Int = 0, size: Int = -1): Unit = + val buf = getUnderlying(buffer) + val s = if size < 0 then bb.remaining() else size + + buf match + case buffer: Buffer.HostBuffer => Buffer.copyBuffer(bb, buffer, offset, 0, s) + case buffer: Buffer.DeviceBuffer => + val stagingBuffer = getStagingBuffer(s) + Buffer.copyBuffer(bb, stagingBuffer, 0, 0, s) + Buffer.copyBuffer(stagingBuffer, buffer, 0, offset, s, commandPool).block().destroy() extension (buffers: GBuffer.type) - def apply[T <: Value: {Tag, FromExpr}](size: Int): GBuffer[T] = ??? + def apply[T <: Value: {Tag, FromExpr}](length: Int): GBuffer[T] = + VkBuffer[T](length).tap(bindings += _) - def apply[T <: Value: {Tag, FromExpr}](buff: ByteBuffer): GBuffer[T] = ??? + def apply[T <: Value: {Tag, FromExpr}](buff: ByteBuffer): GBuffer[T] = + val sizeOfT = typeStride(summon[Tag[T]]) + val length = buff.remaining() / sizeOfT + if buff.remaining() % sizeOfT != 0 then ??? + GBuffer[T](length).tap(_.write(buff)) extension (buffers: GUniform.type) - def apply[T <: Value: {Tag, FromExpr}](buff: ByteBuffer): GUniform[T] = ??? + def apply[T <: Value: {Tag, FromExpr}](buff: ByteBuffer): GUniform[T] = + GUniform[T]().tap(_.write(buff)) + + def apply[T <: Value: {Tag, FromExpr}](): GUniform[T] = + VkUniform[T]().tap(bindings += _) + + extension [Params, L <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, L, RL]) + override def execute(params: Params, layout: L): RL = + ??? + + private def getUnderlying(buffer: GBinding[?]): Buffer = + buffer match + case buffer: VkBuffer[?] => buffer.underlying + case uniform: VkUniform[?] => uniform.underlying + case _ => ??? + + private val bindings = mutable.Buffer[VkUniform[?] | VkBuffer[?]]() + private[cyfra] def close(): Unit = bindings.map(getUnderlying).foreach(_.destroy()) - def apply[T <: Value: {Tag, FromExpr}](): GUniform[T] = ??? + 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 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 new file mode 100644 index 00000000..cda73868 --- /dev/null +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkBuffer.scala @@ -0,0 +1,23 @@ +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/VkCyfraRuntime.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala index 87dbd5ab..cc664698 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala @@ -1,8 +1,13 @@ package io.computenode.cyfra.runtime -import io.computenode.cyfra.core.Allocation import io.computenode.cyfra.core.{Allocation, CyfraRuntime} +import io.computenode.cyfra.vulkan.VulkanContext class VkCyfraRuntime extends CyfraRuntime: - override def allocation(): Allocation = - new VkAllocation() + private val context = new VulkanContext() + import context.given + + override def withAllocation(f: Allocation => Unit): Unit = + val allocation = new VkAllocation(context.commandPool) + f(allocation) + allocation.close() 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 new file mode 100644 index 00000000..ec226952 --- /dev/null +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala @@ -0,0 +1,22 @@ +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.spirv.SpirvTypes.typeStride +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 = typeStride(summon[Tag[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-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/SequenceExecutor.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/SequenceExecutor.scala index 63c61bc2..f367ab5b 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/SequenceExecutor.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/SequenceExecutor.scala @@ -160,8 +160,8 @@ private[cyfra] class SequenceExecutor(computeSequence: ComputationSequence, cont new Buffer.HostBuffer(inputs.map(_.remaining()).max, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT) buffersWithAction(BufferAction.LoadTo).zipWithIndex.foreach { case (buffer, i) => - Buffer.copyBuffer(inputs(i), stagingBuffer, buffer.size) - Buffer.copyBuffer(stagingBuffer, buffer, buffer.size, commandPool).block().destroy() + Buffer.copyBuffer(inputs(i), stagingBuffer, buffer.size, 0, 0) + Buffer.copyBuffer(stagingBuffer, buffer, buffer.size, 0, 0, commandPool).block().destroy() } val fence = new Fence() @@ -177,9 +177,9 @@ private[cyfra] class SequenceExecutor(computeSequence: ComputationSequence, cont fence.block().destroy() val output = buffersWithAction(BufferAction.LoadFrom).map { buffer => - Buffer.copyBuffer(buffer, stagingBuffer, buffer.size, commandPool).block().destroy() + Buffer.copyBuffer(buffer, stagingBuffer, 0, 0, buffer.size, commandPool).block().destroy() val out = BufferUtils.createByteBuffer(buffer.size) - Buffer.copyBuffer(stagingBuffer, out, buffer.size) + Buffer.copyBuffer(stagingBuffer, out, 0, 0, buffer.size) out } 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 60ecb48a..a485653b 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 @@ -15,7 +15,7 @@ import java.nio.ByteBuffer * MarconZet Created 11.05.2019 */ -private[cyfra] sealed class Buffer private (val size: Int, usage: Int, flags: Int)(using allocator: Allocator) extends VulkanObjectHandle: +private[cyfra] sealed abstract class Buffer private (val size: Int, usage: Int, flags: Int)(using allocator: Allocator) extends VulkanObjectHandle: val (handle, allocation) = pushStack: stack => val bufferInfo = VkBufferCreateInfo .calloc(stack) @@ -58,20 +58,20 @@ object Buffer: if flush then vmaFlushAllocation(this.allocator.get, this.allocation, 0, size) vmaUnmapMemory(this.allocator.get, this.allocation) - def copyBuffer(src: ByteBuffer, dst: HostBuffer, bytes: Long): Unit = + def copyBuffer(src: ByteBuffer, dst: HostBuffer, srcOffset: Int , dstOffset: Int , bytes: Int ): Unit = dst.mapped: destination => - memCopy(memAddress(src), memAddress(destination), bytes) + memCopy(memAddress(src) + srcOffset, memAddress(destination) + dstOffset, bytes) - def copyBuffer(src: HostBuffer, dst: ByteBuffer, bytes: Long): Unit = + def copyBuffer(src: HostBuffer, dst: ByteBuffer, srcOffset: Int , dstOffset: Int , bytes: Int ): Unit = src.mappedNoFlush: source => - memCopy(memAddress(source), memAddress(dst), bytes) + memCopy(memAddress(source) + srcOffset, memAddress(dst) + dstOffset, bytes) - def copyBuffer(src: Buffer, dst: Buffer, bytes: Long, commandPool: CommandPool): Fence = + def copyBuffer(src: Buffer, dst: Buffer, srcOffset: Int, dstOffset: Int, bytes: Int, commandPool: CommandPool): Fence = commandPool.executeCommand: commandBuffer => pushStack: stack => val copyRegion = VkBufferCopy .calloc(1, stack) - .srcOffset(0) - .dstOffset(0) + .srcOffset(srcOffset) + .dstOffset(dstOffset) .size(bytes) vkCmdCopyBuffer(commandBuffer, src.get, dst.get, copyRegion) From 20b5bf02be2b35c29807da8b80018de3d387e418 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Wed, 9 Jul 2025 15:46:14 +0200 Subject: [PATCH 02/20] shader loadin^g --- .../io/computenode/cyfra/core/GProgram.scala | 24 ++--- .../computenode/cyfra/core/SpirvProgram.scala | 60 ++++++++++++ .../cyfra/runtime/ExecutionRunner.scala | 5 + .../cyfra/runtime/VkAllocation.scala | 8 +- .../cyfra/runtime/VkCyfraRuntime.scala | 11 ++- .../computenode/cyfra/runtime/VkShader.scala | 31 ++++++ .../vulkan/compute/ComputePipeline.scala | 51 +++++----- .../cyfra/vulkan/compute/LayoutInfo.scala | 12 --- .../cyfra/vulkan/executor/BufferAction.scala | 17 ---- .../vulkan/executor/SequenceExecutor.scala | 96 ++++--------------- .../cyfra/vulkan/memory/DescriptorSet.scala | 17 ++-- .../cyfra/vulkan/SequenceExecutorTest.scala | 7 +- 12 files changed, 181 insertions(+), 158 deletions(-) create mode 100644 cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala create mode 100644 cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionRunner.scala create mode 100644 cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala delete mode 100644 cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/LayoutInfo.scala delete mode 100644 cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/BufferAction.scala diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala index 77586f5e..6e82ae15 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala @@ -13,28 +13,22 @@ import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.struct.GStruct.Empty import izumi.reflect.Tag -sealed trait GProgram[Params, L <: Layout: LayoutStruct] extends GExecution[Params, L, L]: +trait GProgram[Params, L <: Layout: LayoutStruct] extends GExecution[Params, L, L]: val layout: InitProgramLayout => Params => L val dispatch: (L, Params) => ProgramDispatch val workgroupSize: WorkDimensions + private[cyfra] def layoutStruct: LayoutStruct[L] = summon[LayoutStruct[L]] + private[cyfra] def cacheKey: String object GProgram: - class GioProgram[Params, L <: Layout: LayoutStruct]( - val body: L => GIO[?], - val layout: InitProgramLayout => Params => L, - val dispatch: (L, Params) => ProgramDispatch, - val workgroupSize: WorkDimensions, - ) extends GProgram[Params, L]: - private[cyfra] def layoutStruct: LayoutStruct[L] = summon[LayoutStruct[L]] - - class SpirvProgram[Params, L <: Layout: LayoutStruct]( - val code: ByteBuffer, - val layout: InitProgramLayout => Params => L, - val dispatch: (L, Params) => ProgramDispatch, - val workgroupSize: WorkDimensions, + case class GioProgram[Params, L <: Layout: LayoutStruct]( + body: L => GIO[?], + layout: InitProgramLayout => Params => L, + dispatch: (L, Params) => ProgramDispatch, + workgroupSize: WorkDimensions, ) extends GProgram[Params, L]: - private[cyfra] def layoutStruct: LayoutStruct[L] = summon[LayoutStruct[L]] + private[cyfra] def cacheKey: String = toString type WorkDimensions = (Int, Int, Int) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala new file mode 100644 index 00000000..cd55b97f --- /dev/null +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala @@ -0,0 +1,60 @@ +package io.computenode.cyfra.core + +import io.computenode.cyfra.core.layout.Layout +import io.computenode.cyfra.core.layout.LayoutStruct +import io.computenode.cyfra.core.GProgram.{GioProgram, InitProgramLayout, ProgramDispatch, WorkDimensions} +import io.computenode.cyfra.core.SpirvProgram.Operation.ReadWrite +import io.computenode.cyfra.core.SpirvProgram.{Binding, ShaderLayout} +import io.computenode.cyfra.dsl.Value +import io.computenode.cyfra.dsl.Value.FromExpr +import io.computenode.cyfra.dsl.binding.GBinding +import izumi.reflect.Tag + +import java.io.File +import java.io.FileInputStream +import java.nio.ByteBuffer +import java.nio.channels.FileChannel +import java.util.Objects +import scala.util.Try +import scala.util.Using +import scala.util.chaining.* + +case class SpirvProgram[Params, L <: Layout: LayoutStruct]( + layout: InitProgramLayout => Params => L, + dispatch: (L, Params) => ProgramDispatch, + workgroupSize: WorkDimensions, + code: ByteBuffer, + entryPoint: String, + shaderBindings: L => ShaderLayout, +) extends GProgram[Params, L]: + private[cyfra] def cacheKey: String = toString + +object SpirvProgram: + type ShaderLayout = Seq[Seq[Binding]] + case class Binding(binding: GBinding[?], operation: Operation) + enum Operation: + case Read + case Write + case ReadWrite + + def apply[Params, L <: Layout: LayoutStruct]( + path: String, + layout: InitProgramLayout => Params => L, + dispatch: (L, Params) => ProgramDispatch, + ): SpirvProgram[Params, L] = + val code = loadShader(path).get + val workgroupSize = (128, 1, 1) // TODO Extract form shader + val main = "main" + val f: L => ShaderLayout = { case layout: Product => + layout.productIterator.zipWithIndex.map { case (binding: GBinding[?], i) => Binding(binding, ReadWrite) }.toSeq.pipe(Seq(_)) + } + new SpirvProgram[Params, L](layout, dispatch, workgroupSize, code, main, f) + + private def loadShader(path: String, classLoader: ClassLoader = getClass.getClassLoader): Try[ByteBuffer] = + Using.Manager: use => + val file = new File(Objects.requireNonNull(classLoader.getResource(path)).getFile) + val fis = use(new FileInputStream(file)) + val fc = use(fis.getChannel) + fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()) + + def compile[Params, L <: Layout: LayoutStruct](program: GioProgram[Params, L]): SpirvProgram[Params, L] = ??? diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionRunner.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionRunner.scala new file mode 100644 index 00000000..81e605cb --- /dev/null +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionRunner.scala @@ -0,0 +1,5 @@ +package io.computenode.cyfra.runtime + +import io.computenode.cyfra.vulkan.core.Device + +class ExecutionRunner(using device: Device) 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 4f8e93a6..51e3d6de 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 @@ -1,7 +1,8 @@ package io.computenode.cyfra.runtime import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} -import io.computenode.cyfra.core.{Allocation, GExecution} +import io.computenode.cyfra.core.{Allocation, GExecution, GProgram} +import io.computenode.cyfra.core.SpirvProgram import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} @@ -58,9 +59,8 @@ class VkAllocation(commandPool: CommandPool)(using Allocator) extends Allocation VkUniform[T]().tap(bindings += _) extension [Params, L <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, L, RL]) - override def execute(params: Params, layout: L): RL = - ??? - + override def execute(params: Params, layout: L): RL = ??? + private def getUnderlying(buffer: GBinding[?]): Buffer = buffer match case buffer: VkBuffer[?] => buffer.underlying diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala index cc664698..a554263c 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala @@ -1,12 +1,21 @@ package io.computenode.cyfra.runtime -import io.computenode.cyfra.core.{Allocation, CyfraRuntime} +import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} +import io.computenode.cyfra.core.{Allocation, CyfraRuntime, GExecution, GProgram, SpirvProgram} import io.computenode.cyfra.vulkan.VulkanContext +import io.computenode.cyfra.vulkan.compute.ComputePipeline + +import scala.collection.mutable class VkCyfraRuntime extends CyfraRuntime: private val context = new VulkanContext() import context.given + private val shaderCache = mutable.Map.empty[String, ComputePipeline] + + private[cyfra] def getOrLoadProgram[Params, L <: Layout: LayoutStruct](program: GProgram[Params, L]): ComputePipeline = + shaderCache.getOrElseUpdate(program.cacheKey, VkShader(program)) + override def withAllocation(f: Allocation => Unit): Unit = val allocation = new VkAllocation(context.commandPool) f(allocation) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala new file mode 100644 index 00000000..3ace5fb0 --- /dev/null +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala @@ -0,0 +1,31 @@ +package io.computenode.cyfra.runtime + +import io.computenode.cyfra.core.SpirvProgram +import io.computenode.cyfra.core.SpirvProgram.* +import io.computenode.cyfra.core.GProgram +import io.computenode.cyfra.core.GProgram.GioProgram +import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} +import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} +import io.computenode.cyfra.vulkan.compute.ComputePipeline +import io.computenode.cyfra.vulkan.compute.ComputePipeline.* +import io.computenode.cyfra.vulkan.core.Device + +object VkShader: + def apply[P, L <: Layout: LayoutStruct](program: GProgram[P, L])(using Device): ComputePipeline = + val SpirvProgram(layout, dispatch, _workgroupSize, code, entryPoint, shaderBindings) = program match + case p: GioProgram[?, ?] => SpirvProgram.compile(p) + case p: SpirvProgram[?, ?] => p + case _ => throw new IllegalArgumentException(s"Unsupported program type: ${program.getClass.getName}") + + val shaderLayout = shaderBindings(summon[LayoutStruct[L]].layoutRef) + val sets = shaderLayout.map: set => + val descriptors = set.map { case Binding(binding, op) => + val kind = binding match + case buffer: GBuffer[?] => BindingType.StorageBuffer + case uniform: GUniform[?] => BindingType.Uniform + case _ => ??? + DescriptorInfo(kind) + } + DescriptorSetInfo(descriptors) + + ComputePipeline(code, entryPoint, LayoutInfo(sets)) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala index c63d2210..f5cb089f 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala @@ -4,6 +4,7 @@ import io.computenode.cyfra.vulkan.VulkanContext import io.computenode.cyfra.vulkan.core.Device import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import io.computenode.cyfra.vulkan.util.VulkanObjectHandle +import io.computenode.cyfra.vulkan.compute.ComputePipeline.* import org.lwjgl.vulkan.* import org.lwjgl.vulkan.VK10.* @@ -16,7 +17,7 @@ import scala.util.{Try, Using} /** @author * MarconZet Created 14.04.2020 */ -private[cyfra] class ComputePipeline(shaderCode: ByteBuffer, functionName: String, val layoutInfo: LayoutInfo)(using device: Device) +private[cyfra] class ComputePipeline(shaderCode: ByteBuffer, functionName: String, layoutInfo: LayoutInfo)(using device: Device) extends VulkanObjectHandle: private val shader: Long = pushStack: stack => // TODO khr_maintenance5 @@ -31,9 +32,9 @@ private[cyfra] class ComputePipeline(shaderCode: ByteBuffer, functionName: Strin check(vkCreateShaderModule(device.get, shaderModuleCreateInfo, null, pShaderModule), "Failed to create shader module") pShaderModule.get() - val descriptorSetLayouts: Seq[(Long, LayoutSet)] = layoutInfo.sets.map(x => (createDescriptorSetLayout(x), x)) + val pipelineLayout: PipelineLayout = pushStack: stack => + val descriptorSetLayouts: Seq[DescriptorSetLayout] = layoutInfo.sets.map(x => DescriptorSetLayout(createDescriptorSetLayout(x), x)) - val pipelineLayout: Long = pushStack: stack => val pipelineLayoutCreateInfo = VkPipelineLayoutCreateInfo .calloc(stack) .sType$Default() @@ -44,7 +45,8 @@ private[cyfra] class ComputePipeline(shaderCode: ByteBuffer, functionName: Strin val pPipelineLayout = stack.callocLong(1) check(vkCreatePipelineLayout(device.get, pipelineLayoutCreateInfo, null, pPipelineLayout), "Failed to create pipeline layout") - pPipelineLayout.get(0) + val layout = pPipelineLayout.get(0) + PipelineLayout(layout, descriptorSetLayouts) protected val handle: Long = pushStack: stack => val pipelineShaderStageCreateInfo = VkPipelineShaderStageCreateInfo @@ -63,7 +65,7 @@ private[cyfra] class ComputePipeline(shaderCode: ByteBuffer, functionName: Strin .pNext(0) .flags(0) .stage(pipelineShaderStageCreateInfo) - .layout(pipelineLayout) + .layout(pipelineLayout.id) .basePipelineHandle(0) .basePipelineIndex(0) @@ -73,19 +75,19 @@ private[cyfra] class ComputePipeline(shaderCode: ByteBuffer, functionName: Strin protected def close(): Unit = vkDestroyPipeline(device.get, handle, null) - vkDestroyPipelineLayout(device.get, pipelineLayout, null) - descriptorSetLayouts.map(_._1).foreach(vkDestroyDescriptorSetLayout(device.get, _, null)) + vkDestroyPipelineLayout(device.get, pipelineLayout.id, null) + pipelineLayout.sets.map(_.id).foreach(vkDestroyDescriptorSetLayout(device.get, _, null)) vkDestroyShaderModule(device.get, handle, null) - private def createDescriptorSetLayout(set: LayoutSet): Long = pushStack: stack => - val descriptorSetLayoutBindings = VkDescriptorSetLayoutBinding.calloc(set.bindings.length, stack) - set.bindings.foreach: binding => + private def createDescriptorSetLayout(set: DescriptorSetInfo): Long = pushStack: stack => + val descriptorSetLayoutBindings = VkDescriptorSetLayoutBinding.calloc(set.descriptors.length, stack) + set.descriptors.zipWithIndex.foreach: binding => descriptorSetLayoutBindings .get() - .binding(binding.id) - .descriptorType(binding.size match - case InputBufferSize(_) => VK_DESCRIPTOR_TYPE_STORAGE_BUFFER - case UniformSize(_) => VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) + .binding(binding._2) + .descriptorType(binding._1.kind match + case BindingType.StorageBuffer => VK_DESCRIPTOR_TYPE_STORAGE_BUFFER + case BindingType.Uniform => VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) .descriptorCount(1) .stageFlags(VK_SHADER_STAGE_COMPUTE_BIT) .pImmutableSamplers(null) @@ -104,12 +106,15 @@ private[cyfra] class ComputePipeline(shaderCode: ByteBuffer, functionName: Strin pDescriptorSetLayout.get(0) object ComputePipeline: - def loadShader(path: String): Try[ByteBuffer] = - loadShader(path, getClass.getClassLoader) - - private def loadShader(path: String, classLoader: ClassLoader): Try[ByteBuffer] = - Using.Manager: use => - val file = new File(Objects.requireNonNull(classLoader.getResource(path)).getFile) - val fis = use(new FileInputStream(file)) - val fc = use(fis.getChannel) - fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()) + def loadShader(path: String): Try[ByteBuffer] = ??? + + private[cyfra] case class PipelineLayout(id: Long, sets: Seq[DescriptorSetLayout]) + private[cyfra] case class DescriptorSetLayout(id: Long, set: DescriptorSetInfo) + + private[cyfra] case class LayoutInfo(sets: Seq[DescriptorSetInfo]) + private[cyfra] case class DescriptorSetInfo(descriptors: Seq[DescriptorInfo]) + private[cyfra] case class DescriptorInfo(kind: BindingType) + + private[cyfra] enum BindingType: + case StorageBuffer + case Uniform \ No newline at end of file diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/LayoutInfo.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/LayoutInfo.scala deleted file mode 100644 index 1edbcea8..00000000 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/LayoutInfo.scala +++ /dev/null @@ -1,12 +0,0 @@ -package io.computenode.cyfra.vulkan.compute - -/** @author - * MarconZet Created 25.04.2020 - */ -private[cyfra] case class LayoutInfo(sets: Seq[LayoutSet]) -private[cyfra] case class LayoutSet(id: Int, bindings: Seq[Binding]) -private[cyfra] case class Binding(id: Int, size: LayoutElementSize) - -private[cyfra] sealed trait LayoutElementSize -private[cyfra] case class InputBufferSize(elemSize: Int) extends LayoutElementSize -private[cyfra] case class UniformSize(size: Int) extends LayoutElementSize diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/BufferAction.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/BufferAction.scala deleted file mode 100644 index 32b5ba49..00000000 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/BufferAction.scala +++ /dev/null @@ -1,17 +0,0 @@ -package io.computenode.cyfra.vulkan.executor - -import org.lwjgl.vulkan.VK10.{VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_BUFFER_USAGE_TRANSFER_SRC_BIT} - -enum BufferAction(val action: Int): - case DoNothing extends BufferAction(0) - case LoadTo extends BufferAction(VK_BUFFER_USAGE_TRANSFER_DST_BIT) - case LoadFrom extends BufferAction(VK_BUFFER_USAGE_TRANSFER_SRC_BIT) - case LoadFromTo extends BufferAction(VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT) - - private def findAction(action: Int): BufferAction = action match - case VK_BUFFER_USAGE_TRANSFER_DST_BIT => LoadTo - case VK_BUFFER_USAGE_TRANSFER_SRC_BIT => LoadFrom - case 3 => LoadFromTo - case _ => DoNothing - - def |(other: BufferAction): BufferAction = findAction(this.action | other.action) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/SequenceExecutor.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/SequenceExecutor.scala index f367ab5b..92170498 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/SequenceExecutor.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/SequenceExecutor.scala @@ -27,47 +27,7 @@ private[cyfra] class SequenceExecutor(computeSequence: ComputationSequence, cont private val descriptorPool: DescriptorPool = context.descriptorPool private val commandPool: CommandPool = context.commandPool - private val pipelineToDescriptorSets: Map[ComputePipeline, Seq[DescriptorSet]] = pushStack: stack => - val pipelines = computeSequence.sequence.collect: - case Compute(pipeline, _) => pipeline - - val rawSets = pipelines.map(_.layoutInfo.sets) - val numbered = rawSets.flatten.zipWithIndex - val numberedSets = rawSets - .foldLeft((numbered, Seq.empty[Seq[(LayoutSet, Int)]])) { case ((remaining, acc), sequence) => - val (current, rest) = remaining.splitAt(sequence.length) - (rest, acc :+ current) - } - ._2 - - val pipelineToIndex = pipelines.zipWithIndex.toMap - val dependencies = computeSequence.dependencies.map { case Dependency(from, fromSet, to, toSet) => - (pipelineToIndex(from), fromSet, pipelineToIndex(to), toSet) - } - val resolvedSets = dependencies - .foldLeft(numberedSets.map(_.toArray)) { case (sets, (from, fromSet, to, toSet)) => - val a = sets(from)(fromSet) - val b = sets(to)(toSet) - assert(a._1.bindings == b._1.bindings) - val nextIndex = a._2 min b._2 - sets(from).update(fromSet, (a._1, nextIndex)) - sets(to).update(toSet, (b._1, nextIndex)) - sets - } - .map(_.toSeq.map(_._2)) - - val descriptorSetMap = resolvedSets - .zip(pipelines.map(_.descriptorSetLayouts)) - .flatMap { case (sets, layouts) => - sets.zip(layouts) - } - .distinctBy(_._1) - .map { case (set, (id, layout)) => - (set, new DescriptorSet(id, layout.bindings, descriptorPool)) - } - .toMap - - pipelines.zip(resolvedSets.map(_.map(descriptorSetMap(_)))).toMap + private val pipelineToDescriptorSets: Map[ComputePipeline, Seq[DescriptorSet]] = ??? private val descriptorSets = pipelineToDescriptorSets.toSeq.flatMap(_._2).distinctBy(_.get) @@ -102,7 +62,7 @@ private[cyfra] class SequenceExecutor(computeSequence: ComputationSequence, cont vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.get) val pDescriptorSets = stack.longs(pipelineToDescriptorSets(pipeline).map(_.get)*) - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.pipelineLayout, 0, pDescriptorSets, null) + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.pipelineLayout.id, 0, pDescriptorSets, null) vkCmdDispatch(commandBuffer, 8, 1, 1) } @@ -111,35 +71,8 @@ private[cyfra] class SequenceExecutor(computeSequence: ComputationSequence, cont commandBuffer private def createBuffers(): Map[DescriptorSet, Seq[Buffer]] = - - val setToActions = computeSequence.sequence - .collect { case Compute(pipeline, bufferActions) => - pipelineToDescriptorSets(pipeline).zipWithIndex.map { case (descriptorSet, i) => - val descriptorBufferActions = descriptorSet.bindings - .map(_.id) - .map(LayoutLocation(i, _)) - .map(bufferActions.getOrElse(_, BufferAction.DoNothing)) - (descriptorSet, descriptorBufferActions) - } - } - .flatten - .groupMapReduce(_._1)(_._2)((a, b) => a.zip(b).map(x => x._1 | x._2)) - - val setToBuffers = descriptorSets - .map(set => - val actions = setToActions(set) - val buffers = set.bindings.zip(actions).map { case (binding, action) => - binding.size match - case InputBufferSize(elemSize) => - new Buffer.DeviceBuffer(elemSize * 1024, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | action.action) - case UniformSize(size) => - new Buffer.DeviceBuffer(size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | action.action) - } - set.update(buffers) - (set, buffers), - ) - .toMap - + val setToActions = ??? + val setToBuffers = ??? setToBuffers def execute(inputs: Seq[ByteBuffer]): Seq[ByteBuffer] = pushStack: stack => @@ -189,18 +122,29 @@ private[cyfra] class SequenceExecutor(computeSequence: ComputationSequence, cont output - def destroy(): Unit = - descriptorSets.foreach(_.destroy()) + def destroy(): Unit = ??? object SequenceExecutor: private[cyfra] case class ComputationSequence(sequence: Seq[ComputationStep], dependencies: Seq[Dependency]) private[cyfra] sealed trait ComputationStep case class Compute(pipeline: ComputePipeline, bufferActions: Map[LayoutLocation, BufferAction]) extends ComputationStep: - def pumpLayoutLocations: Seq[Seq[BufferAction]] = - pipeline.layoutInfo.sets - .map(x => x.bindings.map(y => (x.id, y.id)).map(x => bufferActions.getOrElse(LayoutLocation.apply.tupled(x), BufferAction.DoNothing))) + def pumpLayoutLocations: Seq[Seq[BufferAction]] = ??? case class LayoutLocation(set: Int, binding: Int) case class Dependency(from: ComputePipeline, fromSet: Int, to: ComputePipeline, toSet: Int) + + enum BufferAction(val action: Int): + case DoNothing extends BufferAction(0) + case LoadTo extends BufferAction(VK_BUFFER_USAGE_TRANSFER_DST_BIT) + case LoadFrom extends BufferAction(VK_BUFFER_USAGE_TRANSFER_SRC_BIT) + case LoadFromTo extends BufferAction(VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT) + + private def findAction(action: Int): BufferAction = action match + case VK_BUFFER_USAGE_TRANSFER_DST_BIT => LoadTo + case VK_BUFFER_USAGE_TRANSFER_SRC_BIT => LoadFrom + case 3 => LoadFromTo + case _ => DoNothing + + def |(other: BufferAction): BufferAction = findAction(this.action | other.action) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorSet.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorSet.scala index 52e001a0..bd33df63 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorSet.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorSet.scala @@ -1,6 +1,6 @@ package io.computenode.cyfra.vulkan.memory -import io.computenode.cyfra.vulkan.compute.{Binding, InputBufferSize, LayoutSet, UniformSize} +import io.computenode.cyfra.vulkan.compute.ComputePipeline.{BindingType, DescriptorSetInfo, DescriptorSetLayout} import io.computenode.cyfra.vulkan.core.Device import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import io.computenode.cyfra.vulkan.util.VulkanObjectHandle @@ -10,11 +10,11 @@ import org.lwjgl.vulkan.{VkDescriptorBufferInfo, VkDescriptorSetAllocateInfo, Vk /** @author * MarconZet Created 15.04.2020 */ -private[cyfra] class DescriptorSet(descriptorSetLayout: Long, val bindings: Seq[Binding], descriptorPool: DescriptorPool)(using device: Device) +private[cyfra] class DescriptorSet(descriptorSetLayout: DescriptorSetLayout, descriptorPool: DescriptorPool)(using device: Device) extends VulkanObjectHandle: protected val handle: Long = pushStack: stack => - val pSetLayout = stack.callocLong(1).put(0, descriptorSetLayout) + val pSetLayout = stack.callocLong(1).put(0, descriptorSetLayout.id) val descriptorSetAllocateInfo = VkDescriptorSetAllocateInfo .calloc(stack) .sType$Default() @@ -26,22 +26,23 @@ private[cyfra] class DescriptorSet(descriptorSetLayout: Long, val bindings: Seq[ pDescriptorSet.get() def update(buffers: Seq[Buffer]): Unit = pushStack: stack => + val bindings = descriptorSetLayout.set.descriptors assert(buffers.length == bindings.length, s"Number of buffers (${buffers.length}) does not match number of bindings (${bindings.length})") val writeDescriptorSet = VkWriteDescriptorSet.calloc(buffers.length, stack) - buffers.zip(bindings).foreach { case (buffer, binding) => + buffers.zip(bindings).zipWithIndex.foreach { case ((buffer, binding), idx) => val descriptorBufferInfo = VkDescriptorBufferInfo .calloc(1, stack) .buffer(buffer.get) .offset(0) .range(VK_WHOLE_SIZE) - val descriptorType = binding.size match - case InputBufferSize(elemSize) => VK_DESCRIPTOR_TYPE_STORAGE_BUFFER - case UniformSize(size) => VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER + val descriptorType = binding.kind match + case BindingType.StorageBuffer => VK_DESCRIPTOR_TYPE_STORAGE_BUFFER + case BindingType.Uniform => VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER writeDescriptorSet .get() .sType$Default() .dstSet(handle) - .dstBinding(binding.id) + .dstBinding(idx) .descriptorCount(1) .descriptorType(descriptorType) .pBufferInfo(descriptorBufferInfo) diff --git a/cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.scala b/cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.scala index 14ab3e46..fec9746b 100644 --- a/cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.scala +++ b/cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.scala @@ -2,8 +2,10 @@ package io.computenode.cyfra.vulkan import io.computenode.cyfra.vulkan.VulkanContext import io.computenode.cyfra.vulkan.compute.* +import io.computenode.cyfra.vulkan.compute.ComputePipeline.* +import io.computenode.cyfra.vulkan.compute.ComputePipeline.BindingType.StorageBuffer import io.computenode.cyfra.vulkan.core.Device -import io.computenode.cyfra.vulkan.executor.BufferAction.{LoadFrom, LoadTo} +import io.computenode.cyfra.vulkan.executor.SequenceExecutor.BufferAction.{LoadFrom, LoadTo} import io.computenode.cyfra.vulkan.executor.SequenceExecutor import io.computenode.cyfra.vulkan.executor.SequenceExecutor.{ComputationSequence, Compute, Dependency, LayoutLocation} import munit.FunSuite @@ -15,7 +17,8 @@ class SequenceExecutorTest extends FunSuite: test("Memory barrier"): val code = ComputePipeline.loadShader("copy_test.spv").get - val layout = LayoutInfo(Seq(LayoutSet(0, Seq(Binding(0, InputBufferSize(4)))), LayoutSet(1, Seq(Binding(0, InputBufferSize(4)))))) + val layout = + LayoutInfo(Seq(DescriptorSetInfo(Seq(DescriptorInfo(StorageBuffer))), DescriptorSetInfo(Seq(DescriptorInfo(StorageBuffer))))) val copy1 = new ComputePipeline(code, "main", layout) val copy2 = new ComputePipeline(code, "main", layout) From 239c04d8d88eb4113ee78de7f98150a950f97544 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Wed, 9 Jul 2025 18:29:29 +0200 Subject: [PATCH 03/20] hell^o --- .../cyfra/runtime/ExecutionHandler.scala | 40 +++++++++++++++++-- .../cyfra/runtime/ExecutionRunner.scala | 5 --- .../cyfra/runtime/VkAllocation.scala | 6 +-- .../cyfra/runtime/VkCyfraRuntime.scala | 7 ++-- 4 files changed, 44 insertions(+), 14 deletions(-) delete mode 100644 cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionRunner.scala 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 df0b470e..9088d2a8 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 @@ -2,10 +2,44 @@ package io.computenode.cyfra.runtime import io.computenode.cyfra.core.{GExecution, GProgram} import io.computenode.cyfra.core.layout.Layout +import io.computenode.cyfra.vulkan.command.CommandPool +import io.computenode.cyfra.vulkan.core.Queue +import io.computenode.cyfra.vulkan.memory.DescriptorPool +import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} +import org.lwjgl.vulkan.VK10.* +import org.lwjgl.vulkan.VkCommandBufferBeginInfo -object ExecutionHandler: +class ExecutionHandler(runtime: VkCyfraRuntime): + private val context = runtime.context + import context.given + + private val queue: Queue = context.computeQueue + private val descriptorPool: DescriptorPool = context.descriptorPool + private val commandPool: CommandPool = context.commandPool + + def handle[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L): RL = pushStack: stack => + val commandBuffer = commandPool.createCommandBuffer() + + val commandBufferBeginInfo = VkCommandBufferBeginInfo + .calloc(stack) + .sType$Default() + .flags(0) + + check(vkBeginCommandBuffer(commandBuffer, commandBufferBeginInfo), "Failed to begin recording command buffer") + record(execution, params, layout) + check(vkEndCommandBuffer(commandBuffer), "Failed to finish recording command buffer") - def handle[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params): List[BoundProgram[?, ?, ?]] = ??? - case class BoundProgram[LParams, Params, L <: Layout](layout: L, paramsMapping: LParams => Params, program: GProgram[Params, L]) + private def record[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L): RL = + execution match + case GExecution.Pure() => ??? + case GExecution.Map(nextExecution, mapResult, contramapLayout, contramapParams) => + val cParams = contramapParams(params) + val cLayout = contramapLayout(layout) + record(nextExecution, cParams, cLayout) + case GExecution.FlatMap(execution, f) => ??? + case program: GProgram[?, ?] => ??? + case _ => ??? + +object ExecutionHandler diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionRunner.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionRunner.scala deleted file mode 100644 index 81e605cb..00000000 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/ExecutionRunner.scala +++ /dev/null @@ -1,5 +0,0 @@ -package io.computenode.cyfra.runtime - -import io.computenode.cyfra.vulkan.core.Device - -class ExecutionRunner(using device: Device) 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 51e3d6de..05110a13 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 @@ -17,7 +17,7 @@ import java.nio.ByteBuffer import scala.collection.mutable import scala.util.chaining.* -class VkAllocation(commandPool: CommandPool)(using Allocator) extends Allocation: +class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler)(using Allocator) extends Allocation: extension (buffer: GBinding[?]) def read(bb: ByteBuffer, offset: Int = 0, size: Int = -1): Unit = val buf = getUnderlying(buffer) @@ -59,8 +59,8 @@ class VkAllocation(commandPool: CommandPool)(using Allocator) extends Allocation VkUniform[T]().tap(bindings += _) extension [Params, L <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, L, RL]) - override def execute(params: Params, layout: L): RL = ??? - + override def execute(params: Params, layout: L): RL = executionHandler.handle(execution, params, layout) + private def getUnderlying(buffer: GBinding[?]): Buffer = buffer match case buffer: VkBuffer[?] => buffer.underlying diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala index a554263c..c2487da0 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala @@ -8,15 +8,16 @@ import io.computenode.cyfra.vulkan.compute.ComputePipeline import scala.collection.mutable class VkCyfraRuntime extends CyfraRuntime: - private val context = new VulkanContext() + val context = new VulkanContext() import context.given - private val shaderCache = mutable.Map.empty[String, ComputePipeline] + private val executionHandler = new ExecutionHandler(this) + private val shaderCache = mutable.Map.empty[String, ComputePipeline] private[cyfra] def getOrLoadProgram[Params, L <: Layout: LayoutStruct](program: GProgram[Params, L]): ComputePipeline = shaderCache.getOrElseUpdate(program.cacheKey, VkShader(program)) override def withAllocation(f: Allocation => Unit): Unit = - val allocation = new VkAllocation(context.commandPool) + val allocation = new VkAllocation(context.commandPool, executionHandler) f(allocation) allocation.close() From bc7887a411de4a54379562ffc8d639cc4a0d8740 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Wed, 9 Jul 2025 21:25:31 +0200 Subject: [PATCH 04/20] wokring sweep --- .../computenode/cyfra/core/GExecution.scala | 6 ++-- .../computenode/cyfra/core/SpirvProgram.scala | 2 -- .../cyfra/dsl/binding/GBinding.scala | 25 +++++++++++-- .../cyfra/dsl/binding/GBuffer.scala | 13 ------- .../cyfra/dsl/binding/GUniform.scala | 19 ---------- .../cyfra/runtime/ExecutionHandler.scala | 36 +++++++++++++------ .../cyfra/runtime/VkCyfraRuntime.scala | 6 ++-- .../computenode/cyfra/runtime/VkShader.scala | 13 ++++--- 8 files changed, 63 insertions(+), 57 deletions(-) delete mode 100644 cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBuffer.scala delete mode 100644 cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GUniform.scala diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala index 0caabec6..1d1aa7fb 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala @@ -33,12 +33,12 @@ trait GExecution[-Params, ExecLayout <: Layout, +ResLayout <: Layout]: object GExecution: def apply[Params, L <: Layout]() = - Pure[Params, L, L]() + Pure[Params, L]() def forParams[Params, L <: Layout, RL <: Layout](f: Params => GExecution[Params, L, RL]): GExecution[Params, L, RL] = - FlatMap[Params, L, RL, RL](Pure[Params, L, RL](), (params: Params, _: RL) => f(params)) + FlatMap[Params, L, L, RL](Pure[Params, L](), (params: Params, _: L) => f(params)) - case class Pure[Params, L <: Layout, RL <: Layout]() extends GExecution[Params, L, RL] + case class Pure[Params, L <: Layout]() extends GExecution[Params, L, L] case class FlatMap[Params, L <: Layout, RL <: Layout, NRL <: Layout]( execution: GExecution[Params, L, RL], diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala index cd55b97f..f546f8db 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala @@ -56,5 +56,3 @@ object SpirvProgram: val fis = use(new FileInputStream(file)) val fc = use(fis.getChannel) fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()) - - def compile[Params, L <: Layout: LayoutStruct](program: GioProgram[Params, L]): SpirvProgram[Params, L] = ??? diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala index d9006d69..1e8d4985 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala @@ -1,7 +1,28 @@ package io.computenode.cyfra.dsl.binding import io.computenode.cyfra.dsl.Value -import io.computenode.cyfra.dsl.Value.FromExpr +import io.computenode.cyfra.dsl.Value.FromExpr.fromExpr +import io.computenode.cyfra.dsl.Value.{FromExpr, Int32} +import io.computenode.cyfra.dsl.gio.GIO +import io.computenode.cyfra.dsl.struct.GStruct import izumi.reflect.Tag -trait GBinding[T <: Value: {Tag, FromExpr}] +sealed trait GBinding[T <: Value: {Tag, FromExpr}] + +trait GBuffer[T <: Value: {FromExpr, Tag}] extends GBinding[T]: + def read(index: Int32): T = FromExpr.fromExpr(ReadBuffer(this, index)) + + def write(index: Int32, value: T): GIO[Unit] = GIO.write(this, index, value) + +object GBuffer + +trait GUniform[T <: Value: {Tag, FromExpr}] extends GBinding[T]: + def read: T = fromExpr(ReadUniform(this)) + + def write(value: T): GIO[Unit] = WriteUniform(this, value) + +object GUniform: + + case class ParamUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] + + def fromParams[T <: GStruct[T]: {Tag, FromExpr}] = ParamUniform[T]() diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBuffer.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBuffer.scala deleted file mode 100644 index b9f849cf..00000000 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBuffer.scala +++ /dev/null @@ -1,13 +0,0 @@ -package io.computenode.cyfra.dsl.binding - -import io.computenode.cyfra.dsl.Value -import io.computenode.cyfra.dsl.Value.{FromExpr, Int32} -import io.computenode.cyfra.dsl.gio.GIO -import izumi.reflect.Tag - -trait GBuffer[T <: Value: {FromExpr, Tag}] extends GBinding[T]: - def read(index: Int32): T = FromExpr.fromExpr(ReadBuffer(this, index)) - - def write(index: Int32, value: T): GIO[Unit] = GIO.write(this, index, value) - -object GBuffer diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GUniform.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GUniform.scala deleted file mode 100644 index 9e753e77..00000000 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GUniform.scala +++ /dev/null @@ -1,19 +0,0 @@ -package io.computenode.cyfra.dsl.binding - -import io.computenode.cyfra.dsl.Value -import io.computenode.cyfra.dsl.Value.FromExpr -import io.computenode.cyfra.dsl.Value.FromExpr.fromExpr -import io.computenode.cyfra.dsl.gio.GIO -import io.computenode.cyfra.dsl.struct.GStruct -import izumi.reflect.Tag - -trait GUniform[T <: Value: {Tag, FromExpr}] extends GBinding[T]: - def read: T = fromExpr(ReadUniform(this)) - - def write(value: T): GIO[Unit] = WriteUniform(this, value) - -object GUniform: - - case class ParamUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] - - def fromParams[T <: GStruct[T]: {Tag, FromExpr}] = ParamUniform[T]() 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 9088d2a8..c60d7587 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 @@ -1,8 +1,11 @@ package io.computenode.cyfra.runtime +import io.computenode.cyfra.core.SpirvProgram.ShaderLayout import io.computenode.cyfra.core.{GExecution, GProgram} import io.computenode.cyfra.core.layout.Layout +import io.computenode.cyfra.runtime.ExecutionHandler.ShaderCall import io.computenode.cyfra.vulkan.command.CommandPool +import io.computenode.cyfra.vulkan.compute.ComputePipeline import io.computenode.cyfra.vulkan.core.Queue import io.computenode.cyfra.vulkan.memory.DescriptorPool import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} @@ -18,28 +21,39 @@ class ExecutionHandler(runtime: VkCyfraRuntime): private val commandPool: CommandPool = context.commandPool def handle[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L): RL = pushStack: stack => - val commandBuffer = commandPool.createCommandBuffer() + val (result, calls) = interpret(execution, params, layout) + + ??? + val commandBuffer = commandPool.createCommandBuffer() val commandBufferBeginInfo = VkCommandBufferBeginInfo .calloc(stack) .sType$Default() .flags(0) check(vkBeginCommandBuffer(commandBuffer, commandBufferBeginInfo), "Failed to begin recording command buffer") - record(execution, params, layout) check(vkEndCommandBuffer(commandBuffer), "Failed to finish recording command buffer") - ??? - private def record[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L): RL = + private def interpret[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L): (RL, Seq[ShaderCall]) = execution match - case GExecution.Pure() => ??? + case GExecution.Pure() => (layout, Seq.empty) case GExecution.Map(nextExecution, mapResult, contramapLayout, contramapParams) => val cParams = contramapParams(params) val cLayout = contramapLayout(layout) - record(nextExecution, cParams, cLayout) - case GExecution.FlatMap(execution, f) => ??? - case program: GProgram[?, ?] => ??? - case _ => ??? - -object ExecutionHandler + val (prevLayout, calls) = interpret(nextExecution, cParams, cLayout) + (mapResult(prevLayout), calls) + case GExecution.FlatMap(execution, f) => + val (prevLayout, calls) = interpret(execution, params, layout) + val nextExecution = f(params, prevLayout) + val (prevLayout2, calls2) = interpret(nextExecution, params, layout) + (prevLayout2, calls ++ calls2) + case program: GProgram[?, ?] => +// val shader = runtime.getOrLoadProgram(program) + val shader = runtime.shaderCache(program.cacheKey).asInstanceOf[VkShader[L]] + val rl = layout.asInstanceOf[RL] + (rl, Seq(ShaderCall(shader.underlying, shader.shaderBindings(layout)))) + case _ => ??? + +object ExecutionHandler: + case class ShaderCall(pipeline: ComputePipeline, layout: ShaderLayout) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala index c2487da0..dbe5883d 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala @@ -13,9 +13,9 @@ class VkCyfraRuntime extends CyfraRuntime: private val executionHandler = new ExecutionHandler(this) - private val shaderCache = mutable.Map.empty[String, ComputePipeline] - private[cyfra] def getOrLoadProgram[Params, L <: Layout: LayoutStruct](program: GProgram[Params, L]): ComputePipeline = - shaderCache.getOrElseUpdate(program.cacheKey, VkShader(program)) + val shaderCache = mutable.Map.empty[String, VkShader[?]] + private[cyfra] def getOrLoadProgram[Params, L <: Layout: LayoutStruct](program: GProgram[Params, L]): VkShader[L] = + shaderCache.getOrElseUpdate(program.cacheKey, VkShader(program)).asInstanceOf[VkShader[L]] override def withAllocation(f: Allocation => Unit): Unit = val allocation = new VkAllocation(context.commandPool, executionHandler) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala index 3ace5fb0..cfbfa74f 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala @@ -9,11 +9,14 @@ import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} import io.computenode.cyfra.vulkan.compute.ComputePipeline import io.computenode.cyfra.vulkan.compute.ComputePipeline.* import io.computenode.cyfra.vulkan.core.Device +import izumi.reflect.Tag + +case class VkShader[L](underlying: ComputePipeline, shaderBindings: L => ShaderLayout) object VkShader: - def apply[P, L <: Layout: LayoutStruct](program: GProgram[P, L])(using Device): ComputePipeline = + def apply[P, L <: Layout: LayoutStruct](program: GProgram[P, L])(using Device): VkShader[L] = val SpirvProgram(layout, dispatch, _workgroupSize, code, entryPoint, shaderBindings) = program match - case p: GioProgram[?, ?] => SpirvProgram.compile(p) + case p: GioProgram[?, ?] => compile(p) case p: SpirvProgram[?, ?] => p case _ => throw new IllegalArgumentException(s"Unsupported program type: ${program.getClass.getName}") @@ -23,9 +26,11 @@ object VkShader: val kind = binding match case buffer: GBuffer[?] => BindingType.StorageBuffer case uniform: GUniform[?] => BindingType.Uniform - case _ => ??? DescriptorInfo(kind) } DescriptorSetInfo(descriptors) - ComputePipeline(code, entryPoint, LayoutInfo(sets)) + val pipeline = ComputePipeline(code, entryPoint, LayoutInfo(sets)) + VkShader(pipeline, shaderBindings) + + def compile[Params, L <: Layout: LayoutStruct](program: GioProgram[Params, L]): SpirvProgram[Params, L] = ??? From 36e03bef1f7268d7c328bc71226108b0b2b6bf5f Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Wed, 9 Jul 2025 22:16:31 +0200 Subject: [PATCH 05/20] better^ --- .../io/computenode/cyfra/core/Allocation.scala | 3 +-- .../io/computenode/cyfra/core/CyfraRuntime.scala | 2 +- .../cyfra/runtime/ExecutionHandler.scala | 15 ++++++++------- .../cyfra/vulkan/compute/ComputePipeline.scala | 2 +- .../computenode/cyfra/vulkan/memory/Buffer.scala | 4 ++-- 5 files changed, 13 insertions(+), 13 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 613d532e..a8b538bd 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 @@ -15,8 +15,7 @@ trait Allocation: def write(bb: ByteBuffer, offset: Int = 0, size: Int = -1): Unit - extension [Params, L <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, L, RL]) - def execute(params: Params, layout: L): RL + extension [Params, L <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, L, RL]) def execute(params: Params, layout: L): RL extension (buffers: GBuffer.type) def apply[T <: Value: {Tag, FromExpr}](size: Int): GBuffer[T] diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/CyfraRuntime.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/CyfraRuntime.scala index 69c9cfa3..a38c620c 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/CyfraRuntime.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/CyfraRuntime.scala @@ -4,4 +4,4 @@ import io.computenode.cyfra.core.Allocation trait CyfraRuntime: - def withAllocation(f: Allocation => Unit): Unit + def withAllocation(f: Allocation => Unit): Unit 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 c60d7587..11377970 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 @@ -33,16 +33,17 @@ class ExecutionHandler(runtime: VkCyfraRuntime): check(vkBeginCommandBuffer(commandBuffer, commandBufferBeginInfo), "Failed to begin recording command buffer") check(vkEndCommandBuffer(commandBuffer), "Failed to finish recording command buffer") - ??? + + result private def interpret[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L): (RL, Seq[ShaderCall]) = execution match - case GExecution.Pure() => (layout, Seq.empty) - case GExecution.Map(nextExecution, mapResult, contramapLayout, contramapParams) => - val cParams = contramapParams(params) - val cLayout = contramapLayout(layout) - val (prevLayout, calls) = interpret(nextExecution, cParams, cLayout) - (mapResult(prevLayout), calls) + case GExecution.Pure() => (layout, Seq.empty) + case GExecution.Map(execution, map, cmap, cmapP) => + val cParams = cmapP(params) + val cLayout = cmap(layout) + val (prevLayout, calls) = interpret(execution, cParams, cLayout) + (map(prevLayout), calls) case GExecution.FlatMap(execution, f) => val (prevLayout, calls) = interpret(execution, params, layout) val nextExecution = f(params, prevLayout) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala index f5cb089f..7f0f81f4 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala @@ -117,4 +117,4 @@ object ComputePipeline: private[cyfra] enum BindingType: case StorageBuffer - case Uniform \ No newline at end of file + case Uniform 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 a485653b..f8926e3d 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 @@ -58,11 +58,11 @@ object Buffer: if flush then vmaFlushAllocation(this.allocator.get, this.allocation, 0, size) vmaUnmapMemory(this.allocator.get, this.allocation) - def copyBuffer(src: ByteBuffer, dst: HostBuffer, srcOffset: Int , dstOffset: Int , bytes: Int ): Unit = + def copyBuffer(src: ByteBuffer, dst: HostBuffer, srcOffset: Int, dstOffset: Int, bytes: Int): Unit = dst.mapped: destination => memCopy(memAddress(src) + srcOffset, memAddress(destination) + dstOffset, bytes) - def copyBuffer(src: HostBuffer, dst: ByteBuffer, srcOffset: Int , dstOffset: Int , bytes: Int ): Unit = + def copyBuffer(src: HostBuffer, dst: ByteBuffer, srcOffset: Int, dstOffset: Int, bytes: Int): Unit = src.mappedNoFlush: source => memCopy(memAddress(source) + srcOffset, memAddress(dst) + dstOffset, bytes) From 4e4deae85953440db9a5148643394278493b086e Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Wed, 9 Jul 2025 23:04:24 +0200 Subject: [PATCH 06/20] working to unimplemented --- .../cyfra/core/GBufferRegion.scala | 13 ++++------- .../io/computenode/cyfra/core/GProgram.scala | 4 ++-- .../computenode/cyfra/core/SpirvProgram.scala | 13 +++++++---- .../src/main/resources/compileAll.ps1 | 4 ++++ .../src/main/resources/compileAll.sh | 7 ++++++ cyfra-examples/src/main/resources/emit.comp | 23 +++++++++++++++++++ cyfra-examples/src/main/resources/filter.comp | 20 ++++++++++++++++ .../cyfra/samples/TestingStuff.scala | 20 ++++++++++++++-- .../computenode/cyfra/runtime/VkShader.scala | 2 +- 9 files changed, 88 insertions(+), 18 deletions(-) create mode 100644 cyfra-examples/src/main/resources/compileAll.ps1 create mode 100644 cyfra-examples/src/main/resources/compileAll.sh create mode 100644 cyfra-examples/src/main/resources/emit.comp create mode 100644 cyfra-examples/src/main/resources/filter.comp 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 472a61e4..c7e2376c 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 @@ -10,22 +10,19 @@ import izumi.reflect.Tag import java.nio.ByteBuffer -sealed trait GBufferRegion[ReqAlloc <: Layout: LayoutStruct, ResAlloc <: Layout: LayoutStruct]: - val initAlloc: ReqAlloc +sealed trait GBufferRegion[ReqAlloc <: Layout: LayoutStruct, ResAlloc <: Layout: LayoutStruct] object GBufferRegion: def allocate[Alloc <: Layout: LayoutStruct]: GBufferRegion[Alloc, Alloc] = AllocRegion(summon[LayoutStruct[Alloc]].layoutRef) - case class AllocRegion[Alloc <: Layout: LayoutStruct](l: Alloc) extends GBufferRegion[Alloc, Alloc]: - val initAlloc: Alloc = l + case class AllocRegion[Alloc <: Layout: LayoutStruct](l: Alloc) extends GBufferRegion[Alloc, Alloc] case class MapRegion[ReqAlloc <: Layout: LayoutStruct, BodyAlloc <: Layout: LayoutStruct, ResAlloc <: Layout: LayoutStruct]( reqRegion: GBufferRegion[ReqAlloc, BodyAlloc], f: Allocation => BodyAlloc => ResAlloc, - ) extends GBufferRegion[ReqAlloc, ResAlloc]: - val initAlloc: ReqAlloc = reqRegion.initAlloc + ) extends GBufferRegion[ReqAlloc, ResAlloc] extension [ReqAlloc <: Layout: LayoutStruct, ResAlloc <: Layout: LayoutStruct](region: GBufferRegion[ReqAlloc, ResAlloc]) def map[NewAlloc <: Layout: LayoutStruct](f: Allocation ?=> ResAlloc => NewAlloc): GBufferRegion[ReqAlloc, NewAlloc] = @@ -33,7 +30,6 @@ object GBufferRegion: def runUnsafe(init: Allocation ?=> ReqAlloc, onDone: Allocation ?=> ResAlloc => Unit)(using cyfraRuntime: CyfraRuntime): Unit = cyfraRuntime.withAllocation: allocation => - init(using allocation) // noinspection ScalaRedundantCast val steps: Seq[Allocation => Layout => Layout] = Seq.unfold(region: GBufferRegion[?, ?]): @@ -41,7 +37,8 @@ object GBufferRegion: case MapRegion(req, f) => Some((f.asInstanceOf[Allocation => Layout => Layout], req)) - val bodyAlloc = steps.foldLeft[Layout](region.initAlloc): (acc, step) => + val initAlloc = init(using allocation) + val bodyAlloc = steps.foldLeft[Layout](initAlloc): (acc, step) => step(allocation)(acc) onDone(using allocation)(bodyAlloc.asInstanceOf[ResAlloc]) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala index 6e82ae15..f6555324 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala @@ -18,7 +18,7 @@ trait GProgram[Params, L <: Layout: LayoutStruct] extends GExecution[Params, L, val dispatch: (L, Params) => ProgramDispatch val workgroupSize: WorkDimensions private[cyfra] def layoutStruct: LayoutStruct[L] = summon[LayoutStruct[L]] - private[cyfra] def cacheKey: String + private[cyfra] def cacheKey: String // TODO better type object GProgram: @@ -28,7 +28,7 @@ object GProgram: dispatch: (L, Params) => ProgramDispatch, workgroupSize: WorkDimensions, ) extends GProgram[Params, L]: - private[cyfra] def cacheKey: String = toString + private[cyfra] def cacheKey: String = if layoutStruct.elementTypes.contains(summon[Tag[Boolean]]) then "filter" else "emit" type WorkDimensions = (Int, Int, Int) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala index f546f8db..5ef0d5a7 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala @@ -19,15 +19,15 @@ import scala.util.Try import scala.util.Using import scala.util.chaining.* -case class SpirvProgram[Params, L <: Layout: LayoutStruct]( +case class SpirvProgram[Params, L <: Layout: LayoutStruct] private ( layout: InitProgramLayout => Params => L, dispatch: (L, Params) => ProgramDispatch, workgroupSize: WorkDimensions, code: ByteBuffer, entryPoint: String, shaderBindings: L => ShaderLayout, -) extends GProgram[Params, L]: - private[cyfra] def cacheKey: String = toString + cacheKey: String, +) extends GProgram[Params, L] object SpirvProgram: type ShaderLayout = Seq[Seq[Binding]] @@ -39,7 +39,7 @@ object SpirvProgram: def apply[Params, L <: Layout: LayoutStruct]( path: String, - layout: InitProgramLayout => Params => L, + layout: InitProgramLayout ?=> Params => L, dispatch: (L, Params) => ProgramDispatch, ): SpirvProgram[Params, L] = val code = loadShader(path).get @@ -48,7 +48,10 @@ object SpirvProgram: val f: L => ShaderLayout = { case layout: Product => layout.productIterator.zipWithIndex.map { case (binding: GBinding[?], i) => Binding(binding, ReadWrite) }.toSeq.pipe(Seq(_)) } - new SpirvProgram[Params, L](layout, dispatch, workgroupSize, code, main, f) + val cacheKey = + val x = new File(path).getName + x.substring(0, x.lastIndexOf('.')) + new SpirvProgram[Params, L]((il: InitProgramLayout) => layout(using il), dispatch, workgroupSize, code, main, f, cacheKey) private def loadShader(path: String, classLoader: ClassLoader = getClass.getClassLoader): Try[ByteBuffer] = Using.Manager: use => diff --git a/cyfra-examples/src/main/resources/compileAll.ps1 b/cyfra-examples/src/main/resources/compileAll.ps1 new file mode 100644 index 00000000..e1755a32 --- /dev/null +++ b/cyfra-examples/src/main/resources/compileAll.ps1 @@ -0,0 +1,4 @@ +Get-ChildItem -Filter *.comp -Name | ForEach-Object -Process { + $name = $_.Replace(".comp", "") + "$Env:VULKAN_SDK\Bin\glslangValidator.exe -V $name.comp -o $name.spv" | Invoke-Expression +} diff --git a/cyfra-examples/src/main/resources/compileAll.sh b/cyfra-examples/src/main/resources/compileAll.sh new file mode 100644 index 00000000..fdd4be8c --- /dev/null +++ b/cyfra-examples/src/main/resources/compileAll.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +for f in *.comp +do + prefix=$(echo "$f" | cut -f 1 -d '.') + glslangValidator -V "$prefix.comp" -o "$prefix.spv" +done diff --git a/cyfra-examples/src/main/resources/emit.comp b/cyfra-examples/src/main/resources/emit.comp new file mode 100644 index 00000000..5789c424 --- /dev/null +++ b/cyfra-examples/src/main/resources/emit.comp @@ -0,0 +1,23 @@ +#version 450 + +layout (local_size_x = 128, local_size_y = 1, local_size_z = 1) in; + +layout (set = 0, binding = 0) buffer InputBuffer { + int inBuffer[]; +}; +layout (set = 0, binding = 1) buffer OutputBuffer { + int outBuffer[]; +}; + +layout (set = 0, binding = 2) uniform InputUniform { + int emitN; +}; + +void main(void) { + uint index = gl_GlobalInvocationID.x; + int element = inBuffer[index]; + uint offset = index * uint(emitN); + for (int i = 0; i < emitN; i++) { + outBuffer[offset + uint(i)] = element; + } +} diff --git a/cyfra-examples/src/main/resources/filter.comp b/cyfra-examples/src/main/resources/filter.comp new file mode 100644 index 00000000..37beef64 --- /dev/null +++ b/cyfra-examples/src/main/resources/filter.comp @@ -0,0 +1,20 @@ +#version 450 + +layout (local_size_x = 128, local_size_y = 1, local_size_z = 1) in; + +layout (set = 0, binding = 0) buffer InputBuffer { + int inBuffer[]; +}; +layout (set = 0, binding = 1) buffer OutputBuffer { + bool outBuffer[]; +}; + +layout (set = 0, binding = 2) uniform InputUniform { + int filterValue; +}; + +void main(void) { + uint index = gl_GlobalInvocationID.x; + int element = inBuffer[index]; + outBuffer[index] = (element == filterValue); +} 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 c328bcd4..11067899 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 @@ -1,7 +1,8 @@ package io.computenode.cyfra.samples +import io.computenode.cyfra.core.GProgram.InitProgramLayout import io.computenode.cyfra.core.archive.GContext -import io.computenode.cyfra.core.{CyfraRuntime, GBufferRegion, GExecution, GProgram} +import io.computenode.cyfra.core.{CyfraRuntime, GBufferRegion, GExecution, GProgram, SpirvProgram} import io.computenode.cyfra.core.layout.* import io.computenode.cyfra.dsl.Value.{GBoolean, Int32} import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} @@ -85,7 +86,22 @@ object TestingStuff: @main def test = - given CyfraRuntime = VkCyfraRuntime() + given runtime: VkCyfraRuntime = VkCyfraRuntime() + + val filter = SpirvProgram[FilterProgramParams, FilterProgramLayout]( + "filter.spv", + layout = (il: InitProgramLayout) ?=> filterProgram.layout(il), + dispatch = filterProgram.dispatch, + ) + + val emit = SpirvProgram[EmitProgramParams, EmitProgramLayout]( + "emit.spv", + layout = (il: InitProgramLayout) ?=> emitProgram.layout(il), + dispatch = emitProgram.dispatch, + ) + + runtime.getOrLoadProgram(filter) + runtime.getOrLoadProgram(emit) val emitFilterParams = EmitFilterParams(inSize = 1024, emitN = 2, filterValue = 42) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala index cfbfa74f..b4308572 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala @@ -15,7 +15,7 @@ case class VkShader[L](underlying: ComputePipeline, shaderBindings: L => ShaderL object VkShader: def apply[P, L <: Layout: LayoutStruct](program: GProgram[P, L])(using Device): VkShader[L] = - val SpirvProgram(layout, dispatch, _workgroupSize, code, entryPoint, shaderBindings) = program match + val SpirvProgram(layout, dispatch, _workgroupSize, code, entryPoint, shaderBindings, _) = program match case p: GioProgram[?, ?] => compile(p) case p: SpirvProgram[?, ?] => p case _ => throw new IllegalArgumentException(s"Unsupported program type: ${program.getClass.getName}") From 24c083cfbc1c2f0f15148ea5048d966a69b10a9e Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Thu, 10 Jul 2025 00:07:36 +0200 Subject: [PATCH 07/20] working and crashing --- .../cyfra/runtime/ExecutionHandler.scala | 98 ++++++++++++++++--- .../cyfra/runtime/VkAllocation.scala | 14 +-- .../cyfra/vulkan/memory/DescriptorPool.scala | 3 + 3 files changed, 97 insertions(+), 18 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 11377970..56a08ff1 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 @@ -3,14 +3,19 @@ package io.computenode.cyfra.runtime import io.computenode.cyfra.core.SpirvProgram.ShaderLayout import io.computenode.cyfra.core.{GExecution, GProgram} import io.computenode.cyfra.core.layout.Layout -import io.computenode.cyfra.runtime.ExecutionHandler.ShaderCall -import io.computenode.cyfra.vulkan.command.CommandPool +import io.computenode.cyfra.dsl.binding.GBinding +import io.computenode.cyfra.runtime.ExecutionHandler.{Dispatch, DispatchType, ExecutionStep, PipelineBarrier, ShaderCall} +import io.computenode.cyfra.runtime.ExecutionHandler.DispatchType.* +import io.computenode.cyfra.utility.Utility.timed +import io.computenode.cyfra.vulkan.command.{CommandPool, Fence} import io.computenode.cyfra.vulkan.compute.ComputePipeline import io.computenode.cyfra.vulkan.core.Queue -import io.computenode.cyfra.vulkan.memory.DescriptorPool +import io.computenode.cyfra.vulkan.memory.{DescriptorPool, DescriptorSet} import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} +import org.lwjgl.vulkan.KHRSynchronization2.vkCmdPipelineBarrier2KHR import org.lwjgl.vulkan.VK10.* -import org.lwjgl.vulkan.VkCommandBufferBeginInfo +import org.lwjgl.vulkan.VK13.{VK_ACCESS_2_SHADER_READ_BIT, VK_ACCESS_2_SHADER_WRITE_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT} +import org.lwjgl.vulkan.{VkCommandBuffer, VkCommandBufferBeginInfo, VkDependencyInfo, VkMemoryBarrier2, VkSubmitInfo} class ExecutionHandler(runtime: VkCyfraRuntime): private val context = runtime.context @@ -21,18 +26,35 @@ class ExecutionHandler(runtime: VkCyfraRuntime): private val commandPool: CommandPool = context.commandPool def handle[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L): RL = pushStack: stack => - val (result, calls) = interpret(execution, params, layout) + val (result, shaderCalls) = interpret(execution, params, layout) - ??? + val descriptorSets = shaderCalls.map { case ShaderCall(pipeline, layout) => + pipeline.pipelineLayout.sets.map(descriptorPool.allocate).zip(layout).map { case (set, bindings) => + set.update(bindings.map(x => VkAllocation.getUnderlying(x.binding))) + set + } + } - val commandBuffer = commandPool.createCommandBuffer() - val commandBufferBeginInfo = VkCommandBufferBeginInfo + val dispatches: Seq[Dispatch] = ??? + + val (executeSteps, _) = dispatches.foldLeft((Seq.empty[ExecutionStep], Set.empty[GBinding[?]])): + case ((steps, dirty), step) => + val bindings = step.layout.flatten.map(_.binding) + if bindings.exists(dirty.contains) then (steps.appendedAll(Seq(PipelineBarrier, step)), Set.empty[GBinding[?]]) + else (steps.appended(step), dirty ++ bindings) + + val commandBuffer = recordCommandBuffer(executeSteps) + + val pCommandBuffer = stack.callocPointer(1).put(0, commandBuffer) + val submitInfo = VkSubmitInfo .calloc(stack) .sType$Default() - .flags(0) + .pCommandBuffers(pCommandBuffer) - check(vkBeginCommandBuffer(commandBuffer, commandBufferBeginInfo), "Failed to begin recording command buffer") - check(vkEndCommandBuffer(commandBuffer), "Failed to finish recording command buffer") + val fence = new Fence() + timed("Vulkan render command"): + check(vkQueueSubmit(queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") + fence.block().destroy() result @@ -49,12 +71,64 @@ class ExecutionHandler(runtime: VkCyfraRuntime): val nextExecution = f(params, prevLayout) val (prevLayout2, calls2) = interpret(nextExecution, params, layout) (prevLayout2, calls ++ calls2) - case program: GProgram[?, ?] => + case program: GProgram[Params, L] => // val shader = runtime.getOrLoadProgram(program) val shader = runtime.shaderCache(program.cacheKey).asInstanceOf[VkShader[L]] + program.dispatch(layout, params) match + case GProgram.DynamicDispatch(buffer, offset) => DispatchType.Indirect(buffer, offset) + case GProgram.StaticDispatch(size) => DispatchType.Direct(size._1, size._2, size._3) val rl = layout.asInstanceOf[RL] (rl, Seq(ShaderCall(shader.underlying, shader.shaderBindings(layout)))) case _ => ??? + private def recordCommandBuffer(steps: Seq[ExecutionStep]): VkCommandBuffer = pushStack: stack => + val commandBuffer = commandPool.createCommandBuffer() + val commandBufferBeginInfo = VkCommandBufferBeginInfo + .calloc(stack) + .sType$Default() + .flags(0) + + check(vkBeginCommandBuffer(commandBuffer, commandBufferBeginInfo), "Failed to begin recording command buffer") + + steps.foreach: + case PipelineBarrier => + val memoryBarrier = VkMemoryBarrier2 + .calloc(1, stack) + .sType$Default() + .srcStageMask(VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT) + .srcAccessMask(VK_ACCESS_2_SHADER_WRITE_BIT) + .dstStageMask(VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT) + .dstAccessMask(VK_ACCESS_2_SHADER_READ_BIT) + + val dependencyInfo = VkDependencyInfo + .calloc(stack) + .sType$Default() + .pMemoryBarriers(memoryBarrier) + + vkCmdPipelineBarrier2KHR(commandBuffer, dependencyInfo) + + case Dispatch(pipeline, layout, descriptorSets, dispatch) => + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.get) + + val pDescriptorSets = stack.longs(descriptorSets.map(_.get)*) + vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.pipelineLayout.id, 0, pDescriptorSets, null) + + dispatch match + case Direct(x, y, z) => vkCmdDispatch(commandBuffer, x, y, z) + case Indirect(buffer, offset) => vkCmdDispatchIndirect(commandBuffer, VkAllocation.getUnderlying(buffer).get, offset) + + check(vkEndCommandBuffer(commandBuffer), "Failed to finish recording command buffer") + commandBuffer + object ExecutionHandler: case class ShaderCall(pipeline: ComputePipeline, layout: ShaderLayout) + + sealed trait ExecutionStep + case class Dispatch(pipeline: ComputePipeline, layout: ShaderLayout, descriptorSets: Seq[DescriptorSet], dispatch: DispatchType) + extends ExecutionStep + case object PipelineBarrier extends ExecutionStep + + sealed trait DispatchType + object DispatchType: + case class Direct(x: Int, y: Int, z: Int) extends DispatchType + case class Indirect(buffer: GBinding[?], offset: Int) extends DispatchType 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 05110a13..f2f1a68c 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 @@ -6,6 +6,7 @@ import io.computenode.cyfra.core.SpirvProgram 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.VkAllocation.getUnderlying import io.computenode.cyfra.spirv.SpirvTypes.typeStride import io.computenode.cyfra.vulkan.command.CommandPool import io.computenode.cyfra.vulkan.memory.{Allocator, Buffer} @@ -61,12 +62,6 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) extension [Params, L <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, L, RL]) override def execute(params: Params, layout: L): RL = executionHandler.handle(execution, params, layout) - private def getUnderlying(buffer: GBinding[?]): Buffer = - buffer match - case buffer: VkBuffer[?] => buffer.underlying - case uniform: VkUniform[?] => uniform.underlying - case _ => ??? - private val bindings = mutable.Buffer[VkUniform[?] | VkBuffer[?]]() private[cyfra] def close(): Unit = bindings.map(getUnderlying).foreach(_.destroy()) @@ -79,3 +74,10 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) val newBuffer = Buffer.HostBuffer(size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT) stagingBuffer = Some(newBuffer) newBuffer + +object VkAllocation: + private[runtime] def getUnderlying(buffer: GBinding[?]): Buffer = + buffer match + case buffer: VkBuffer[?] => buffer.underlying + case uniform: VkUniform[?] => uniform.underlying + case _ => ??? diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorPool.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorPool.scala index 4cb57928..a7756743 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorPool.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorPool.scala @@ -1,5 +1,6 @@ package io.computenode.cyfra.vulkan.memory +import io.computenode.cyfra.vulkan.compute.ComputePipeline.DescriptorSetLayout import io.computenode.cyfra.vulkan.core.Device import io.computenode.cyfra.vulkan.memory.DescriptorPool.MAX_SETS import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} @@ -31,5 +32,7 @@ private[cyfra] class DescriptorPool(using device: Device) extends VulkanObjectHa check(vkCreateDescriptorPool(device.get, descriptorPoolCreateInfo, null, pDescriptorPool), "Failed to create descriptor pool") pDescriptorPool.get() + def allocate(descriptorSetLayout: DescriptorSetLayout): DescriptorSet = DescriptorSet(descriptorSetLayout, this) + override protected def close(): Unit = vkDestroyDescriptorPool(device.get, handle, null) From 314cb4b30fbd101c5d6643e254c81d7fc78e395d Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Thu, 10 Jul 2025 13:43:55 +0200 Subject: [PATCH 08/20] final pre layout --- .../io/computenode/cyfra/core/GProgram.scala | 10 +-- .../cyfra/runtime/ExecutionHandler.scala | 73 +++++++++++-------- .../cyfra/runtime/VkAllocation.scala | 18 ++++- .../cyfra/runtime/VkCyfraRuntime.scala | 2 +- .../cyfra/vulkan/memory/DescriptorPool.scala | 12 ++- 5 files changed, 72 insertions(+), 43 deletions(-) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala index f6555324..03c5e5cd 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala @@ -7,7 +7,7 @@ import io.computenode.cyfra.core.layout.Layout import java.nio.ByteBuffer import GProgram.* import io.computenode.cyfra.dsl.{Expression, Value} -import io.computenode.cyfra.dsl.Value.{FromExpr, Int32} +import io.computenode.cyfra.dsl.Value.{FromExpr, GBoolean, Int32} import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.struct.GStruct.Empty @@ -28,7 +28,7 @@ object GProgram: dispatch: (L, Params) => ProgramDispatch, workgroupSize: WorkDimensions, ) extends GProgram[Params, L]: - private[cyfra] def cacheKey: String = if layoutStruct.elementTypes.contains(summon[Tag[Boolean]]) then "filter" else "emit" + private[cyfra] def cacheKey: String = if layoutStruct.elementTypes.contains(summon[Tag[GBoolean]]) then "filter" else "emit" type WorkDimensions = (Int, Int, Int) @@ -40,8 +40,6 @@ object GProgram: private[cyfra] case class BufferSizeSpec[T <: Value: {Tag, FromExpr}](size: Int) extends GBuffer[T] - private[cyfra] case class ParamUniform[T <: GStruct[T]: {Tag, FromExpr}](value: T) extends GUniform[T] - private[cyfra] case class DynamicUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] trait InitProgramLayout: @@ -50,11 +48,9 @@ object GProgram: BufferSizeSpec[T](size) extension (uniforms: GUniform.type) - def apply[T <: GStruct[T]: {Tag, FromExpr}](value: T): GUniform[T] = - ParamUniform[T](value) - def apply[T <: GStruct[T]: {Tag, FromExpr}](): GUniform[T] = DynamicUniform[T]() + def apply[T <: GStruct[T]: {Tag, FromExpr}](value: T): GUniform[T] def apply[Params, L <: Layout: LayoutStruct]( layout: InitProgramLayout ?=> Params => L, 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 56a08ff1..53ab38f8 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 @@ -1,8 +1,9 @@ package io.computenode.cyfra.runtime +import io.computenode.cyfra.core.GProgram.InitProgramLayout import io.computenode.cyfra.core.SpirvProgram.ShaderLayout import io.computenode.cyfra.core.{GExecution, GProgram} -import io.computenode.cyfra.core.layout.Layout +import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} import io.computenode.cyfra.dsl.binding.GBinding import io.computenode.cyfra.runtime.ExecutionHandler.{Dispatch, DispatchType, ExecutionStep, PipelineBarrier, ShaderCall} import io.computenode.cyfra.runtime.ExecutionHandler.DispatchType.* @@ -25,40 +26,47 @@ class ExecutionHandler(runtime: VkCyfraRuntime): private val descriptorPool: DescriptorPool = context.descriptorPool private val commandPool: CommandPool = context.commandPool - def handle[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L): RL = pushStack: stack => - val (result, shaderCalls) = interpret(execution, params, layout) + def handle[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L)(using VkAllocation): RL = pushStack: + stack => + val (result, shaderCalls) = interpret(execution, params, layout) - val descriptorSets = shaderCalls.map { case ShaderCall(pipeline, layout) => - pipeline.pipelineLayout.sets.map(descriptorPool.allocate).zip(layout).map { case (set, bindings) => - set.update(bindings.map(x => VkAllocation.getUnderlying(x.binding))) - set + val descriptorSets = shaderCalls.map { case ShaderCall(pipeline, layout, _) => + pipeline.pipelineLayout.sets.map(descriptorPool.allocate).zip(layout).map { case (set, bindings) => + set.update(bindings.map(x => VkAllocation.getUnderlying(x.binding))) + set + } } - } - val dispatches: Seq[Dispatch] = ??? + val dispatches: Seq[Dispatch] = shaderCalls + .zip(descriptorSets) + .map: + case (ShaderCall(pipeline, layout, dispatch), sets) => + Dispatch(pipeline, layout, sets, dispatch) - val (executeSteps, _) = dispatches.foldLeft((Seq.empty[ExecutionStep], Set.empty[GBinding[?]])): - case ((steps, dirty), step) => - val bindings = step.layout.flatten.map(_.binding) - if bindings.exists(dirty.contains) then (steps.appendedAll(Seq(PipelineBarrier, step)), Set.empty[GBinding[?]]) - else (steps.appended(step), dirty ++ bindings) + val (executeSteps, _) = dispatches.foldLeft((Seq.empty[ExecutionStep], Set.empty[GBinding[?]])): + case ((steps, dirty), step) => + val bindings = step.layout.flatten.map(_.binding) + if bindings.exists(dirty.contains) then (steps.appendedAll(Seq(PipelineBarrier, step)), Set.empty[GBinding[?]]) + else (steps.appended(step), dirty ++ bindings) - val commandBuffer = recordCommandBuffer(executeSteps) + val commandBuffer = recordCommandBuffer(executeSteps) - val pCommandBuffer = stack.callocPointer(1).put(0, commandBuffer) - val submitInfo = VkSubmitInfo - .calloc(stack) - .sType$Default() - .pCommandBuffers(pCommandBuffer) + 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(queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") - fence.block().destroy() + val fence = new Fence() + timed("Vulkan render command"): + check(vkQueueSubmit(queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") + fence.block().destroy() - result + result - private def interpret[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L): (RL, Seq[ShaderCall]) = + private def interpret[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L)(using + VkAllocation, + ): (RL, Seq[ShaderCall]) = execution match case GExecution.Pure() => (layout, Seq.empty) case GExecution.Map(execution, map, cmap, cmapP) => @@ -72,13 +80,16 @@ class ExecutionHandler(runtime: VkCyfraRuntime): val (prevLayout2, calls2) = interpret(nextExecution, params, layout) (prevLayout2, calls ++ calls2) case program: GProgram[Params, L] => -// val shader = runtime.getOrLoadProgram(program) - val shader = runtime.shaderCache(program.cacheKey).asInstanceOf[VkShader[L]] - program.dispatch(layout, params) match + given LayoutStruct[L] = program.layoutStruct + val shader = runtime.getOrLoadProgram(program) + val initProgram: InitProgramLayout = summon[VkAllocation].getInitProgramLayout + val layoutInit = program.layout(initProgram)(params).asInstanceOf[L] + ??? + val dispatch = program.dispatch(layout, params) match case GProgram.DynamicDispatch(buffer, offset) => DispatchType.Indirect(buffer, offset) case GProgram.StaticDispatch(size) => DispatchType.Direct(size._1, size._2, size._3) val rl = layout.asInstanceOf[RL] - (rl, Seq(ShaderCall(shader.underlying, shader.shaderBindings(layout)))) + (rl, Seq(ShaderCall(shader.underlying, shader.shaderBindings(layout), dispatch))) case _ => ??? private def recordCommandBuffer(steps: Seq[ExecutionStep]): VkCommandBuffer = pushStack: stack => @@ -121,7 +132,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime): commandBuffer object ExecutionHandler: - case class ShaderCall(pipeline: ComputePipeline, layout: ShaderLayout) + case class ShaderCall(pipeline: ComputePipeline, layout: ShaderLayout, dispatch: DispatchType) sealed trait ExecutionStep case class Dispatch(pipeline: ComputePipeline, layout: ShaderLayout, descriptorSets: Seq[DescriptorSet], dispatch: DispatchType) 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 f2f1a68c..c07b5417 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 @@ -6,11 +6,15 @@ import io.computenode.cyfra.core.SpirvProgram 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.dsl.struct.GStruct import io.computenode.cyfra.runtime.VkAllocation.getUnderlying import io.computenode.cyfra.spirv.SpirvTypes.typeStride import io.computenode.cyfra.vulkan.command.CommandPool import io.computenode.cyfra.vulkan.memory.{Allocator, Buffer} +import io.computenode.cyfra.vulkan.util.Util.pushStack import izumi.reflect.Tag +import org.lwjgl.BufferUtils +import org.lwjgl.system.MemoryUtil import org.lwjgl.vulkan.VK10 import org.lwjgl.vulkan.VK10.{VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_BUFFER_USAGE_TRANSFER_SRC_BIT} @@ -60,7 +64,19 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) VkUniform[T]().tap(bindings += _) extension [Params, L <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, L, RL]) - override def execute(params: Params, layout: L): RL = executionHandler.handle(execution, params, layout) + override def execute(params: Params, layout: L): RL = executionHandler.handle(execution, params, layout)(using this) + + private def direct[T <: Value: {Tag, FromExpr}](buff: ByteBuffer): GUniform[T] = + GUniform[T](buff) + + def getInitProgramLayout: GProgram.InitProgramLayout = + new GProgram.InitProgramLayout: + extension (uniforms: GUniform.type) + def apply[T <: GStruct[T]: {Tag, FromExpr}](value: T): GUniform[T] = pushStack: stack => + val bb = value.productElement(0) match + case x: Int => MemoryUtil.memByteBuffer(stack.ints(x)) + case _ => ??? + direct(bb) private val bindings = mutable.Buffer[VkUniform[?] | VkBuffer[?]]() private[cyfra] def close(): Unit = bindings.map(getUnderlying).foreach(_.destroy()) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala index dbe5883d..dc2c4d2d 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala @@ -13,7 +13,7 @@ class VkCyfraRuntime extends CyfraRuntime: private val executionHandler = new ExecutionHandler(this) - val shaderCache = mutable.Map.empty[String, VkShader[?]] + private val shaderCache = mutable.Map.empty[String, VkShader[?]] private[cyfra] def getOrLoadProgram[Params, L <: Layout: LayoutStruct](program: GProgram[Params, L]): VkShader[L] = shaderCache.getOrElseUpdate(program.cacheKey, VkShader(program)).asInstanceOf[VkShader[L]] diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorPool.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorPool.scala index a7756743..093fb350 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorPool.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorPool.scala @@ -15,12 +15,18 @@ object DescriptorPool: val MAX_SETS = 100 private[cyfra] class DescriptorPool(using device: Device) extends VulkanObjectHandle: protected val handle: Long = pushStack: stack => - val descriptorPoolSize = VkDescriptorPoolSize.calloc(1, stack) + val descriptorPoolSize = VkDescriptorPoolSize.calloc(2, stack) descriptorPoolSize - .get(0) - .`type`(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) // TODO this is sus when using with uniform buffers + .get() + .`type`(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) .descriptorCount(2 * MAX_SETS) + descriptorPoolSize + .get() + .`type`(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER) + .descriptorCount(2 * MAX_SETS) + descriptorPoolSize.rewind() + val descriptorPoolCreateInfo = VkDescriptorPoolCreateInfo .calloc(stack) .sType$Default() From f5a72614739ede12f9250246052b600848d3c7d3 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Fri, 11 Jul 2025 06:22:34 +0200 Subject: [PATCH 09/20] running^ --- .../io/computenode/cyfra/core/GProgram.scala | 2 +- .../cyfra/core/layout/Layout.scala | 20 ++- .../cyfra/dsl/binding/GBinding.scala | 2 +- .../cyfra/samples/TestingStuff.scala | 26 ++-- .../cyfra/runtime/ExecutionHandler.scala | 114 +++++++++++++----- .../cyfra/runtime/VkAllocation.scala | 6 +- .../computenode/cyfra/runtime/VkUniform.scala | 4 +- 7 files changed, 130 insertions(+), 44 deletions(-) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala index 03c5e5cd..23d03ef4 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala @@ -38,7 +38,7 @@ object GProgram: case class StaticDispatch(size: WorkDimensions) extends ProgramDispatch - private[cyfra] case class BufferSizeSpec[T <: Value: {Tag, FromExpr}](size: Int) extends GBuffer[T] + private[cyfra] case class BufferSizeSpec[T <: Value: {Tag, FromExpr}](length: Int) extends GBuffer[T] private[cyfra] case class DynamicUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/Layout.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/Layout.scala index d03d858f..979b4410 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/Layout.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/Layout.scala @@ -1,6 +1,22 @@ package io.computenode.cyfra.core.layout +import io.computenode.cyfra.core.layout.Layout.IllegalLayoutElement import io.computenode.cyfra.dsl.Value -import io.computenode.cyfra.dsl.binding.GBuffer +import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer} -trait Layout +trait Layout: + self: Product => + + lazy val bindings: Seq[GBinding[?]] = + validateElements() + self.productIterator + .map(_.asInstanceOf[GBinding[?]]) + .toSeq + + private def validateElements(): Unit = + val invalidIndex = self.productIterator.indexWhere(!_.isInstanceOf[GBinding[?]]) + if invalidIndex != -1 then throw IllegalLayoutElement(self.productElementName(invalidIndex)) + +object Layout: + + case class IllegalLayoutElement(name: String) extends Exception(s"All Layout members must be GBindings, $name is not") diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala index 1e8d4985..896d53e4 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala @@ -23,6 +23,6 @@ trait GUniform[T <: Value: {Tag, FromExpr}] extends GBinding[T]: object GUniform: - case class ParamUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] + class ParamUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] def fromParams[T <: GStruct[T]: {Tag, FromExpr}] = ParamUniform[T]() 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 11067899..4c15dc23 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 @@ -80,7 +80,7 @@ object TestingStuff: layout => EmitProgramLayout(in = layout.inBuffer, out = layout.emitBuffer), ) .addProgram(filterProgram)( - params => FilterProgramParams(inSize = params.inSize, filterValue = params.filterValue), + params => FilterProgramParams(inSize = 2 * params.inSize, filterValue = params.filterValue), layout => FilterProgramLayout(in = layout.emitBuffer, out = layout.filterBuffer), ) @@ -88,20 +88,20 @@ object TestingStuff: def test = given runtime: VkCyfraRuntime = VkCyfraRuntime() - val filter = SpirvProgram[FilterProgramParams, FilterProgramLayout]( - "filter.spv", - layout = (il: InitProgramLayout) ?=> filterProgram.layout(il), - dispatch = filterProgram.dispatch, - ) - val emit = SpirvProgram[EmitProgramParams, EmitProgramLayout]( "emit.spv", layout = (il: InitProgramLayout) ?=> emitProgram.layout(il), dispatch = emitProgram.dispatch, ) - runtime.getOrLoadProgram(filter) + val filter = SpirvProgram[FilterProgramParams, FilterProgramLayout]( + "filter.spv", + layout = (il: InitProgramLayout) ?=> filterProgram.layout(il), + dispatch = filterProgram.dispatch, + ) + runtime.getOrLoadProgram(emit) + runtime.getOrLoadProgram(filter) val emitFilterParams = EmitFilterParams(inSize = 1024, emitN = 2, filterValue = 42) @@ -110,7 +110,7 @@ object TestingStuff: .map: region => emitFilterExecution.execute(emitFilterParams, region) - val data = (0 to 1024).toArray + val data = (0 until 1024).toArray val buffer = BufferUtils.createByteBuffer(data.length * 4) buffer.asIntBuffer().put(data).flip() @@ -123,3 +123,11 @@ object TestingStuff: ), onDone = layout => layout.filterBuffer.read(result), ) + + 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(_ == 42) + expected + .zip(actual) + .zipWithIndex + .foreach: + case ((e, a), i) => assert(e == a, s"Mismatch at index $i: expected $e, got $a") 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 53ab38f8..52ba0f2c 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 @@ -1,10 +1,11 @@ package io.computenode.cyfra.runtime import io.computenode.cyfra.core.GProgram.InitProgramLayout -import io.computenode.cyfra.core.SpirvProgram.ShaderLayout +import io.computenode.cyfra.core.SpirvProgram.* +import io.computenode.cyfra.core.binding.{BufferRef, UniformRef} import io.computenode.cyfra.core.{GExecution, GProgram} import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} -import io.computenode.cyfra.dsl.binding.GBinding +import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} import io.computenode.cyfra.runtime.ExecutionHandler.{Dispatch, DispatchType, ExecutionStep, PipelineBarrier, ShaderCall} import io.computenode.cyfra.runtime.ExecutionHandler.DispatchType.* import io.computenode.cyfra.utility.Utility.timed @@ -18,6 +19,9 @@ 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} import org.lwjgl.vulkan.{VkCommandBuffer, VkCommandBufferBeginInfo, VkDependencyInfo, VkMemoryBarrier2, VkSubmitInfo} +import scala.collection.immutable.{AbstractSeq, LinearSeq} +import scala.collection.mutable + class ExecutionHandler(runtime: VkCyfraRuntime): private val context = runtime.context import context.given @@ -67,30 +71,86 @@ class ExecutionHandler(runtime: VkCyfraRuntime): private def interpret[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L)(using VkAllocation, ): (RL, Seq[ShaderCall]) = - execution match - case GExecution.Pure() => (layout, Seq.empty) - case GExecution.Map(execution, map, cmap, cmapP) => - val cParams = cmapP(params) - val cLayout = cmap(layout) - val (prevLayout, calls) = interpret(execution, cParams, cLayout) - (map(prevLayout), calls) - case GExecution.FlatMap(execution, f) => - val (prevLayout, calls) = interpret(execution, params, layout) - val nextExecution = f(params, prevLayout) - val (prevLayout2, calls2) = interpret(nextExecution, params, layout) - (prevLayout2, calls ++ calls2) - case program: GProgram[Params, L] => - given LayoutStruct[L] = program.layoutStruct - val shader = runtime.getOrLoadProgram(program) - val initProgram: InitProgramLayout = summon[VkAllocation].getInitProgramLayout - val layoutInit = program.layout(initProgram)(params).asInstanceOf[L] - ??? - val dispatch = program.dispatch(layout, params) match - case GProgram.DynamicDispatch(buffer, offset) => DispatchType.Indirect(buffer, offset) - case GProgram.StaticDispatch(size) => DispatchType.Direct(size._1, size._2, size._3) - val rl = layout.asInstanceOf[RL] - (rl, Seq(ShaderCall(shader.underlying, shader.shaderBindings(layout), dispatch))) - case _ => ??? + val bindingsAcc: mutable.Map[GBinding[?], mutable.Buffer[GBinding[?]]] = mutable.Map.from(layout.bindings.map(x => (x, mutable.Buffer.empty))) + + def interpretImpl[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L): (RL, Seq[ShaderCall]) = + execution match + case GExecution.Pure() => (layout, Seq.empty) + case GExecution.Map(execution, map, cmap, cmapP) => + val cParams = cmapP(params) + val cLayout = cmap(layout) + val (prevLayout, calls) = interpretImpl(execution, cParams, cLayout) + (map(prevLayout), calls) + case GExecution.FlatMap(execution, f) => + val (prevLayout, calls) = interpretImpl(execution, params, layout) + val nextExecution = f(params, prevLayout) + val (prevLayout2, calls2) = interpretImpl(nextExecution, params, layout) + (prevLayout2, calls ++ calls2) + case program: GProgram[Params, L] => + val shader = + given LayoutStruct[L] = program.layoutStruct + runtime.getOrLoadProgram(program) + val layoutInit = + val initProgram: InitProgramLayout = summon[VkAllocation].getInitProgramLayout + program.layout(initProgram)(params).asInstanceOf[L] + layout.bindings + .zip(layoutInit.bindings) + .foreach: + case (binding, initBinding) => + bindingsAcc.getOrElseUpdate(binding, mutable.Buffer.empty).append(initBinding) + val dispatch = program.dispatch(layout, params) match + case GProgram.DynamicDispatch(buffer, offset) => DispatchType.Indirect(buffer, offset) + case GProgram.StaticDispatch(size) => DispatchType.Direct(size._1, size._2, size._3) + val rl = layout.asInstanceOf[RL] + (rl, Seq(ShaderCall(shader.underlying, shader.shaderBindings(layout), dispatch))) + case _ => ??? + + val (rl, steps) = interpretImpl(execution, params, layout) + val bindingsMap = bindingsAcc.view.mapValues(_.toSeq).map(x => (x._1, interpretBinding(x._1, x._2))).toMap + + val nextSteps = steps.map: + case ShaderCall(pipeline, layout, dispatch) => + val nextLayout = layout.map: + _.map: + case Binding(binding, operation) => Binding(bindingsMap(binding), operation) + val nextDispatch = dispatch match + case x: Direct => x + case Indirect(buffer, offset) => Indirect(bindingsMap(buffer), offset) + ShaderCall(pipeline, nextLayout, nextDispatch) + + (rl, nextSteps) + + private def interpretBinding(binding: GBinding[?], limiters: Seq[GBinding[?]])(using VkAllocation): GBinding[?] = + val bindings = limiters.appended(binding) + binding match + case buffer: GBuffer[?] => + val (allocations, sizeSpec) = bindings.partitionMap: + case x: VkBuffer[?] => Left(x) + case x: GProgram.BufferSizeSpec[?] => Right(x) + case _ => throw new IllegalArgumentException(s"Unsupported binding type: ${binding.getClass.getName}") + if allocations.size > 1 then throw new IllegalArgumentException(s"Multiple allocations for buffer: (${allocations.size})") + val all = allocations.headOption + + val maxi = sizeSpec.map(_.length).distinct + if maxi.size > 1 then throw new IllegalArgumentException(s"Multiple conflicting sizes for buffer: ($maxi)") + val max = maxi.headOption + + (all, max) match + case (Some(buffer: VkBuffer[?]), Some(length)) => + assert(buffer.length == length, s"Buffer length mismatch: ${buffer.length} != $length for binding $binding") + buffer + case (Some(buffer: VkBuffer[?]), None) => buffer + case (None, Some(length)) => ??? + case (None, None) => throw new IllegalArgumentException(s"Cannot create buffer without size or allocation: $binding") + + case uniform: GUniform[?] => + val allocations = bindings.filter: + case uniform: VkUniform[?] => true + case GProgram.DynamicUniform() => false + case _: GUniform.ParamUniform[?] => false + case _ => throw new IllegalArgumentException(s"Unsupported binding type: ${binding.getClass.getName}") + if allocations.size > 1 then throw new IllegalArgumentException(s"Multiple allocations for uniform: (${allocations.size})") + allocations.headOption.getOrElse(throw new IllegalArgumentException(s"Uniform never allocated: $binding")) private def recordCommandBuffer(steps: Seq[ExecutionStep]): VkCommandBuffer = pushStack: stack => val commandBuffer = commandPool.createCommandBuffer() @@ -102,7 +162,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime): check(vkBeginCommandBuffer(commandBuffer, commandBufferBeginInfo), "Failed to begin recording command buffer") steps.foreach: - case PipelineBarrier => + case PipelineBarrier => // TODO WaR and WaW errors val memoryBarrier = VkMemoryBarrier2 .calloc(1, stack) .sType$Default() 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 c07b5417..c5a0dfef 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 @@ -3,6 +3,7 @@ package io.computenode.cyfra.runtime import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} import io.computenode.cyfra.core.{Allocation, GExecution, GProgram} import io.computenode.cyfra.core.SpirvProgram +import io.computenode.cyfra.dsl.Expression.ConstInt32 import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} @@ -12,6 +13,7 @@ import io.computenode.cyfra.spirv.SpirvTypes.typeStride 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 izumi.reflect.Tag import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil @@ -74,8 +76,8 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) extension (uniforms: GUniform.type) def apply[T <: GStruct[T]: {Tag, FromExpr}](value: T): GUniform[T] = pushStack: stack => val bb = value.productElement(0) match - case x: Int => MemoryUtil.memByteBuffer(stack.ints(x)) - case _ => ??? + case Int32(tree: ConstInt32) => MemoryUtil.memByteBuffer(stack.ints(tree.value)) + case _ => ??? direct(bb) private val bindings = mutable.Buffer[VkUniform[?] | VkBuffer[?]]() 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 index ec226952..f85e805e 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala @@ -10,13 +10,13 @@ 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 = typeStride(summon[Tag[T]]) + val sizeOfT: Int = 4 // typeStride(summon[Tag[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 sizeOfT = 4 // typeStride(summon[Tag[T]]) val buffer = new Buffer.DeviceBuffer(sizeOfT, UsageFlags) new VkUniform[T](buffer) From 117791e83404b8f0bbbc93e815eee913152f02fe Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Fri, 11 Jul 2025 11:54:27 +0200 Subject: [PATCH 10/20] fizes --- .../scala/io/computenode/cyfra/samples/TestingStuff.scala | 4 ++-- .../io/computenode/cyfra/runtime/ExecutionHandler.scala | 7 ++++--- 2 files changed, 6 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 4c15dc23..07be2f04 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 @@ -34,7 +34,7 @@ object TestingStuff: out = GBuffer[Int32](params.inSize * params.emitN), args = GUniform(EmitProgramUniform(params.emitN)), ), - dispatch = (layout, args) => GProgram.StaticDispatch((args.inSize, 1, 1)), + dispatch = (layout, args) => GProgram.StaticDispatch((args.inSize / 128, 1, 1)), ): layout => val EmitProgramUniform(emitN) = layout.args.read val invocId = GIO.invocationId @@ -59,7 +59,7 @@ object TestingStuff: out = GBuffer[GBoolean](params.inSize), params = GUniform(FilterProgramUniform(params.filterValue)), ), - dispatch = (layout, args) => GProgram.StaticDispatch((args.inSize, 1, 1)), + dispatch = (layout, args) => GProgram.StaticDispatch((args.inSize / 128, 1, 1)), ): layout => val invocId = GIO.invocationId val element = GIO.read(layout.in, invocId) 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 52ba0f2c..d85379d1 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 @@ -26,8 +26,8 @@ class ExecutionHandler(runtime: VkCyfraRuntime): private val context = runtime.context import context.given - private val queue: Queue = context.computeQueue - private val descriptorPool: DescriptorPool = context.descriptorPool + private val queue: Queue = context.computeQueue // TODO queue multithreading + private val descriptorPool: DescriptorPool = context.descriptorPool // TODO descriptor pool manager private val commandPool: CommandPool = context.commandPool def handle[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L)(using VkAllocation): RL = pushStack: @@ -79,6 +79,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime): case GExecution.Map(execution, map, cmap, cmapP) => val cParams = cmapP(params) val cLayout = cmap(layout) + cLayout.bindings.foreach(x => bindingsAcc.getOrElseUpdate(x, mutable.Buffer.empty)) val (prevLayout, calls) = interpretImpl(execution, cParams, cLayout) (map(prevLayout), calls) case GExecution.FlatMap(execution, f) => @@ -97,7 +98,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime): .zip(layoutInit.bindings) .foreach: case (binding, initBinding) => - bindingsAcc.getOrElseUpdate(binding, mutable.Buffer.empty).append(initBinding) + bindingsAcc(binding).append(initBinding) val dispatch = program.dispatch(layout, params) match case GProgram.DynamicDispatch(buffer, offset) => DispatchType.Indirect(buffer, offset) case GProgram.StaticDispatch(size) => DispatchType.Direct(size._1, size._2, size._3) From c5292fbd58c8558787627ae7f326fb5e5d6fed01 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Fri, 11 Jul 2025 13:02:16 +0200 Subject: [PATCH 11/20] surender --- .../scala/io/computenode/cyfra/samples/TestingStuff.scala | 1 + .../io/computenode/cyfra/runtime/ExecutionHandler.scala | 5 ++++- .../scala/io/computenode/cyfra/runtime/VkAllocation.scala | 4 +++- .../scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala | 4 ++++ .../computenode/cyfra/vulkan/compute/ComputePipeline.scala | 2 +- .../io/computenode/cyfra/vulkan/core/DebugCallback.scala | 2 +- 6 files changed, 14 insertions(+), 4 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 07be2f04..6b87458e 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 @@ -123,6 +123,7 @@ object TestingStuff: ), onDone = layout => layout.filterBuffer.read(result), ) + runtime.close() 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(_ == 42) 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 d85379d1..d84988d0 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 @@ -64,7 +64,10 @@ class ExecutionHandler(runtime: VkCyfraRuntime): val fence = new Fence() timed("Vulkan render command"): check(vkQueueSubmit(queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") - fence.block().destroy() + fence.block() + fence.destroy() + + commandPool.freeCommandBuffer(commandBuffer) result 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 c5a0dfef..9565f5d9 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 @@ -81,7 +81,9 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) direct(bb) private val bindings = mutable.Buffer[VkUniform[?] | VkBuffer[?]]() - private[cyfra] def close(): Unit = bindings.map(getUnderlying).foreach(_.destroy()) + private[cyfra] def close(): Unit = + bindings.map(getUnderlying).foreach(_.destroy()) + stagingBuffer.foreach(_.destroy()) private var stagingBuffer: Option[Buffer.HostBuffer] = None private def getStagingBuffer(size: Int): Buffer.HostBuffer = diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala index dc2c4d2d..f597c495 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala @@ -21,3 +21,7 @@ class VkCyfraRuntime extends CyfraRuntime: val allocation = new VkAllocation(context.commandPool, executionHandler) f(allocation) allocation.close() + + def close(): Unit = + shaderCache.values.foreach(_.underlying.destroy()) + context.destroy() diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala index 7f0f81f4..188c1e0b 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala @@ -77,7 +77,7 @@ private[cyfra] class ComputePipeline(shaderCode: ByteBuffer, functionName: Strin vkDestroyPipeline(device.get, handle, null) vkDestroyPipelineLayout(device.get, pipelineLayout.id, null) pipelineLayout.sets.map(_.id).foreach(vkDestroyDescriptorSetLayout(device.get, _, null)) - vkDestroyShaderModule(device.get, handle, null) + vkDestroyShaderModule(device.get, shader, null) private def createDescriptorSetLayout(set: DescriptorSetInfo): Long = pushStack: stack => val descriptorSetLayoutBindings = VkDescriptorSetLayoutBinding.calloc(set.descriptors.length, stack) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/DebugCallback.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/DebugCallback.scala index f3e83712..3721e9b3 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/DebugCallback.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/DebugCallback.scala @@ -35,7 +35,7 @@ private[cyfra] class DebugCallback(instance: Instance) extends VulkanObjectHandl case VK_DEBUG_REPORT_DEBUG_BIT_EXT => logger.debug(decodedMessage) case VK_DEBUG_REPORT_ERROR_BIT_EXT => - logger.error(decodedMessage, new RuntimeException()) + logger.error(decodedMessage) case VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT => logger.warn(decodedMessage) case VK_DEBUG_REPORT_INFORMATION_BIT_EXT => From 8c51868cb2fb0f7e05f9bd81d989b885aeec13c4 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Fri, 11 Jul 2025 18:30:44 +0200 Subject: [PATCH 12/20] working --- .../io/computenode/cyfra/core/GProgram.scala | 5 +++- .../computenode/cyfra/core/SpirvProgram.scala | 2 +- .../cyfra/samples/TestingStuff.scala | 26 +++++-------------- .../computenode/cyfra/runtime/VkShader.scala | 11 ++++++-- .../vulkan/compute/ComputePipeline.scala | 2 -- .../cyfra/vulkan/SequenceExecutorTest.scala | 2 +- 6 files changed, 21 insertions(+), 27 deletions(-) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala index 23d03ef4..ff0d02a0 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala @@ -28,7 +28,10 @@ object GProgram: dispatch: (L, Params) => ProgramDispatch, workgroupSize: WorkDimensions, ) extends GProgram[Params, L]: - private[cyfra] def cacheKey: String = if layoutStruct.elementTypes.contains(summon[Tag[GBoolean]]) then "filter" else "emit" + private[cyfra] def cacheKey: String = layoutStruct.elementTypes match + case x if x.size == 2 => "addOne" + case x if x.contains(summon[Tag[GBoolean]]) => "filter" + case _ => "emit" type WorkDimensions = (Int, Int, Int) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala index 5ef0d5a7..c4533840 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala @@ -53,7 +53,7 @@ object SpirvProgram: x.substring(0, x.lastIndexOf('.')) new SpirvProgram[Params, L]((il: InitProgramLayout) => layout(using il), dispatch, workgroupSize, code, main, f, cacheKey) - private def loadShader(path: String, classLoader: ClassLoader = getClass.getClassLoader): Try[ByteBuffer] = + def loadShader(path: String, classLoader: ClassLoader = getClass.getClassLoader): Try[ByteBuffer] = Using.Manager: use => val file = new File(Objects.requireNonNull(classLoader.getResource(path)).getFile) val fis = use(new FileInputStream(file)) 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 6b87458e..30e649ba 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 @@ -1,16 +1,16 @@ package io.computenode.cyfra.samples -import io.computenode.cyfra.core.GProgram.InitProgramLayout import io.computenode.cyfra.core.archive.GContext -import io.computenode.cyfra.core.{CyfraRuntime, GBufferRegion, GExecution, GProgram, SpirvProgram} import io.computenode.cyfra.core.layout.* +import io.computenode.cyfra.core.{GBufferRegion, GExecution, GProgram} import io.computenode.cyfra.dsl.Value.{GBoolean, Int32} import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} import io.computenode.cyfra.dsl.gio.GIO import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.{*, given} -import org.lwjgl.BufferUtils import io.computenode.cyfra.runtime.VkCyfraRuntime +import org.lwjgl.BufferUtils +import org.lwjgl.system.MemoryUtil object TestingStuff: given GContext = GContext() @@ -88,21 +88,6 @@ object TestingStuff: def test = given runtime: VkCyfraRuntime = VkCyfraRuntime() - val emit = SpirvProgram[EmitProgramParams, EmitProgramLayout]( - "emit.spv", - layout = (il: InitProgramLayout) ?=> emitProgram.layout(il), - dispatch = emitProgram.dispatch, - ) - - val filter = SpirvProgram[FilterProgramParams, FilterProgramLayout]( - "filter.spv", - layout = (il: InitProgramLayout) ?=> filterProgram.layout(il), - dispatch = filterProgram.dispatch, - ) - - runtime.getOrLoadProgram(emit) - runtime.getOrLoadProgram(filter) - val emitFilterParams = EmitFilterParams(inSize = 1024, emitN = 2, filterValue = 42) val region = GBufferRegion @@ -114,14 +99,15 @@ object TestingStuff: val buffer = BufferUtils.createByteBuffer(data.length * 4) buffer.asIntBuffer().put(data).flip() - val result = BufferUtils.createByteBuffer(data.length * 2) + val result = BufferUtils.createIntBuffer(data.length * 2) + val rbb = MemoryUtil.memByteBuffer(result) region.runUnsafe( init = EmitFilterLayout( inBuffer = GBuffer[Int32](buffer), emitBuffer = GBuffer[Int32](data.length * 2), filterBuffer = GBuffer[GBoolean](data.length * 2), ), - onDone = layout => layout.filterBuffer.read(result), + onDone = layout => layout.filterBuffer.read(rbb), ) runtime.close() diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala index b4308572..40784d49 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala @@ -3,7 +3,7 @@ package io.computenode.cyfra.runtime import io.computenode.cyfra.core.SpirvProgram import io.computenode.cyfra.core.SpirvProgram.* import io.computenode.cyfra.core.GProgram -import io.computenode.cyfra.core.GProgram.GioProgram +import io.computenode.cyfra.core.GProgram.{GioProgram, InitProgramLayout} import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} import io.computenode.cyfra.vulkan.compute.ComputePipeline @@ -11,6 +11,8 @@ import io.computenode.cyfra.vulkan.compute.ComputePipeline.* import io.computenode.cyfra.vulkan.core.Device import izumi.reflect.Tag +import scala.util.{Failure, Success} + case class VkShader[L](underlying: ComputePipeline, shaderBindings: L => ShaderLayout) object VkShader: @@ -33,4 +35,9 @@ object VkShader: val pipeline = ComputePipeline(code, entryPoint, LayoutInfo(sets)) VkShader(pipeline, shaderBindings) - def compile[Params, L <: Layout: LayoutStruct](program: GioProgram[Params, L]): SpirvProgram[Params, L] = ??? + def compile[Params, L <: Layout: LayoutStruct](program: GioProgram[Params, L]): SpirvProgram[Params, L] = + val GioProgram(_, layout, dispatch, workgroupSize) = program + val name = program.cacheKey + ".spv" + loadShader(name) match + case Failure(exception) => ??? + case Success(value) => SpirvProgram(name, (il: InitProgramLayout) ?=> layout(il), dispatch) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala index 188c1e0b..2fe2c35d 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/compute/ComputePipeline.scala @@ -106,8 +106,6 @@ private[cyfra] class ComputePipeline(shaderCode: ByteBuffer, functionName: Strin pDescriptorSetLayout.get(0) object ComputePipeline: - def loadShader(path: String): Try[ByteBuffer] = ??? - private[cyfra] case class PipelineLayout(id: Long, sets: Seq[DescriptorSetLayout]) private[cyfra] case class DescriptorSetLayout(id: Long, set: DescriptorSetInfo) diff --git a/cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.scala b/cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.scala index fec9746b..1a90b83b 100644 --- a/cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.scala +++ b/cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.scala @@ -16,7 +16,7 @@ class SequenceExecutorTest extends FunSuite: import vulkanContext.given test("Memory barrier"): - val code = ComputePipeline.loadShader("copy_test.spv").get + val code = ??? val layout = LayoutInfo(Seq(DescriptorSetInfo(Seq(DescriptorInfo(StorageBuffer))), DescriptorSetInfo(Seq(DescriptorInfo(StorageBuffer))))) val copy1 = new ComputePipeline(code, "main", layout) From 10557a992de7096b28b2d2c4051fb74ce0623d12 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Fri, 11 Jul 2025 18:39:24 +0200 Subject: [PATCH 13/20] final?^ --- .../io/computenode/cyfra/core/GProgram.scala | 24 +------------------ .../core/{SpirvProgram.scala => exile.scala} | 24 +++++++++++++++++-- .../cyfra/samples/TestingStuff.scala | 8 +++---- .../computenode/cyfra/runtime/VkShader.scala | 5 ++-- 4 files changed, 29 insertions(+), 32 deletions(-) rename cyfra-core/src/main/scala/io/computenode/cyfra/core/{SpirvProgram.scala => exile.scala} (68%) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala index ff0d02a0..3dee2631 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala @@ -21,28 +21,13 @@ trait GProgram[Params, L <: Layout: LayoutStruct] extends GExecution[Params, L, private[cyfra] def cacheKey: String // TODO better type object GProgram: - - case class GioProgram[Params, L <: Layout: LayoutStruct]( - body: L => GIO[?], - layout: InitProgramLayout => Params => L, - dispatch: (L, Params) => ProgramDispatch, - workgroupSize: WorkDimensions, - ) extends GProgram[Params, L]: - private[cyfra] def cacheKey: String = layoutStruct.elementTypes match - case x if x.size == 2 => "addOne" - case x if x.contains(summon[Tag[GBoolean]]) => "filter" - case _ => "emit" - type WorkDimensions = (Int, Int, Int) sealed trait ProgramDispatch - case class DynamicDispatch[L <: Layout](buffer: GBinding[?], offset: Int) extends ProgramDispatch - case class StaticDispatch(size: WorkDimensions) extends ProgramDispatch private[cyfra] case class BufferSizeSpec[T <: Value: {Tag, FromExpr}](length: Int) extends GBuffer[T] - private[cyfra] case class DynamicUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] trait InitProgramLayout: @@ -53,11 +38,4 @@ object GProgram: extension (uniforms: GUniform.type) def apply[T <: GStruct[T]: {Tag, FromExpr}](): GUniform[T] = DynamicUniform[T]() - def apply[T <: GStruct[T]: {Tag, FromExpr}](value: T): GUniform[T] - - def apply[Params, L <: Layout: LayoutStruct]( - layout: InitProgramLayout ?=> Params => L, - dispatch: (L, Params) => ProgramDispatch, - workgroupSize: WorkDimensions = (128, 1, 1), - )(body: L => GIO[?]): GProgram[Params, L] = - new GioProgram[Params, L](body, s => layout(using s), dispatch, workgroupSize) + def apply[T <: GStruct[T]: {Tag, FromExpr}](value: T): GUniform[T] \ No newline at end of file diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/exile.scala similarity index 68% rename from cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala rename to cyfra-core/src/main/scala/io/computenode/cyfra/core/exile.scala index c4533840..a5ac1b9b 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/exile.scala @@ -2,12 +2,13 @@ package io.computenode.cyfra.core import io.computenode.cyfra.core.layout.Layout import io.computenode.cyfra.core.layout.LayoutStruct -import io.computenode.cyfra.core.GProgram.{GioProgram, InitProgramLayout, ProgramDispatch, WorkDimensions} +import io.computenode.cyfra.core.GProgram.{InitProgramLayout, ProgramDispatch, WorkDimensions} import io.computenode.cyfra.core.SpirvProgram.Operation.ReadWrite import io.computenode.cyfra.core.SpirvProgram.{Binding, ShaderLayout} import io.computenode.cyfra.dsl.Value -import io.computenode.cyfra.dsl.Value.FromExpr +import io.computenode.cyfra.dsl.Value.{FromExpr, GBoolean} import io.computenode.cyfra.dsl.binding.GBinding +import io.computenode.cyfra.dsl.gio.GIO import izumi.reflect.Tag import java.io.File @@ -59,3 +60,22 @@ object SpirvProgram: val fis = use(new FileInputStream(file)) val fc = use(fis.getChannel) fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()) + +case class GioProgram[Params, L <: Layout: LayoutStruct]( + body: L => GIO[?], + layout: InitProgramLayout => Params => L, + dispatch: (L, Params) => ProgramDispatch, + workgroupSize: WorkDimensions, +) extends GProgram[Params, L]: + private[cyfra] def cacheKey: String = layoutStruct.elementTypes match + case x if x.size == 2 => "addOne" + case x if x.contains(summon[Tag[GBoolean]]) => "filter" + case _ => "emit" + +object GioProgram: + def apply[Params, L <: Layout: LayoutStruct]( + layout: InitProgramLayout ?=> Params => L, + dispatch: (L, Params) => ProgramDispatch, + workgroupSize: WorkDimensions = (128, 1, 1), + )(body: L => GIO[?]): GProgram[Params, L] = + new GioProgram[Params, L](body, s => layout(using s), dispatch, workgroupSize) 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 30e649ba..d4bf6016 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 @@ -2,7 +2,7 @@ package io.computenode.cyfra.samples import io.computenode.cyfra.core.archive.GContext import io.computenode.cyfra.core.layout.* -import io.computenode.cyfra.core.{GBufferRegion, GExecution, GProgram} +import io.computenode.cyfra.core.{GBufferRegion, GExecution, GProgram, GioProgram} import io.computenode.cyfra.dsl.Value.{GBoolean, Int32} import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} import io.computenode.cyfra.dsl.gio.GIO @@ -27,7 +27,7 @@ object TestingStuff: args: GUniform[EmitProgramUniform] = GUniform.fromParams, // todo will be different in the future ) extends Layout - val emitProgram = GProgram[EmitProgramParams, EmitProgramLayout]( + val emitProgram = GioProgram[EmitProgramParams, EmitProgramLayout]( layout = params => EmitProgramLayout( in = GBuffer[Int32](params.inSize), @@ -52,7 +52,7 @@ object TestingStuff: case class FilterProgramLayout(in: GBuffer[Int32], out: GBuffer[GBoolean], params: GUniform[FilterProgramUniform] = GUniform.fromParams) extends Layout - val filterProgram = GProgram[FilterProgramParams, FilterProgramLayout]( + val filterProgram = GioProgram[FilterProgramParams, FilterProgramLayout]( layout = params => FilterProgramLayout( in = GBuffer[Int32](params.inSize), @@ -112,7 +112,7 @@ object TestingStuff: runtime.close() 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(_ == 42) + val expected = (0 until 1024).flatMap(x => Seq.fill(emitFilterParams.emitN)(x)).map(_ == emitFilterParams.filterValue) expected .zip(actual) .zipWithIndex diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala index 40784d49..28e75ce0 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala @@ -1,9 +1,8 @@ package io.computenode.cyfra.runtime -import io.computenode.cyfra.core.SpirvProgram +import io.computenode.cyfra.core.{GProgram, GioProgram, SpirvProgram} import io.computenode.cyfra.core.SpirvProgram.* -import io.computenode.cyfra.core.GProgram -import io.computenode.cyfra.core.GProgram.{GioProgram, InitProgramLayout} +import io.computenode.cyfra.core.GProgram.InitProgramLayout import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} import io.computenode.cyfra.vulkan.compute.ComputePipeline From 2c7963008a682da53eb51a331c38fd1d86465e8c Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Sat, 12 Jul 2025 00:49:02 +0200 Subject: [PATCH 14/20] final test --- .../io/computenode/cyfra/core/exile.scala | 2 +- cyfra-examples/src/main/resources/addOne.comp | 48 ++++++++ .../cyfra/samples/TestingStuff.scala | 107 ++++++++++++++++++ 3 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 cyfra-examples/src/main/resources/addOne.comp diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/exile.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/exile.scala index a5ac1b9b..56ac02ed 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/exile.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/exile.scala @@ -68,7 +68,7 @@ case class GioProgram[Params, L <: Layout: LayoutStruct]( workgroupSize: WorkDimensions, ) extends GProgram[Params, L]: private[cyfra] def cacheKey: String = layoutStruct.elementTypes match - case x if x.size == 2 => "addOne" + case x if x.size == 12 => "addOne" case x if x.contains(summon[Tag[GBoolean]]) => "filter" case _ => "emit" diff --git a/cyfra-examples/src/main/resources/addOne.comp b/cyfra-examples/src/main/resources/addOne.comp new file mode 100644 index 00000000..091de31f --- /dev/null +++ b/cyfra-examples/src/main/resources/addOne.comp @@ -0,0 +1,48 @@ +#version 450 + +layout (local_size_x = 128, local_size_y = 1, local_size_z = 1) in; + +layout (set = 0, binding = 0) buffer In1 { + int in1[]; +}; +layout (set = 0, binding = 1) buffer In2 { + int in2[]; +}; +layout (set = 0, binding = 2) buffer In3 { + int in3[]; +}; +layout (set = 0, binding = 3) buffer In4 { + int in4[]; +}; +layout (set = 0, binding = 4) buffer In5 { + int in5[]; +}; +layout (set = 0, binding = 5) buffer Out1 { + int out1[]; +}; +layout (set = 0, binding = 6) buffer Out2 { + int out2[]; +}; +layout (set = 0, binding = 7) buffer Out3 { + int out3[]; +}; +layout (set = 0, binding = 8) buffer Out4 { + int out4[]; +}; +layout (set = 0, binding = 9) buffer Out5 { + int out5[]; +}; +layout (set = 0, binding = 10) uniform U1 { + int a; +}; +layout (set = 0, binding = 11) uniform U2 { + int b; +}; +void main(void) { + uint index = gl_GlobalInvocationID.x; + out1[index] = in1[index] + a + b; + out2[index] = in2[index] + a + b; + out3[index] = in3[index] + a + b; + out4[index] = in4[index] + a + b; + out5[index] = in5[index] + a + b; +} 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 d4bf6016..ae24b8bc 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,3 +118,110 @@ object TestingStuff: .zipWithIndex .foreach: case ((e, a), i) => assert(e == a, s"Mismatch at index $i: expected $e, got $a") + +// Test case: Use one program 10 times, copying values from five input buffers to five output buffers and adding values from two uniforms + case class AddProgramParams(bufferSize: Int, addA: Int, addB: Int) + case class AddProgramUniform(a: Int32) extends GStruct[AddProgramUniform] + case class AddProgramLayout( + in1: GBuffer[Int32], + in2: GBuffer[Int32], + in3: GBuffer[Int32], + in4: GBuffer[Int32], + in5: GBuffer[Int32], + out1: GBuffer[Int32], + out2: GBuffer[Int32], + out3: GBuffer[Int32], + out4: GBuffer[Int32], + out5: GBuffer[Int32], + u1: GUniform[AddProgramUniform] = GUniform.fromParams, + u2: GUniform[AddProgramUniform] = GUniform.fromParams, + ) extends Layout + + case class AddProgramExecLayout( + in1: GBuffer[Int32], + in2: GBuffer[Int32], + in3: GBuffer[Int32], + in4: GBuffer[Int32], + in5: GBuffer[Int32], + out1: GBuffer[Int32], + out2: GBuffer[Int32], + out3: GBuffer[Int32], + out4: GBuffer[Int32], + out5: GBuffer[Int32], + ) extends Layout + + val addProgram: GProgram[AddProgramParams, AddProgramLayout] = GioProgram[AddProgramParams, AddProgramLayout]( + layout = params => + AddProgramLayout( + in1 = GBuffer[Int32](params.bufferSize), + in2 = GBuffer[Int32](params.bufferSize), + in3 = GBuffer[Int32](params.bufferSize), + in4 = GBuffer[Int32](params.bufferSize), + in5 = GBuffer[Int32](params.bufferSize), + out1 = GBuffer[Int32](params.bufferSize), + out2 = GBuffer[Int32](params.bufferSize), + out3 = GBuffer[Int32](params.bufferSize), + out4 = GBuffer[Int32](params.bufferSize), + out5 = GBuffer[Int32](params.bufferSize), + u1 = GUniform(AddProgramUniform(params.addA)), + u2 = GUniform(AddProgramUniform(params.addB)), + ), + dispatch = (layout, args) => GProgram.StaticDispatch((args.bufferSize / 128, 1, 1)), + )(_ => ???) + def swap(l: AddProgramLayout): AddProgramLayout = + val AddProgramLayout(in1, in2, in3, in4, in5, out1, out2, out3, out4, out5, u1, u2) = l + AddProgramLayout(out1, out2, out3, out4, out5, in1, in2, in3, in4, in5, u1, u2) + + def fromExecLayout(l: AddProgramExecLayout): AddProgramLayout = + val AddProgramExecLayout(in1, in2, in3, in4, in5, out1, out2, out3, out4, out5) = l + AddProgramLayout(in1, in2, in3, in4, in5, out1, out2, out3, out4, out5) + + val execution = (0 until 11).foldLeft( + GExecution[AddProgramParams, AddProgramExecLayout]().asInstanceOf[GExecution[AddProgramParams, AddProgramExecLayout, AddProgramExecLayout]], + )((x, i) => + if i % 2 == 0 then x.addProgram(addProgram)(mapParams = identity[AddProgramParams], mapLayout = fromExecLayout) + else x.addProgram(addProgram)(mapParams = identity, mapLayout = x => swap(fromExecLayout(x))), + ) + + @main + def testAddProgram10Times = + given runtime: VkCyfraRuntime = VkCyfraRuntime() + val bufferSize = 1280 + val params = AddProgramParams(bufferSize, addA = 3, addB = 7) + val region = GBufferRegion + .allocate[AddProgramExecLayout] + .map: region => + execution.execute(params, region) + val inData = (0 until bufferSize).toArray + val inBuffers = List.fill(5)(BufferUtils.createByteBuffer(bufferSize * 4)) + inBuffers.foreach(_.asIntBuffer().put(inData).flip()) + val outBuffers = List.fill(5)(BufferUtils.createIntBuffer(bufferSize)) + val rbbList = outBuffers.map(MemoryUtil.memByteBuffer) + region.runUnsafe( + init = AddProgramExecLayout( + in1 = GBuffer[Int32](inBuffers(0)), + in2 = GBuffer[Int32](inBuffers(1)), + in3 = GBuffer[Int32](inBuffers(2)), + in4 = GBuffer[Int32](inBuffers(3)), + in5 = GBuffer[Int32](inBuffers(4)), + out1 = GBuffer[Int32](bufferSize), + out2 = GBuffer[Int32](bufferSize), + out3 = GBuffer[Int32](bufferSize), + out4 = GBuffer[Int32](bufferSize), + out5 = GBuffer[Int32](bufferSize), + ), + onDone = layout => { + layout.out1.read(rbbList(0)) + layout.out2.read(rbbList(1)) + layout.out3.read(rbbList(2)) + layout.out4.read(rbbList(3)) + layout.out5.read(rbbList(4)) + }, + ) + runtime.close() + val expected = inData.map(_ + 11 * (params.addA + params.addB)) + outBuffers.foreach { buf => + (0 until bufferSize).foreach { i => + assert(buf.get(i) == expected(i), s"Mismatch at index $i: expected ${expected(i)}, got ${buf.get(i)}") + } + } From 00f6265c0e3a99744edc6100ecb44207cd55fdbf Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Sat, 12 Jul 2025 11:46:54 +0200 Subject: [PATCH 15/20] final^ --- .../computenode/cyfra/core/Allocation.scala | 2 +- .../cyfra/core/GBufferRegion.scala | 2 +- .../computenode/cyfra/core/GExecution.scala | 28 ++-- .../io/computenode/cyfra/core/GProgram.scala | 10 +- .../io/computenode/cyfra/core/exile.scala | 7 +- .../cyfra/runtime/ExecutionHandler.scala | 62 ++++---- .../cyfra/runtime/VkAllocation.scala | 6 +- .../vulkan/executor/SequenceExecutor.scala | 150 ------------------ .../src/test/resources/compileAll.ps1 | 4 - cyfra-vulkan/src/test/resources/compileAll.sh | 7 - .../src/test/resources/copy_test.comp | 15 -- .../cyfra/vulkan/SequenceExecutorTest.scala | 15 +- 12 files changed, 67 insertions(+), 241 deletions(-) delete mode 100644 cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/SequenceExecutor.scala delete mode 100644 cyfra-vulkan/src/test/resources/compileAll.ps1 delete mode 100644 cyfra-vulkan/src/test/resources/compileAll.sh delete mode 100644 cyfra-vulkan/src/test/resources/copy_test.comp 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 a8b538bd..7d79e5b0 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 @@ -15,7 +15,7 @@ trait Allocation: def write(bb: ByteBuffer, offset: Int = 0, size: Int = -1): Unit - extension [Params, L <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, L, RL]) def execute(params: Params, layout: L): RL + extension [Params, EL <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, EL, RL]) def execute(params: Params, layout: EL): RL extension (buffers: GBuffer.type) def apply[T <: Value: {Tag, FromExpr}](size: Int): GBuffer[T] 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 c7e2376c..78bf8734 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,7 +1,7 @@ package io.computenode.cyfra.core import io.computenode.cyfra.core.Allocation -import io.computenode.cyfra.core.GProgram.BufferSizeSpec +import io.computenode.cyfra.core.GProgram.BufferLengthSpec import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala index 1d1aa7fb..e757f7f8 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala @@ -18,7 +18,7 @@ trait GExecution[-Params, ExecLayout <: Layout, +ResLayout <: Layout]: def map[NRL <: Layout](f: ResLayout => NRL): GExecution[Params, ExecLayout, NRL] = Map(this, f, identity, identity) - def contramap[NL <: Layout](f: NL => ExecLayout): GExecution[Params, NL, ResLayout] = + def contramap[NEL <: Layout](f: NEL => ExecLayout): GExecution[Params, NEL, ResLayout] = Map(this, identity, f, identity) def contramapParams[NP](f: NP => Params): GExecution[NP, ExecLayout, ResLayout] = @@ -35,28 +35,28 @@ object GExecution: def apply[Params, L <: Layout]() = Pure[Params, L]() - def forParams[Params, L <: Layout, RL <: Layout](f: Params => GExecution[Params, L, RL]): GExecution[Params, L, RL] = - FlatMap[Params, L, L, RL](Pure[Params, L](), (params: Params, _: L) => f(params)) + def forParams[Params, EL <: Layout, RL <: Layout](f: Params => GExecution[Params, EL, RL]): GExecution[Params, EL, RL] = + FlatMap[Params, EL, EL, RL](Pure[Params, EL](), (params: Params, _: EL) => f(params)) case class Pure[Params, L <: Layout]() extends GExecution[Params, L, L] - case class FlatMap[Params, L <: Layout, RL <: Layout, NRL <: Layout]( - execution: GExecution[Params, L, RL], - f: (Params, RL) => GExecution[Params, L, NRL], - ) extends GExecution[Params, L, NRL] + case class FlatMap[Params, EL <: Layout, RL <: Layout, NRL <: Layout]( + execution: GExecution[Params, EL, RL], + f: (Params, RL) => GExecution[Params, EL, NRL], + ) extends GExecution[Params, EL, NRL] - case class Map[P, NP, L <: Layout, NL <: Layout, RL <: Layout, NRL <: Layout]( - execution: GExecution[P, L, RL], + case class Map[P, NP, EL <: Layout, NEL <: Layout, RL <: Layout, NRL <: Layout]( + execution: GExecution[P, EL, RL], mapResult: RL => NRL, - contramapLayout: NL => L, + contramapLayout: NEL => EL, contramapParams: NP => P, - ) extends GExecution[NP, NL, NRL]: + ) extends GExecution[NP, NEL, NRL]: - override def map[NNRL <: Layout](f: NRL => NNRL): GExecution[NP, NL, NNRL] = + override def map[NNRL <: Layout](f: NRL => NNRL): GExecution[NP, NEL, NNRL] = Map(execution, mapResult andThen f, contramapLayout, contramapParams) - override def contramapParams[NNP](f: NNP => NP): GExecution[NNP, NL, NRL] = + override def contramapParams[NNP](f: NNP => NP): GExecution[NNP, NEL, NRL] = Map(execution, mapResult, contramapLayout, f andThen contramapParams) - override def contramap[NNL <: Layout](f: NNL => NL): GExecution[NP, NNL, NRL] = + override def contramap[NNL <: Layout](f: NNL => NEL): GExecution[NP, NNL, NRL] = Map(execution, mapResult, f andThen contramapLayout, contramapParams) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala index 3dee2631..7d634bb0 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala @@ -27,15 +27,15 @@ object GProgram: case class DynamicDispatch[L <: Layout](buffer: GBinding[?], offset: Int) extends ProgramDispatch case class StaticDispatch(size: WorkDimensions) extends ProgramDispatch - private[cyfra] case class BufferSizeSpec[T <: Value: {Tag, FromExpr}](length: Int) extends GBuffer[T] - private[cyfra] case class DynamicUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] + private[cyfra] class BufferLengthSpec[T <: Value: {Tag, FromExpr}](val length: Int) extends GBuffer[T] + private[cyfra] class DynamicUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] trait InitProgramLayout: extension (buffers: GBuffer.type) - def apply[T <: Value: {Tag, FromExpr}](size: Int): GBuffer[T] = - BufferSizeSpec[T](size) + def apply[T <: Value: {Tag, FromExpr}](length: Int): GBuffer[T] = + BufferLengthSpec[T](length) extension (uniforms: GUniform.type) def apply[T <: GStruct[T]: {Tag, FromExpr}](): GUniform[T] = DynamicUniform[T]() - def apply[T <: GStruct[T]: {Tag, FromExpr}](value: T): GUniform[T] \ No newline at end of file + def apply[T <: GStruct[T]: {Tag, FromExpr}](value: T): GUniform[T] diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/exile.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/exile.scala index 56ac02ed..3585884a 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/exile.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/exile.scala @@ -68,9 +68,10 @@ case class GioProgram[Params, L <: Layout: LayoutStruct]( workgroupSize: WorkDimensions, ) extends GProgram[Params, L]: private[cyfra] def cacheKey: String = layoutStruct.elementTypes match - case x if x.size == 12 => "addOne" - case x if x.contains(summon[Tag[GBoolean]]) => "filter" - case _ => "emit" + case x if x.size == 12 => "addOne" + case x if x.contains(summon[Tag[GBoolean]]) && x.size == 3 => "filter" + case x if x.size == 3 => "emit" + case _ => ??? object GioProgram: def apply[Params, L <: Layout: LayoutStruct]( 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 d84988d0..7b834c32 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 @@ -6,7 +6,7 @@ import io.computenode.cyfra.core.binding.{BufferRef, UniformRef} import io.computenode.cyfra.core.{GExecution, GProgram} import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} -import io.computenode.cyfra.runtime.ExecutionHandler.{Dispatch, DispatchType, ExecutionStep, PipelineBarrier, ShaderCall} +import io.computenode.cyfra.runtime.ExecutionHandler.{BindingLogicError, Dispatch, DispatchType, ExecutionStep, PipelineBarrier, ShaderCall} import io.computenode.cyfra.runtime.ExecutionHandler.DispatchType.* import io.computenode.cyfra.utility.Utility.timed import io.computenode.cyfra.vulkan.command.{CommandPool, Fence} @@ -30,8 +30,8 @@ class ExecutionHandler(runtime: VkCyfraRuntime): private val descriptorPool: DescriptorPool = context.descriptorPool // TODO descriptor pool manager private val commandPool: CommandPool = context.commandPool - def handle[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L)(using VkAllocation): RL = pushStack: - stack => + def handle[Params, EL <: Layout, RL <: Layout](execution: GExecution[Params, EL, RL], params: Params, layout: EL)(using VkAllocation): RL = + pushStack: stack => val (result, shaderCalls) = interpret(execution, params, layout) val descriptorSets = shaderCalls.map { case ShaderCall(pipeline, layout, _) => @@ -71,12 +71,13 @@ class ExecutionHandler(runtime: VkCyfraRuntime): result - private def interpret[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L)(using + private def interpret[Params, EL <: Layout, RL <: Layout](execution: GExecution[Params, EL, RL], params: Params, layout: EL)(using VkAllocation, ): (RL, Seq[ShaderCall]) = val bindingsAcc: mutable.Map[GBinding[?], mutable.Buffer[GBinding[?]]] = mutable.Map.from(layout.bindings.map(x => (x, mutable.Buffer.empty))) - def interpretImpl[Params, L <: Layout, RL <: Layout](execution: GExecution[Params, L, RL], params: Params, layout: L): (RL, Seq[ShaderCall]) = + // noinspection TypeParameterShadow + def interpretImpl[Params, EL <: Layout, RL <: Layout](execution: GExecution[Params, EL, RL], params: Params, layout: EL): (RL, Seq[ShaderCall]) = execution match case GExecution.Pure() => (layout, Seq.empty) case GExecution.Map(execution, map, cmap, cmapP) => @@ -90,13 +91,13 @@ class ExecutionHandler(runtime: VkCyfraRuntime): val nextExecution = f(params, prevLayout) val (prevLayout2, calls2) = interpretImpl(nextExecution, params, layout) (prevLayout2, calls ++ calls2) - case program: GProgram[Params, L] => + case program: GProgram[Params, EL] => val shader = - given LayoutStruct[L] = program.layoutStruct + given LayoutStruct[EL] = program.layoutStruct runtime.getOrLoadProgram(program) val layoutInit = val initProgram: InitProgramLayout = summon[VkAllocation].getInitProgramLayout - program.layout(initProgram)(params).asInstanceOf[L] + program.layout(initProgram)(params) layout.bindings .zip(layoutInit.bindings) .foreach: @@ -105,6 +106,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime): val dispatch = program.dispatch(layout, params) match case GProgram.DynamicDispatch(buffer, offset) => DispatchType.Indirect(buffer, offset) case GProgram.StaticDispatch(size) => DispatchType.Direct(size._1, size._2, size._3) + // noinspection ScalaRedundantCast val rl = layout.asInstanceOf[RL] (rl, Seq(ShaderCall(shader.underlying, shader.shaderBindings(layout), dispatch))) case _ => ??? @@ -129,32 +131,33 @@ class ExecutionHandler(runtime: VkCyfraRuntime): binding match case buffer: GBuffer[?] => val (allocations, sizeSpec) = bindings.partitionMap: - case x: VkBuffer[?] => Left(x) - case x: GProgram.BufferSizeSpec[?] => Right(x) - case _ => throw new IllegalArgumentException(s"Unsupported binding type: ${binding.getClass.getName}") - if allocations.size > 1 then throw new IllegalArgumentException(s"Multiple allocations for buffer: (${allocations.size})") + case x: VkBuffer[?] => Left(x) + case x: GProgram.BufferLengthSpec[?] => Right(x) + case x => throw BindingLogicError(x, "Unsupported buffer type") + if allocations.size > 1 then throw BindingLogicError(allocations, "Multiple allocations for buffer") val all = allocations.headOption - val maxi = sizeSpec.map(_.length).distinct - if maxi.size > 1 then throw new IllegalArgumentException(s"Multiple conflicting sizes for buffer: ($maxi)") - val max = maxi.headOption + val lengths = sizeSpec.distinctBy(_.length) + if lengths.size > 1 then throw BindingLogicError(lengths, "Multiple conflicting lengths for buffer") + val length = lengths.headOption - (all, max) match - case (Some(buffer: VkBuffer[?]), Some(length)) => - assert(buffer.length == length, s"Buffer length mismatch: ${buffer.length} != $length for binding $binding") + (all, length) match + case (Some(buffer), Some(sizeSpec)) => + if buffer.length != sizeSpec.length then + throw BindingLogicError(Seq(buffer, sizeSpec), s"Buffer length mismatch, ${buffer.length} != ${sizeSpec.length}") buffer - case (Some(buffer: VkBuffer[?]), None) => buffer - case (None, Some(length)) => ??? - case (None, None) => throw new IllegalArgumentException(s"Cannot create buffer without size or allocation: $binding") + case (Some(buffer), None) => buffer + case (None, Some(length)) => ??? + case (None, None) => throw BindingLogicError(binding, "Cannot create buffer without size or allocation") case uniform: GUniform[?] => val allocations = bindings.filter: - case uniform: VkUniform[?] => true - case GProgram.DynamicUniform() => false - case _: GUniform.ParamUniform[?] => false - case _ => throw new IllegalArgumentException(s"Unsupported binding type: ${binding.getClass.getName}") - if allocations.size > 1 then throw new IllegalArgumentException(s"Multiple allocations for uniform: (${allocations.size})") - allocations.headOption.getOrElse(throw new IllegalArgumentException(s"Uniform never allocated: $binding")) + case _: VkUniform[?] => true + case _: GProgram.DynamicUniform[?] => false + case _: GUniform.ParamUniform[?] => false + case x => throw BindingLogicError(x, "Unsupported binding type") + if allocations.size > 1 then throw BindingLogicError(allocations, "Multiple allocations for uniform") + allocations.headOption.getOrElse(throw BindingLogicError(binding, "Uniform never allocated")) private def recordCommandBuffer(steps: Seq[ExecutionStep]): VkCommandBuffer = pushStack: stack => val commandBuffer = commandPool.createCommandBuffer() @@ -207,3 +210,8 @@ object ExecutionHandler: object DispatchType: case class Direct(x: Int, y: Int, z: Int) extends DispatchType case class Indirect(buffer: GBinding[?], offset: Int) extends DispatchType + + case class BindingLogicError(bindings: Seq[GBinding[?]], message: String) extends RuntimeException(s"Error in binding logic for $bindings: $message") + object BindingLogicError: + def apply(binding: GBinding[?], message: String): BindingLogicError = + new BindingLogicError(Seq(binding), message) 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 9565f5d9..467c5582 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 @@ -65,8 +65,8 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) def apply[T <: Value: {Tag, FromExpr}](): GUniform[T] = VkUniform[T]().tap(bindings += _) - extension [Params, L <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, L, RL]) - override def execute(params: Params, layout: L): RL = executionHandler.handle(execution, params, layout)(using this) + extension [Params, EL <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, EL, RL]) + override def execute(params: Params, layout: EL): RL = executionHandler.handle(execution, params, layout)(using this) private def direct[T <: Value: {Tag, FromExpr}](buff: ByteBuffer): GUniform[T] = GUniform[T](buff) @@ -100,4 +100,4 @@ object VkAllocation: buffer match case buffer: VkBuffer[?] => buffer.underlying case uniform: VkUniform[?] => uniform.underlying - case _ => ??? + case _ => throw new IllegalArgumentException(s"Tried to get underlying of non-VkBinding $buffer") diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/SequenceExecutor.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/SequenceExecutor.scala deleted file mode 100644 index 92170498..00000000 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/executor/SequenceExecutor.scala +++ /dev/null @@ -1,150 +0,0 @@ -package io.computenode.cyfra.vulkan.executor - -import io.computenode.cyfra.utility.Utility.timed -import io.computenode.cyfra.vulkan.VulkanContext -import io.computenode.cyfra.vulkan.command.* -import io.computenode.cyfra.vulkan.compute.* -import io.computenode.cyfra.vulkan.core.* -import io.computenode.cyfra.vulkan.executor.SequenceExecutor.* -import io.computenode.cyfra.vulkan.memory.* -import io.computenode.cyfra.vulkan.util.Util.* -import org.lwjgl.BufferUtils -import org.lwjgl.util.vma.Vma.* -import org.lwjgl.vulkan.* -import org.lwjgl.vulkan.KHRSynchronization2.vkCmdPipelineBarrier2KHR -import org.lwjgl.vulkan.VK10.* -import org.lwjgl.vulkan.VK13.* - -import java.nio.ByteBuffer - -/** @author - * MarconZet Created 15.04.2020 - */ -private[cyfra] class SequenceExecutor(computeSequence: ComputationSequence, context: VulkanContext): - import context.given - - private val queue: Queue = context.computeQueue - private val descriptorPool: DescriptorPool = context.descriptorPool - private val commandPool: CommandPool = context.commandPool - - private val pipelineToDescriptorSets: Map[ComputePipeline, Seq[DescriptorSet]] = ??? - - private val descriptorSets = pipelineToDescriptorSets.toSeq.flatMap(_._2).distinctBy(_.get) - - private def recordCommandBuffer() = pushStack: stack => - val pipelinesHasDependencies = computeSequence.dependencies.map(_.to).toSet - val commandBuffer = commandPool.createCommandBuffer() - - val commandBufferBeginInfo = VkCommandBufferBeginInfo - .calloc(stack) - .sType$Default() - .flags(0) - - check(vkBeginCommandBuffer(commandBuffer, commandBufferBeginInfo), "Failed to begin recording command buffer") - - computeSequence.sequence.foreach { case Compute(pipeline, _) => - if pipelinesHasDependencies(pipeline) then - val memoryBarrier = VkMemoryBarrier2 - .calloc(1, stack) - .sType$Default() - .srcStageMask(VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT) - .srcAccessMask(VK_ACCESS_2_SHADER_WRITE_BIT) - .dstStageMask(VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT) - .dstAccessMask(VK_ACCESS_2_SHADER_READ_BIT) - - val dependencyInfo = VkDependencyInfo - .calloc(stack) - .sType$Default() - .pMemoryBarriers(memoryBarrier) - - vkCmdPipelineBarrier2KHR(commandBuffer, dependencyInfo) - - vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.get) - - val pDescriptorSets = stack.longs(pipelineToDescriptorSets(pipeline).map(_.get)*) - vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.pipelineLayout.id, 0, pDescriptorSets, null) - - vkCmdDispatch(commandBuffer, 8, 1, 1) - } - - check(vkEndCommandBuffer(commandBuffer), "Failed to finish recording command buffer") - commandBuffer - - private def createBuffers(): Map[DescriptorSet, Seq[Buffer]] = - val setToActions = ??? - val setToBuffers = ??? - setToBuffers - - def execute(inputs: Seq[ByteBuffer]): Seq[ByteBuffer] = pushStack: stack => - timed("Vulkan full execute"): - val setToBuffers = createBuffers() - - def buffersWithAction(bufferAction: BufferAction): Seq[Buffer] = - computeSequence.sequence.collect { case x: Compute => - pipelineToDescriptorSets(x.pipeline) - .map(setToBuffers) - .zip(x.pumpLayoutLocations) - .flatMap(x => x._1.zip(x._2)) - .collect: - case (buffer, action) if (action.action & bufferAction.action) != 0 => buffer - }.flatten - - val stagingBuffer = - new Buffer.HostBuffer(inputs.map(_.remaining()).max, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT) - - buffersWithAction(BufferAction.LoadTo).zipWithIndex.foreach { case (buffer, i) => - Buffer.copyBuffer(inputs(i), stagingBuffer, buffer.size, 0, 0) - Buffer.copyBuffer(stagingBuffer, buffer, buffer.size, 0, 0, commandPool).block().destroy() - } - - val fence = new Fence() - val commandBuffer = recordCommandBuffer() - val pCommandBuffer = stack.callocPointer(1).put(0, commandBuffer) - val submitInfo = VkSubmitInfo - .calloc(stack) - .sType$Default() - .pCommandBuffers(pCommandBuffer) - - timed("Vulkan render command"): - check(vkQueueSubmit(queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") - fence.block().destroy() - - val output = buffersWithAction(BufferAction.LoadFrom).map { buffer => - Buffer.copyBuffer(buffer, stagingBuffer, 0, 0, buffer.size, commandPool).block().destroy() - val out = BufferUtils.createByteBuffer(buffer.size) - Buffer.copyBuffer(stagingBuffer, out, 0, 0, buffer.size) - out - } - - stagingBuffer.destroy() - commandPool.freeCommandBuffer(commandBuffer) - setToBuffers.flatMap(_._2).foreach(_.destroy()) - - output - - def destroy(): Unit = ??? - -object SequenceExecutor: - private[cyfra] case class ComputationSequence(sequence: Seq[ComputationStep], dependencies: Seq[Dependency]) - - private[cyfra] sealed trait ComputationStep - case class Compute(pipeline: ComputePipeline, bufferActions: Map[LayoutLocation, BufferAction]) extends ComputationStep: - def pumpLayoutLocations: Seq[Seq[BufferAction]] = ??? - - case class LayoutLocation(set: Int, binding: Int) - - case class Dependency(from: ComputePipeline, fromSet: Int, to: ComputePipeline, toSet: Int) - - enum BufferAction(val action: Int): - case DoNothing extends BufferAction(0) - case LoadTo extends BufferAction(VK_BUFFER_USAGE_TRANSFER_DST_BIT) - case LoadFrom extends BufferAction(VK_BUFFER_USAGE_TRANSFER_SRC_BIT) - case LoadFromTo extends BufferAction(VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT) - - private def findAction(action: Int): BufferAction = action match - case VK_BUFFER_USAGE_TRANSFER_DST_BIT => LoadTo - case VK_BUFFER_USAGE_TRANSFER_SRC_BIT => LoadFrom - case 3 => LoadFromTo - case _ => DoNothing - - def |(other: BufferAction): BufferAction = findAction(this.action | other.action) diff --git a/cyfra-vulkan/src/test/resources/compileAll.ps1 b/cyfra-vulkan/src/test/resources/compileAll.ps1 deleted file mode 100644 index e1755a32..00000000 --- a/cyfra-vulkan/src/test/resources/compileAll.ps1 +++ /dev/null @@ -1,4 +0,0 @@ -Get-ChildItem -Filter *.comp -Name | ForEach-Object -Process { - $name = $_.Replace(".comp", "") - "$Env:VULKAN_SDK\Bin\glslangValidator.exe -V $name.comp -o $name.spv" | Invoke-Expression -} diff --git a/cyfra-vulkan/src/test/resources/compileAll.sh b/cyfra-vulkan/src/test/resources/compileAll.sh deleted file mode 100644 index fdd4be8c..00000000 --- a/cyfra-vulkan/src/test/resources/compileAll.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -for f in *.comp -do - prefix=$(echo "$f" | cut -f 1 -d '.') - glslangValidator -V "$prefix.comp" -o "$prefix.spv" -done diff --git a/cyfra-vulkan/src/test/resources/copy_test.comp b/cyfra-vulkan/src/test/resources/copy_test.comp deleted file mode 100644 index 13f55532..00000000 --- a/cyfra-vulkan/src/test/resources/copy_test.comp +++ /dev/null @@ -1,15 +0,0 @@ -#version 450 - -layout (local_size_x = 128, local_size_y = 1, local_size_z = 1) in; - -layout (binding = 0, set = 0) buffer InputBuffer { - int inArray[]; -}; -layout (binding = 0, set = 1) buffer OutputBuffer { - int outArray[]; -}; - -void main(void){ - uint index = gl_GlobalInvocationID.x; - outArray[index] = inArray[index] + 10000; -} diff --git a/cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.scala b/cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.scala index 1a90b83b..ce0e296a 100644 --- a/cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.scala +++ b/cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.scala @@ -5,9 +5,6 @@ import io.computenode.cyfra.vulkan.compute.* import io.computenode.cyfra.vulkan.compute.ComputePipeline.* import io.computenode.cyfra.vulkan.compute.ComputePipeline.BindingType.StorageBuffer import io.computenode.cyfra.vulkan.core.Device -import io.computenode.cyfra.vulkan.executor.SequenceExecutor.BufferAction.{LoadFrom, LoadTo} -import io.computenode.cyfra.vulkan.executor.SequenceExecutor -import io.computenode.cyfra.vulkan.executor.SequenceExecutor.{ComputationSequence, Compute, Dependency, LayoutLocation} import munit.FunSuite import org.lwjgl.BufferUtils @@ -22,17 +19,13 @@ class SequenceExecutorTest extends FunSuite: val copy1 = new ComputePipeline(code, "main", layout) val copy2 = new ComputePipeline(code, "main", layout) - val sequence = - ComputationSequence( - Seq(Compute(copy1, Map(LayoutLocation(0, 0) -> LoadTo)), Compute(copy2, Map(LayoutLocation(1, 0) -> LoadFrom))), - Seq(Dependency(copy1, 1, copy2, 0)), - ) - val sequenceExecutor = new SequenceExecutor(sequence, vulkanContext) + val sequence = ??? + val sequenceExecutor = ??? val input = 0 until 1024 val buffer = BufferUtils.createByteBuffer(input.length * 4) input.foreach(buffer.putInt) buffer.flip() - val res = sequenceExecutor.execute(Seq(buffer)) - val output = input.map(_ => res.head.getInt) + val res = ??? + val output = input.map(_ => ???) assertEquals(input.map(_ + 20000).toList, output.toList) From e869488eb98c6eb5d7c7376befc420c400de353c Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Mon, 14 Jul 2025 22:49:27 +0200 Subject: [PATCH 16/20] better^ --- .../computenode/cyfra/spirv/SpirvTypes.scala | 9 +++++++++ .../computenode/cyfra/core/Allocation.scala | 2 +- .../io/computenode/cyfra/core/GProgram.scala | 14 ++++++++++--- .../computenode/cyfra/core/GioProgram.scala | 19 ++++++++++++++++++ .../core/{exile.scala => SpirvProgram.scala} | 20 ------------------- .../cyfra/samples/TestingStuff.scala | 8 ++++---- 6 files changed, 44 insertions(+), 28 deletions(-) create mode 100644 cyfra-core/src/main/scala/io/computenode/cyfra/core/GioProgram.scala rename cyfra-core/src/main/scala/io/computenode/cyfra/core/{exile.scala => SpirvProgram.scala} (71%) 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..e2bb0340 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 @@ -2,6 +2,7 @@ package io.computenode.cyfra.spirv import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.* +import io.computenode.cyfra.dsl.struct.{GStructConstructor, GStructSchema} import io.computenode.cyfra.spirv.Opcodes.* import izumi.reflect.Tag import izumi.reflect.macrortti.{LTag, LightTypeTag} @@ -56,6 +57,14 @@ private[cyfra] object SpirvTypes: vecSize(v) * typeStride(v.typeArgs.head) def typeStride(tag: Tag[?]): Int = typeStride(tag.tag) + + def totalStride(gs: GStructSchema[?]): Int = gs.fields.map { + case (_, fromExpr, t) if t <:< gs.gStructTag => + val constructor = fromExpr.asInstanceOf[GStructConstructor[?]] + totalStride(constructor.schema) + case (_, _, t) => + typeStride(t) + }.sum def toWord(tpe: Tag[?], value: Any): Words = tpe match case t if t == Int32Tag => 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 7d79e5b0..e9b8cde7 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 @@ -18,7 +18,7 @@ trait Allocation: extension [Params, EL <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, EL, RL]) def execute(params: Params, layout: EL): RL extension (buffers: GBuffer.type) - def apply[T <: Value: {Tag, FromExpr}](size: Int): GBuffer[T] + def apply[T <: Value: {Tag, FromExpr}](length: Int): GBuffer[T] def apply[T <: Value: {Tag, FromExpr}](buff: ByteBuffer): GBuffer[T] diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala index 7d634bb0..124d33dd 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala @@ -26,9 +26,17 @@ object GProgram: sealed trait ProgramDispatch case class DynamicDispatch[L <: Layout](buffer: GBinding[?], offset: Int) extends ProgramDispatch case class StaticDispatch(size: WorkDimensions) extends ProgramDispatch - - private[cyfra] class BufferLengthSpec[T <: Value: {Tag, FromExpr}](val length: Int) extends GBuffer[T] - private[cyfra] class DynamicUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] + + def apply[Params, L <: Layout: LayoutStruct]( + layout: InitProgramLayout ?=> Params => L, + dispatch: (L, Params) => ProgramDispatch, + workgroupSize: WorkDimensions = (128, 1, 1), + )(body: L => GIO[?]): GProgram[Params, L] = + new GioProgram[Params, L](body, s => layout(using s), dispatch, workgroupSize) + + private[cyfra] case class BufferLengthSpec[T <: Value: {Tag, FromExpr}](length: Int) extends GBuffer[T]: + private[cyfra] def materialise(using x: Allocation): GBuffer[T] = GBuffer.apply[T](length) + private[cyfra] case class DynamicUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] trait InitProgramLayout: extension (buffers: GBuffer.type) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GioProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GioProgram.scala new file mode 100644 index 00000000..c36bb0ec --- /dev/null +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GioProgram.scala @@ -0,0 +1,19 @@ +package io.computenode.cyfra.core + +import io.computenode.cyfra.core.GProgram.* +import io.computenode.cyfra.core.layout.* +import io.computenode.cyfra.dsl.Value.GBoolean +import io.computenode.cyfra.dsl.gio.GIO +import izumi.reflect.Tag + +case class GioProgram[Params, L <: Layout: LayoutStruct]( + body: L => GIO[?], + layout: InitProgramLayout => Params => L, + dispatch: (L, Params) => ProgramDispatch, + workgroupSize: WorkDimensions, +) extends GProgram[Params, L]: + private[cyfra] def cacheKey: String = layoutStruct.elementTypes match + case x if x.size == 12 => "addOne" + case x if x.contains(summon[Tag[GBoolean]]) && x.size == 3 => "filter" + case x if x.size == 3 => "emit" + case _ => ??? diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/exile.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala similarity index 71% rename from cyfra-core/src/main/scala/io/computenode/cyfra/core/exile.scala rename to cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala index 3585884a..87af9c2e 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/exile.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala @@ -60,23 +60,3 @@ object SpirvProgram: val fis = use(new FileInputStream(file)) val fc = use(fis.getChannel) fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()) - -case class GioProgram[Params, L <: Layout: LayoutStruct]( - body: L => GIO[?], - layout: InitProgramLayout => Params => L, - dispatch: (L, Params) => ProgramDispatch, - workgroupSize: WorkDimensions, -) extends GProgram[Params, L]: - private[cyfra] def cacheKey: String = layoutStruct.elementTypes match - case x if x.size == 12 => "addOne" - case x if x.contains(summon[Tag[GBoolean]]) && x.size == 3 => "filter" - case x if x.size == 3 => "emit" - case _ => ??? - -object GioProgram: - def apply[Params, L <: Layout: LayoutStruct]( - layout: InitProgramLayout ?=> Params => L, - dispatch: (L, Params) => ProgramDispatch, - workgroupSize: WorkDimensions = (128, 1, 1), - )(body: L => GIO[?]): GProgram[Params, L] = - new GioProgram[Params, L](body, s => layout(using s), dispatch, workgroupSize) 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 ae24b8bc..a5483cdc 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 @@ -2,7 +2,7 @@ package io.computenode.cyfra.samples import io.computenode.cyfra.core.archive.GContext import io.computenode.cyfra.core.layout.* -import io.computenode.cyfra.core.{GBufferRegion, GExecution, GProgram, GioProgram} +import io.computenode.cyfra.core.{GBufferRegion, GExecution, GProgram} import io.computenode.cyfra.dsl.Value.{GBoolean, Int32} import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} import io.computenode.cyfra.dsl.gio.GIO @@ -27,7 +27,7 @@ object TestingStuff: args: GUniform[EmitProgramUniform] = GUniform.fromParams, // todo will be different in the future ) extends Layout - val emitProgram = GioProgram[EmitProgramParams, EmitProgramLayout]( + val emitProgram = GProgram[EmitProgramParams, EmitProgramLayout]( layout = params => EmitProgramLayout( in = GBuffer[Int32](params.inSize), @@ -52,7 +52,7 @@ object TestingStuff: case class FilterProgramLayout(in: GBuffer[Int32], out: GBuffer[GBoolean], params: GUniform[FilterProgramUniform] = GUniform.fromParams) extends Layout - val filterProgram = GioProgram[FilterProgramParams, FilterProgramLayout]( + val filterProgram = GProgram[FilterProgramParams, FilterProgramLayout]( layout = params => FilterProgramLayout( in = GBuffer[Int32](params.inSize), @@ -150,7 +150,7 @@ object TestingStuff: out5: GBuffer[Int32], ) extends Layout - val addProgram: GProgram[AddProgramParams, AddProgramLayout] = GioProgram[AddProgramParams, AddProgramLayout]( + val addProgram: GProgram[AddProgramParams, AddProgramLayout] = GProgram[AddProgramParams, AddProgramLayout]( layout = params => AddProgramLayout( in1 = GBuffer[Int32](params.bufferSize), From 93694b56e3dadf152bacd6ac80cd59057c5b0b89 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Mon, 14 Jul 2025 23:48:25 +0200 Subject: [PATCH 17/20] fixes^ --- build.sbt | 5 +- .../io/computenode/cyfra/core/GProgram.scala | 6 +- .../cyfra/runtime/ExecutionHandler.scala | 80 ++++++++++--------- .../cyfra/vulkan/core/Instance.scala | 2 +- 4 files changed, 49 insertions(+), 44 deletions(-) diff --git a/build.sbt b/build.sbt index 44acec5f..1c3566eb 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,8 @@ ThisBuild / organization := "com.computenode.cyfra" ThisBuild / scalaVersion := "3.6.4" -ThisBuild / version := "0.1.0-SNAPSHOT" +ThisBuild / version := "0.2.0-SNAPSHOT" -val lwjglVersion = "3.3.6" +val lwjglVersion = "3.4.0-SNAPSHOT" val jomlVersion = "1.10.0" lazy val osName = System.getProperty("os.name").toLowerCase @@ -37,6 +37,7 @@ lazy val vulkanNatives = lazy val commonSettings = Seq( scalacOptions ++= Seq("-feature", "-deprecation", "-unchecked", "-language:implicitConversions"), + resolvers += "maven snapshots" at "https://central.sonatype.com/repository/maven-snapshots/", libraryDependencies ++= Seq( "dev.zio" % "izumi-reflect_3" % "2.3.10", "com.lihaoyi" % "pprint_3" % "0.9.0", diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala index 124d33dd..5f2c0dae 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala @@ -34,9 +34,9 @@ object GProgram: )(body: L => GIO[?]): GProgram[Params, L] = new GioProgram[Params, L](body, s => layout(using s), dispatch, workgroupSize) - private[cyfra] case class BufferLengthSpec[T <: Value: {Tag, FromExpr}](length: Int) extends GBuffer[T]: - private[cyfra] def materialise(using x: Allocation): GBuffer[T] = GBuffer.apply[T](length) - private[cyfra] case class DynamicUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] + private[cyfra] class BufferLengthSpec[T <: Value: {Tag, FromExpr}](val length: Int) extends GBuffer[T]: + private[cyfra] def materialise()(using x: Allocation): GBuffer[T] = GBuffer.apply[T](length) + private[cyfra] class DynamicUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] trait InitProgramLayout: extension (buffers: GBuffer.type) 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 7b834c32..1ee63805 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 @@ -6,7 +6,15 @@ import io.computenode.cyfra.core.binding.{BufferRef, UniformRef} import io.computenode.cyfra.core.{GExecution, GProgram} import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} -import io.computenode.cyfra.runtime.ExecutionHandler.{BindingLogicError, Dispatch, DispatchType, 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.utility.Utility.timed import io.computenode.cyfra.vulkan.command.{CommandPool, Fence} @@ -14,47 +22,44 @@ import io.computenode.cyfra.vulkan.compute.ComputePipeline import io.computenode.cyfra.vulkan.core.Queue import io.computenode.cyfra.vulkan.memory.{DescriptorPool, DescriptorSet} import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} -import org.lwjgl.vulkan.KHRSynchronization2.vkCmdPipelineBarrier2KHR 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} +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 scala.collection.immutable.{AbstractSeq, LinearSeq} import scala.collection.mutable class ExecutionHandler(runtime: VkCyfraRuntime): private val context = runtime.context import context.given - private val queue: Queue = context.computeQueue // TODO queue multithreading - private val descriptorPool: DescriptorPool = context.descriptorPool // TODO descriptor pool manager - private val commandPool: CommandPool = context.commandPool + private val queue: Queue = context.computeQueue // TODO multiple queues - multithreading support + private val descriptorPool: DescriptorPool = context.descriptorPool // TODO descriptor pool manager - descriptor allocation and reclamation support + private val commandPool: CommandPool = context.commandPool // TODO multiple command pools - different command pools for different workloads def handle[Params, EL <: Layout, RL <: Layout](execution: GExecution[Params, EL, RL], params: Params, layout: EL)(using VkAllocation): RL = - pushStack: stack => - val (result, shaderCalls) = interpret(execution, params, layout) + val (result, shaderCalls) = interpret(execution, params, layout) - val descriptorSets = shaderCalls.map { case ShaderCall(pipeline, layout, _) => - pipeline.pipelineLayout.sets.map(descriptorPool.allocate).zip(layout).map { case (set, bindings) => - set.update(bindings.map(x => VkAllocation.getUnderlying(x.binding))) - set - } + val descriptorSets = shaderCalls.map { case ShaderCall(pipeline, layout, _) => + pipeline.pipelineLayout.sets.map(descriptorPool.allocate).zip(layout).map { case (set, bindings) => + set.update(bindings.map(x => VkAllocation.getUnderlying(x.binding))) + set } + } - val dispatches: Seq[Dispatch] = shaderCalls - .zip(descriptorSets) - .map: - case (ShaderCall(pipeline, layout, dispatch), sets) => - Dispatch(pipeline, layout, sets, dispatch) - - val (executeSteps, _) = dispatches.foldLeft((Seq.empty[ExecutionStep], Set.empty[GBinding[?]])): - case ((steps, dirty), step) => - val bindings = step.layout.flatten.map(_.binding) - if bindings.exists(dirty.contains) then (steps.appendedAll(Seq(PipelineBarrier, step)), Set.empty[GBinding[?]]) - else (steps.appended(step), dirty ++ bindings) + val dispatches: Seq[Dispatch] = shaderCalls + .zip(descriptorSets) + .map: + case (ShaderCall(pipeline, layout, dispatch), sets) => + Dispatch(pipeline, layout, sets, dispatch) - val commandBuffer = recordCommandBuffer(executeSteps) + val (executeSteps, _) = dispatches.foldLeft((Seq.empty[ExecutionStep], Set.empty[GBinding[?]])): + case ((steps, dirty), step) => + val bindings = step.layout.flatten.map(_.binding) + if bindings.exists(dirty.contains) then (steps.appendedAll(Seq(PipelineBarrier, step)), Set.empty[GBinding[?]]) + 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) @@ -64,12 +69,9 @@ class ExecutionHandler(runtime: VkCyfraRuntime): val fence = new Fence() timed("Vulkan render command"): check(vkQueueSubmit(queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") - fence.block() - fence.destroy() - - commandPool.freeCommandBuffer(commandBuffer) - - result + fence.block().destroy() + commandPool.freeCommandBuffer(commandBuffer) + result private def interpret[Params, EL <: Layout, RL <: Layout](execution: GExecution[Params, EL, RL], params: Params, layout: EL)(using VkAllocation, @@ -147,7 +149,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime): throw BindingLogicError(Seq(buffer, sizeSpec), s"Buffer length mismatch, ${buffer.length} != ${sizeSpec.length}") buffer case (Some(buffer), None) => buffer - case (None, Some(length)) => ??? + case (None, Some(length)) => length.materialise() case (None, None) => throw BindingLogicError(binding, "Cannot create buffer without size or allocation") case uniform: GUniform[?] => @@ -169,21 +171,21 @@ class ExecutionHandler(runtime: VkCyfraRuntime): check(vkBeginCommandBuffer(commandBuffer, commandBufferBeginInfo), "Failed to begin recording command buffer") steps.foreach: - case PipelineBarrier => // TODO WaR and WaW errors - val memoryBarrier = VkMemoryBarrier2 + case PipelineBarrier => + val memoryBarrier = VkMemoryBarrier2 // TODO don't synchronise everything .calloc(1, stack) .sType$Default() .srcStageMask(VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT) - .srcAccessMask(VK_ACCESS_2_SHADER_WRITE_BIT) + .srcAccessMask(VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT) .dstStageMask(VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT) - .dstAccessMask(VK_ACCESS_2_SHADER_READ_BIT) + .dstAccessMask(VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT) val dependencyInfo = VkDependencyInfo .calloc(stack) .sType$Default() .pMemoryBarriers(memoryBarrier) - vkCmdPipelineBarrier2KHR(commandBuffer, dependencyInfo) + vkCmdPipelineBarrier2(commandBuffer, dependencyInfo) case Dispatch(pipeline, layout, descriptorSets, dispatch) => vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline.get) @@ -211,6 +213,8 @@ object ExecutionHandler: case class Direct(x: Int, y: Int, z: Int) extends DispatchType case class Indirect(buffer: GBinding[?], offset: Int) extends DispatchType + class ExecutionBinding + case class BindingLogicError(bindings: Seq[GBinding[?]], message: String) extends RuntimeException(s"Error in binding logic for $bindings: $message") object BindingLogicError: def apply(binding: GBinding[?], message: String): BindingLogicError = diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala index dfb585d1..b29ebeb4 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala @@ -103,7 +103,7 @@ private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObj if enableValidationLayers then extensions.addAll(Instance.ValidationLayersExtensions) val filteredExtensions = extensions.filter(ext => - availableExtensions.contains(ext).tap { x => + availableExtensions.contains(ext).tap { x => // TODO detect when this extension is needed if !x then logger.warn(s"Requested Vulkan instance extension '$ext' is not available") }, ) From a5d3821963a85e0741b0a0b674e484863f1e9aeb Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Tue, 22 Jul 2025 11:33:26 +0200 Subject: [PATCH 18/20] working --- build.sbt | 2 +- .../computenode/cyfra/spirv/SpirvTypes.scala | 2 +- .../computenode/cyfra/core/Allocation.scala | 5 +- .../cyfra/core/GBufferRegion.scala | 15 ++- .../computenode/cyfra/core/GExecution.scala | 27 +++-- .../io/computenode/cyfra/core/GProgram.scala | 17 ++- .../computenode/cyfra/core/GioProgram.scala | 4 +- .../computenode/cyfra/core/SpirvProgram.scala | 7 +- .../cyfra/core/layout/Layout.scala | 21 +--- .../cyfra/core/layout/LayoutBinding.scala | 34 ++++++ .../cyfra/dsl/binding/GBinding.scala | 8 +- .../cyfra/samples/TestingStuff.scala | 4 +- .../cyfra/runtime/ExecutionHandler.scala | 107 ++++++++++++------ .../cyfra/runtime/VkAllocation.scala | 8 +- .../cyfra/runtime/VkCyfraRuntime.scala | 4 +- .../computenode/cyfra/runtime/VkShader.scala | 12 +- 16 files changed, 168 insertions(+), 109 deletions(-) create mode 100644 cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutBinding.scala diff --git a/build.sbt b/build.sbt index 1c3566eb..35a46c2c 100644 --- a/build.sbt +++ b/build.sbt @@ -39,7 +39,7 @@ lazy val commonSettings = Seq( scalacOptions ++= Seq("-feature", "-deprecation", "-unchecked", "-language:implicitConversions"), resolvers += "maven snapshots" at "https://central.sonatype.com/repository/maven-snapshots/", libraryDependencies ++= Seq( - "dev.zio" % "izumi-reflect_3" % "2.3.10", + "dev.zio" % "izumi-reflect_3" % "3.0.5", "com.lihaoyi" % "pprint_3" % "0.9.0", "com.diogonunes" % "JColor" % "5.5.1", "org.lwjgl" % "lwjgl" % lwjglVersion, 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 e2bb0340..be02eb99 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 @@ -57,7 +57,7 @@ private[cyfra] object SpirvTypes: vecSize(v) * typeStride(v.typeArgs.head) def typeStride(tag: Tag[?]): Int = typeStride(tag.tag) - + def totalStride(gs: GStructSchema[?]): Int = gs.fields.map { case (_, fromExpr, t) if t <:< gs.gStructTag => val constructor = fromExpr.asInstanceOf[GStructConstructor[?]] 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 e9b8cde7..783efaff 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 @@ -1,6 +1,6 @@ package io.computenode.cyfra.core -import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} +import io.computenode.cyfra.core.layout.{Layout, LayoutBinding} import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer, GUniform} @@ -15,7 +15,8 @@ trait Allocation: def write(bb: ByteBuffer, offset: Int = 0, size: Int = -1): Unit - extension [Params, EL <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, EL, RL]) def execute(params: Params, layout: EL): RL + extension [Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding](execution: GExecution[Params, EL, RL]) + def execute(params: Params, layout: EL): RL extension (buffers: GBuffer.type) def apply[T <: Value: {Tag, FromExpr}](length: Int): GBuffer[T] 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 78bf8734..cfd3ad8d 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 @@ -2,7 +2,7 @@ package io.computenode.cyfra.core import io.computenode.cyfra.core.Allocation import io.computenode.cyfra.core.GProgram.BufferLengthSpec -import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} +import io.computenode.cyfra.core.layout.{Layout, LayoutBinding} import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.FromExpr import io.computenode.cyfra.dsl.binding.GBuffer @@ -10,22 +10,21 @@ import izumi.reflect.Tag import java.nio.ByteBuffer -sealed trait GBufferRegion[ReqAlloc <: Layout: LayoutStruct, ResAlloc <: Layout: LayoutStruct] +sealed trait GBufferRegion[ReqAlloc <: Layout: LayoutBinding, ResAlloc <: Layout: LayoutBinding] object GBufferRegion: - def allocate[Alloc <: Layout: LayoutStruct]: GBufferRegion[Alloc, Alloc] = - AllocRegion(summon[LayoutStruct[Alloc]].layoutRef) + def allocate[Alloc <: Layout: LayoutBinding]: GBufferRegion[Alloc, Alloc] = AllocRegion() - case class AllocRegion[Alloc <: Layout: LayoutStruct](l: Alloc) extends GBufferRegion[Alloc, Alloc] + case class AllocRegion[Alloc <: Layout: LayoutBinding]() extends GBufferRegion[Alloc, Alloc] - case class MapRegion[ReqAlloc <: Layout: LayoutStruct, BodyAlloc <: Layout: LayoutStruct, ResAlloc <: Layout: LayoutStruct]( + case class MapRegion[ReqAlloc <: Layout: LayoutBinding, BodyAlloc <: Layout: LayoutBinding, ResAlloc <: Layout: LayoutBinding]( reqRegion: GBufferRegion[ReqAlloc, BodyAlloc], f: Allocation => BodyAlloc => ResAlloc, ) extends GBufferRegion[ReqAlloc, ResAlloc] - extension [ReqAlloc <: Layout: LayoutStruct, ResAlloc <: Layout: LayoutStruct](region: GBufferRegion[ReqAlloc, ResAlloc]) - def map[NewAlloc <: Layout: LayoutStruct](f: Allocation ?=> ResAlloc => NewAlloc): GBufferRegion[ReqAlloc, NewAlloc] = + 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 = diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala index e757f7f8..22418ead 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GExecution.scala @@ -10,15 +10,18 @@ import io.computenode.cyfra.spirv.compilers.ExpressionCompiler.UniformStructRef import izumi.reflect.Tag import GExecution.* -trait GExecution[-Params, ExecLayout <: Layout, +ResLayout <: Layout]: +trait GExecution[-Params, ExecLayout <: Layout: LayoutBinding, ResLayout <: Layout: LayoutBinding]: - def flatMap[NRL <: Layout, NP <: Params](f: ResLayout => GExecution[NP, ExecLayout, NRL]): GExecution[NP, ExecLayout, NRL] = + def layoutBinding: LayoutBinding[ExecLayout] = summon[LayoutBinding[ExecLayout]] + def resLayoutBinding: LayoutBinding[ResLayout] = summon[LayoutBinding[ResLayout]] + + def flatMap[NRL <: Layout: LayoutBinding, NP <: Params](f: ResLayout => GExecution[NP, ExecLayout, NRL]): GExecution[NP, ExecLayout, NRL] = FlatMap(this, (p, r) => f(r)) - def map[NRL <: Layout](f: ResLayout => NRL): GExecution[Params, ExecLayout, NRL] = + def map[NRL <: Layout: LayoutBinding](f: ResLayout => NRL): GExecution[Params, ExecLayout, NRL] = Map(this, f, identity, identity) - def contramap[NEL <: Layout](f: NEL => ExecLayout): GExecution[Params, NEL, ResLayout] = + def contramap[NEL <: Layout: LayoutBinding](f: NEL => ExecLayout): GExecution[Params, NEL, ResLayout] = Map(this, identity, f, identity) def contramapParams[NP](f: NP => Params): GExecution[NP, ExecLayout, ResLayout] = @@ -32,31 +35,33 @@ trait GExecution[-Params, ExecLayout <: Layout, +ResLayout <: Layout]: object GExecution: - def apply[Params, L <: Layout]() = + def apply[Params, L <: Layout: LayoutBinding]() = Pure[Params, L]() - def forParams[Params, EL <: Layout, RL <: Layout](f: Params => GExecution[Params, EL, RL]): GExecution[Params, EL, RL] = + def forParams[Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding]( + f: Params => GExecution[Params, EL, RL], + ): GExecution[Params, EL, RL] = FlatMap[Params, EL, EL, RL](Pure[Params, EL](), (params: Params, _: EL) => f(params)) - case class Pure[Params, L <: Layout]() extends GExecution[Params, L, L] + case class Pure[Params, L <: Layout: LayoutBinding]() extends GExecution[Params, L, L] - case class FlatMap[Params, EL <: Layout, RL <: Layout, NRL <: Layout]( + case class FlatMap[Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding, NRL <: Layout: LayoutBinding]( execution: GExecution[Params, EL, RL], f: (Params, RL) => GExecution[Params, EL, NRL], ) extends GExecution[Params, EL, NRL] - case class Map[P, NP, EL <: Layout, NEL <: Layout, RL <: Layout, NRL <: Layout]( + case class Map[P, NP, EL <: Layout: LayoutBinding, NEL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding, NRL <: Layout: LayoutBinding]( execution: GExecution[P, EL, RL], mapResult: RL => NRL, contramapLayout: NEL => EL, contramapParams: NP => P, ) extends GExecution[NP, NEL, NRL]: - override def map[NNRL <: Layout](f: NRL => NNRL): GExecution[NP, NEL, NNRL] = + override def map[NNRL <: Layout: LayoutBinding](f: NRL => NNRL): GExecution[NP, NEL, NNRL] = Map(execution, mapResult andThen f, contramapLayout, contramapParams) override def contramapParams[NNP](f: NNP => NP): GExecution[NNP, NEL, NRL] = Map(execution, mapResult, contramapLayout, f andThen contramapParams) - override def contramap[NNL <: Layout](f: NNL => NEL): GExecution[NP, NNL, NRL] = + override def contramap[NNL <: Layout: LayoutBinding](f: NNL => NEL): GExecution[NP, NNL, NRL] = Map(execution, mapResult, f andThen contramapLayout, contramapParams) diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala index 5f2c0dae..da5407df 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GProgram.scala @@ -1,8 +1,7 @@ package io.computenode.cyfra.core -import io.computenode.cyfra.core.layout.LayoutStruct +import io.computenode.cyfra.core.layout.{Layout, LayoutBinding, LayoutStruct} import io.computenode.cyfra.dsl.gio.GIO -import io.computenode.cyfra.core.layout.Layout import java.nio.ByteBuffer import GProgram.* @@ -13,12 +12,12 @@ import io.computenode.cyfra.dsl.struct.GStruct import io.computenode.cyfra.dsl.struct.GStruct.Empty import izumi.reflect.Tag -trait GProgram[Params, L <: Layout: LayoutStruct] extends GExecution[Params, L, L]: +trait GProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}] extends GExecution[Params, L, L]: val layout: InitProgramLayout => Params => L val dispatch: (L, Params) => ProgramDispatch val workgroupSize: WorkDimensions - private[cyfra] def layoutStruct: LayoutStruct[L] = summon[LayoutStruct[L]] private[cyfra] def cacheKey: String // TODO better type + def layoutStruct = summon[LayoutStruct[L]] object GProgram: type WorkDimensions = (Int, Int, Int) @@ -26,8 +25,8 @@ object GProgram: sealed trait ProgramDispatch case class DynamicDispatch[L <: Layout](buffer: GBinding[?], offset: Int) extends ProgramDispatch case class StaticDispatch(size: WorkDimensions) extends ProgramDispatch - - def apply[Params, L <: Layout: LayoutStruct]( + + def apply[Params, L <: Layout: {LayoutBinding, LayoutStruct}]( layout: InitProgramLayout ?=> Params => L, dispatch: (L, Params) => ProgramDispatch, workgroupSize: WorkDimensions = (128, 1, 1), @@ -35,15 +34,15 @@ object GProgram: new GioProgram[Params, L](body, s => layout(using s), dispatch, workgroupSize) private[cyfra] class BufferLengthSpec[T <: Value: {Tag, FromExpr}](val length: Int) extends GBuffer[T]: - private[cyfra] def materialise()(using x: Allocation): GBuffer[T] = GBuffer.apply[T](length) + private[cyfra] def materialise()(using Allocation): GBuffer[T] = GBuffer.apply[T](length) private[cyfra] class DynamicUniform[T <: GStruct[T]: {Tag, FromExpr}]() extends GUniform[T] trait InitProgramLayout: - extension (buffers: GBuffer.type) + extension (_buffers: GBuffer.type) def apply[T <: Value: {Tag, FromExpr}](length: Int): GBuffer[T] = BufferLengthSpec[T](length) - extension (uniforms: GUniform.type) + extension (_uniforms: GUniform.type) def apply[T <: GStruct[T]: {Tag, FromExpr}](): GUniform[T] = DynamicUniform[T]() def apply[T <: GStruct[T]: {Tag, FromExpr}](value: T): GUniform[T] diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GioProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GioProgram.scala index c36bb0ec..d97f97b2 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/GioProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/GioProgram.scala @@ -6,13 +6,13 @@ import io.computenode.cyfra.dsl.Value.GBoolean import io.computenode.cyfra.dsl.gio.GIO import izumi.reflect.Tag -case class GioProgram[Params, L <: Layout: LayoutStruct]( +case class GioProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}]( body: L => GIO[?], layout: InitProgramLayout => Params => L, dispatch: (L, Params) => ProgramDispatch, workgroupSize: WorkDimensions, ) extends GProgram[Params, L]: - private[cyfra] def cacheKey: String = layoutStruct.elementTypes match + private[cyfra] def cacheKey: String = summon[LayoutStruct[L]].elementTypes match case x if x.size == 12 => "addOne" case x if x.contains(summon[Tag[GBoolean]]) && x.size == 3 => "filter" case x if x.size == 3 => "emit" diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala index 87af9c2e..c7b9f29a 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/SpirvProgram.scala @@ -1,7 +1,6 @@ package io.computenode.cyfra.core -import io.computenode.cyfra.core.layout.Layout -import io.computenode.cyfra.core.layout.LayoutStruct +import io.computenode.cyfra.core.layout.{Layout, LayoutBinding, LayoutStruct} import io.computenode.cyfra.core.GProgram.{InitProgramLayout, ProgramDispatch, WorkDimensions} import io.computenode.cyfra.core.SpirvProgram.Operation.ReadWrite import io.computenode.cyfra.core.SpirvProgram.{Binding, ShaderLayout} @@ -20,7 +19,7 @@ import scala.util.Try import scala.util.Using import scala.util.chaining.* -case class SpirvProgram[Params, L <: Layout: LayoutStruct] private ( +case class SpirvProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}] private ( layout: InitProgramLayout => Params => L, dispatch: (L, Params) => ProgramDispatch, workgroupSize: WorkDimensions, @@ -38,7 +37,7 @@ object SpirvProgram: case Write case ReadWrite - def apply[Params, L <: Layout: LayoutStruct]( + def apply[Params, L <: Layout: {LayoutBinding, LayoutStruct}]( path: String, layout: InitProgramLayout ?=> Params => L, dispatch: (L, Params) => ProgramDispatch, diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/Layout.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/Layout.scala index 979b4410..37f369e8 100644 --- a/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/Layout.scala +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/Layout.scala @@ -1,22 +1,3 @@ package io.computenode.cyfra.core.layout -import io.computenode.cyfra.core.layout.Layout.IllegalLayoutElement -import io.computenode.cyfra.dsl.Value -import io.computenode.cyfra.dsl.binding.{GBinding, GBuffer} - -trait Layout: - self: Product => - - lazy val bindings: Seq[GBinding[?]] = - validateElements() - self.productIterator - .map(_.asInstanceOf[GBinding[?]]) - .toSeq - - private def validateElements(): Unit = - val invalidIndex = self.productIterator.indexWhere(!_.isInstanceOf[GBinding[?]]) - if invalidIndex != -1 then throw IllegalLayoutElement(self.productElementName(invalidIndex)) - -object Layout: - - case class IllegalLayoutElement(name: String) extends Exception(s"All Layout members must be GBindings, $name is not") +trait Layout diff --git a/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutBinding.scala b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutBinding.scala new file mode 100644 index 00000000..5a7eaa52 --- /dev/null +++ b/cyfra-core/src/main/scala/io/computenode/cyfra/core/layout/LayoutBinding.scala @@ -0,0 +1,34 @@ +package io.computenode.cyfra.core.layout + +import io.computenode.cyfra.dsl.binding.GBinding + +import scala.Tuple.* +import scala.compiletime.{constValue, erasedValue, error} +import scala.deriving.Mirror + +trait LayoutBinding[L <: Layout]: + def fromBindings(bindings: Seq[GBinding[?]]): L + def toBindings(layout: L): Seq[GBinding[?]] + +object LayoutBinding: + inline given derived[L <: Layout](using m: Mirror.ProductOf[L]): LayoutBinding[L] = + allElementsAreBindings[m.MirroredElemTypes, m.MirroredElemLabels]() + val size = constValue[Size[m.MirroredElemTypes]] + val constructor = m.fromProduct + new DerivedLayoutBinding[L](size, constructor) + + // noinspection NoTailRecursionAnnotation + private inline def allElementsAreBindings[Types <: Tuple, Names <: Tuple](): Unit = + inline erasedValue[Types] match + case _: EmptyTuple => () + case _: (GBinding[?] *: t) => allElementsAreBindings[t, Tail[Names]]() + case _ => + val name = constValue[Head[Names]] + error(s"$name is not a GBinding, all elements of a Layout must be GBindings") + + class DerivedLayoutBinding[L <: Layout](size: Int, constructor: Product => L) extends LayoutBinding[L]: + override def fromBindings(bindings: Seq[GBinding[?]]): L = + assert(bindings.size == size, s"Expected $size) bindings, got ${bindings.size}") + constructor(Tuple.fromArray(bindings.toArray)) + override def toBindings(layout: L): Seq[GBinding[?]] = + layout.asInstanceOf[Product].productIterator.map(_.asInstanceOf[GBinding[?]]).toSeq diff --git a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala index 896d53e4..60c53cac 100644 --- a/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala +++ b/cyfra-dsl/src/main/scala/io/computenode/cyfra/dsl/binding/GBinding.scala @@ -1,13 +1,15 @@ package io.computenode.cyfra.dsl.binding import io.computenode.cyfra.dsl.Value -import io.computenode.cyfra.dsl.Value.FromExpr.fromExpr +import io.computenode.cyfra.dsl.Value.FromExpr.fromExpr as fromExprEval import io.computenode.cyfra.dsl.Value.{FromExpr, Int32} import io.computenode.cyfra.dsl.gio.GIO import io.computenode.cyfra.dsl.struct.GStruct import izumi.reflect.Tag -sealed trait GBinding[T <: Value: {Tag, FromExpr}] +sealed trait GBinding[T <: Value: {Tag, FromExpr}]: + def tag = summon[Tag[T]] + def fromExpr = summon[FromExpr[T]] trait GBuffer[T <: Value: {FromExpr, Tag}] extends GBinding[T]: def read(index: Int32): T = FromExpr.fromExpr(ReadBuffer(this, index)) @@ -17,7 +19,7 @@ trait GBuffer[T <: Value: {FromExpr, Tag}] extends GBinding[T]: object GBuffer trait GUniform[T <: Value: {Tag, FromExpr}] extends GBinding[T]: - def read: T = fromExpr(ReadUniform(this)) + def read: T = fromExprEval(ReadUniform(this)) def write(value: T): GIO[Unit] = WriteUniform(this, value) 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 a5483cdc..dce316d3 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 @@ -34,7 +34,7 @@ object TestingStuff: out = GBuffer[Int32](params.inSize * params.emitN), args = GUniform(EmitProgramUniform(params.emitN)), ), - dispatch = (layout, args) => GProgram.StaticDispatch((args.inSize / 128, 1, 1)), + dispatch = (_, args) => GProgram.StaticDispatch((args.inSize / 128, 1, 1)), ): layout => val EmitProgramUniform(emitN) = layout.args.read val invocId = GIO.invocationId @@ -59,7 +59,7 @@ object TestingStuff: out = GBuffer[GBoolean](params.inSize), params = GUniform(FilterProgramUniform(params.filterValue)), ), - dispatch = (layout, args) => GProgram.StaticDispatch((args.inSize / 128, 1, 1)), + dispatch = (_, args) => GProgram.StaticDispatch((args.inSize / 128, 1, 1)), ): layout => val invocId = GIO.invocationId val element = GIO.read(layout.in, invocId) 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 1ee63805..6fc76e42 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 @@ -4,7 +4,9 @@ import io.computenode.cyfra.core.GProgram.InitProgramLayout import io.computenode.cyfra.core.SpirvProgram.* import io.computenode.cyfra.core.binding.{BufferRef, UniformRef} import io.computenode.cyfra.core.{GExecution, GProgram} -import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} +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, @@ -16,12 +18,14 @@ import io.computenode.cyfra.runtime.ExecutionHandler.{ ShaderCall, } import io.computenode.cyfra.runtime.ExecutionHandler.DispatchType.* +import io.computenode.cyfra.runtime.ExecutionHandler.ExecutionBinding.{BufferBinding, UniformBinding} import io.computenode.cyfra.utility.Utility.timed import io.computenode.cyfra.vulkan.command.{CommandPool, Fence} import io.computenode.cyfra.vulkan.compute.ComputePipeline import io.computenode.cyfra.vulkan.core.Queue import io.computenode.cyfra.vulkan.memory.{DescriptorPool, DescriptorSet} 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} @@ -36,7 +40,9 @@ class ExecutionHandler(runtime: VkCyfraRuntime): private val descriptorPool: DescriptorPool = context.descriptorPool // TODO descriptor pool manager - descriptor allocation and reclamation support private val commandPool: CommandPool = context.commandPool // TODO multiple command pools - different command pools for different workloads - def handle[Params, EL <: Layout, RL <: Layout](execution: GExecution[Params, EL, RL], params: Params, layout: EL)(using VkAllocation): RL = + def handle[Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding](execution: GExecution[Params, EL, RL], params: Params, layout: EL)( + using VkAllocation, + ): RL = val (result, shaderCalls) = interpret(execution, params, layout) val descriptorSets = shaderCalls.map { case ShaderCall(pipeline, layout, _) => @@ -73,35 +79,58 @@ class ExecutionHandler(runtime: VkCyfraRuntime): commandPool.freeCommandBuffer(commandBuffer) result - private def interpret[Params, EL <: Layout, RL <: Layout](execution: GExecution[Params, EL, RL], params: Params, layout: EL)(using - VkAllocation, - ): (RL, Seq[ShaderCall]) = - val bindingsAcc: mutable.Map[GBinding[?], mutable.Buffer[GBinding[?]]] = mutable.Map.from(layout.bindings.map(x => (x, mutable.Buffer.empty))) + private def interpret[Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding]( + execution: GExecution[Params, EL, RL], + params: Params, + layout: EL, + )(using VkAllocation): (RL, Seq[ShaderCall]) = + val bindingsAcc: mutable.Map[GBinding[?], mutable.Buffer[GBinding[?]]] = mutable.Map.empty + + def mockBindings[L <: Layout: LayoutBinding](layout: L): L = + val mapper = summon[LayoutBinding[L]] + val res = mapper + .toBindings(layout) + .map: + case x: ExecutionBinding[?] => x + case x: GBinding[?] => + val e = ExecutionBinding(x)(using x.fromExpr, x.tag) + bindingsAcc.put(e, mutable.Buffer(x)) + e + + mapper.fromBindings(res) // noinspection TypeParameterShadow - def interpretImpl[Params, EL <: Layout, RL <: Layout](execution: GExecution[Params, EL, RL], params: Params, layout: EL): (RL, Seq[ShaderCall]) = + def interpretImpl[Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding]( + execution: GExecution[Params, EL, RL], + params: Params, + layout: EL, + ): (RL, Seq[ShaderCall]) = execution match case GExecution.Pure() => (layout, Seq.empty) - case GExecution.Map(execution, map, cmap, cmapP) => + case GExecution.Map(innerExec, map, cmap, cmapP) => + val pel = innerExec.layoutBinding + val prl = innerExec.resLayoutBinding val cParams = cmapP(params) - val cLayout = cmap(layout) - cLayout.bindings.foreach(x => bindingsAcc.getOrElseUpdate(x, mutable.Buffer.empty)) - val (prevLayout, calls) = interpretImpl(execution, cParams, cLayout) - (map(prevLayout), calls) + val cLayout = mockBindings(cmap(layout))(using pel) + val (prevRl, calls) = interpretImpl(innerExec, cParams, cLayout)(using pel, prl) + val nextRl = mockBindings(map(prevRl)) + (nextRl, calls) case GExecution.FlatMap(execution, f) => - val (prevLayout, calls) = interpretImpl(execution, params, layout) - val nextExecution = f(params, prevLayout) - val (prevLayout2, calls2) = interpretImpl(nextExecution, params, layout) - (prevLayout2, calls ++ calls2) + val el = execution.layoutBinding + val (rl, calls) = interpretImpl(execution, params, layout)(using el, execution.resLayoutBinding) + val nextExecution = f(params, rl) + val (rl2, calls2) = interpretImpl(nextExecution, params, layout)(using el, nextExecution.resLayoutBinding) + (rl2, calls ++ calls2) case program: GProgram[Params, EL] => + given lb: LayoutBinding[EL] = program.layoutBinding + given LayoutStruct[EL] = program.layoutStruct val shader = - given LayoutStruct[EL] = program.layoutStruct runtime.getOrLoadProgram(program) val layoutInit = val initProgram: InitProgramLayout = summon[VkAllocation].getInitProgramLayout program.layout(initProgram)(params) - layout.bindings - .zip(layoutInit.bindings) + lb.toBindings(layout) + .zip(lb.toBindings(layoutInit)) .foreach: case (binding, initBinding) => bindingsAcc(binding).append(initBinding) @@ -109,57 +138,58 @@ class ExecutionHandler(runtime: VkCyfraRuntime): case GProgram.DynamicDispatch(buffer, offset) => DispatchType.Indirect(buffer, offset) case GProgram.StaticDispatch(size) => DispatchType.Direct(size._1, size._2, size._3) // noinspection ScalaRedundantCast - val rl = layout.asInstanceOf[RL] - (rl, Seq(ShaderCall(shader.underlying, shader.shaderBindings(layout), dispatch))) + (layout.asInstanceOf[RL], Seq(ShaderCall(shader.underlying, shader.shaderBindings(layout), dispatch))) case _ => ??? - val (rl, steps) = interpretImpl(execution, params, layout) - val bindingsMap = bindingsAcc.view.mapValues(_.toSeq).map(x => (x._1, interpretBinding(x._1, x._2))).toMap + val (rl, steps) = interpretImpl(execution, params, mockBindings(layout)) + val bingingToVk = bindingsAcc.map(x => (x._1, interpretBinding(x._1, x._2.toSeq))) val nextSteps = steps.map: case ShaderCall(pipeline, layout, dispatch) => val nextLayout = layout.map: _.map: - case Binding(binding, operation) => Binding(bindingsMap(binding), operation) + case Binding(binding, operation) => Binding(bingingToVk(binding), operation) val nextDispatch = dispatch match case x: Direct => x - case Indirect(buffer, offset) => Indirect(bindingsMap(buffer), offset) + case Indirect(buffer, offset) => Indirect(bingingToVk(buffer), offset) ShaderCall(pipeline, nextLayout, nextDispatch) - (rl, nextSteps) + val mapper = summon[LayoutBinding[RL]] + val res = mapper.fromBindings(mapper.toBindings(rl).map(bingingToVk.apply)) + (res, nextSteps) - private def interpretBinding(binding: GBinding[?], limiters: Seq[GBinding[?]])(using VkAllocation): GBinding[?] = - val bindings = limiters.appended(binding) + private def interpretBinding(binding: GBinding[?], bindings: Seq[GBinding[?]])(using VkAllocation): GBinding[?] = binding match - case buffer: GBuffer[?] => + case _: BufferBinding[?] => val (allocations, sizeSpec) = bindings.partitionMap: case x: VkBuffer[?] => Left(x) case x: GProgram.BufferLengthSpec[?] => Right(x) case x => throw BindingLogicError(x, "Unsupported buffer type") if allocations.size > 1 then throw BindingLogicError(allocations, "Multiple allocations for buffer") - val all = allocations.headOption + val alloc = allocations.headOption val lengths = sizeSpec.distinctBy(_.length) if lengths.size > 1 then throw BindingLogicError(lengths, "Multiple conflicting lengths for buffer") val length = lengths.headOption - (all, length) match + (alloc, length) match case (Some(buffer), Some(sizeSpec)) => if buffer.length != sizeSpec.length then throw BindingLogicError(Seq(buffer, sizeSpec), s"Buffer length mismatch, ${buffer.length} != ${sizeSpec.length}") buffer case (Some(buffer), None) => buffer case (None, Some(length)) => length.materialise() - case (None, None) => throw BindingLogicError(binding, "Cannot create buffer without size or allocation") + case (None, None) => throw new IllegalStateException("Cannot create buffer without size or allocation") - case uniform: GUniform[?] => + case _: UniformBinding[?] => val allocations = bindings.filter: case _: VkUniform[?] => true case _: GProgram.DynamicUniform[?] => false case _: GUniform.ParamUniform[?] => false case x => throw BindingLogicError(x, "Unsupported binding type") if allocations.size > 1 then throw BindingLogicError(allocations, "Multiple allocations for uniform") - allocations.headOption.getOrElse(throw BindingLogicError(binding, "Uniform never allocated")) + allocations.headOption.getOrElse(throw new IllegalStateException("Uniform never allocated")) + case x => throw new IllegalArgumentException(s"Binding of type ${x.getClass.getName} should not be here") private def recordCommandBuffer(steps: Seq[ExecutionStep]): VkCommandBuffer = pushStack: stack => val commandBuffer = commandPool.createCommandBuffer() @@ -213,7 +243,14 @@ object ExecutionHandler: case class Direct(x: Int, y: Int, z: Int) extends DispatchType case class Indirect(buffer: GBinding[?], offset: Int) extends DispatchType - class ExecutionBinding + sealed trait ExecutionBinding[T <: Value: {FromExpr, Tag}] + object ExecutionBinding: + class UniformBinding[T <: Value: {FromExpr, Tag}] extends ExecutionBinding[T] with GUniform[T] + class BufferBinding[T <: Value: {FromExpr, Tag}] extends ExecutionBinding[T] with GBuffer[T] + + def apply[T <: Value: {FromExpr, Tag}](binding: GBinding[T]): ExecutionBinding[T] & GBinding[T] = binding match + case _: GUniform[T] => new UniformBinding() + case _: GBuffer[T] => new BufferBinding() case class BindingLogicError(bindings: Seq[GBinding[?]], message: String) extends RuntimeException(s"Error in binding logic for $bindings: $message") object BindingLogicError: 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 467c5582..59142676 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 @@ -1,6 +1,6 @@ package io.computenode.cyfra.runtime -import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} +import io.computenode.cyfra.core.layout.{Layout, LayoutBinding} import io.computenode.cyfra.core.{Allocation, GExecution, GProgram} import io.computenode.cyfra.core.SpirvProgram import io.computenode.cyfra.dsl.Expression.ConstInt32 @@ -25,6 +25,8 @@ import scala.collection.mutable import scala.util.chaining.* class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler)(using Allocator) extends Allocation: + given VkAllocation = this + extension (buffer: GBinding[?]) def read(bb: ByteBuffer, offset: Int = 0, size: Int = -1): Unit = val buf = getUnderlying(buffer) @@ -65,8 +67,8 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) def apply[T <: Value: {Tag, FromExpr}](): GUniform[T] = VkUniform[T]().tap(bindings += _) - extension [Params, EL <: Layout, RL <: Layout: LayoutStruct](execution: GExecution[Params, EL, RL]) - override def execute(params: Params, layout: EL): RL = executionHandler.handle(execution, params, layout)(using this) + extension [Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding](execution: GExecution[Params, EL, RL]) + def execute(params: Params, layout: EL): RL = executionHandler.handle(execution, params, layout) private def direct[T <: Value: {Tag, FromExpr}](buff: ByteBuffer): GUniform[T] = GUniform[T](buff) diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala index f597c495..f28cfd65 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala @@ -1,6 +1,6 @@ package io.computenode.cyfra.runtime -import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} +import io.computenode.cyfra.core.layout.{Layout, LayoutBinding, LayoutStruct} import io.computenode.cyfra.core.{Allocation, CyfraRuntime, GExecution, GProgram, SpirvProgram} import io.computenode.cyfra.vulkan.VulkanContext import io.computenode.cyfra.vulkan.compute.ComputePipeline @@ -14,7 +14,7 @@ class VkCyfraRuntime extends CyfraRuntime: private val executionHandler = new ExecutionHandler(this) private val shaderCache = mutable.Map.empty[String, VkShader[?]] - private[cyfra] def getOrLoadProgram[Params, L <: Layout: LayoutStruct](program: GProgram[Params, L]): VkShader[L] = + private[cyfra] def getOrLoadProgram[Params, L <: Layout: {LayoutBinding, LayoutStruct}](program: GProgram[Params, L]): VkShader[L] = shaderCache.getOrElseUpdate(program.cacheKey, VkShader(program)).asInstanceOf[VkShader[L]] override def withAllocation(f: Allocation => Unit): Unit = diff --git a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala index 28e75ce0..4544553e 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkShader.scala @@ -3,7 +3,7 @@ package io.computenode.cyfra.runtime import io.computenode.cyfra.core.{GProgram, GioProgram, SpirvProgram} import io.computenode.cyfra.core.SpirvProgram.* import io.computenode.cyfra.core.GProgram.InitProgramLayout -import io.computenode.cyfra.core.layout.{Layout, LayoutStruct} +import io.computenode.cyfra.core.layout.{Layout, LayoutBinding, LayoutStruct} import io.computenode.cyfra.dsl.binding.{GBuffer, GUniform} import io.computenode.cyfra.vulkan.compute.ComputePipeline import io.computenode.cyfra.vulkan.compute.ComputePipeline.* @@ -15,7 +15,7 @@ import scala.util.{Failure, Success} case class VkShader[L](underlying: ComputePipeline, shaderBindings: L => ShaderLayout) object VkShader: - def apply[P, L <: Layout: LayoutStruct](program: GProgram[P, L])(using Device): VkShader[L] = + def apply[P, L <: Layout: {LayoutBinding, LayoutStruct}](program: GProgram[P, L])(using Device): VkShader[L] = val SpirvProgram(layout, dispatch, _workgroupSize, code, entryPoint, shaderBindings, _) = program match case p: GioProgram[?, ?] => compile(p) case p: SpirvProgram[?, ?] => p @@ -34,9 +34,9 @@ object VkShader: val pipeline = ComputePipeline(code, entryPoint, LayoutInfo(sets)) VkShader(pipeline, shaderBindings) - def compile[Params, L <: Layout: LayoutStruct](program: GioProgram[Params, L]): SpirvProgram[Params, L] = - val GioProgram(_, layout, dispatch, workgroupSize) = program + def compile[Params, L <: Layout: {LayoutBinding, LayoutStruct}](program: GioProgram[Params, L]): SpirvProgram[Params, L] = + val GioProgram(_, layout, dispatch, _) = program val name = program.cacheKey + ".spv" loadShader(name) match - case Failure(exception) => ??? - case Success(value) => SpirvProgram(name, (il: InitProgramLayout) ?=> layout(il), dispatch) + case Failure(_) => ??? + case Success(_) => SpirvProgram(name, (il: InitProgramLayout) ?=> layout(il), dispatch) From b4c94b62f55bf18367c4ea48f61d12fbae92ebf3 Mon Sep 17 00:00:00 2001 From: MarconZet <25779550+MarconZet@users.noreply.github.com> Date: Fri, 25 Jul 2025 13:22:21 +0200 Subject: [PATCH 19/20] Fixed memory bug --- README.md | 23 +++++-- .../src/main/resources/compileAll.sh | 2 +- .../cyfra/samples/TestingStuff.scala | 21 ++++--- .../cyfra/runtime/ExecutionHandler.scala | 2 +- .../cyfra/vulkan/core/Instance.scala | 16 ++++- flake.lock | 61 +++++++++++++++++++ flake.nix | 33 ++++++++++ 7 files changed, 141 insertions(+), 17 deletions(-) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/README.md b/README.md index c4488e56..e59b4dcb 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,10 @@ Library provides a way to compile Scala 3 DSL to SPIR-V and to run it with Vulkan runtime on GPUs. It is multiplatform. It works on: - - Linux, Windows, and Mac (for Mac requires installation of moltenvk). - - Any dedicated or integrated GPUs that support Vulkan. In practice, it means almost all moderately modern devices from most manufacturers including Nvidia, AMD, Intel, Apple. + +- Linux, Windows, and Mac (for Mac requires installation of moltenvk). +- Any dedicated or integrated GPUs that support Vulkan. In practice, it means almost all moderately modern devices from + most manufacturers including Nvidia, AMD, Intel, Apple. Library is in an early stage - alpha release and proper documentation are coming. @@ -15,12 +17,15 @@ Included Foton library provides a clean and fun way to animate functions and ray ## Examples ### Ray traced animation + ![output](https://github.com/user-attachments/assets/3eac9f7f-72df-4a5d-b768-9117d651c78d) [code](https://github.com/ComputeNode/cyfra/blob/50aecea/cyfra-examples/src/main/scala/io/computenode/samples/cyfra/foton/AnimatedRaytrace.scala) -(this is API usage, to see ray tracing implementation look at [RtRenderer](https://github.com/ComputeNode/cyfra/blob/50aecea132188776021afe0b407817665676a021/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/RtRenderer.scala)) +(this is API usage, to see ray tracing implementation look +at [RtRenderer](https://github.com/ComputeNode/cyfra/blob/50aecea132188776021afe0b407817665676a021/cyfra-foton/src/main/scala/io/computenode/cyfra/foton/rt/RtRenderer.scala)) ### Animated Julia set + [code](https://github.com/ComputeNode/cyfra/blob/50aecea132188776021afe0b407817665676a021/cyfra-examples/src/main/scala/io/computenode/samples/cyfra/foton/AnimatedJulia.scala) @@ -28,9 +33,11 @@ Included Foton library provides a clean and fun way to animate functions and ray ## Animation features examples ### Custom animated functions + ### Animated ray traced scene + ## Coding features examples @@ -48,9 +55,15 @@ Included Foton library provides a clean and fun way to animate functions and ray ## Development -To enable validation layers for vulkan, you need to install vulkan SKD. After installing, set the following VM options: +To enable validation layers for vulkan, you need to install vulkan SKD. After installing, set the following VM option: + ``` --Dorg.lwjgl.vulkan.libname=libvulkan.1.dylib -Dio.computenode.cyfra.vulkan.validation=true +``` + +If you are on MacOs, then also add: + +``` +-Dorg.lwjgl.vulkan.libname=libvulkan.1.dylib -Djava.library.path=$VULKAN_SDK/lib ``` \ No newline at end of file diff --git a/cyfra-examples/src/main/resources/compileAll.sh b/cyfra-examples/src/main/resources/compileAll.sh index fdd4be8c..e4f70140 100644 --- a/cyfra-examples/src/main/resources/compileAll.sh +++ b/cyfra-examples/src/main/resources/compileAll.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash for f in *.comp do 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 dce316d3..e604bde3 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 @@ -187,23 +187,26 @@ object TestingStuff: def testAddProgram10Times = given runtime: VkCyfraRuntime = VkCyfraRuntime() val bufferSize = 1280 - val params = AddProgramParams(bufferSize, addA = 3, addB = 7) + val params = AddProgramParams(bufferSize, addA = 0, addB = 1) val region = GBufferRegion .allocate[AddProgramExecLayout] .map: region => execution.execute(params, region) - val inData = (0 until bufferSize).toArray - val inBuffers = List.fill(5)(BufferUtils.createByteBuffer(bufferSize * 4)) - inBuffers.foreach(_.asIntBuffer().put(inData).flip()) + + val inBuffers = List.fill(5)(BufferUtils.createIntBuffer(bufferSize)) + val wbbList = inBuffers.map(MemoryUtil.memByteBuffer) val outBuffers = List.fill(5)(BufferUtils.createIntBuffer(bufferSize)) val rbbList = outBuffers.map(MemoryUtil.memByteBuffer) + + val inData = (0 until bufferSize).toArray + inBuffers.foreach(_.put(inData).flip()) region.runUnsafe( init = AddProgramExecLayout( - in1 = GBuffer[Int32](inBuffers(0)), - in2 = GBuffer[Int32](inBuffers(1)), - in3 = GBuffer[Int32](inBuffers(2)), - in4 = GBuffer[Int32](inBuffers(3)), - in5 = GBuffer[Int32](inBuffers(4)), + in1 = GBuffer[Int32](wbbList(0)), + in2 = GBuffer[Int32](wbbList(1)), + in3 = GBuffer[Int32](wbbList(2)), + in4 = GBuffer[Int32](wbbList(3)), + in5 = GBuffer[Int32](wbbList(4)), out1 = GBuffer[Int32](bufferSize), out2 = GBuffer[Int32](bufferSize), out3 = GBuffer[Int32](bufferSize), 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 6fc76e42..91b445a9 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 @@ -61,7 +61,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime): val (executeSteps, _) = dispatches.foldLeft((Seq.empty[ExecutionStep], Set.empty[GBinding[?]])): case ((steps, dirty), step) => val bindings = step.layout.flatten.map(_.binding) - if bindings.exists(dirty.contains) then (steps.appendedAll(Seq(PipelineBarrier, step)), Set.empty[GBinding[?]]) + if bindings.exists(dirty.contains) then (steps.appendedAll(Seq(PipelineBarrier, step)), bindings.toSet) else (steps.appended(step), dirty ++ bindings) val commandBuffer = recordCommandBuffer(executeSteps) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala index b29ebeb4..7452ec0a 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala @@ -4,10 +4,11 @@ import io.computenode.cyfra.utility.Logger.logger import io.computenode.cyfra.vulkan.VulkanContext.ValidationLayer import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import io.computenode.cyfra.vulkan.util.VulkanObject -import org.lwjgl.system.MemoryStack +import org.lwjgl.system.{MemoryStack, MemoryUtil} import org.lwjgl.system.MemoryUtil.NULL import org.lwjgl.vulkan.* import org.lwjgl.vulkan.EXTDebugReport.VK_EXT_DEBUG_REPORT_EXTENSION_NAME +import org.lwjgl.vulkan.EXTLayerSettings.VK_LAYER_SETTING_TYPE_BOOL32_EXT import org.lwjgl.vulkan.KHRPortabilityEnumeration.{VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME} import org.lwjgl.vulkan.VK10.* @@ -67,6 +68,19 @@ private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObj .pApplicationInfo(appInfo) .ppEnabledExtensionNames(ppEnabledExtensionNames) .ppEnabledLayerNames(ppEnabledLayerNames) + + if enableValidationLayers then + val layerSettings = VkLayerSettingEXT.calloc(1, stack) + layerSettings + .get(0) + .pLayerName(stack.ASCII(ValidationLayer)) + .pSettingName(stack.ASCII("validate_sync")) + .`type`(VK_LAYER_SETTING_TYPE_BOOL32_EXT) + .valueCount(1) + .pValues(MemoryUtil.memByteBuffer(stack.ints(1))) + val layerSettingsCI = VkLayerSettingsCreateInfoEXT.calloc(stack).sType$Default().pSettings(layerSettings) + pCreateInfo.pNext(layerSettingsCI) + val pInstance = stack.mallocPointer(1) check(vkCreateInstance(pCreateInfo, null, pInstance), "Failed to create VkInstance") new VkInstance(pInstance.get(0), pCreateInfo) diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..2468070f --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1735563628, + "narHash": "sha256-OnSAY7XDSx7CtDoqNh8jwVwh4xNL/2HaJxGjryLWzX8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b134951a4c9f3c995fd7be05f3243f8ecd65d798", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..b3691241 --- /dev/null +++ b/flake.nix @@ -0,0 +1,33 @@ +{ + description = "Dev shell for Vulkan + Java 21"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + jdk = pkgs.jdk21; + in { + devShells.default = pkgs.mkShell { + buildInputs = with pkgs; [ + jdk + sbt + vulkan-tools + vulkan-loader + vulkan-validation-layers + glslang + pkg-config + ]; + + JAVA_HOME = jdk; + VK_LAYER_PATH = "${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d"; + LD_LIBRARY_PATH="${pkgs.vulkan-loader}/lib:${pkgs.vulkan-validation-layers}/lib:$LD_LIBRARY_PATH"; + + }; + }); +} + From 5651956da596ff1ccd0568d73775c144d830a408 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Sat, 26 Jul 2025 00:39:20 +0200 Subject: [PATCH 20/20] cleanup --- .../computenode/cyfra/spirv/SpirvTypes.scala | 9 ------ .../computenode/cyfra/runtime/VkUniform.scala | 3 +- .../cyfra/vulkan/SequenceExecutorTest.scala | 31 ------------------- 3 files changed, 1 insertion(+), 42 deletions(-) delete mode 100644 cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.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 be02eb99..9fe1b386 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 @@ -2,7 +2,6 @@ package io.computenode.cyfra.spirv import io.computenode.cyfra.dsl.Value import io.computenode.cyfra.dsl.Value.* -import io.computenode.cyfra.dsl.struct.{GStructConstructor, GStructSchema} import io.computenode.cyfra.spirv.Opcodes.* import izumi.reflect.Tag import izumi.reflect.macrortti.{LTag, LightTypeTag} @@ -58,14 +57,6 @@ private[cyfra] object SpirvTypes: def typeStride(tag: Tag[?]): Int = typeStride(tag.tag) - def totalStride(gs: GStructSchema[?]): Int = gs.fields.map { - case (_, fromExpr, t) if t <:< gs.gStructTag => - val constructor = fromExpr.asInstanceOf[GStructConstructor[?]] - totalStride(constructor.schema) - case (_, _, t) => - typeStride(t) - }.sum - def toWord(tpe: Tag[?], value: Any): Words = tpe match case t if t == Int32Tag => IntWord(value.asInstanceOf[Int]) 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 index f85e805e..f8c75da7 100644 --- a/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala +++ b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkUniform.scala @@ -3,14 +3,13 @@ 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.spirv.SpirvTypes.typeStride 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 // typeStride(summon[Tag[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 | diff --git a/cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.scala b/cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.scala deleted file mode 100644 index ce0e296a..00000000 --- a/cyfra-vulkan/src/test/scala/io/computenode/cyfra/vulkan/SequenceExecutorTest.scala +++ /dev/null @@ -1,31 +0,0 @@ -package io.computenode.cyfra.vulkan - -import io.computenode.cyfra.vulkan.VulkanContext -import io.computenode.cyfra.vulkan.compute.* -import io.computenode.cyfra.vulkan.compute.ComputePipeline.* -import io.computenode.cyfra.vulkan.compute.ComputePipeline.BindingType.StorageBuffer -import io.computenode.cyfra.vulkan.core.Device -import munit.FunSuite -import org.lwjgl.BufferUtils - -class SequenceExecutorTest extends FunSuite: - val vulkanContext = VulkanContext() - import vulkanContext.given - - test("Memory barrier"): - val code = ??? - val layout = - LayoutInfo(Seq(DescriptorSetInfo(Seq(DescriptorInfo(StorageBuffer))), DescriptorSetInfo(Seq(DescriptorInfo(StorageBuffer))))) - val copy1 = new ComputePipeline(code, "main", layout) - val copy2 = new ComputePipeline(code, "main", layout) - - val sequence = ??? - val sequenceExecutor = ??? - val input = 0 until 1024 - val buffer = BufferUtils.createByteBuffer(input.length * 4) - input.foreach(buffer.putInt) - buffer.flip() - val res = ??? - val output = input.map(_ => ???) - - assertEquals(input.map(_ + 20000).toList, output.toList)