From 132edd1dba5a486c6e063771e50c89f817e7d2c9 Mon Sep 17 00:00:00 2001 From: rudrabeniwal Date: Sat, 2 Aug 2025 19:03:52 +0530 Subject: [PATCH 1/3] swapchainManager --- .../cyfra/rtrp/SwapchainManager.scala | 144 ++++++++++++++++++ .../surface/SurfaceIntegrationExample.scala | 8 +- .../cyfra/rtrp/surface/SurfaceManager.scala | 10 +- .../{RenderSurface.scala => Surface.scala} | 5 +- .../surface/core/SurfaceCapabilities.scala | 14 +- .../rtrp/surface/core/SurfaceConfig.scala | 29 ++-- .../rtrp/surface/core/SurfaceEvents.scala | 2 +- .../rtrp/surface/core/SurfaceTypes.scala | 44 ------ .../rtrp/surface/vulkan/VulkanSurface.scala | 4 +- .../vulkan/VulkanSurfaceCapabilities.scala | 37 +---- .../cyfra/rtrp/window/WindowManager.scala | 4 +- .../cyfra/vulkan/core/Device.scala | 6 +- 12 files changed, 193 insertions(+), 114 deletions(-) create mode 100644 cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/SwapchainManager.scala rename cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/{RenderSurface.scala => Surface.scala} (87%) delete mode 100644 cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceTypes.scala diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/SwapchainManager.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/SwapchainManager.scala new file mode 100644 index 00000000..3258d722 --- /dev/null +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/SwapchainManager.scala @@ -0,0 +1,144 @@ +package io.computenode.cyfra.rtrp + +import io.computenode.cyfra.vulkan.VulkanContext +import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} +import io.computenode.cyfra.rtrp.surface.core.* +import io.computenode.cyfra.rtrp.surface.vulkan.* +import org.lwjgl.system.MemoryStack +import org.lwjgl.vulkan.KHRSurface.* +import org.lwjgl.vulkan.KHRSwapchain.* +import org.lwjgl.vulkan.VK10.* +import io.computenode.cyfra.vulkan.util.{VulkanAssertionError, VulkanObjectHandle} +import org.lwjgl.vulkan.{VkExtent2D, VkSwapchainCreateInfoKHR, VkImageViewCreateInfo, VkSurfaceFormatKHR, VkPresentInfoKHR, VkSemaphoreCreateInfo, VkSurfaceCapabilitiesKHR} +import scala.util.{Try, Success, Failure} + +import scala.collection.mutable.ArrayBuffer + +private[cyfra] class SwapchainManager (context: VulkanContext, surface: Surface) extends VulkanObjectHandle: + + private val device = context.device + private val physicalDevice = device.physicalDevice + private var swapchainHandle: Long = VK_NULL_HANDLE + private var swapchainImages: Array[Long] = _ + + override protected def close(): Unit = + if swapchainHandle != VK_NULL_HANDLE then + vkDestroySwapchainKHR(device.get, swapchainHandle, null) + swapchainHandle = VK_NULL_HANDLE + + override def get: Long = + if !alive then + throw new IllegalStateException() + swapchainHandle + + private var swapchainImageFormat: Int = _ + private var swapchainColorSpace: Int = _ + private var swapchainPresentMode: Int = _ + private var swapchainExtent: VkExtent2D = _ + + // Get the raw Vulkan capabilities for low-level access + private val vkCapabilities = pushStack: stack => + val vkCapabilities = VkSurfaceCapabilitiesKHR.calloc(stack) + check( + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface.nativeHandle, vkCapabilities), + "Failed to get surface capabilities" + ) + vkCapabilities + + // Get the high-level surface capabilities for format/mode queries + private val surfaceCapabilities = surface.getCapabilities() match + case Success(caps) => caps + case Failure(exception) => + throw new RuntimeException("Failed to get surface capabilities", exception) + + val (width, height) = (vkCapabilities.currentExtent().width(), vkCapabilities.currentExtent().height()) + val minImageExtent = vkCapabilities.minImageExtent() + val maxImageExtent = vkCapabilities.maxImageExtent() + + def initialize (surfaceConfig: SurfaceConfig): Boolean = pushStack: stack => + //cleanup() + + val preferredPresentMode = surfaceConfig.preferredPresentMode + + // Use the surface capabilities abstraction + val availableFormats: List[Int] = surfaceCapabilities.supportedFormats + val availableColorSpaces: List[Int] = surfaceCapabilities.supportedColorSpaces + + val exactFmtMatch = availableFormats.find(fmt => fmt == surfaceConfig.preferredFormat) + val exactCsMatch = availableColorSpaces.find(cs => cs == surfaceConfig.preferredColorSpace) + + val chosenFormat = exactFmtMatch.orElse(availableFormats.headOption).getOrElse( + throw new RuntimeException("No supported surface formats available") + ) + val chosenColorSpace = exactCsMatch.orElse(availableColorSpaces.headOption).getOrElse( + throw new RuntimeException("No supported color spaces available") + ) + + // Choose present mode + val availableModes = surfaceCapabilities.supportedPresentModes + val presentMode = if (availableModes.contains(preferredPresentMode)) preferredPresentMode else VK_PRESENT_MODE_FIFO_KHR + + // Choose swap extent + val (chosenWidth, chosenHeight) = if (width != -1 && height != -1) + (width, height) + else + val (desiredWidth, desiredHeight) = (800, 600) // TODO: get from window/config + if surfaceCapabilities.isExtentSupported(desiredWidth, desiredHeight) then + (desiredWidth, desiredHeight) + else + surfaceCapabilities.clampExtent(desiredWidth, desiredHeight) + + // Determine image count + var imageCount = surfaceCapabilities.minImageCount + 1 + if (surfaceCapabilities.maxImageCount != 0) + imageCount = Math.min(imageCount, surfaceCapabilities.maxImageCount) + + // Convert from surface abstraction to Vulkan constants + swapchainImageFormat = chosenFormat + swapchainColorSpace = chosenColorSpace + swapchainPresentMode = presentMode + swapchainExtent = VkExtent2D.calloc(stack).width(chosenWidth).height(chosenHeight) + + // Create swapchain + val createInfo = VkSwapchainCreateInfoKHR.calloc(stack) + .sType$Default() + .surface(surface.nativeHandle) + .minImageCount(imageCount) + .imageFormat(swapchainImageFormat) + .imageColorSpace(swapchainColorSpace) + .imageExtent(swapchainExtent) + .imageArrayLayers(1) + .imageUsage(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) + .preTransform(vkCapabilities.currentTransform()) + .compositeAlpha(VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) + .presentMode(swapchainPresentMode) + .clipped(true) + .oldSwapchain(VK_NULL_HANDLE) + .imageSharingMode(VK_SHARING_MODE_EXCLUSIVE) + .queueFamilyIndexCount(0) + .pQueueFamilyIndices(null) + + val pSwapchain = stack.callocLong(1) + + val result = vkCreateSwapchainKHR(device.get, createInfo, null, pSwapchain) + if (result != VK_SUCCESS) + throw new VulkanAssertionError("Failed to create swap chain", result) + + swapchainHandle = pSwapchain.get(0) + alive = true + + // Get swap chain images + val pImageCount = stack.callocInt(1) + vkGetSwapchainImagesKHR(device.get, swapchainHandle, pImageCount, null) + val actualImageCount = pImageCount.get(0) + + val pSwapchainImages = stack.callocLong(actualImageCount) + vkGetSwapchainImagesKHR(device.get, swapchainHandle, pImageCount, pSwapchainImages) + + swapchainImages = new Array[Long](actualImageCount) + for (i <- 0 until actualImageCount) + swapchainImages(i) = pSwapchainImages.get(i) + + // createImageViews() + + true diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/SurfaceIntegrationExample.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/SurfaceIntegrationExample.scala index 8e2b9573..5c6085dd 100644 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/SurfaceIntegrationExample.scala +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/SurfaceIntegrationExample.scala @@ -85,7 +85,7 @@ object SurfaceIntegrationExample: private def createTestWindows( manager: WindowManager, - ): List[(io.computenode.cyfra.rtrp.window.core.Window, io.computenode.cyfra.rtrp.surface.core.RenderSurface)] = + ): List[(io.computenode.cyfra.rtrp.window.core.Window, io.computenode.cyfra.rtrp.surface.core.Surface)] = val configs = List( // Main window - gaming configuration (WindowConfig(width = 1024, height = 768, title = "Main Window", position = Some(WindowPosition.Centered)), SurfaceConfig.gaming), @@ -104,7 +104,7 @@ object SurfaceIntegrationExample: List.empty private def inspectSurfaceCapabilities( - windowSurfacePairs: List[(io.computenode.cyfra.rtrp.window.core.Window, io.computenode.cyfra.rtrp.surface.core.RenderSurface)], + windowSurfacePairs: List[(io.computenode.cyfra.rtrp.window.core.Window, io.computenode.cyfra.rtrp.surface.core.Surface)], ): Unit = windowSurfacePairs.foreach { case (window, surface) => println(s"\n Surface ${surface.id} (Window: ${window.properties.title}):") @@ -127,7 +127,7 @@ object SurfaceIntegrationExample: private def runMainLoop( manager: WindowManager, - windowSurfacePairs: List[(io.computenode.cyfra.rtrp.window.core.Window, io.computenode.cyfra.rtrp.surface.core.RenderSurface)], + windowSurfacePairs: List[(io.computenode.cyfra.rtrp.window.core.Window, io.computenode.cyfra.rtrp.surface.core.Surface)], ): Unit = var frameCount = 0 val maxFrames = 300 // 5 seconds at 60fps @@ -159,7 +159,7 @@ object SurfaceIntegrationExample: logger.info("Main loop completed") - private def testSurfaceRecreation(manager: WindowManager, surface: io.computenode.cyfra.rtrp.surface.core.RenderSurface): Unit = + private def testSurfaceRecreation(manager: WindowManager, surface: io.computenode.cyfra.rtrp.surface.core.Surface): Unit = logger.info(s"Testing recreation of surface ${surface.id}...") manager.getSurfaceManager() match diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/SurfaceManager.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/SurfaceManager.scala index 67e06372..ea60a5c1 100644 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/SurfaceManager.scala +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/SurfaceManager.scala @@ -12,12 +12,12 @@ import io.computenode.cyfra.utility.Logger.logger class SurfaceManager(vulkanContext: VulkanContext): private val surfaceFactory = new VulkanSurfaceFactory(vulkanContext) - private val activeSurfaces = mutable.Map[WindowId, RenderSurface]() + private val activeSurfaces = mutable.Map[WindowId, Surface]() private val surfaceConfigs = mutable.Map[WindowId, SurfaceConfig]() private val eventHandlers = mutable.Map[Class[? <: SurfaceEvent], SurfaceEvent => Unit]() // Create a surface for a window. - def createSurface(window: Window, config: SurfaceConfig = SurfaceConfig.default): Try[RenderSurface] = + def createSurface(window: Window, config: SurfaceConfig = SurfaceConfig.default): Try[Surface] = if activeSurfaces.contains(window.id) then return Failure(new IllegalStateException(s"Surface already exists for window ${window.id}")) surfaceFactory @@ -33,7 +33,7 @@ class SurfaceManager(vulkanContext: VulkanContext): surface - def createSurfaces(windows: List[Window], config: SurfaceConfig = SurfaceConfig.default): Try[List[RenderSurface]] = + def createSurfaces(windows: List[Window], config: SurfaceConfig = SurfaceConfig.default): Try[List[Surface]] = val results = windows.map(createSurface(_, config)) val failures = results.collect { case Failure(ex) => ex } @@ -42,10 +42,10 @@ class SurfaceManager(vulkanContext: VulkanContext): Failure(new RuntimeException(s"Failed to create ${failures.size} surfaces")) else Success(results.collect { case Success(surface) => surface }) - def getSurface(windowId: WindowId): Option[RenderSurface] = + def getSurface(windowId: WindowId): Option[Surface] = activeSurfaces.get(windowId) - def getActiveSurfaces(): Map[WindowId, RenderSurface] = activeSurfaces.toMap + def getActiveSurfaces(): Map[WindowId, Surface] = activeSurfaces.toMap def getSurfaceConfig(windowId: WindowId): Option[SurfaceConfig] = surfaceConfigs.get(windowId) diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/RenderSurface.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/Surface.scala similarity index 87% rename from cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/RenderSurface.scala rename to cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/Surface.scala index 888c6ea3..2b5f4bcd 100644 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/RenderSurface.scala +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/Surface.scala @@ -3,8 +3,11 @@ package io.computenode.cyfra.rtrp.surface.core import io.computenode.cyfra.rtrp.window.core.* import scala.util.Try +// Unique id for surfaces +case class SurfaceId(value: Long) extends AnyVal + // Render surface abstraction -trait RenderSurface: +trait Surface: def id: SurfaceId def windowId: WindowId def nativeHandle: Long diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceCapabilities.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceCapabilities.scala index 5c5cc315..a11c9e1d 100644 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceCapabilities.scala +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceCapabilities.scala @@ -2,9 +2,9 @@ package io.computenode.cyfra.rtrp.surface.core // Surface capabilities - what the surface can do trait SurfaceCapabilities: - def supportedFormats: List[SurfaceFormat] - def supportedColorSpaces: List[ColorSpace] - def supportedPresentModes: List[PresentMode] + def supportedFormats: List[Int] + def supportedColorSpaces: List[Int] + def supportedPresentModes: List[Int] def minImageExtent: (Int, Int) def maxImageExtent: (Int, Int) def currentExtent: (Int, Int) @@ -13,16 +13,16 @@ trait SurfaceCapabilities: def supportsAlpha: Boolean def supportsTransform: Boolean - def supportsFormat(format: SurfaceFormat): Boolean = + def supportsFormat(format: Int): Boolean = supportedFormats.contains(format) - def supportsPresentMode(mode: PresentMode): Boolean = + def supportsPresentMode(mode: Int): Boolean = supportedPresentModes.contains(mode) - def chooseBestFormat(preferences: List[SurfaceFormat]): Option[SurfaceFormat] = + def chooseBestFormat(preferences: List[Int]): Option[Int] = preferences.find(supportsFormat) - def chooseBestPresentMode(preferences: List[PresentMode]): Option[PresentMode] = + def chooseBestPresentMode(preferences: List[Int]): Option[Int] = preferences.find(supportsPresentMode) // Check if the given extent is within supported bounds diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceConfig.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceConfig.scala index 02271322..ae65099a 100644 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceConfig.scala +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceConfig.scala @@ -1,24 +1,27 @@ package io.computenode.cyfra.rtrp.surface.core +import org.lwjgl.vulkan.VK10.* +import org.lwjgl.vulkan.KHRSurface.* +import org.lwjgl.vulkan.KHRSwapchain.* // Configuration for surface creation case class SurfaceConfig( - preferredFormat: SurfaceFormat = SurfaceFormat.B8G8R8A8_SRGB, - preferredColorSpace: ColorSpace = ColorSpace.SRGB_NONLINEAR, - preferredPresentMode: PresentMode = PresentMode.MAILBOX, + preferredFormat: Int = VK_FORMAT_B8G8R8A8_SRGB, + preferredColorSpace: Int = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR, + preferredPresentMode: Int = VK_PRESENT_MODE_MAILBOX_KHR, enableVSync: Boolean = true, minImageCount: Option[Int] = None, maxImageCount: Option[Int] = None, ): // Create a copy with different format, present mode, VSync settings, or image count constraints - def withFormat(format: SurfaceFormat): SurfaceConfig = + def withFormat(format: Int): SurfaceConfig = copy(preferredFormat = format) - def withPresentMode(mode: PresentMode): SurfaceConfig = + def withPresentMode(mode: Int): SurfaceConfig = copy(preferredPresentMode = mode) def withVSync(enabled: Boolean): SurfaceConfig = - val mode = if enabled then PresentMode.FIFO else PresentMode.IMMEDIATE + val mode = if enabled then VK_PRESENT_MODE_FIFO_KHR else VK_PRESENT_MODE_IMMEDIATE_KHR copy(enableVSync = enabled, preferredPresentMode = mode) def withImageCount(min: Int, max: Int): SurfaceConfig = @@ -30,23 +33,23 @@ object SurfaceConfig: def default: SurfaceConfig = SurfaceConfig() def gaming: SurfaceConfig = SurfaceConfig( - preferredFormat = SurfaceFormat.B8G8R8A8_SRGB, - preferredPresentMode = PresentMode.MAILBOX, + preferredFormat = VK_FORMAT_B8G8R8A8_SRGB, + preferredPresentMode = VK_PRESENT_MODE_MAILBOX_KHR, enableVSync = false, minImageCount = Some(2), maxImageCount = Some(3), ) def quality: SurfaceConfig = SurfaceConfig( - preferredFormat = SurfaceFormat.R8G8B8A8_SRGB, - preferredColorSpace = ColorSpace.DISPLAY_P3_NONLINEAR, - preferredPresentMode = PresentMode.FIFO, + preferredFormat = VK_FORMAT_R8G8B8A8_SRGB, + preferredColorSpace = 1000104001 , + preferredPresentMode = VK_PRESENT_MODE_FIFO_KHR, enableVSync = true, ) def lowLatency: SurfaceConfig = SurfaceConfig( - preferredFormat = SurfaceFormat.B8G8R8A8_UNORM, - preferredPresentMode = PresentMode.IMMEDIATE, + preferredFormat = VK_FORMAT_B8G8R8A8_UNORM, + preferredPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR, enableVSync = false, minImageCount = Some(1), maxImageCount = Some(2), diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceEvents.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceEvents.scala index 628bc935..7d1676e1 100644 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceEvents.scala +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceEvents.scala @@ -21,4 +21,4 @@ object SurfaceEvent: case class SurfaceLost(windowId: WindowId, surfaceId: SurfaceId, error: String) extends SurfaceEvent - case class SurfaceFormatChanged(windowId: WindowId, surfaceId: SurfaceId, oldFormat: SurfaceFormat, newFormat: SurfaceFormat) extends SurfaceEvent + case class FormatChanged(windowId: WindowId, surfaceId: SurfaceId, oldFormat: Int, newFormat: Int) extends SurfaceEvent diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceTypes.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceTypes.scala deleted file mode 100644 index bef8500b..00000000 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceTypes.scala +++ /dev/null @@ -1,44 +0,0 @@ -package io.computenode.cyfra.rtrp.surface.core - -import io.computenode.cyfra.rtrp.window.core.* - -// Unique id for surfaces -case class SurfaceId(value: Long) extends AnyVal - -// Surface format definitions -sealed trait SurfaceFormat: - def vulkanValue: Int - -object SurfaceFormat: - case object B8G8R8A8_SRGB extends SurfaceFormat: - val vulkanValue = 50 - case object B8G8R8A8_UNORM extends SurfaceFormat: - val vulkanValue = 44 - case object R8G8B8A8_SRGB extends SurfaceFormat: - val vulkanValue = 43 - case object R8G8B8A8_UNORM extends SurfaceFormat: - val vulkanValue = 37 - -// Color space definitions -sealed trait ColorSpace: - def vulkanValue: Int - -object ColorSpace: - case object SRGB_NONLINEAR extends ColorSpace: - val vulkanValue = 0 // VK_COLOR_SPACE_SRGB_NONLINEAR_KHR - case object DISPLAY_P3_NONLINEAR extends ColorSpace: - val vulkanValue = 1000104001 // VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT - -// Present mode definitions -sealed trait PresentMode: - def vulkanValue: Int - -object PresentMode: - case object IMMEDIATE extends PresentMode: - val vulkanValue = 0 // VK_PRESENT_MODE_IMMEDIATE_KHR - case object MAILBOX extends PresentMode: - val vulkanValue = 1 // VK_PRESENT_MODE_MAILBOX_KHR - case object FIFO extends PresentMode: - val vulkanValue = 2 // VK_PRESENT_MODE_FIFO_KHR - case object FIFO_RELAXED extends PresentMode: - val vulkanValue = 3 // VK_PRESENT_MODE_FIFO_RELAXED_KHR diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurface.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurface.scala index 1fca335b..8c96b81b 100644 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurface.scala +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurface.scala @@ -10,9 +10,9 @@ import org.lwjgl.vulkan.VK10.* import scala.util.* import java.util.concurrent.atomic.AtomicBoolean -// Vulkan implementation of RenderSurface +// Vulkan implementation of Surface class VulkanSurface(val id: SurfaceId, val windowId: WindowId, val nativeHandle: Long, private val vulkanContext: VulkanContext) - extends RenderSurface: + extends Surface: private val destroyed = new AtomicBoolean(false) private var surfaceCapabilities: Option[VulkanSurfaceCapabilities] = None diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurfaceCapabilities.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurfaceCapabilities.scala index 20e7d525..d25021c4 100644 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurfaceCapabilities.scala +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurfaceCapabilities.scala @@ -17,14 +17,11 @@ class VulkanSurfaceCapabilities(vulkanContext: VulkanContext, surface: VulkanSur private val vkFormats = queryAvailableFormats() private val vkPresentModes = queryPresentModes() - override def supportedFormats: List[SurfaceFormat] = - vkFormats.map(convertVulkanFormat).distinct + override def supportedFormats: List[Int] = vkFormats.map(_.format()) - override def supportedColorSpaces: List[ColorSpace] = - vkFormats.map(convertVulkanColorSpace).distinct + override def supportedColorSpaces: List[Int] = vkFormats.map(_.colorSpace()) - override def supportedPresentModes: List[PresentMode] = - vkPresentModes.map(convertVulkanPresentMode) + override def supportedPresentModes: List[Int] = vkPresentModes override def minImageExtent: (Int, Int) = (vkCapabilities.minImageExtent().width(), vkCapabilities.minImageExtent().height()) @@ -34,10 +31,8 @@ class VulkanSurfaceCapabilities(vulkanContext: VulkanContext, surface: VulkanSur override def currentExtent: (Int, Int) = val extent = vkCapabilities.currentExtent() - // If width/height is 0xFFFFFFFF, then surface size will be determined by the swapchain if extent.width() == 0xffffffff || extent.height() == 0xffffffff then - // Return a reasonable default - (800, 600) + (-1, -1) else (extent.width(), extent.height()) override def minImageCount: Int = vkCapabilities.minImageCount() @@ -104,27 +99,3 @@ class VulkanSurfaceCapabilities(vulkanContext: VulkanContext, surface: VulkanSur (0 until modeCount).map(modes.get).toList finally MemoryStack.stackPop() - - // Conversion methods - - private def convertVulkanFormat(vkFormat: VkSurfaceFormatKHR): SurfaceFormat = - vkFormat.format() match - case VK_FORMAT_B8G8R8A8_SRGB => SurfaceFormat.B8G8R8A8_SRGB - case VK_FORMAT_B8G8R8A8_UNORM => SurfaceFormat.B8G8R8A8_UNORM - case VK_FORMAT_R8G8B8A8_SRGB => SurfaceFormat.R8G8B8A8_SRGB - case VK_FORMAT_R8G8B8A8_UNORM => SurfaceFormat.R8G8B8A8_UNORM - case _ => SurfaceFormat.B8G8R8A8_SRGB // Default fallback - - private def convertVulkanColorSpace(vkFormat: VkSurfaceFormatKHR): ColorSpace = - vkFormat.colorSpace() match - case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR => ColorSpace.SRGB_NONLINEAR - case 1000104001 => ColorSpace.DISPLAY_P3_NONLINEAR // VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT - case _ => ColorSpace.SRGB_NONLINEAR // Default fallback - - private def convertVulkanPresentMode(vkMode: Int): PresentMode = - vkMode match - case VK_PRESENT_MODE_IMMEDIATE_KHR => PresentMode.IMMEDIATE - case VK_PRESENT_MODE_MAILBOX_KHR => PresentMode.MAILBOX - case VK_PRESENT_MODE_FIFO_KHR => PresentMode.FIFO - case VK_PRESENT_MODE_FIFO_RELAXED_KHR => PresentMode.FIFO_RELAXED - case _ => PresentMode.FIFO // Default fallback diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/window/WindowManager.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/window/WindowManager.scala index 883ab641..07080353 100644 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/window/WindowManager.scala +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/window/WindowManager.scala @@ -51,7 +51,7 @@ class WindowManager: def createWindowWithSurface( windowConfig: WindowConfig = WindowConfig(), surfaceConfig: SurfaceConfig = SurfaceConfig.default, - ): Try[(Window, RenderSurface)] = + ): Try[(Window, Surface)] = surfaceManager match case Some(surfMgr) => @@ -64,7 +64,7 @@ class WindowManager: Failure(new IllegalStateException("Surface manager not initialized. Call initializeWithVulkan() first.")) // Create multiple windows with surfaces (All-or-nothing approach for now) - def createWindowsWithSurfaces(configs: List[(WindowConfig, SurfaceConfig)]): Try[List[(Window, RenderSurface)]] = + def createWindowsWithSurfaces(configs: List[(WindowConfig, SurfaceConfig)]): Try[List[(Window, Surface)]] = val results = configs.map { case (winConfig, surfConfig) => createWindowWithSurface(winConfig, surfConfig) } 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..1e10a68e 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,7 +1,7 @@ package io.computenode.cyfra.vulkan.core import io.computenode.cyfra.vulkan.VulkanContext.ValidationLayer -import Device.{MacOsExtension, SyncExtension} +import Device.{MacOsExtension, SwapchainExtension, SyncExtension} import io.computenode.cyfra.vulkan.util.Util.{check, pushStack} import io.computenode.cyfra.vulkan.util.VulkanObject import org.lwjgl.vulkan.* @@ -9,6 +9,7 @@ import org.lwjgl.vulkan.KHRPortabilitySubset.VK_KHR_PORTABILITY_SUBSET_EXTENSION import org.lwjgl.vulkan.KHRSynchronization2.VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME import org.lwjgl.vulkan.VK10.* import org.lwjgl.vulkan.VK11.* +import org.lwjgl.vulkan.KHRSwapchain.VK_KHR_SWAPCHAIN_EXTENSION_NAME import java.nio.ByteBuffer import scala.jdk.CollectionConverters.given @@ -20,6 +21,7 @@ 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 + final val SwapchainExtension = VK_KHR_SWAPCHAIN_EXTENSION_NAME private[cyfra] class Device(instance: Instance) extends VulkanObject: @@ -106,7 +108,7 @@ private[cyfra] class Device(instance: Instance) extends VulkanObject: .queueFamilyIndex(computeQueueFamily) .pQueuePriorities(pQueuePriorities) - val extensions = Seq(MacOsExtension, SyncExtension).filter(deviceExtensionsSet) + val extensions = Seq(MacOsExtension, SwapchainExtension, SyncExtension).filter(deviceExtensionsSet) val ppExtensionNames = stack.callocPointer(extensions.length) extensions.foreach(extension => ppExtensionNames.put(stack.ASCII(extension))) ppExtensionNames.flip() From 99d012b4e3aef44a80a6baa92c0a50631a9a0243 Mon Sep 17 00:00:00 2001 From: rudrabeniwal Date: Thu, 7 Aug 2025 05:07:05 +0530 Subject: [PATCH 2/3] Refactor SwapchainManager to implement Swapchain class and image view creation --- .../io/computenode/cyfra/rtrp/Swapchain.scala | 12 ++++ .../cyfra/rtrp/SwapchainManager.scala | 69 +++++++++++++++---- 2 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/Swapchain.scala diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/Swapchain.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/Swapchain.scala new file mode 100644 index 00000000..85ce3ca7 --- /dev/null +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/Swapchain.scala @@ -0,0 +1,12 @@ +package io.computenode.cyfra.rtrp + +import org.lwjgl.vulkan.VkExtent2D + +class Swapchain( + handle: Long, + images: Array[Long], + imageViews: Array[Long], + format: Int, + colorSpace: Int, + extent: VkExtent2D +) \ No newline at end of file diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/SwapchainManager.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/SwapchainManager.scala index 3258d722..f49dcf0a 100644 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/SwapchainManager.scala +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/SwapchainManager.scala @@ -14,27 +14,18 @@ import scala.util.{Try, Success, Failure} import scala.collection.mutable.ArrayBuffer -private[cyfra] class SwapchainManager (context: VulkanContext, surface: Surface) extends VulkanObjectHandle: +private[cyfra] class SwapchainManager (context: VulkanContext, surface: Surface) : Swapchain = ( private val device = context.device private val physicalDevice = device.physicalDevice private var swapchainHandle: Long = VK_NULL_HANDLE private var swapchainImages: Array[Long] = _ - - override protected def close(): Unit = - if swapchainHandle != VK_NULL_HANDLE then - vkDestroySwapchainKHR(device.get, swapchainHandle, null) - swapchainHandle = VK_NULL_HANDLE - - override def get: Long = - if !alive then - throw new IllegalStateException() - swapchainHandle private var swapchainImageFormat: Int = _ private var swapchainColorSpace: Int = _ private var swapchainPresentMode: Int = _ private var swapchainExtent: VkExtent2D = _ + private var swapchainImageViews: Array[Long] = _ // Get the raw Vulkan capabilities for low-level access private val vkCapabilities = pushStack: stack => @@ -55,7 +46,7 @@ private[cyfra] class SwapchainManager (context: VulkanContext, surface: Surface) val minImageExtent = vkCapabilities.minImageExtent() val maxImageExtent = vkCapabilities.maxImageExtent() - def initialize (surfaceConfig: SurfaceConfig): Boolean = pushStack: stack => + def initialize (surfaceConfig: SurfaceConfig): Swapchain = pushStack: stack => //cleanup() val preferredPresentMode = surfaceConfig.preferredPresentMode @@ -125,7 +116,6 @@ private[cyfra] class SwapchainManager (context: VulkanContext, surface: Surface) throw new VulkanAssertionError("Failed to create swap chain", result) swapchainHandle = pSwapchain.get(0) - alive = true // Get swap chain images val pImageCount = stack.callocInt(1) @@ -139,6 +129,55 @@ private[cyfra] class SwapchainManager (context: VulkanContext, surface: Surface) for (i <- 0 until actualImageCount) swapchainImages(i) = pSwapchainImages.get(i) - // createImageViews() + createImageViews() + + Swapchain( + handle = swapchainHandle + images = swapchainImages + imageViews = swapchainImageViews + format = swapchainImageFormat, + colorSpace = swapchainColorSpace, + extent = swapchainExtent + ) - true + private def createImageViews(): Unit = pushStack: Stack => + if (swapchainImages == null || swapchainImages.isEmpty) + throw new VulkanAssertionError("Cannot create image views: swap chain images not initialized", -1) + + if (swapchainImageViews != null) + swapchainImageViews.foreach(imageView => + if (imageView != VK_NULL_HANDLE) + vkDestroyImageView(device.get, imageView, null) + ) + + swapchainImageViews = new Array[Long](swapchainImages.length) + + for (i <- swapchainImages.indices) + val createInfo = VkImageViewCreateInfo.calloc(stack) + .sType$Default() + .image(swapchainImages(i)) + .viewType(VK_IMAGE_VIEW_TYPE_2D) + .format(swapchainImageFormat) + + createInfo.components: components => + components + .r(VK_COMPONENT_SWIZZLE_IDENTITY) + .g(VK_COMPONENT_SWIZZLE_IDENTITY) + .b(VK_COMPONENT_SWIZZLE_IDENTITY) + .a(VK_COMPONENT_SWIZZLE_IDENTITY) + + createInfo.subresourceRange: range => + range + .aspectMask(VK_IMAGE_ASPECT_COLOR_BIT) + .baseMipLevel(0) + .levelCount(1) + .baseArrayLayer(0) + .layerCount(1) + + val pImageView = stack.callocLong(1) + check ( + vkCreateImageView(device.get, createInfo, null, pImageView), + s"Failed to create image view for swap chain image $i" + ) + swapchainImageViews(i) = pImageView.get(i) +) \ No newline at end of file From 4cfe36a796ca14e53644e8558db49249b70554da Mon Sep 17 00:00:00 2001 From: rudrabeniwal Date: Fri, 8 Aug 2025 06:46:10 +0530 Subject: [PATCH 3/3] small refactor --- .../io/computenode/cyfra/rtrp/Swapchain.scala | 27 +- .../cyfra/rtrp/SwapchainManager.scala | 329 +++++++++--------- .../rtrp/surface/core/SurfaceConfig.scala | 2 +- .../rtrp/surface/vulkan/VulkanSurface.scala | 3 +- .../vulkan/VulkanSurfaceCapabilities.scala | 3 +- 5 files changed, 184 insertions(+), 180 deletions(-) diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/Swapchain.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/Swapchain.scala index 85ce3ca7..616afbc6 100644 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/Swapchain.scala +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/Swapchain.scala @@ -1,12 +1,25 @@ package io.computenode.cyfra.rtrp import org.lwjgl.vulkan.VkExtent2D +import org.lwjgl.vulkan.{VkDevice, VkExtent2D} +import org.lwjgl.vulkan.KHRSwapchain.vkDestroySwapchainKHR +import org.lwjgl.vulkan.VK10.vkDestroyImageView +import io.computenode.cyfra.vulkan.util.VulkanObjectHandle class Swapchain( - handle: Long, - images: Array[Long], - imageViews: Array[Long], - format: Int, - colorSpace: Int, - extent: VkExtent2D -) \ No newline at end of file + val device: VkDevice, + override val handle: Long, + val images: Array[Long], + val imageViews: Array[Long], + val format: Int, + val colorSpace: Int, + val extent: VkExtent2D, +) extends VulkanObjectHandle: + + override protected def close(): Unit = + if imageViews != null then + imageViews.foreach: imageView => + if imageView != 0L then vkDestroyImageView(device, imageView, null) + + vkDestroySwapchainKHR(device, handle, null) + alive = false diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/SwapchainManager.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/SwapchainManager.scala index f49dcf0a..1f955e84 100644 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/SwapchainManager.scala +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/SwapchainManager.scala @@ -9,175 +9,168 @@ import org.lwjgl.vulkan.KHRSurface.* import org.lwjgl.vulkan.KHRSwapchain.* import org.lwjgl.vulkan.VK10.* import io.computenode.cyfra.vulkan.util.{VulkanAssertionError, VulkanObjectHandle} -import org.lwjgl.vulkan.{VkExtent2D, VkSwapchainCreateInfoKHR, VkImageViewCreateInfo, VkSurfaceFormatKHR, VkPresentInfoKHR, VkSemaphoreCreateInfo, VkSurfaceCapabilitiesKHR} +import org.lwjgl.vulkan.{ + VkExtent2D, + VkSwapchainCreateInfoKHR, + VkImageViewCreateInfo, + VkSurfaceFormatKHR, + VkPresentInfoKHR, + VkSemaphoreCreateInfo, + VkSurfaceCapabilitiesKHR, +} import scala.util.{Try, Success, Failure} import scala.collection.mutable.ArrayBuffer -private[cyfra] class SwapchainManager (context: VulkanContext, surface: Surface) : Swapchain = ( - - private val device = context.device - private val physicalDevice = device.physicalDevice - private var swapchainHandle: Long = VK_NULL_HANDLE - private var swapchainImages: Array[Long] = _ - - private var swapchainImageFormat: Int = _ - private var swapchainColorSpace: Int = _ - private var swapchainPresentMode: Int = _ - private var swapchainExtent: VkExtent2D = _ - private var swapchainImageViews: Array[Long] = _ - - // Get the raw Vulkan capabilities for low-level access - private val vkCapabilities = pushStack: stack => - val vkCapabilities = VkSurfaceCapabilitiesKHR.calloc(stack) - check( - vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface.nativeHandle, vkCapabilities), - "Failed to get surface capabilities" - ) - vkCapabilities - - // Get the high-level surface capabilities for format/mode queries - private val surfaceCapabilities = surface.getCapabilities() match - case Success(caps) => caps - case Failure(exception) => - throw new RuntimeException("Failed to get surface capabilities", exception) - - val (width, height) = (vkCapabilities.currentExtent().width(), vkCapabilities.currentExtent().height()) - val minImageExtent = vkCapabilities.minImageExtent() - val maxImageExtent = vkCapabilities.maxImageExtent() - - def initialize (surfaceConfig: SurfaceConfig): Swapchain = pushStack: stack => - //cleanup() - - val preferredPresentMode = surfaceConfig.preferredPresentMode - - // Use the surface capabilities abstraction - val availableFormats: List[Int] = surfaceCapabilities.supportedFormats - val availableColorSpaces: List[Int] = surfaceCapabilities.supportedColorSpaces - - val exactFmtMatch = availableFormats.find(fmt => fmt == surfaceConfig.preferredFormat) - val exactCsMatch = availableColorSpaces.find(cs => cs == surfaceConfig.preferredColorSpace) - - val chosenFormat = exactFmtMatch.orElse(availableFormats.headOption).getOrElse( - throw new RuntimeException("No supported surface formats available") - ) - val chosenColorSpace = exactCsMatch.orElse(availableColorSpaces.headOption).getOrElse( - throw new RuntimeException("No supported color spaces available") - ) - - // Choose present mode - val availableModes = surfaceCapabilities.supportedPresentModes - val presentMode = if (availableModes.contains(preferredPresentMode)) preferredPresentMode else VK_PRESENT_MODE_FIFO_KHR - - // Choose swap extent - val (chosenWidth, chosenHeight) = if (width != -1 && height != -1) - (width, height) - else - val (desiredWidth, desiredHeight) = (800, 600) // TODO: get from window/config - if surfaceCapabilities.isExtentSupported(desiredWidth, desiredHeight) then - (desiredWidth, desiredHeight) - else - surfaceCapabilities.clampExtent(desiredWidth, desiredHeight) - - // Determine image count - var imageCount = surfaceCapabilities.minImageCount + 1 - if (surfaceCapabilities.maxImageCount != 0) - imageCount = Math.min(imageCount, surfaceCapabilities.maxImageCount) - - // Convert from surface abstraction to Vulkan constants - swapchainImageFormat = chosenFormat - swapchainColorSpace = chosenColorSpace - swapchainPresentMode = presentMode - swapchainExtent = VkExtent2D.calloc(stack).width(chosenWidth).height(chosenHeight) - - // Create swapchain - val createInfo = VkSwapchainCreateInfoKHR.calloc(stack) - .sType$Default() - .surface(surface.nativeHandle) - .minImageCount(imageCount) - .imageFormat(swapchainImageFormat) - .imageColorSpace(swapchainColorSpace) - .imageExtent(swapchainExtent) - .imageArrayLayers(1) - .imageUsage(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) - .preTransform(vkCapabilities.currentTransform()) - .compositeAlpha(VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) - .presentMode(swapchainPresentMode) - .clipped(true) - .oldSwapchain(VK_NULL_HANDLE) - .imageSharingMode(VK_SHARING_MODE_EXCLUSIVE) - .queueFamilyIndexCount(0) - .pQueueFamilyIndices(null) - - val pSwapchain = stack.callocLong(1) - - val result = vkCreateSwapchainKHR(device.get, createInfo, null, pSwapchain) - if (result != VK_SUCCESS) - throw new VulkanAssertionError("Failed to create swap chain", result) - - swapchainHandle = pSwapchain.get(0) - - // Get swap chain images - val pImageCount = stack.callocInt(1) - vkGetSwapchainImagesKHR(device.get, swapchainHandle, pImageCount, null) - val actualImageCount = pImageCount.get(0) - - val pSwapchainImages = stack.callocLong(actualImageCount) - vkGetSwapchainImagesKHR(device.get, swapchainHandle, pImageCount, pSwapchainImages) - - swapchainImages = new Array[Long](actualImageCount) - for (i <- 0 until actualImageCount) - swapchainImages(i) = pSwapchainImages.get(i) - - createImageViews() - - Swapchain( - handle = swapchainHandle - images = swapchainImages - imageViews = swapchainImageViews - format = swapchainImageFormat, - colorSpace = swapchainColorSpace, - extent = swapchainExtent - ) - - private def createImageViews(): Unit = pushStack: Stack => - if (swapchainImages == null || swapchainImages.isEmpty) - throw new VulkanAssertionError("Cannot create image views: swap chain images not initialized", -1) - - if (swapchainImageViews != null) - swapchainImageViews.foreach(imageView => - if (imageView != VK_NULL_HANDLE) - vkDestroyImageView(device.get, imageView, null) - ) - - swapchainImageViews = new Array[Long](swapchainImages.length) - - for (i <- swapchainImages.indices) - val createInfo = VkImageViewCreateInfo.calloc(stack) - .sType$Default() - .image(swapchainImages(i)) - .viewType(VK_IMAGE_VIEW_TYPE_2D) - .format(swapchainImageFormat) - - createInfo.components: components => - components - .r(VK_COMPONENT_SWIZZLE_IDENTITY) - .g(VK_COMPONENT_SWIZZLE_IDENTITY) - .b(VK_COMPONENT_SWIZZLE_IDENTITY) - .a(VK_COMPONENT_SWIZZLE_IDENTITY) - - createInfo.subresourceRange: range => - range - .aspectMask(VK_IMAGE_ASPECT_COLOR_BIT) - .baseMipLevel(0) - .levelCount(1) - .baseArrayLayer(0) - .layerCount(1) - - val pImageView = stack.callocLong(1) - check ( - vkCreateImageView(device.get, createInfo, null, pImageView), - s"Failed to create image view for swap chain image $i" - ) - swapchainImageViews(i) = pImageView.get(i) -) \ No newline at end of file +private[cyfra] class SwapchainManager(context: VulkanContext, surface: Surface): + + private val device = context.device + private val physicalDevice = device.physicalDevice + private var swapchainHandle: Long = VK_NULL_HANDLE + private var swapchainImages: Array[Long] = _ + + private var swapchainImageFormat: Int = _ + private var swapchainColorSpace: Int = _ + private var swapchainPresentMode: Int = _ + private var swapchainExtent: VkExtent2D = _ + private var swapchainImageViews: Array[Long] = _ + + // Get the raw Vulkan capabilities for low-level access + private val vkCapabilities = pushStack: Stack => + val vkCapabilities = VkSurfaceCapabilitiesKHR.calloc(Stack) + check(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface.nativeHandle, vkCapabilities), "Failed to get surface capabilities") + vkCapabilities + + // Get the high-level surface capabilities for format/mode queries + private val surfaceCapabilities = surface.getCapabilities() match + case Success(caps) => caps + case Failure(exception) => + throw new RuntimeException("Failed to get surface capabilities", exception) + + val (width, height) = (vkCapabilities.currentExtent().width(), vkCapabilities.currentExtent().height()) + val minImageExtent = vkCapabilities.minImageExtent() + val maxImageExtent = vkCapabilities.maxImageExtent() + + def initialize(surfaceConfig: SurfaceConfig): Swapchain = pushStack: Stack => + // cleanup() + + val preferredPresentMode = surfaceConfig.preferredPresentMode + + // Use the surface capabilities abstraction + val availableFormats: List[Int] = surfaceCapabilities.supportedFormats + val availableColorSpaces: List[Int] = surfaceCapabilities.supportedColorSpaces + + val exactFmtMatch = availableFormats.find(fmt => fmt == surfaceConfig.preferredFormat) + val exactCsMatch = availableColorSpaces.find(cs => cs == surfaceConfig.preferredColorSpace) + + val chosenFormat = exactFmtMatch.orElse(availableFormats.headOption).getOrElse(throw new RuntimeException("No supported surface formats available")) + val chosenColorSpace = + exactCsMatch.orElse(availableColorSpaces.headOption).getOrElse(throw new RuntimeException("No supported color spaces available")) + + // Choose present mode + val availableModes = surfaceCapabilities.supportedPresentModes + val presentMode = if availableModes.contains(preferredPresentMode) then preferredPresentMode else VK_PRESENT_MODE_FIFO_KHR + + // Choose swap extent + val (chosenWidth, chosenHeight) = + if width != -1 && height != -1 then (width, height) + else + val (desiredWidth, desiredHeight) = (800, 600) // TODO: get from window/config + if surfaceCapabilities.isExtentSupported(desiredWidth, desiredHeight) then (desiredWidth, desiredHeight) + else surfaceCapabilities.clampExtent(desiredWidth, desiredHeight) + + // Determine image count + var imageCount = surfaceCapabilities.minImageCount + 1 + if surfaceCapabilities.maxImageCount != 0 then imageCount = Math.min(imageCount, surfaceCapabilities.maxImageCount) + + // Convert from surface abstraction to Vulkan constants + swapchainImageFormat = chosenFormat + swapchainColorSpace = chosenColorSpace + swapchainPresentMode = presentMode + swapchainExtent = VkExtent2D.calloc(Stack).width(chosenWidth).height(chosenHeight) + + // Create swapchain + val createInfo = VkSwapchainCreateInfoKHR + .calloc(Stack) + .sType$Default() + .surface(surface.nativeHandle) + .minImageCount(imageCount) + .imageFormat(swapchainImageFormat) + .imageColorSpace(swapchainColorSpace) + .imageExtent(swapchainExtent) + .imageArrayLayers(1) + .imageUsage(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) + .preTransform(vkCapabilities.currentTransform()) + .compositeAlpha(VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) + .presentMode(swapchainPresentMode) + .clipped(true) + .oldSwapchain(VK_NULL_HANDLE) + .imageSharingMode(VK_SHARING_MODE_EXCLUSIVE) + .queueFamilyIndexCount(0) + .pQueueFamilyIndices(null) + + val pSwapchain = Stack.callocLong(1) + + val result = vkCreateSwapchainKHR(device.get, createInfo, null, pSwapchain) + if result != VK_SUCCESS then throw new VulkanAssertionError("Failed to create swap chain", result) + + swapchainHandle = pSwapchain.get(0) + + // Get swap chain images + val pImageCount = Stack.callocInt(1) + vkGetSwapchainImagesKHR(device.get, swapchainHandle, pImageCount, null) + val actualImageCount = pImageCount.get(0) + + val pSwapchainImages = Stack.callocLong(actualImageCount) + vkGetSwapchainImagesKHR(device.get, swapchainHandle, pImageCount, pSwapchainImages) + + swapchainImages = new Array[Long](actualImageCount) + for i <- 0 until actualImageCount do swapchainImages(i) = pSwapchainImages.get(i) + + createImageViews() + + Swapchain( + device = device.get, + handle = swapchainHandle, + images = swapchainImages, + imageViews = swapchainImageViews, + format = swapchainImageFormat, + colorSpace = swapchainColorSpace, + extent = swapchainExtent, + ) + + private def createImageViews(): Unit = pushStack: Stack => + if swapchainImages == null || swapchainImages.isEmpty then + throw new VulkanAssertionError("Cannot create image views: swap chain images not initialized", -1) + + if swapchainImageViews != null then + swapchainImageViews.foreach(imageView => if imageView != VK_NULL_HANDLE then vkDestroyImageView(device.get, imageView, null)) + + swapchainImageViews = new Array[Long](swapchainImages.length) + + for i <- swapchainImages.indices do + val createInfo = VkImageViewCreateInfo + .calloc(Stack) + .sType$Default() + .image(swapchainImages(i)) + .viewType(VK_IMAGE_VIEW_TYPE_2D) + .format(swapchainImageFormat) + + createInfo.components: components => + components + .r(VK_COMPONENT_SWIZZLE_IDENTITY) + .g(VK_COMPONENT_SWIZZLE_IDENTITY) + .b(VK_COMPONENT_SWIZZLE_IDENTITY) + .a(VK_COMPONENT_SWIZZLE_IDENTITY) + + createInfo.subresourceRange: range => + range + .aspectMask(VK_IMAGE_ASPECT_COLOR_BIT) + .baseMipLevel(0) + .levelCount(1) + .baseArrayLayer(0) + .layerCount(1) + + val pImageView = Stack.callocLong(1) + check(vkCreateImageView(device.get, createInfo, null, pImageView), s"Failed to create image view for swap chain image $i") + swapchainImageViews(i) = pImageView.get(i) diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceConfig.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceConfig.scala index ae65099a..54228c5f 100644 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceConfig.scala +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/core/SurfaceConfig.scala @@ -42,7 +42,7 @@ object SurfaceConfig: def quality: SurfaceConfig = SurfaceConfig( preferredFormat = VK_FORMAT_R8G8B8A8_SRGB, - preferredColorSpace = 1000104001 , + preferredColorSpace = 1000104001, preferredPresentMode = VK_PRESENT_MODE_FIFO_KHR, enableVSync = true, ) diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurface.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurface.scala index 8c96b81b..ea987fd8 100644 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurface.scala +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurface.scala @@ -11,8 +11,7 @@ import scala.util.* import java.util.concurrent.atomic.AtomicBoolean // Vulkan implementation of Surface -class VulkanSurface(val id: SurfaceId, val windowId: WindowId, val nativeHandle: Long, private val vulkanContext: VulkanContext) - extends Surface: +class VulkanSurface(val id: SurfaceId, val windowId: WindowId, val nativeHandle: Long, private val vulkanContext: VulkanContext) extends Surface: private val destroyed = new AtomicBoolean(false) private var surfaceCapabilities: Option[VulkanSurfaceCapabilities] = None diff --git a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurfaceCapabilities.scala b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurfaceCapabilities.scala index d25021c4..367bd89c 100644 --- a/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurfaceCapabilities.scala +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/surface/vulkan/VulkanSurfaceCapabilities.scala @@ -31,8 +31,7 @@ class VulkanSurfaceCapabilities(vulkanContext: VulkanContext, surface: VulkanSur override def currentExtent: (Int, Int) = val extent = vkCapabilities.currentExtent() - if extent.width() == 0xffffffff || extent.height() == 0xffffffff then - (-1, -1) + if extent.width() == 0xffffffff || extent.height() == 0xffffffff then (-1, -1) else (extent.width(), extent.height()) override def minImageCount: Int = vkCapabilities.minImageCount()