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..616afbc6 --- /dev/null +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/Swapchain.scala @@ -0,0 +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( + 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 new file mode 100644 index 00000000..1f955e84 --- /dev/null +++ b/cyfra-rtrp/src/main/scala/io/computenode/cyfra/rtrp/SwapchainManager.scala @@ -0,0 +1,176 @@ +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): + + 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/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..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 @@ -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..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 @@ -10,9 +10,8 @@ import org.lwjgl.vulkan.VK10.* import scala.util.* import java.util.concurrent.atomic.AtomicBoolean -// Vulkan implementation of RenderSurface -class VulkanSurface(val id: SurfaceId, val windowId: WindowId, val nativeHandle: Long, private val vulkanContext: VulkanContext) - extends RenderSurface: +// Vulkan implementation of 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 20e7d525..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 @@ -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,7 @@ 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) + if extent.width() == 0xffffffff || extent.height() == 0xffffffff then (-1, -1) else (extent.width(), extent.height()) override def minImageCount: Int = vkCapabilities.minImageCount() @@ -104,27 +98,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()