From 5f8e930c56b015fcc2fd4a47f9c8be4584e18991 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Mon, 28 Jul 2025 17:57:32 +0200 Subject: [PATCH 01/11] core rewrite^ --- .../cyfra/runtime/ExecutionHandler.scala | 3 +- .../cyfra/vulkan/VulkanContext.scala | 23 ++-- .../cyfra/vulkan/command/CommandPool.scala | 11 +- .../cyfra/vulkan/core/Device.scala | 109 +++--------------- .../cyfra/vulkan/core/Instance.scala | 13 +-- .../cyfra/vulkan/core/PhysicalDevice.scala | 86 ++++++++++++++ .../computenode/cyfra/vulkan/core/Queue.scala | 6 +- .../cyfra/vulkan/memory/Allocator.scala | 6 +- .../cyfra/vulkan/util/VulkanObject.scala | 9 +- .../vulkan/util/VulkanObjectHandle.scala | 7 +- 10 files changed, 142 insertions(+), 131 deletions(-) create mode 100644 cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/PhysicalDevice.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 91b445a9..bec75ecc 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 @@ -36,7 +36,6 @@ class ExecutionHandler(runtime: VkCyfraRuntime): private val context = runtime.context 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 @@ -74,7 +73,7 @@ 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) result 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..e2665208 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,7 +3,7 @@ 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.core.{DebugCallback, Device, Instance, PhysicalDevice, Queue} import io.computenode.cyfra.vulkan.memory.{Allocator, DescriptorPool} import org.lwjgl.system.Configuration @@ -12,26 +12,29 @@ import org.lwjgl.system.Configuration */ 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) + 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) + + val queues = device.getQueues val descriptorPool: DescriptorPool = new DescriptorPool() - val commandPool: CommandPool = new CommandPool.Standard(computeQueue) + val commandPool: CommandPool = new CommandPool.Standard(queues.head) logger.debug("Vulkan context created") - logger.debug("Running on device: " + device.physicalDeviceName) + logger.debug("Running on device: " + physicalDevice.name) def destroy(): Unit = commandPool.destroy() descriptorPool.destroy() - computeQueue.destroy() + queues.foreach(_.destroy()) allocator.destroy() device.destroy() debugCallback.foreach(_.destroy()) 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..0e662647 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) @@ -40,10 +40,9 @@ private[cyfra] abstract class CommandPool private (flags: Int, queue: Queue)(usi 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) + val commandBuffer = beginSingleTimeCommands() + block(commandBuffer) + endSingleTimeCommands(commandBuffer) private def beginSingleTimeCommands(): VkCommandBuffer = pushStack: stack => @@ -80,7 +79,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/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..3776c472 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 @@ -95,10 +94,8 @@ private[cyfra] class Instance(enableValidationLayers: Boolean) extends VulkanObj 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) 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..ee80d6ca --- /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/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] From 24f6f1d500e340cb0345f690de8d844e3ca0548c Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Tue, 29 Jul 2025 20:32:31 +0200 Subject: [PATCH 02/11] basic multithreading wok^r --- .../cyfra/runtime/ExecutionHandler.scala | 10 ++--- .../cyfra/runtime/VkCyfraRuntime.scala | 12 +++--- .../cyfra/vulkan/VulkanContext.scala | 23 +++++++---- .../cyfra/vulkan/VulkanThreadContext.scala | 12 ++++++ .../cyfra/vulkan/memory/DescriptorPool.scala | 3 +- .../vulkan/memory/DescriptorPoolManager.scala | 20 ++++++++++ .../cyfra/vulkan/memory/DescriptorSet.scala | 38 ++++++++++--------- .../vulkan/memory/DescriptorSetManager.scala | 36 ++++++++++++++++++ 8 files changed, 117 insertions(+), 37 deletions(-) create mode 100644 cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanThreadContext.scala create mode 100644 cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorPoolManager.scala create mode 100644 cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorSetManager.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 bec75ecc..6689fc28 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,12 +33,11 @@ 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 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 descriptorPool: 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, 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-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanContext.scala index e2665208..fdb19c5d 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 @@ -4,9 +4,13 @@ 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, PhysicalDevice, Queue} -import io.computenode.cyfra.vulkan.memory.{Allocator, DescriptorPool} +import io.computenode.cyfra.vulkan.memory.{Allocator, DescriptorPool, DescriptorPoolManager} 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 */ @@ -24,17 +28,22 @@ private[cyfra] class VulkanContext: given device: Device = new Device(instance, physicalDevice) given allocator: Allocator = new Allocator(instance, physicalDevice, device) - val queues = device.getQueues - val descriptorPool: DescriptorPool = new DescriptorPool() - val commandPool: CommandPool = new CommandPool.Standard(queues.head) + private val descriptorPoolManager = new DescriptorPoolManager() + private val commandPools = device.getQueues.map(new CommandPool.Transient(_)) logger.debug("Vulkan context created") 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 = + val commandPool = blockingQueue.take() + val threadContext = new VulkanThreadContext(commandPool, descriptorPoolManager) + try f(threadContext) + finally threadContext.destroy() + def destroy(): Unit = - commandPool.destroy() - descriptorPool.destroy() - queues.foreach(_.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..b02ea6a6 --- /dev/null +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanThreadContext.scala @@ -0,0 +1,12 @@ +package io.computenode.cyfra.vulkan + +import io.computenode.cyfra.vulkan.command.CommandPool +import io.computenode.cyfra.vulkan.core.{Device, Queue} +import io.computenode.cyfra.vulkan.memory.{DescriptorPoolManager, DescriptorSetManager} + +class VulkanThreadContext(val commandPool: CommandPool, poolManager: DescriptorPoolManager)(using Device) { + val descriptorSetManager = new DescriptorSetManager(poolManager) + + def destroy(): Unit = + descriptorSetManager.destroy() +} 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..0240a73c 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 @@ -31,14 +31,13 @@ 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) 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..17d55514 --- /dev/null +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorPoolManager.scala @@ -0,0 +1,20 @@ +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(pool: DescriptorPool*): Unit = synchronized: + freePools.enqueueAll(pool) + + 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..193ada16 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,28 +5,16 @@ 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) => @@ -50,5 +38,21 @@ private[cyfra] class DescriptorSet(descriptorSetLayout: DescriptorSetLayout, des 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..e611e01f --- /dev/null +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/memory/DescriptorSetManager.scala @@ -0,0 +1,36 @@ +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[DescriptorSetLayout, mutable.Queue[DescriptorSet]] + + def allocate(layout: DescriptorSetLayout): DescriptorSet = + freeSets.get(layout).flatMap(_.removeHeadOption(true)).getOrElse(allocateNew(layout)) + + def free(descriptorSet: DescriptorSet): Unit = + freeSets.getOrElseUpdate(descriptorSet.layout, 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) + } + +} From 8ebe385829422824a8a056ad3056614cf23094f0 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Tue, 29 Jul 2025 21:02:56 +0200 Subject: [PATCH 03/11] endurance test --- .../cyfra/samples/TestingStuff.scala | 41 +++++++++++++++++++ .../cyfra/vulkan/VulkanContext.scala | 4 +- .../cyfra/vulkan/memory/DescriptorPool.scala | 2 + .../vulkan/memory/DescriptorPoolManager.scala | 5 ++- 4 files changed, 49 insertions(+), 3 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 e604bde3..d2f75fbc 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 @@ -228,3 +228,44 @@ 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) + (1 to 1000).foreach: _ => + 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](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)) + }, + ) + runtime.close() + println("Endurance test completed successfully") 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 fdb19c5d..527d4436 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 @@ -39,7 +39,9 @@ private[cyfra] class VulkanContext: val commandPool = blockingQueue.take() val threadContext = new VulkanThreadContext(commandPool, descriptorPoolManager) try f(threadContext) - finally threadContext.destroy() + finally + threadContext.destroy() + blockingQueue.put(commandPool) def destroy(): Unit = commandPools.foreach(_.destroy()) 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 0240a73c..bb997e6e 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 @@ -38,6 +38,8 @@ private[cyfra] class DescriptorPool(using device: Device) extends VulkanObjectHa pDescriptorPool.get() 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 index 17d55514..03993518 100644 --- 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 @@ -10,8 +10,9 @@ class DescriptorPoolManager(using Device) { case Some(value) => value case None => new DescriptorPool() } - def free(pool: DescriptorPool*): Unit = synchronized: - freePools.enqueueAll(pool) + def free(pools: DescriptorPool*): Unit = synchronized: + pools.foreach(_.reset()) + freePools.enqueueAll(pools) def destroy(): Unit = synchronized: freePools.foreach(_.destroy()) From 76add04e04b8a20986a1bc708b48a8666e822843 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Wed, 30 Jul 2025 12:13:26 +0200 Subject: [PATCH 04/11] multi fail --- build.sbt | 1 + .../main/scala/io/computenode/cyfra/samples/TestingStuff.scala | 2 ++ .../scala/io/computenode/cyfra/vulkan/VulkanThreadContext.scala | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) 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-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala index d2f75fbc..39ecd594 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,8 @@ import io.computenode.cyfra.dsl.{*, given} import io.computenode.cyfra.runtime.VkCyfraRuntime import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil +import scala.collection.parallel.CollectionConverters.given + object TestingStuff: given GContext = GContext() 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 index b02ea6a6..cee462ae 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanThreadContext.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanThreadContext.scala @@ -1,7 +1,7 @@ package io.computenode.cyfra.vulkan import io.computenode.cyfra.vulkan.command.CommandPool -import io.computenode.cyfra.vulkan.core.{Device, Queue} +import io.computenode.cyfra.vulkan.core.Device import io.computenode.cyfra.vulkan.memory.{DescriptorPoolManager, DescriptorSetManager} class VulkanThreadContext(val commandPool: CommandPool, poolManager: DescriptorPoolManager)(using Device) { From 25428b5c789198323ab0486b6f806ee32e224e0f Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Sat, 2 Aug 2025 16:50:34 +0200 Subject: [PATCH 05/11] sig^ --- .../cyfra/samples/TestingStuff.scala | 77 +++++++++++-------- .../cyfra/runtime/ExecutionHandler.scala | 5 +- .../cyfra/runtime/VkCyfraRuntime.scala | 1 + .../cyfra/vulkan/VulkanContext.scala | 7 +- .../cyfra/vulkan/VulkanThreadContext.scala | 10 +-- 5 files changed, 61 insertions(+), 39 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 39ecd594..41a10088 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,7 +11,10 @@ import io.computenode.cyfra.dsl.{*, given} import io.computenode.cyfra.runtime.VkCyfraRuntime import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil -import scala.collection.parallel.CollectionConverters.given + +import java.util.concurrent.{Executors, Semaphore} +import scala.concurrent.duration.Duration +import scala.concurrent.{Await, ExecutionContext, Future} object TestingStuff: @@ -240,34 +243,48 @@ object TestingStuff: .allocate[AddProgramExecLayout] .map: region => execution.execute(params, region) - (1 to 1000).foreach: _ => - 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](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 x = () => + (1 to 10000).foreach: i => + 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](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)) + }, + ) + println(s"Iteration $i completed") + + +// val t = new Thread: +// override def run(): Unit = x() +// t.start() +// t.join() + x() + + +// given ExecutionContext = ExecutionContext.global +// val fut = Future(x()) +// Await.result(fut, Duration.Inf) 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 6689fc28..f25c042a 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 @@ -72,9 +72,8 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte .pCommandBuffers(pCommandBuffer) val fence = new Fence() - timed("Vulkan render command"): - check(vkQueueSubmit(commandPool.queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") - fence.block().destroy() + check(vkQueueSubmit(commandPool.queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") + fence.block().destroy() commandPool.freeCommandBuffer(commandBuffer) result 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 ccd6585f..fce65d09 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 @@ -20,6 +20,7 @@ class VkCyfraRuntime extends CyfraRuntime: val executionHandler = new ExecutionHandler(this, threadContext, context) val allocation = new VkAllocation(threadContext.commandPool, executionHandler) f(allocation) + threadContext.descriptorSetManager.destroy() allocation.close() def close(): Unit = 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 527d4436..305745b4 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 @@ -36,12 +36,17 @@ private[cyfra] class VulkanContext: 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 threadContext = new VulkanThreadContext(commandPool, descriptorPoolManager) + VulkanThreadContext.guard.set(threadContext.hashCode()) try f(threadContext) finally - threadContext.destroy() blockingQueue.put(commandPool) + VulkanThreadContext.guard.set(0) def destroy(): Unit = commandPools.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 index cee462ae..2312f496 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanThreadContext.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanThreadContext.scala @@ -4,9 +4,9 @@ import io.computenode.cyfra.vulkan.command.CommandPool import io.computenode.cyfra.vulkan.core.Device import io.computenode.cyfra.vulkan.memory.{DescriptorPoolManager, DescriptorSetManager} -class VulkanThreadContext(val commandPool: CommandPool, poolManager: DescriptorPoolManager)(using Device) { +class VulkanThreadContext(val commandPool: CommandPool, poolManager: DescriptorPoolManager)(using Device): val descriptorSetManager = new DescriptorSetManager(poolManager) - - def destroy(): Unit = - descriptorSetManager.destroy() -} + +object VulkanThreadContext: + val guard: ThreadLocal[Int] = new ThreadLocal[Int]: + override def initialValue(): Int = 0 From 19a7594bcaf117a0d1b513dbcee724768957b774 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Sat, 2 Aug 2025 17:25:10 +0200 Subject: [PATCH 06/11] descriptor set fix^ --- .../cyfra/runtime/ExecutionHandler.scala | 1 + .../vulkan/memory/DescriptorSetManager.scala | 16 ++++++---------- 2 files changed, 7 insertions(+), 10 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 f25c042a..ed29dfa8 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 @@ -75,6 +75,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte check(vkQueueSubmit(commandPool.queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") fence.block().destroy() commandPool.freeCommandBuffer(commandBuffer) + descriptorSets.flatten.foreach(descriptorPool.free) result private def interpret[Params, EL <: Layout: LayoutBinding, RL <: Layout: LayoutBinding]( 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 index e611e01f..249ecf54 100644 --- 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 @@ -5,32 +5,28 @@ import io.computenode.cyfra.vulkan.compute.ComputePipeline.DescriptorSetLayout import scala.annotation.tailrec import scala.collection.mutable -class DescriptorSetManager(poolManager: DescriptorPoolManager) { +class DescriptorSetManager(poolManager: DescriptorPoolManager): private var currentPool: Option[DescriptorPool] = None private val exhaustedPools = mutable.Buffer.empty[DescriptorPool] - private val freeSets = mutable.HashMap.empty[DescriptorSetLayout, mutable.Queue[DescriptorSet]] + private val freeSets = mutable.HashMap.empty[Long, mutable.Queue[DescriptorSet]] def allocate(layout: DescriptorSetLayout): DescriptorSet = - freeSets.get(layout).flatMap(_.removeHeadOption(true)).getOrElse(allocateNew(layout)) + freeSets.get(layout.id).flatMap(_.removeHeadOption(true)).getOrElse(allocateNew(layout)) def free(descriptorSet: DescriptorSet): Unit = - freeSets.getOrElseUpdate(descriptorSet.layout, mutable.Queue.empty) += descriptorSet + freeSets.getOrElseUpdate(descriptorSet.layout.id, mutable.Queue.empty) += descriptorSet - def destroy(): Unit = { + 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 { + currentPool.flatMap(_.allocate(layout)) match case Some(value) => value case None => currentPool.foreach(exhaustedPools += _) currentPool = Some(poolManager.allocate()) this.allocateNew(layout) - } - -} From 3bcd6274fcf73af9f53c013182be05a30ccc51ba Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Sun, 3 Aug 2025 20:57:03 +0200 Subject: [PATCH 07/11] finally --- .../cyfra/samples/TestingStuff.scala | 80 +++++++++---------- .../cyfra/runtime/ExecutionHandler.scala | 6 +- .../cyfra/runtime/VkCyfraRuntime.scala | 1 - .../cyfra/vulkan/VulkanContext.scala | 6 +- .../cyfra/vulkan/VulkanThreadContext.scala | 3 +- .../cyfra/vulkan/memory/DescriptorPool.scala | 4 +- 6 files changed, 46 insertions(+), 54 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 41a10088..f751d79a 100644 --- a/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala +++ b/cyfra-examples/src/main/scala/io/computenode/cyfra/samples/TestingStuff.scala @@ -12,9 +12,11 @@ import io.computenode.cyfra.runtime.VkCyfraRuntime import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil +import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.{Executors, Semaphore} import scala.concurrent.duration.Duration import scala.concurrent.{Await, ExecutionContext, Future} +import scala.collection.parallel.CollectionConverters.given object TestingStuff: @@ -243,48 +245,38 @@ object TestingStuff: .allocate[AddProgramExecLayout] .map: region => execution.execute(params, region) - val x = () => - (1 to 10000).foreach: i => - 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](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)) - }, - ) - println(s"Iteration $i completed") - - -// val t = new Thread: -// override def run(): Unit = x() -// t.start() -// t.join() - x() - - -// given ExecutionContext = ExecutionContext.global -// val fut = Future(x()) -// Await.result(fut, Duration.Inf) + 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 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](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") + println("Endurance test completed successfully") \ No newline at end of file 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 ed29dfa8..a7130cac 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 @@ -36,7 +36,7 @@ import scala.collection.mutable class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadContext, context: VulkanContext): import context.given - private val descriptorPool: DescriptorSetManager = threadContext.descriptorSetManager + 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)( @@ -45,7 +45,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte 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) => + pipeline.pipelineLayout.sets.map(dsManager.allocate).zip(layout).map { case (set, bindings) => set.update(bindings.map(x => VkAllocation.getUnderlying(x.binding))) set } @@ -75,7 +75,7 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte check(vkQueueSubmit(commandPool.queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") fence.block().destroy() commandPool.freeCommandBuffer(commandBuffer) - descriptorSets.flatten.foreach(descriptorPool.free) + 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/VkCyfraRuntime.scala b/cyfra-runtime/src/main/scala/io/computenode/cyfra/runtime/VkCyfraRuntime.scala index fce65d09..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 @@ -20,7 +20,6 @@ class VkCyfraRuntime extends CyfraRuntime: val executionHandler = new ExecutionHandler(this, threadContext, context) val allocation = new VkAllocation(threadContext.commandPool, executionHandler) f(allocation) - threadContext.descriptorSetManager.destroy() allocation.close() def close(): Unit = 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 305745b4..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 @@ -4,7 +4,7 @@ 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, PhysicalDevice, Queue} -import io.computenode.cyfra.vulkan.memory.{Allocator, DescriptorPool, DescriptorPoolManager} +import io.computenode.cyfra.vulkan.memory.{Allocator, DescriptorPool, DescriptorPoolManager, DescriptorSetManager} import org.lwjgl.system.Configuration import java.util.concurrent.{ArrayBlockingQueue, BlockingQueue} @@ -41,11 +41,13 @@ private[cyfra] class VulkanContext: "VulkanThreadContext is not thread-safe. Each thread can have only one VulkanThreadContext at a time. You cannot stack VulkanThreadContext.", ) val commandPool = blockingQueue.take() - val threadContext = new VulkanThreadContext(commandPool, descriptorPoolManager) + 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 = 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 index 2312f496..cf59ed81 100644 --- a/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanThreadContext.scala +++ b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/VulkanThreadContext.scala @@ -4,8 +4,7 @@ import io.computenode.cyfra.vulkan.command.CommandPool import io.computenode.cyfra.vulkan.core.Device import io.computenode.cyfra.vulkan.memory.{DescriptorPoolManager, DescriptorSetManager} -class VulkanThreadContext(val commandPool: CommandPool, poolManager: DescriptorPoolManager)(using Device): - val descriptorSetManager = new DescriptorSetManager(poolManager) +case class VulkanThreadContext(commandPool: CommandPool, descriptorSetManager: DescriptorSetManager) object VulkanThreadContext: val guard: ThreadLocal[Int] = new ThreadLocal[Int]: 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 bb997e6e..65651e2a 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() From 7e3ff0046f9717c5cb246bb41ca672a8ed59702f Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Sun, 3 Aug 2025 22:26:46 +0200 Subject: [PATCH 08/11] final final --- .../computenode/cyfra/core/Allocation.scala | 4 +-- .../src/main/resources/compileAll.sh | 0 .../cyfra/samples/TestingStuff.scala | 8 ++--- .../cyfra/runtime/VkAllocation.scala | 34 ++++++++----------- .../cyfra/vulkan/command/CommandPool.scala | 7 ++-- .../cyfra/vulkan/command/Fence.scala | 3 +- .../cyfra/vulkan/memory/Buffer.scala | 17 ++++------ 7 files changed, 30 insertions(+), 43 deletions(-) mode change 100644 => 100755 cyfra-examples/src/main/resources/compileAll.sh 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 f751d79a..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 @@ -13,9 +13,6 @@ import org.lwjgl.BufferUtils import org.lwjgl.system.MemoryUtil import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.{Executors, Semaphore} -import scala.concurrent.duration.Duration -import scala.concurrent.{Await, ExecutionContext, Future} import scala.collection.parallel.CollectionConverters.given object TestingStuff: @@ -249,8 +246,7 @@ object TestingStuff: (1 to 10000).par.foreach: i => 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 rbbList = List.fill(5)(BufferUtils.createByteBuffer(bufferSize * 4)) val inData = (0 until bufferSize).toArray inBuffers.foreach(_.put(inData).flip()) @@ -279,4 +275,4 @@ object TestingStuff: if prev % 100 == 0 then println(s"Iteration $prev completed") runtime.close() - println("Endurance test completed successfully") \ No newline at end of file + println("Endurance test completed successfully") 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-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 0e662647..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 @@ -39,10 +39,11 @@ private[cyfra] abstract class CommandPool private (flags: Int, val queue: Queue) check(vkAllocateCommandBuffers(device.get, allocateInfo, pointerBuffer), "Failed to allocate command buffers") 0 until n map (i => pointerBuffer.get(i)) map (new VkCommandBuffer(_, device.get)) - def executeCommand(block: VkCommandBuffer => Unit): Fence = + def executeCommand(block: VkCommandBuffer => Unit): Unit = val commandBuffer = beginSingleTimeCommands() block(commandBuffer) - endSingleTimeCommands(commandBuffer) + endSingleTimeCommands(commandBuffer).block().destroy() + freeCommandBuffer(commandBuffer) private def beginSingleTimeCommands(): VkCommandBuffer = pushStack: stack => @@ -64,7 +65,7 @@ private[cyfra] abstract class CommandPool private (flags: Int, val queue: Queue) .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 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/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 From 8da0b3eed7ab2809223dcdb101eca91ce5846104 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Sun, 3 Aug 2025 23:51:41 +0200 Subject: [PATCH 09/11] x --- .../io/computenode/cyfra/runtime/ExecutionHandler.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 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 a7130cac..2c47e740 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 @@ -72,8 +72,9 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte .pCommandBuffers(pCommandBuffer) val fence = new Fence() - check(vkQueueSubmit(commandPool.queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") - fence.block().destroy() + timed("Vulkan render command"): + check(vkQueueSubmit(commandPool.queue.get, submitInfo, fence.get), "Failed to submit command buffer to queue") + fence.block().destroy() commandPool.freeCommandBuffer(commandBuffer) descriptorSets.flatten.foreach(dsManager.free) result From ba8a7ed91cbff78bcef21b8f6d2f9159098ce922 Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Mon, 4 Aug 2025 00:15:44 +0200 Subject: [PATCH 10/11] ddx --- .../io/computenode/cyfra/vulkan/memory/DescriptorPool.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 65651e2a..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 @@ -38,7 +38,7 @@ private[cyfra] class DescriptorPool(using device: Device) extends VulkanObjectHa pDescriptorPool.get() 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 = From b4292c5dab32ec436c886fd76ad9a4ad2e6d62db Mon Sep 17 00:00:00 2001 From: marcin-zlakowski Date: Sat, 16 Aug 2025 16:01:50 +0200 Subject: [PATCH 11/11] fix^ --- .../cyfra/runtime/ExecutionHandler.scala | 15 ++++--- .../computenode/cyfra/runtime/VkShader.scala | 12 +++--- .../cyfra/vulkan/core/Instance.scala | 13 +++---- .../cyfra/vulkan/core/PhysicalDevice.scala | 2 +- .../vulkan/memory/DescriptorPoolManager.scala | 8 ++-- .../cyfra/vulkan/memory/DescriptorSet.scala | 39 ++++++++++--------- 6 files changed, 46 insertions(+), 43 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 2c47e740..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 @@ -44,12 +44,15 @@ class ExecutionHandler(runtime: VkCyfraRuntime, threadContext: VulkanThreadConte ): RL = val (result, shaderCalls) = interpret(execution, params, layout) - 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 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) 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/core/Instance.scala b/cyfra-vulkan/src/main/scala/io/computenode/cyfra/vulkan/core/Instance.scala index 3776c472..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 @@ -86,13 +86,12 @@ 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 - } override protected def close(): Unit = vkDestroyInstance(handle, null) @@ -105,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 index ee80d6ca..d06d62c8 100644 --- 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 @@ -19,7 +19,7 @@ class PhysicalDevice(instance: Instance) extends VulkanObject[VkPhysicalDevice] check(vkEnumeratePhysicalDevices(instance.get, pPhysicalDeviceCount, pPhysicalDevices), "Failed to get physical devices") new VkPhysicalDevice(pPhysicalDevices.get(), instance.get) - override protected def close(): Unit = {} + override protected def close(): Unit = () private val pdp: VkPhysicalDeviceProperties = val pProperties = VkPhysicalDeviceProperties.create() 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 index 03993518..293bde45 100644 --- 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 @@ -2,14 +2,14 @@ package io.computenode.cyfra.vulkan.memory import io.computenode.cyfra.vulkan.core.Device -class DescriptorPoolManager(using Device) { +class DescriptorPoolManager(using Device): private val freePools: collection.mutable.Queue[DescriptorPool] = collection.mutable.Queue.empty def allocate(): DescriptorPool = synchronized: - freePools.removeHeadOption() match { + freePools.removeHeadOption() match case Some(value) => value case None => new DescriptorPool() - } + def free(pools: DescriptorPool*): Unit = synchronized: pools.foreach(_.reset()) freePools.enqueueAll(pools) @@ -17,5 +17,3 @@ class DescriptorPoolManager(using Device) { 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 193ada16..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 @@ -17,24 +17,27 @@ private[cyfra] class DescriptorSet private (protected val handle: Long, val layo 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)