diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp new file mode 100644 index 00000000..a76d61f8 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp @@ -0,0 +1,138 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include "SwapchainObject.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + SwapchainObject::SwapchainCapabilities SwapchainObject::GetSwapchainCapabilities(VkPhysicalDevice physicalDevice, + VkSurfaceKHR surface) { + SwapchainCapabilities swapchainCapabilities{}; + + VK_VERIFY( + vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &swapchainCapabilities.capabilities)); + + Uint32 formatCount = 0; + VK_VERIFY(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, nullptr)); + if (formatCount != 0) { + swapchainCapabilities.surfaceFormats.resize(formatCount); + VK_VERIFY(vkGetPhysicalDeviceSurfaceFormatsKHR( + physicalDevice, surface, &formatCount, swapchainCapabilities.surfaceFormats.data())); + } + + Uint32 presentModeCount = 0; + VK_VERIFY( + vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, nullptr)); + if (presentModeCount != 0) { + swapchainCapabilities.presentModes.resize(presentModeCount); + VK_VERIFY(vkGetPhysicalDeviceSurfacePresentModesKHR( + physicalDevice, surface, &presentModeCount, swapchainCapabilities.presentModes.data())); + } + + return swapchainCapabilities; + } + + VkSurfaceFormatKHR SwapchainObject::ChooseSwapchainSurfaceFormat( + const Vector& availableFormats) { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && + availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + return availableFormat; + } + } + + // TODO: Properly rank other formats + return availableFormats[0]; + } + + VkPresentModeKHR SwapchainObject::ChooseSwapchainPresentMode( + const Vector& availablePresentModes) { + for (auto desiredPresentMode : s_desiredPresentModes) { + for (const auto& presentMode : availablePresentModes) { + if (presentMode == desiredPresentMode) { + return presentMode; + } + } + } + + // TODO: Properly rank other modes + return availablePresentModes[0]; + } + + void SwapchainObject::Create(VkDevice device, const VkSwapchainCreateInfoKHR& createInfo) { + m_surfaceFormat = {createInfo.imageFormat, createInfo.imageColorSpace}; + m_extent = createInfo.imageExtent; + + VK_VERIFY(vkCreateSwapchainKHR(device, &createInfo, nullptr, &m_swapchain)); + + Uint32 imageCount = 0; + VK_VERIFY(vkGetSwapchainImagesKHR(device, m_swapchain, &imageCount, nullptr)); + + m_images.resize(imageCount, VK_NULL_HANDLE); + VK_VERIFY(vkGetSwapchainImagesKHR(device, m_swapchain, &imageCount, m_images.data())); + m_imageLayouts.assign(imageCount, VK_IMAGE_LAYOUT_UNDEFINED); + + CreateImageViews(device); + + MGLOG_I("Swapchain created, extent = %dx%d, swapchain imageCount = %d", m_extent.width, m_extent.height, + imageCount); + } + + void SwapchainObject::Shutdown(VkDevice device) { + for (auto imageView : m_imageViews) { + vkDestroyImageView(device, imageView, nullptr); + } + m_imageViews.clear(); + + if (m_swapchain != VK_NULL_HANDLE) { + vkDestroySwapchainKHR(device, m_swapchain, nullptr); + m_swapchain = VK_NULL_HANDLE; + } + + m_images.clear(); + m_imageLayouts.clear(); + } + + VkImage SwapchainObject::GetImage(Uint32 index) const { + MOBILEGL_ASSERT(index < m_images.size(), "Swapchain image index out of range"); + return m_images[index]; + } + + VkImageLayout SwapchainObject::GetImageLayout(Uint32 index) const { + MOBILEGL_ASSERT(index < m_imageLayouts.size(), "Swapchain image layout index out of range"); + return m_imageLayouts[index]; + } + + void SwapchainObject::SetImageLayout(Uint32 index, VkImageLayout layout) { + MOBILEGL_ASSERT(index < m_imageLayouts.size(), "Swapchain image layout index out of range"); + m_imageLayouts[index] = layout; + } + + void SwapchainObject::CreateImageViews(VkDevice device) { + m_imageViews.resize(m_images.size(), VK_NULL_HANDLE); + for (SizeT i = 0; i < m_imageViews.size(); i++) { + VkImageViewCreateInfo createInfo{}; + createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + createInfo.image = m_images[i]; + createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; + createInfo.format = m_surfaceFormat.format; + createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; + createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + VK_VERIFY(vkCreateImageView(device, &createInfo, nullptr, &m_imageViews[i])); + } + MGLOG_I("Swapchain image views created"); + } + +#undef VK_VERIFY +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.h new file mode 100644 index 00000000..30c7c73a --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.h @@ -0,0 +1,60 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once + +#include +#include "../VkIncludes.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + class SwapchainObject { + public: + struct SwapchainCapabilities { + VkSurfaceCapabilitiesKHR capabilities; + Vector surfaceFormats; + Vector presentModes; + + Bool IsComplete() const { + return !surfaceFormats.empty() && !presentModes.empty(); + } + }; + + static SwapchainCapabilities GetSwapchainCapabilities(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface); + static VkSurfaceFormatKHR ChooseSwapchainSurfaceFormat(const Vector& availableFormats); + static VkPresentModeKHR ChooseSwapchainPresentMode(const Vector& availablePresentModes); + + void Create(VkDevice device, const VkSwapchainCreateInfoKHR& createInfo); + void Shutdown(VkDevice device); + + VkSwapchainKHR GetHandle() const { return m_swapchain; } + const VkSurfaceFormatKHR& GetSurfaceFormat() const { return m_surfaceFormat; } + VkExtent2D GetExtent() const { return m_extent; } + const Vector& GetImages() const { return m_images; } + const Vector& GetImageViews() const { return m_imageViews; } + VkImage GetImage(Uint32 index) const; + VkImageLayout GetImageLayout(Uint32 index) const; + void SetImageLayout(Uint32 index, VkImageLayout layout); + SizeT GetImageCount() const { return m_images.size(); } + + private: + void CreateImageViews(VkDevice device); + static constexpr VkPresentModeKHR s_desiredPresentModes[] { + VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_IMMEDIATE_KHR, + VK_PRESENT_MODE_FIFO_RELAXED_KHR, + VK_PRESENT_MODE_FIFO_KHR + }; + + VkSwapchainKHR m_swapchain = VK_NULL_HANDLE; + VkSurfaceFormatKHR m_surfaceFormat{}; + VkExtent2D m_extent{}; + Vector m_images; + Vector m_imageViews; + Vector m_imageLayouts; + }; +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 2b19831c..9001fe45 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -168,11 +168,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkViewport vp{}; vp.x = 0; vp.y = 0; - vp.width = (float)m_swapchainExtent.width; - vp.height = (float)m_swapchainExtent.height; + const auto swapchainExtent = m_swapchainObject.GetExtent(); + vp.width = (float)swapchainExtent.width; + vp.height = (float)swapchainExtent.height; vp.minDepth = 0; vp.maxDepth = 1; - VkRect2D scissor{{0, 0}, m_swapchainExtent}; + VkRect2D scissor{{0, 0}, swapchainExtent}; VkPipelineViewportStateCreateInfo vpci{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; vpci.viewportCount = 1; vpci.pViewports = &vp; @@ -256,7 +257,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Prime the first frame so Render() always targets an acquired swapchain image. VK_VERIFY(vkWaitForFences(m_device, 1, &m_imageInFlightFences[m_currentFrameIndex], VK_TRUE, UINT64_MAX)); VK_VERIFY(vkResetFences(m_device, 1, &m_imageInFlightFences[m_currentFrameIndex])); - VK_VERIFY(vkAcquireNextImageKHR(m_device, m_swapchain, UINT64_MAX, + VK_VERIFY(vkAcquireNextImageKHR(m_device, m_swapchainObject.GetHandle(), UINT64_MAX, m_imageAvailableSemaphores[m_currentFrameIndex], VK_NULL_HANDLE, &m_imageIndexAcquired)); @@ -335,7 +336,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { renderPassInfo.renderPass = m_renderPass; renderPassInfo.framebuffer = m_framebuffers[m_imageIndexAcquired]; renderPassInfo.renderArea.offset = {0, 0}; - renderPassInfo.renderArea.extent = m_swapchainExtent; + const auto swapchainExtent = m_swapchainObject.GetExtent(); + renderPassInfo.renderArea.extent = swapchainExtent; VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; renderPassInfo.clearValueCount = 1; renderPassInfo.pClearValues = &clearColor; @@ -347,15 +349,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkViewport viewport{}; viewport.x = 0.0f; viewport.y = 0.0f; - viewport.width = static_cast(m_swapchainExtent.width); - viewport.height = static_cast(m_swapchainExtent.height); + viewport.width = static_cast(swapchainExtent.width); + viewport.height = static_cast(swapchainExtent.height); viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; vkCmdSetViewport(m_commandBuffers[m_currentFrameIndex], 0, 1, &viewport); VkRect2D scissor{}; scissor.offset = {0, 0}; - scissor.extent = m_swapchainExtent; + scissor.extent = swapchainExtent; vkCmdSetScissor(m_commandBuffers[m_currentFrameIndex], 0, 1, &scissor); vkCmdDraw(m_commandBuffers[m_currentFrameIndex], 3, 1, 0, 0); @@ -369,12 +371,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void VulkanRenderer::Present() { - MOBILEGL_ASSERT(m_imageIndexAcquired < m_swapchainImages.size(), "Present, acquired image index out of range"); + MOBILEGL_ASSERT(m_imageIndexAcquired < m_swapchainObject.GetImageCount(), + "Present, acquired image index out of range"); const Bool hasRecordedWork = m_hasCommandBufferRecorded[m_currentFrameIndex]; Bool needsLayoutTransitionForPresent = false; if (!hasRecordedWork) { - auto& acquiredImageLayout = m_swapchainImageLayouts[m_imageIndexAcquired]; + const auto acquiredImageLayout = m_swapchainObject.GetImageLayout(m_imageIndexAcquired); if (acquiredImageLayout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR && acquiredImageLayout != VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR) { needsLayoutTransitionForPresent = true; @@ -395,7 +398,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { presentBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; presentBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; presentBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - presentBarrier.image = m_swapchainImages[m_imageIndexAcquired]; + presentBarrier.image = m_swapchainObject.GetImage(m_imageIndexAcquired); presentBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; presentBarrier.subresourceRange.baseMipLevel = 0; presentBarrier.subresourceRange.levelCount = 1; @@ -427,14 +430,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { submitInfo.pSignalSemaphores = signalSemaphores; VK_VERIFY(vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, m_imageInFlightFences[m_currentFrameIndex])); m_hasCommandBufferRecorded[m_currentFrameIndex] = false; - m_swapchainImageLayouts[m_imageIndexAcquired] = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); // 2) Present current frame. VkPresentInfoKHR presentInfo{}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; presentInfo.pWaitSemaphores = signalSemaphores; - VkSwapchainKHR swapChains[] = {m_swapchain}; + VkSwapchainKHR swapChains[] = {m_swapchainObject.GetHandle()}; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = swapChains; presentInfo.pImageIndices = &m_imageIndexAcquired; @@ -455,8 +458,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { "Present, vkWaitForFences"); VK_VERIFY(vkResetFences(m_device, 1, &m_imageInFlightFences[m_currentFrameIndex]), "Present, vkResetFences"); result = - vkAcquireNextImageKHR(m_device, m_swapchain, UINT64_MAX, m_imageAvailableSemaphores[m_currentFrameIndex], - VK_NULL_HANDLE, &m_imageIndexAcquired); + vkAcquireNextImageKHR(m_device, m_swapchainObject.GetHandle(), UINT64_MAX, + m_imageAvailableSemaphores[m_currentFrameIndex], VK_NULL_HANDLE, + &m_imageIndexAcquired); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { MGLOG_D("Present, vkAcquireNextImageKHR got %d, recreating swapchain", result); RecreateSwapchain(); @@ -644,8 +648,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } // Check swapchain capabilities - newDevice.swapchainCapabilities = GetSwapchainCapabilities(newVkDevice, surface); - if (!newDevice.swapchainCapabilities.IsComplete()) { + auto swapchainCapabilities = SwapchainObject::GetSwapchainCapabilities(newVkDevice, surface); + if (!swapchainCapabilities.IsComplete()) { outBetterDevice = otherDevice; MGLOG_I(" Ignored physical device. (Reason: Swapchain capabilities not met)"); return false; @@ -695,32 +699,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return false; } - VkSurfaceFormatKHR VulkanRenderer::ChooseSwapchainSurfaceFormat( - const Vector& availableFormats) { - for (const auto& availableFormat : availableFormats) { - if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && - availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { - return availableFormat; - } - } - - // TODO: Properly rank other formats - return availableFormats[0]; - } - - VkPresentModeKHR VulkanRenderer::ChooseSwapchainPresentMode(const Vector& availablePresentModes) { - for (auto desiredPresentMode : s_desiredPresentModes) { - for (const auto& presentMode : availablePresentModes) { - if (presentMode == desiredPresentMode) { - return presentMode; - } - } - } - - // TODO: Properly rank other modes - return availablePresentModes[0]; - } - Bool VulkanRenderer::IsNecessaryDeviceExtensionSupported(VkPhysicalDevice device) { Uint32 extensionCount = 0; vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); @@ -751,32 +729,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - VulkanRenderer::SwapchainCapabilities VulkanRenderer::GetSwapchainCapabilities(VkPhysicalDevice device, - VkSurfaceKHR surface) { - SwapchainCapabilities swapchainCapabilities; - - VK_VERIFY(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &swapchainCapabilities.capabilities)); - Uint32 formatCount; - VK_VERIFY(vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr)); - - if (formatCount != 0) { - swapchainCapabilities.surfaceFormats.resize(formatCount); - VK_VERIFY(vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, - swapchainCapabilities.surfaceFormats.data())); - } - - Uint32 presentModeCount; - VK_VERIFY(vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr)); - - if (presentModeCount != 0) { - swapchainCapabilities.presentModes.resize(presentModeCount); - VK_VERIFY(vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, - swapchainCapabilities.presentModes.data())); - } - - return swapchainCapabilities; - } - void VulkanRenderer::CreateLogicalDeviceAndQueues() { Float queuePriority = 1.0f; @@ -1154,35 +1106,35 @@ namespace MobileGL::MG_Backend::DirectVulkan { } }; - m_physicalDevice.swapchainCapabilities = GetSwapchainCapabilities(m_physicalDevice.handle, m_surface); + const auto swapchainCapabilities = SwapchainObject::GetSwapchainCapabilities(m_physicalDevice.handle, m_surface); - auto& supportedSurfaceFormats = m_physicalDevice.swapchainCapabilities.surfaceFormats; + auto& supportedSurfaceFormats = swapchainCapabilities.surfaceFormats; MGLOG_I("Got %d surface formats: ", supportedSurfaceFormats.size()); for (auto& surfaceFormat : supportedSurfaceFormats) { MGLOG_I(" [%s, %s]", surfaceFormatToStr(surfaceFormat.format), colorSpaceToStr(surfaceFormat.colorSpace)); } - m_swapchainSurfaceFormat = ChooseSwapchainSurfaceFormat(supportedSurfaceFormats); - MGLOG_I("Picked surface format: [%s, %s]", surfaceFormatToStr(m_swapchainSurfaceFormat.format), - colorSpaceToStr(m_swapchainSurfaceFormat.colorSpace)); + const auto pickedSurfaceFormat = SwapchainObject::ChooseSwapchainSurfaceFormat(supportedSurfaceFormats); + MGLOG_I("Picked surface format: [%s, %s]", surfaceFormatToStr(pickedSurfaceFormat.format), + colorSpaceToStr(pickedSurfaceFormat.colorSpace)); - auto& supportedPresentModes = m_physicalDevice.swapchainCapabilities.presentModes; + auto& supportedPresentModes = swapchainCapabilities.presentModes; MGLOG_I("Got %d present mode: ", supportedPresentModes.size()); for (auto& presentMode : supportedPresentModes) { MGLOG_I(" %s", presentModeToStr(presentMode)); } - auto presentMode = ChooseSwapchainPresentMode(supportedPresentModes); + auto presentMode = SwapchainObject::ChooseSwapchainPresentMode(supportedPresentModes); MGLOG_I("Picked present mode: %s", presentModeToStr(presentMode)); - const auto& swapchainCaps = m_physicalDevice.swapchainCapabilities.capabilities; + const auto& swapchainCaps = swapchainCapabilities.capabilities; auto imageCount = std::max(m_config.MaxFramesInFlight, swapchainCaps.minImageCount); MGLOG_I("Set minImageCount = %u", imageCount); VkSwapchainCreateInfoKHR sci{VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR}; sci.surface = m_surface; sci.minImageCount = imageCount; - sci.imageFormat = m_swapchainSurfaceFormat.format; - sci.imageColorSpace = m_swapchainSurfaceFormat.colorSpace; + sci.imageFormat = pickedSurfaceFormat.format; + sci.imageColorSpace = pickedSurfaceFormat.colorSpace; sci.imageExtent = swapchainCaps.currentExtent; sci.imageArrayLayers = 1; sci.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; @@ -1204,39 +1156,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { sci.presentMode = presentMode; sci.clipped = VK_TRUE; sci.oldSwapchain = VK_NULL_HANDLE; - VK_VERIFY(vkCreateSwapchainKHR(m_device, &sci, nullptr, &m_swapchain)); - - Uint32 gotImageCount = 0; - VK_VERIFY(vkGetSwapchainImagesKHR(m_device, m_swapchain, &gotImageCount, nullptr)); - m_swapchainImages.resize(gotImageCount, VK_NULL_HANDLE); - VK_VERIFY(vkGetSwapchainImagesKHR(m_device, m_swapchain, &gotImageCount, m_swapchainImages.data())); - m_swapchainImageLayouts.assign(gotImageCount, VK_IMAGE_LAYOUT_UNDEFINED); - m_swapchainExtent = swapchainCaps.currentExtent; - - MGLOG_I("Swapchain created, extent = %dx%d, swapchain imageCount = %d", m_swapchainExtent.width, - m_swapchainExtent.height, gotImageCount); - } - - void VulkanRenderer::CreateSwapchainImageViews() { - m_swapchainImageViews.resize(m_swapchainImages.size(), VK_NULL_HANDLE); - for (size_t i = 0; i < m_swapchainImageViews.size(); i++) { - VkImageViewCreateInfo createInfo{}; - createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - createInfo.image = m_swapchainImages[i]; - createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; - createInfo.format = m_swapchainSurfaceFormat.format; - createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; - createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - createInfo.subresourceRange.baseMipLevel = 0; - createInfo.subresourceRange.levelCount = 1; - createInfo.subresourceRange.baseArrayLayer = 0; - createInfo.subresourceRange.layerCount = 1; - VK_VERIFY(vkCreateImageView(m_device, &createInfo, nullptr, &m_swapchainImageViews[i])); - } - MGLOG_I("Swapchain image views created"); + m_swapchainObject.Create(m_device, sci); } void VulkanRenderer::CreateCommandPool() { @@ -1260,7 +1180,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VulkanRenderer::CreateDefaultRenderPass() { VkAttachmentDescription color{}; - color.format = m_swapchainSurfaceFormat.format; + color.format = m_swapchainObject.GetSurfaceFormat().format; color.samples = VK_SAMPLE_COUNT_1_BIT; color.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; color.storeOp = VK_ATTACHMENT_STORE_OP_STORE; @@ -1286,7 +1206,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VulkanRenderer::CreateDefaultFramebuffers() { // Create framebuffers now (use swapchain imageviews) - const auto& imageViews = m_swapchainImageViews; + const auto& imageViews = m_swapchainObject.GetImageViews(); + const auto swapchainExtent = m_swapchainObject.GetExtent(); Vector& fbs = m_framebuffers; fbs.reserve(imageViews.size()); for (auto iv : imageViews) { @@ -1295,8 +1216,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { fbci.renderPass = m_renderPass; fbci.attachmentCount = 1; fbci.pAttachments = attachments; - fbci.width = m_swapchainExtent.width; - fbci.height = m_swapchainExtent.height; + fbci.width = swapchainExtent.width; + fbci.height = swapchainExtent.height; fbci.layers = 1; VkFramebuffer fb; VK_VERIFY(vkCreateFramebuffer(m_device, &fbci, nullptr, &fb), "vkCreateFramebuffer"); @@ -1406,24 +1327,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_renderPass = VK_NULL_HANDLE; } - for (auto imageView : m_swapchainImageViews) { - vkDestroyImageView(m_device, imageView, nullptr); - } - m_swapchainImageViews.clear(); - - if (m_swapchain != VK_NULL_HANDLE) { - vkDestroySwapchainKHR(m_device, m_swapchain, nullptr); - m_swapchain = VK_NULL_HANDLE; - } - m_swapchainImages.clear(); - m_swapchainImageLayouts.clear(); + m_swapchainObject.Shutdown(m_device); } void VulkanRenderer::RecreateSwapchain() { // Handle cases like minimize on Windows, where swapchain could return a 0x0 extent - m_physicalDevice.swapchainCapabilities = GetSwapchainCapabilities(m_physicalDevice.handle, m_surface); - if (m_physicalDevice.swapchainCapabilities.capabilities.currentExtent.width == 0 || - m_physicalDevice.swapchainCapabilities.capabilities.currentExtent.height == 0) { + const auto swapchainCapabilities = SwapchainObject::GetSwapchainCapabilities(m_physicalDevice.handle, m_surface); + if (swapchainCapabilities.capabilities.currentExtent.width == 0 || + swapchainCapabilities.capabilities.currentExtent.height == 0) { return; } @@ -1432,7 +1343,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { ShutdownSwapchain(); CreateSwapchain(); - CreateSwapchainImageViews(); CreateDefaultRenderPass(); CreateDefaultFramebuffers(); std::fill(m_hasCommandBufferRecorded.begin(), m_hasCommandBufferRecorded.end(), false); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 4833970e..7f815cf6 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -8,15 +8,10 @@ #pragma once #include "Config.h" +#include "SwapchainObject.h" #include -#define VK_VERIFY(expr, ...) \ - do { \ - VkResult _vk_verify_result = (expr); \ - MOBILEGL_ASSERT(_vk_verify_result == VK_SUCCESS, "Vulkan error %d at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, _vk_verify_result, __FILE__, __LINE__); \ - } while (0) - -#define ENUM_STR_CASE(c) case c: return #c; +#include "../VkIncludes.h" namespace MobileGL::MG_Backend::DirectVulkan { using RenderCallback = std::function; @@ -48,20 +43,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { Int32 presentFamily = -1; }; - struct SwapchainCapabilities { - VkSurfaceCapabilitiesKHR capabilities; - Vector surfaceFormats; - Vector presentModes; - - Bool IsComplete() const { - return !surfaceFormats.empty() && !presentModes.empty(); - } - }; - struct PhysicalDevice { QueueFamilyIndices queueFamilies; VkPhysicalDeviceProperties properties; - SwapchainCapabilities swapchainCapabilities; VkPhysicalDevice handle = VK_NULL_HANDLE; Bool IsComplete() const { @@ -83,12 +67,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; VkDevice m_device = VK_NULL_HANDLE; VkSurfaceKHR m_surface = VK_NULL_HANDLE; - VkSwapchainKHR m_swapchain = VK_NULL_HANDLE; - VkSurfaceFormatKHR m_swapchainSurfaceFormat; - Vector m_swapchainImages; - Vector m_swapchainImageLayouts; - VkExtent2D m_swapchainExtent; - Vector m_swapchainImageViews; + SwapchainObject m_swapchainObject; // Vector m_queueFamilies; // QueueFamilyIndices m_queueFamilyIndices; @@ -120,7 +99,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { void PickPhysicalDevice(); void CreateLogicalDeviceAndQueues(); void CreateSwapchain(); - void CreateSwapchainImageViews(); void CreateCommandPool(); void CreateCommandBuffers(); void CreateDefaultRenderPass(); @@ -137,20 +115,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { static Int GetQueueFamilyIndex(const Vector& queueFamilies, VkQueueFlagBits flag); static Vector EnumerateInstanceExtensions(); static Bool IsNecessaryDeviceExtensionSupported(VkPhysicalDevice device); - static SwapchainCapabilities GetSwapchainCapabilities(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface); static Bool GetMoreCapablePhysicalDevice(VkPhysicalDevice newVkDevice, VkSurfaceKHR surface, const PhysicalDevice& compareWithDevice, PhysicalDevice& outBetterDevice); - static VkSurfaceFormatKHR ChooseSwapchainSurfaceFormat(const Vector& availableFormats); - static VkPresentModeKHR ChooseSwapchainPresentMode(const Vector& availablePresentModes); static constexpr VkDynamicState s_dynamicStates[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; - static constexpr VkPresentModeKHR s_desiredPresentModes[] { - VK_PRESENT_MODE_MAILBOX_KHR, - VK_PRESENT_MODE_IMMEDIATE_KHR, - VK_PRESENT_MODE_FIFO_RELAXED_KHR, - VK_PRESENT_MODE_FIFO_KHR - }; static constexpr const char* s_validationLayerNames[] = { "VK_LAYER_KHRONOS_validation" }; diff --git a/MobileGL/MG_Backend/DirectVulkan/VkIncludes.h b/MobileGL/MG_Backend/DirectVulkan/VkIncludes.h new file mode 100644 index 00000000..cb0f7a83 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/VkIncludes.h @@ -0,0 +1,17 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/VkIncludes.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once + +#define VK_VERIFY(expr, ...) \ + do { \ + VkResult _vk_verify_result = (expr); \ + MOBILEGL_ASSERT(_vk_verify_result == VK_SUCCESS, "Vulkan error %d at %s:%d" __VA_OPT__(" - ") __VA_ARGS__, _vk_verify_result, __FILE__, __LINE__); \ + } while (0) + +#define ENUM_STR_CASE(c) case c: return #c; \ No newline at end of file