diff --git a/build.sbt b/build.sbt index 35a46c2c..419d75a4 100644 --- a/build.sbt +++ b/build.sbt @@ -91,6 +91,7 @@ lazy val foton = (project in file("cyfra-foton")) lazy val examples = (project in file("cyfra-examples")) .settings(commonSettings, runnerSettings) + .settings(libraryDependencies += "org.scala-lang.modules" % "scala-parallel-collections_3" % "1.2.0") .dependsOn(foton) lazy val vscode = (project in file("cyfra-vscode")) 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 783efaff..bdc1d5a7 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,9 +11,9 @@ import java.nio.ByteBuffer trait Allocation: extension (buffer: GBinding[?]) - def read(bb: ByteBuffer, offset: Int = 0, size: Int = -1): Unit + def read(bb: ByteBuffer, offset: Int = 0): Unit - def write(bb: ByteBuffer, offset: Int = 0, size: Int = -1): Unit + def write(bb: ByteBuffer, offset: Int = 0): Unit extension [Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding](execution: GExecution[Params, EL, RL]) def execute(params: Params, layout: EL): RL diff --git a/cyfra-examples/src/main/resources/compileAll.sh b/cyfra-examples/src/main/resources/compileAll.sh old mode 100644 new mode 100755 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 e604bde3..8b9a5014 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 @@ -11,6 +11,10 @@ import io.computenode.cyfra.dsl.{*, given} import io.computenode.cyfra.runtime.VkCyfraRuntime import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil + +import java.util.concurrent.atomic.AtomicInteger +import scala.collection.parallel.CollectionConverters.given + object TestingStuff: given GContext = GContext() @@ -228,3 +232,47 @@ object TestingStuff: assert(buf.get(i) == expected(i), s"Mismatch at index $i: expected ${expected(i)}, got ${buf.get(i)}") } } + + @main + def enduranceTest = + given runtime: VkCyfraRuntime = VkCyfraRuntime() + val bufferSize = 1280 + val params = AddProgramParams(bufferSize, addA = 0, addB = 1) + val region = GBufferRegion + .allocate[AddProgramExecLayout] + .map: region => + execution.execute(params, region) + val aInt = new AtomicInteger(0) + (1 to 10000).par.foreach: i => + val inBuffers = List.fill(5)(BufferUtils.createIntBuffer(bufferSize)) + val wbbList = inBuffers.map(MemoryUtil.memByteBuffer) + val rbbList = List.fill(5)(BufferUtils.createByteBuffer(bufferSize * 4)) + + val inData = (0 until bufferSize).toArray + inBuffers.foreach(_.put(inData).flip()) + region.runUnsafe( + init = AddProgramExecLayout( + 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), + 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)) + }, + ) + val prev = aInt.getAndAdd(1) + if prev % 100 == 0 then println(s"Iteration $prev completed") + + runtime.close() + println("Endurance test completed successfully") 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 91b445a9..782b2a85 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 @@ -20,10 +20,11 @@ import io.computenode.cyfra.runtime.ExecutionHandler.{ 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.{VulkanContext, VulkanThreadContext} 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.memory.{DescriptorPool, DescriptorPoolManager, DescriptorSet, DescriptorSetManager} import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import izumi.reflect.Tag import org.lwjgl.vulkan.VK10.* @@ -32,25 +33,26 @@ import org.lwjgl.vulkan.{VkCommandBuffer, VkCommandBufferBeginInfo, VkDependency import scala.collection.mutable -class ExecutionHandler(runtime: VkCyfraRuntime): - private val context = runtime.context +class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadContext, context: VulkanContext): import context.given - 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 + private val dsManager: DescriptorSetManager = threadContext.descriptorSetManager + private val commandPool: CommandPool = threadContext.commandPool 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, _) => - 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(dsManager.allocate) + .zip(layout) + .map: + case (set, bindings) => + set.update(bindings.map(x => VkAllocation.getUnderlying(x.binding))) + set val dispatches: Seq[Dispatch] = shaderCalls .zip(descriptorSets) @@ -74,9 +76,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") + check(vkQueueSubmit(commandPool.queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") fence.block().destroy() commandPool.freeCommandBuffer(commandBuffer) + descriptorSets.flatten.foreach(dsManager.free) result private def interpret[Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding]( 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 59142676..a038ac7b 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 @@ -28,27 +28,23 @@ class VkAllocation(commandPool: CommandPool, executionHandler: ExecutionHandler) given VkAllocation = this extension (buffer: GBinding[?]) - 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 - - buf match - case buffer: Buffer.HostBuffer => Buffer.copyBuffer(buffer, bb, offset, 0, s) + def read(bb: ByteBuffer, offset: Int = 0): Unit = + val size = bb.remaining() + getUnderlying(buffer) match + case buffer: Buffer.HostBuffer => buffer.copyTo(bb, offset) case buffer: Buffer.DeviceBuffer => - val stagingBuffer = getStagingBuffer(s) - Buffer.copyBuffer(buffer, stagingBuffer, offset, 0, s, commandPool).block().destroy() - Buffer.copyBuffer(stagingBuffer, bb, 0, 0, s) - - 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) + val stagingBuffer = getStagingBuffer(size) + Buffer.copyBuffer(buffer, stagingBuffer, offset, 0, size, commandPool) + stagingBuffer.copyTo(bb, 0) + + def write(bb: ByteBuffer, offset: Int = 0): Unit = + val size = bb.remaining() + getUnderlying(buffer) match + case buffer: Buffer.HostBuffer => buffer.copyFrom(bb, offset) case buffer: Buffer.DeviceBuffer => - val stagingBuffer = getStagingBuffer(s) - Buffer.copyBuffer(bb, stagingBuffer, 0, 0, s) - Buffer.copyBuffer(stagingBuffer, buffer, 0, offset, s, commandPool).block().destroy() + val stagingBuffer = getStagingBuffer(size) + stagingBuffer.copyFrom(bb, offset) + Buffer.copyBuffer(stagingBuffer, buffer, 0, offset, size, commandPool) extension (buffers: GBuffer.type) def apply[T <: Value: {Tag, FromExpr}](length: Int): GBuffer[T] = 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 f28cfd65..ccd6585f 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,19 +8,19 @@ import io.computenode.cyfra.vulkan.compute.ComputePipeline import scala.collection.mutable class VkCyfraRuntime extends CyfraRuntime: - val context = new VulkanContext() + private val context = new VulkanContext() import context.given - private val executionHandler = new ExecutionHandler(this) - private val shaderCache = mutable.Map.empty[String, VkShader[?]] 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 = - val allocation = new VkAllocation(context.commandPool, executionHandler) - f(allocation) - allocation.close() + context.withThreadContext: threadContext => + val executionHandler = new ExecutionHandler(this, threadContext, context) + val allocation = new VkAllocation(threadContext.commandPool, executionHandler) + f(allocation) + allocation.close() def close(): Unit = shaderCache.values.foreach(_.underlying.destroy()) 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 4544553e..f0885cb9 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 @@ -23,12 +23,12 @@ object VkShader: 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 - DescriptorInfo(kind) - } + val descriptors = set.map: + case Binding(binding, op) => + val kind = binding match + case buffer: GBuffer[?] => BindingType.StorageBuffer + case uniform: GUniform[?] => BindingType.Uniform + DescriptorInfo(kind) DescriptorSetInfo(descriptors) val pipeline = ComputePipeline(code, entryPoint, LayoutInfo(sets)) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala index bd806c4a..9c8c99c6 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala @@ -3,35 +3,56 @@ package io.computenode.cyfra.vulkan import io.computenode.cyfra.utility.Logger.logger import io.computenode.cyfra.vulkan.VulkanContext.ValidationLayers import io.computenode.cyfra.vulkan.command.CommandPool -import io.computenode.cyfra.vulkan.core.{DebugCallback, Device, Instance, Queue} -import io.computenode.cyfra.vulkan.memory.{Allocator, DescriptorPool} +import io.computenode.cyfra.vulkan.core.{DebugCallback, Device, Instance, PhysicalDevice, Queue} +import io.computenode.cyfra.vulkan.memory.{Allocator, DescriptorPool, DescriptorPoolManager, DescriptorSetManager} import org.lwjgl.system.Configuration +import java.util.concurrent.{ArrayBlockingQueue, BlockingQueue} +import scala.util.chaining.* +import scala.jdk.CollectionConverters.* + /** @author * MarconZet Created 13.04.2020 */ private[cyfra] object VulkanContext: val ValidationLayer: String = "VK_LAYER_KHRONOS_validation" - val SyncLayer: String = "VK_LAYER_KHRONOS_synchronization2" private val ValidationLayers: Boolean = System.getProperty("io.computenode.cyfra.vulkan.validation", "false").toBoolean if Configuration.STACK_SIZE.get() < 100 then logger.warn(s"Small stack size. Increase with org.lwjgl.system.stackSize") private[cyfra] class VulkanContext: - val instance: Instance = new Instance(ValidationLayers) - val debugCallback: Option[DebugCallback] = if ValidationLayers then Some(new DebugCallback(instance)) else None - given device: Device = new Device(instance) - given allocator: Allocator = new Allocator(instance, device) - val computeQueue: Queue = new Queue(device.computeQueueFamily, 0, device) - val descriptorPool: DescriptorPool = new DescriptorPool() - val commandPool: CommandPool = new CommandPool.Standard(computeQueue) + private val instance: Instance = new Instance(ValidationLayers) + private val debugCallback: Option[DebugCallback] = if ValidationLayers then Some(new DebugCallback(instance)) else None + private val physicalDevice = new PhysicalDevice(instance) + physicalDevice.assertRequirements() + + given device: Device = new Device(instance, physicalDevice) + given allocator: Allocator = new Allocator(instance, physicalDevice, device) + + private val descriptorPoolManager = new DescriptorPoolManager() + private val commandPools = device.getQueues.map(new CommandPool.Transient(_)) logger.debug("Vulkan context created") - logger.debug("Running on device: " + device.physicalDeviceName) + logger.debug("Running on device: " + physicalDevice.name) + + private val blockingQueue: BlockingQueue[CommandPool] = new ArrayBlockingQueue[CommandPool](commandPools.length).tap(_.addAll(commandPools.asJava)) + def withThreadContext[T](f: VulkanThreadContext => T): T = + assert( + VulkanThreadContext.guard.get() == 0, + "VulkanThreadContext is not thread-safe. Each thread can have only one VulkanThreadContext at a time. You cannot stack VulkanThreadContext.", + ) + val commandPool = blockingQueue.take() + val descriptorSetManager = new DescriptorSetManager(descriptorPoolManager) + val threadContext = new VulkanThreadContext(commandPool, descriptorSetManager) + VulkanThreadContext.guard.set(threadContext.hashCode()) + try f(threadContext) + finally + blockingQueue.put(commandPool) + descriptorSetManager.destroy() + VulkanThreadContext.guard.set(0) def destroy(): Unit = - commandPool.destroy() - descriptorPool.destroy() - computeQueue.destroy() + commandPools.foreach(_.destroy()) + descriptorPoolManager.destroy() allocator.destroy() device.destroy() debugCallback.foreach(_.destroy()) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanThreadContext.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanThreadContext.scala new file mode 100644 index 00000000..cf59ed81 --- /dev/null +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanThreadContext.scala @@ -0,0 +1,11 @@ +package io.computenode.cyfra.vulkan + +import io.computenode.cyfra.vulkan.command.CommandPool +import io.computenode.cyfra.vulkan.core.Device +import io.computenode.cyfra.vulkan.memory.{DescriptorPoolManager, DescriptorSetManager} + +case class VulkanThreadContext(commandPool: CommandPool, descriptorSetManager: DescriptorSetManager) + +object VulkanThreadContext: + val guard: ThreadLocal[Int] = new ThreadLocal[Int]: + override def initialValue(): Int = 0 diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala index a1117a02..3db65668 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/CommandPool.scala @@ -9,7 +9,7 @@ import org.lwjgl.vulkan.VK10.* /** @author * MarconZet Created 13.04.2020 Copied from Wrap */ -private[cyfra] abstract class CommandPool private (flags: Int, queue: Queue)(using device: Device) extends VulkanObjectHandle: +private[cyfra] abstract class CommandPool private (flags: Int, val queue: Queue)(using device: Device) extends VulkanObjectHandle: protected val handle: Long = pushStack: stack => val createInfo = VkCommandPoolCreateInfo .calloc(stack) @@ -39,11 +39,11 @@ private[cyfra] abstract class CommandPool private (flags: Int, queue: Queue)(usi check(vkAllocateCommandBuffers(device.get, allocateInfo, pointerBuffer), "Failed to allocate command buffers") 0 until n map (i => pointerBuffer.get(i)) map (new VkCommandBuffer(_, device.get)) - def executeCommand(block: VkCommandBuffer => Unit): Fence = - pushStack: stack => - val commandBuffer = beginSingleTimeCommands() - block(commandBuffer) - endSingleTimeCommands(commandBuffer) + def executeCommand(block: VkCommandBuffer => Unit): Unit = + val commandBuffer = beginSingleTimeCommands() + block(commandBuffer) + endSingleTimeCommands(commandBuffer).block().destroy() + freeCommandBuffer(commandBuffer) private def beginSingleTimeCommands(): VkCommandBuffer = pushStack: stack => @@ -65,7 +65,7 @@ private[cyfra] abstract class CommandPool private (flags: Int, queue: Queue)(usi .calloc(stack) .sType$Default() .pCommandBuffers(pointerBuffer) - val fence = new Fence(0, () => freeCommandBuffer(commandBuffer)) + val fence = Fence() check(vkQueueSubmit(queue.get, submitInfo, fence.get), "Failed to submit single time command buffer") fence @@ -80,7 +80,7 @@ private[cyfra] abstract class CommandPool private (flags: Int, queue: Queue)(usi vkDestroyCommandPool(device.get, commandPool, null) object CommandPool: - private[cyfra] class OneTime(queue: Queue)(using device: Device) + private[cyfra] class Transient(queue: Queue)(using device: Device) extends CommandPool(VK_COMMAND_POOL_CREATE_TRANSIENT_BIT, queue)(using device: Device) // TODO check if flags should be used differently private[cyfra] class Standard(queue: Queue)(using device: Device) extends CommandPool(0, queue)(using device: Device) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Fence.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Fence.scala index 50c350ca..630fa924 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Fence.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/command/Fence.scala @@ -9,7 +9,7 @@ import org.lwjgl.vulkan.VkFenceCreateInfo /** @author * MarconZet Created 13.04.2020 */ -private[cyfra] class Fence(flags: Int = 0, onDestroy: () => Unit = () => ())(using device: Device) extends VulkanObjectHandle: +private[cyfra] class Fence(flags: Int = 0)(using device: Device) extends VulkanObjectHandle: protected val handle: Long = pushStack(stack => val fenceInfo = VkFenceCreateInfo .calloc(stack) @@ -23,7 +23,6 @@ private[cyfra] class Fence(flags: Int = 0, onDestroy: () => Unit = () => ())(usi ) override def close(): Unit = - onDestroy.apply() vkDestroyFence(device.get, handle, null) def isSignaled: Boolean = diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Device.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Device.scala index e23ea52e..6908fcfa 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Device.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Device.scala @@ -1,9 +1,9 @@ package io.computenode.cyfra.vulkan.core import io.computenode.cyfra.vulkan.VulkanContext.ValidationLayer -import Device.{MacOsExtension, SyncExtension} +import Device.MacOsExtension import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} -import io.computenode.cyfra.vulkan.util.VulkanObject +import io.computenode.cyfra.vulkan.util.{VulkanObject, VulkanObjectHandle} import org.lwjgl.vulkan.* import org.lwjgl.vulkan.KHRPortabilitySubset.VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME import org.lwjgl.vulkan.KHRSynchronization2.VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME @@ -19,94 +19,20 @@ import scala.jdk.CollectionConverters.given object Device: final val MacOsExtension = VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME - final val SyncExtension = VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME - -private[cyfra] class Device(instance: Instance) extends VulkanObject: - - val physicalDevice: VkPhysicalDevice = pushStack: stack => - val pPhysicalDeviceCount = stack.callocInt(1) - check(vkEnumeratePhysicalDevices(instance.get, pPhysicalDeviceCount, null), "Failed to get number of physical devices") - val deviceCount = pPhysicalDeviceCount.get(0) - if deviceCount == 0 then throw new AssertionError("Failed to find GPUs with Vulkan support") - val pPhysicalDevices = stack.callocPointer(deviceCount) - check(vkEnumeratePhysicalDevices(instance.get, pPhysicalDeviceCount, pPhysicalDevices), "Failed to get physical devices") - new VkPhysicalDevice(pPhysicalDevices.get(), instance.get) - - val physicalDeviceName: String = pushStack: stack => - val pProperties = VkPhysicalDeviceProperties.calloc(stack) - vkGetPhysicalDeviceProperties(physicalDevice, pProperties) - pProperties.deviceNameString() - - val computeQueueFamily: Int = pushStack: stack => - val pQueueFamilyCount = stack.callocInt(1) - vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, pQueueFamilyCount, null) - val queueFamilyCount = pQueueFamilyCount.get(0) - - val pQueueFamilies = VkQueueFamilyProperties.calloc(queueFamilyCount, stack) - vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, pQueueFamilyCount, pQueueFamilies) - - val queues = 0 until queueFamilyCount - queues - .find { i => - val queueFamily = pQueueFamilies.get(i) - val maskedFlags = ~(VK_QUEUE_TRANSFER_BIT | VK_QUEUE_SPARSE_BINDING_BIT) & queueFamily.queueFlags() - ~(VK_QUEUE_GRAPHICS_BIT & maskedFlags) > 0 && (VK_QUEUE_COMPUTE_BIT & maskedFlags) > 0 - } - .orElse(queues.find { i => - val queueFamily = pQueueFamilies.get(i) - val maskedFlags = ~(VK_QUEUE_TRANSFER_BIT | VK_QUEUE_SPARSE_BINDING_BIT) & queueFamily.queueFlags() - (VK_QUEUE_COMPUTE_BIT & maskedFlags) > 0 - }) - .getOrElse(throw new AssertionError("No suitable queue family found for computing")) - - private val device: VkDevice = pushStack: stack => - val pPropertiesCount = stack.callocInt(1) - check( - vkEnumerateDeviceExtensionProperties(physicalDevice, null.asInstanceOf[ByteBuffer], pPropertiesCount, null), - "Failed to get number of properties extension", - ) - val propertiesCount = pPropertiesCount.get(0) - - val pProperties = VkExtensionProperties.calloc(propertiesCount, stack) - check( - vkEnumerateDeviceExtensionProperties(physicalDevice, null.asInstanceOf[ByteBuffer], pPropertiesCount, pProperties), - "Failed to get extension properties", - ) - - val deviceExtensions = pProperties.iterator().asScala.map(_.extensionNameString()) - val deviceExtensionsSet = deviceExtensions.toSet - - val vulkan12Features = VkPhysicalDeviceVulkan12Features - .calloc(stack) - .sType$Default() - - val vulkan13Features = VkPhysicalDeviceVulkan13Features - .calloc(stack) - .sType$Default() - - val physicalDeviceFeatures = VkPhysicalDeviceFeatures2 - .calloc(stack) - .sType$Default() - .pNext(vulkan12Features) - .pNext(vulkan13Features) - - vkGetPhysicalDeviceFeatures2(physicalDevice, physicalDeviceFeatures) - - val additionalExtension = pProperties.stream().anyMatch(x => x.extensionNameString().equals(MacOsExtension)) - - val pQueuePriorities = stack.callocFloat(1).put(1.0f) - pQueuePriorities.flip() +private[cyfra] class Device(instance: Instance, physicalDevice: PhysicalDevice) extends VulkanObject[VkDevice]: + protected val handle: VkDevice = pushStack: stack => + val (queueFamily, queueCount) = physicalDevice.selectComputeQueueFamily val pQueueCreateInfo = VkDeviceQueueCreateInfo.calloc(1, stack) pQueueCreateInfo .get(0) .sType$Default() .pNext(0) .flags(0) - .queueFamilyIndex(computeQueueFamily) - .pQueuePriorities(pQueuePriorities) + .queueFamilyIndex(queueFamily) + .pQueuePriorities(stack.callocFloat(queueCount)) - val extensions = Seq(MacOsExtension, SyncExtension).filter(deviceExtensionsSet) + val extensions = Seq(MacOsExtension).filter(physicalDevice.deviceExtensionsSet) val ppExtensionNames = stack.callocPointer(extensions.length) extensions.foreach(extension => ppExtensionNames.put(stack.ASCII(extension))) ppExtensionNames.flip() @@ -123,17 +49,20 @@ private[cyfra] class Device(instance: Instance) extends VulkanObject: .pQueueCreateInfos(pQueueCreateInfo) .ppEnabledExtensionNames(ppExtensionNames) - if instance.enabledLayers.contains(ValidationLayer) then - val ppValidationLayers = stack.callocPointer(1).put(stack.ASCII(ValidationLayer)) + if instance.enabledLayers.nonEmpty then + val ppValidationLayers = stack.callocPointer(instance.enabledLayers.length) + instance.enabledLayers.foreach: layer => + ppValidationLayers.put(stack.ASCII(layer)) pCreateInfo.ppEnabledLayerNames(ppValidationLayers.flip()) - assert(vulkan13Features.synchronization2() || extensions.contains(SyncExtension)) - val pDevice = stack.callocPointer(1) - check(vkCreateDevice(physicalDevice, pCreateInfo, null, pDevice), "Failed to create device") - new VkDevice(pDevice.get(0), physicalDevice, pCreateInfo) + check(vkCreateDevice(physicalDevice.get, pCreateInfo, null, pDevice), "Failed to create device") + val device = new VkDevice(pDevice.get(0), physicalDevice.get, pCreateInfo) + device - def get: VkDevice = device + def getQueues: Seq[Queue] = + val (queueFamily, queueCount) = physicalDevice.selectComputeQueueFamily + (0 until queueCount).map(new Queue(queueFamily, _, this)) override protected def close(): Unit = - vkDestroyDevice(device, null) + vkDestroyDevice(handle, null) 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 7452ec0a..43072840 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 @@ -40,9 +40,9 @@ object Instance: lazy val version: Int = VK.getInstanceVersionSupported -private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObject: +private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObject[VkInstance]: - private val instance: VkInstance = pushStack: stack => + protected val handle: VkInstance = pushStack: stack => val appInfo = VkApplicationInfo .calloc(stack) .sType$Default() @@ -55,9 +55,8 @@ private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObj val ppEnabledExtensionNames = getInstanceExtensions(stack) val ppEnabledLayerNames = - val layers = enabledLayers - val pointer = stack.callocPointer(layers.length) - layers.foreach(x => pointer.put(stack.ASCII(x))) + val pointer = stack.callocPointer(enabledLayers.length) + enabledLayers.foreach(x => pointer.put(stack.ASCII(x))) pointer.flip() val pCreateInfo = VkInstanceCreateInfo @@ -87,18 +86,15 @@ private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObj lazy val enabledLayers: Seq[String] = List .empty[String] - .pipe { x => + .pipe: x => if Instance.layers.contains(ValidationLayer) && enableValidationLayers then ValidationLayer +: x else if enableValidationLayers then logger.error("Validation layers requested but not available") x else x - } - - def get: VkInstance = instance override protected def close(): Unit = - vkDestroyInstance(instance, null) + vkDestroyInstance(handle, null) private def getInstanceExtensions(stack: MemoryStack) = val n = stack.callocInt(1) @@ -108,18 +104,18 @@ private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObj val availableExtensions = val buf = mutable.Buffer[String]() - buffer.forEach { ext => + buffer.forEach: ext => buf.addOne(ext.extensionNameString()) - } buf.toSet val extensions = mutable.Buffer.from(Instance.MoltenVkExtensions) if enableValidationLayers then extensions.addAll(Instance.ValidationLayersExtensions) val filteredExtensions = extensions.filter(ext => - 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") - }, + 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"), ) val ppEnabledExtensionNames = stack.callocPointer(extensions.size) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/PhysicalDevice.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/PhysicalDevice.scala new file mode 100644 index 00000000..d06d62c8 --- /dev/null +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/PhysicalDevice.scala @@ -0,0 +1,86 @@ +package io.computenode.cyfra.vulkan.core + +import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} +import io.computenode.cyfra.vulkan.util.VulkanObject +import org.lwjgl.vulkan.VK10.* +import org.lwjgl.vulkan.VK11.vkGetPhysicalDeviceFeatures2 +import org.lwjgl.vulkan.* + +import java.nio.ByteBuffer +import scala.jdk.CollectionConverters.given + +class PhysicalDevice(instance: Instance) extends VulkanObject[VkPhysicalDevice] { + protected val handle: VkPhysicalDevice = pushStack: stack => + val pPhysicalDeviceCount = stack.callocInt(1) + check(vkEnumeratePhysicalDevices(instance.get, pPhysicalDeviceCount, null), "Failed to get number of physical devices") + val deviceCount = pPhysicalDeviceCount.get(0) + if deviceCount == 0 then throw new AssertionError("Failed to find GPUs with Vulkan support") + val pPhysicalDevices = stack.callocPointer(deviceCount) + check(vkEnumeratePhysicalDevices(instance.get, pPhysicalDeviceCount, pPhysicalDevices), "Failed to get physical devices") + new VkPhysicalDevice(pPhysicalDevices.get(), instance.get) + + override protected def close(): Unit = () + + private val pdp: VkPhysicalDeviceProperties = + val pProperties = VkPhysicalDeviceProperties.create() + vkGetPhysicalDeviceProperties(handle, pProperties) + pProperties + + private val (pdf, v11f, v12f, v13f) + : (VkPhysicalDeviceFeatures, VkPhysicalDeviceVulkan11Features, VkPhysicalDeviceVulkan12Features, VkPhysicalDeviceVulkan13Features) = + val vulkan11Features = VkPhysicalDeviceVulkan11Features.create().sType$Default() + val vulkan12Features = VkPhysicalDeviceVulkan12Features.create().sType$Default() + val vulkan13Features = VkPhysicalDeviceVulkan13Features.create().sType$Default() + + val physicalDeviceFeatures = VkPhysicalDeviceFeatures2 + .create() + .sType$Default() + .pNext(vulkan11Features) + .pNext(vulkan12Features) + .pNext(vulkan13Features) + + vkGetPhysicalDeviceFeatures2(handle, physicalDeviceFeatures) + val features = VkPhysicalDeviceFeatures.create().set(physicalDeviceFeatures.features()) + (features, vulkan11Features, vulkan12Features, vulkan13Features) + + private val extensionProperties = pushStack: stack => + val pPropertiesCount = stack.callocInt(1) + check( + vkEnumerateDeviceExtensionProperties(handle, null.asInstanceOf[ByteBuffer], pPropertiesCount, null), + "Failed to get number of properties extension", + ) + val propertiesCount = pPropertiesCount.get(0) + + val pProperties = VkExtensionProperties.create(propertiesCount) + check( + vkEnumerateDeviceExtensionProperties(handle, null.asInstanceOf[ByteBuffer], pPropertiesCount, pProperties), + "Failed to get extension properties", + ) + pProperties + + def assertRequirements(): Unit = + assert(v13f.synchronization2(), "Vulkan 1.3 synchronization2 feature is required") + + def name: String = pdp.deviceNameString() + def deviceExtensionsSet: Set[String] = extensionProperties.iterator().asScala.map(_.extensionNameString()).toSet + + def selectComputeQueueFamily: (Int, Int) = pushStack: stack => + val pQueueFamilyCount = stack.callocInt(1) + vkGetPhysicalDeviceQueueFamilyProperties(handle, pQueueFamilyCount, null) + val queueFamilyCount = pQueueFamilyCount.get(0) + + val pQueueFamilies = VkQueueFamilyProperties.calloc(queueFamilyCount, stack) + vkGetPhysicalDeviceQueueFamilyProperties(handle, pQueueFamilyCount, pQueueFamilies) + + val queues = pQueueFamilies.iterator().asScala.map(_.queueFlags()).zipWithIndex.toSeq + val onlyCompute = queues.find: (flags, _) => + ~(VK_QUEUE_GRAPHICS_BIT & flags) > 0 && (VK_QUEUE_COMPUTE_BIT & flags) > 0 + val hasCompute = queues.find: (flags, _) => + (VK_QUEUE_COMPUTE_BIT & flags) > 0 + + val (_, index) = onlyCompute + .orElse(hasCompute) + .getOrElse(throw new AssertionError("No suitable queue family found for computing")) + + (index, pQueueFamilies.get(index).queueCount()) +} diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Queue.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Queue.scala index 7d894ba8..5f584492 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Queue.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Queue.scala @@ -10,12 +10,10 @@ import org.lwjgl.vulkan.{VkQueue, VkSubmitInfo} /** @author * MarconZet Created 13.04.2020 */ -private[cyfra] class Queue(val familyIndex: Int, queueIndex: Int, device: Device) extends VulkanObject: - private val queue: VkQueue = pushStack: stack => +private[cyfra] class Queue(val familyIndex: Int, queueIndex: Int, device: Device) extends VulkanObject[VkQueue]: + protected val handle: VkQueue = pushStack: stack => val pQueue = stack.callocPointer(1) vkGetDeviceQueue(device.get, familyIndex, queueIndex, pQueue) new VkQueue(pQueue.get(0), device.get) - def get: VkQueue = queue - protected def close(): Unit = () diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Allocator.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Allocator.scala index 147e1eda..54c0cb83 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Allocator.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/Allocator.scala @@ -1,6 +1,6 @@ package io.computenode.cyfra.vulkan.memory -import io.computenode.cyfra.vulkan.core.{Device, Instance} +import io.computenode.cyfra.vulkan.core.{Device, Instance, PhysicalDevice} import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import io.computenode.cyfra.vulkan.util.VulkanObjectHandle import org.lwjgl.util.vma.Vma.{vmaCreateAllocator, vmaDestroyAllocator} @@ -9,7 +9,7 @@ import org.lwjgl.util.vma.{VmaAllocatorCreateInfo, VmaVulkanFunctions} /** @author * MarconZet Created 13.04.2020 */ -private[cyfra] class Allocator(instance: Instance, device: Device) extends VulkanObjectHandle: +private[cyfra] class Allocator(instance: Instance, physicalDevice: PhysicalDevice, device: Device) extends VulkanObjectHandle: protected val handle: Long = pushStack: stack => val functions = VmaVulkanFunctions.calloc(stack) @@ -17,7 +17,7 @@ private[cyfra] class Allocator(instance: Instance, device: Device) extends Vulka val allocatorInfo = VmaAllocatorCreateInfo .calloc(stack) .device(device.get) - .physicalDevice(device.physicalDevice) + .physicalDevice(physicalDevice.get) .instance(instance.get) .pVulkanFunctions(functions) 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 f8926e3d..963aa1cd 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 @@ -45,10 +45,7 @@ object Buffer: private[cyfra] class HostBuffer(size: Int, usage: Int)(using allocator: Allocator) extends Buffer(size, usage, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)(using allocator): - def mapped(f: ByteBuffer => Unit): Unit = mappedImpl(f, flush = true) - def mappedNoFlush(f: ByteBuffer => Unit): Unit = mappedImpl(f, flush = false) - - private def mappedImpl(f: ByteBuffer => Unit, flush: Boolean): Unit = pushStack: stack => + def mapped(flush: Boolean)(f: ByteBuffer => Unit): Unit = pushStack: stack => val pData = stack.callocPointer(1) check(vmaMapMemory(this.allocator.get, this.allocation, pData), "Failed to map buffer to memory") val data = pData.get() @@ -58,15 +55,13 @@ 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 = - dst.mapped: destination => - memCopy(memAddress(src) + srcOffset, memAddress(destination) + dstOffset, bytes) + def copyTo(dst: ByteBuffer, srcOffset: Int): Unit = pushStack: stack => + vmaCopyAllocationToMemory(allocator.get, allocation, srcOffset, dst) - def copyBuffer(src: HostBuffer, dst: ByteBuffer, srcOffset: Int, dstOffset: Int, bytes: Int): Unit = - src.mappedNoFlush: source => - memCopy(memAddress(source) + srcOffset, memAddress(dst) + dstOffset, bytes) + def copyFrom(src: ByteBuffer, dstOffset: Int): Unit = pushStack: stack => + vmaCopyMemoryToAllocation(allocator.get, src, allocation, dstOffset) - def copyBuffer(src: Buffer, dst: Buffer, srcOffset: Int, dstOffset: Int, bytes: Int, commandPool: CommandPool): Fence = + def copyBuffer(src: Buffer, dst: Buffer, srcOffset: Int, dstOffset: Int, bytes: Int, commandPool: CommandPool): Unit = commandPool.executeCommand: commandBuffer => pushStack: stack => val copyRegion = VkBufferCopy 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 093fb350..afd2f793 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 @@ -12,14 +12,14 @@ import org.lwjgl.vulkan.{VkDescriptorPoolCreateInfo, VkDescriptorPoolSize} * MarconZet Created 14.04.2019 */ object DescriptorPool: - val MAX_SETS = 100 + val MAX_SETS = 1000 private[cyfra] class DescriptorPool(using device: Device) extends VulkanObjectHandle: protected val handle: Long = pushStack: stack => val descriptorPoolSize = VkDescriptorPoolSize.calloc(2, stack) descriptorPoolSize .get() .`type`(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER) - .descriptorCount(2 * MAX_SETS) + .descriptorCount(10 * MAX_SETS) descriptorPoolSize .get() @@ -31,14 +31,15 @@ private[cyfra] class DescriptorPool(using device: Device) extends VulkanObjectHa .calloc(stack) .sType$Default() .maxSets(MAX_SETS) - .flags(VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT) .pPoolSizes(descriptorPoolSize) val pDescriptorPool = stack.callocLong(1) check(vkCreateDescriptorPool(device.get, descriptorPoolCreateInfo, null, pDescriptorPool), "Failed to create descriptor pool") pDescriptorPool.get() - def allocate(descriptorSetLayout: DescriptorSetLayout): DescriptorSet = DescriptorSet(descriptorSetLayout, this) + def allocate(layout: DescriptorSetLayout): Option[DescriptorSet] = DescriptorSet(layout, this) + + def reset(): Unit = check(vkResetDescriptorPool(device.get, handle, 0), "Failed to reset descriptor pool") override protected def close(): Unit = vkDestroyDescriptorPool(device.get, handle, null) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorPoolManager.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorPoolManager.scala new file mode 100644 index 00000000..293bde45 --- /dev/null +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorPoolManager.scala @@ -0,0 +1,19 @@ +package io.computenode.cyfra.vulkan.memory + +import io.computenode.cyfra.vulkan.core.Device + +class DescriptorPoolManager(using Device): + private val freePools: collection.mutable.Queue[DescriptorPool] = collection.mutable.Queue.empty + + def allocate(): DescriptorPool = synchronized: + freePools.removeHeadOption() match + case Some(value) => value + case None => new DescriptorPool() + + def free(pools: DescriptorPool*): Unit = synchronized: + pools.foreach(_.reset()) + freePools.enqueueAll(pools) + + def destroy(): Unit = synchronized: + freePools.foreach(_.destroy()) + freePools.clear() 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 bd33df63..65296a77 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 @@ -5,50 +5,57 @@ import io.computenode.cyfra.vulkan.core.Device import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import io.computenode.cyfra.vulkan.util.VulkanObjectHandle import org.lwjgl.vulkan.VK10.* -import org.lwjgl.vulkan.{VkDescriptorBufferInfo, VkDescriptorSetAllocateInfo, VkWriteDescriptorSet} +import org.lwjgl.vulkan.{VK10, VK11, VkDescriptorBufferInfo, VkDescriptorSetAllocateInfo, VkWriteDescriptorSet} /** @author * MarconZet Created 15.04.2020 */ -private[cyfra] class DescriptorSet(descriptorSetLayout: DescriptorSetLayout, descriptorPool: DescriptorPool)(using device: Device) +private[cyfra] class DescriptorSet private (protected val handle: Long, val layout: DescriptorSetLayout)(using device: Device) extends VulkanObjectHandle: - protected val handle: Long = pushStack: stack => - val pSetLayout = stack.callocLong(1).put(0, descriptorSetLayout.id) - val descriptorSetAllocateInfo = VkDescriptorSetAllocateInfo - .calloc(stack) - .sType$Default() - .descriptorPool(descriptorPool.get) - .pSetLayouts(pSetLayout) - - val pDescriptorSet = stack.callocLong(1) - check(vkAllocateDescriptorSets(device.get, descriptorSetAllocateInfo, pDescriptorSet), "Failed to allocate descriptor set") - pDescriptorSet.get() - def update(buffers: Seq[Buffer]): Unit = pushStack: stack => - val bindings = descriptorSetLayout.set.descriptors + val bindings = layout.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).zipWithIndex.foreach { case ((buffer, binding), idx) => - val descriptorBufferInfo = VkDescriptorBufferInfo - .calloc(1, stack) - .buffer(buffer.get) - .offset(0) - .range(VK_WHOLE_SIZE) - 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(idx) - .descriptorCount(1) - .descriptorType(descriptorType) - .pBufferInfo(descriptorBufferInfo) - } + 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.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(idx) + .descriptorCount(1) + .descriptorType(descriptorType) + .pBufferInfo(descriptorBufferInfo) writeDescriptorSet.rewind() vkUpdateDescriptorSets(device.get, writeDescriptorSet, null) - override protected def close(): Unit = - vkFreeDescriptorSets(device.get, descriptorPool.get, handle) + override protected def close(): Unit = () + +object DescriptorSet: + def apply(layout: DescriptorSetLayout, descriptorPool: DescriptorPool)(using device: Device): Option[DescriptorSet] = + pushStack: stack => + val pSetLayout = stack.callocLong(1).put(0, layout.id) + val descriptorSetAllocateInfo = VkDescriptorSetAllocateInfo + .calloc(stack) + .sType$Default() + .descriptorPool(descriptorPool.get) + .pSetLayouts(pSetLayout) + + val pDescriptorSet = stack.callocLong(1) + val err = vkAllocateDescriptorSets(device.get, descriptorSetAllocateInfo, pDescriptorSet) + if err == VK11.VK_ERROR_OUT_OF_POOL_MEMORY || err == VK10.VK_ERROR_FRAGMENTED_POOL then None + else + check(err, "Failed to allocate descriptor set") + Some(new DescriptorSet(pDescriptorSet.get(), layout)) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorSetManager.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorSetManager.scala new file mode 100644 index 00000000..249ecf54 --- /dev/null +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorSetManager.scala @@ -0,0 +1,32 @@ +package io.computenode.cyfra.vulkan.memory + +import io.computenode.cyfra.vulkan.compute.ComputePipeline.DescriptorSetLayout + +import scala.annotation.tailrec +import scala.collection.mutable + +class DescriptorSetManager(poolManager: DescriptorPoolManager): + private var currentPool: Option[DescriptorPool] = None + private val exhaustedPools = mutable.Buffer.empty[DescriptorPool] + private val freeSets = mutable.HashMap.empty[Long, mutable.Queue[DescriptorSet]] + + def allocate(layout: DescriptorSetLayout): DescriptorSet = + freeSets.get(layout.id).flatMap(_.removeHeadOption(true)).getOrElse(allocateNew(layout)) + + def free(descriptorSet: DescriptorSet): Unit = + freeSets.getOrElseUpdate(descriptorSet.layout.id, mutable.Queue.empty) += descriptorSet + + def destroy(): Unit = + currentPool.foreach(poolManager.free(_)) + poolManager.free(exhaustedPools.toSeq*) + currentPool = None + exhaustedPools.clear() + + @tailrec + private def allocateNew(layout: DescriptorSetLayout): DescriptorSet = + currentPool.flatMap(_.allocate(layout)) match + case Some(value) => value + case None => + currentPool.foreach(exhaustedPools += _) + currentPool = Some(poolManager.allocate()) + this.allocateNew(layout) diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObject.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObject.scala index b896706b..3ec34726 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObject.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObject.scala @@ -3,8 +3,13 @@ package io.computenode.cyfra.vulkan.util /** @author * MarconZet Created 13.04.2020 */ -private[cyfra] abstract class VulkanObject: - protected var alive: Boolean = true +private[cyfra] abstract class VulkanObject[T]: + protected val handle: T + private var alive: Boolean = true + + def get: T = + if !alive then throw new IllegalStateException() + else handle def destroy(): Unit = if !alive then throw new IllegalStateException() diff --git a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObjectHandle.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObjectHandle.scala index acc448c7..1b7b8d67 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObjectHandle.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/util/VulkanObjectHandle.scala @@ -3,9 +3,4 @@ package io.computenode.cyfra.vulkan.util /** @author * MarconZet Created 13.04.2020 */ -private[cyfra] abstract class VulkanObjectHandle extends VulkanObject: - protected val handle: Long - - def get: Long = - if !alive then throw new IllegalStateException() - else handle +private[cyfra] abstract class VulkanObjectHandle extends VulkanObject[Long]