[Chore] (MG_Test/Backend/DirectVulkan): encapsulate SwapchainObject

This commit is contained in:
2026-02-16 09:59:56 +08:00
parent 533e6bd57c
commit e01ebc8fc7
5 changed files with 259 additions and 165 deletions
@@ -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<VkSurfaceFormatKHR>& 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<VkPresentModeKHR>& 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
@@ -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 <Includes.h>
#include "../VkIncludes.h"
namespace MobileGL::MG_Backend::DirectVulkan {
class SwapchainObject {
public:
struct SwapchainCapabilities {
VkSurfaceCapabilitiesKHR capabilities;
Vector<VkSurfaceFormatKHR> surfaceFormats;
Vector<VkPresentModeKHR> presentModes;
Bool IsComplete() const {
return !surfaceFormats.empty() && !presentModes.empty();
}
};
static SwapchainCapabilities GetSwapchainCapabilities(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface);
static VkSurfaceFormatKHR ChooseSwapchainSurfaceFormat(const Vector<VkSurfaceFormatKHR>& availableFormats);
static VkPresentModeKHR ChooseSwapchainPresentMode(const Vector<VkPresentModeKHR>& 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<VkImage>& GetImages() const { return m_images; }
const Vector<VkImageView>& 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<VkImage> m_images;
Vector<VkImageView> m_imageViews;
Vector<VkImageLayout> m_imageLayouts;
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -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<float>(m_swapchainExtent.width);
viewport.height = static_cast<float>(m_swapchainExtent.height);
viewport.width = static_cast<float>(swapchainExtent.width);
viewport.height = static_cast<float>(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<VkSurfaceFormatKHR>& 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<VkPresentModeKHR>& 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<uint32_t>(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<VkFramebuffer>& 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);
@@ -8,15 +8,10 @@
#pragma once
#include "Config.h"
#include "SwapchainObject.h"
#include <Includes.h>
#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<void(VkCommandBuffer cmdBuf, uint32_t imageIndex, VkExtent2D extent)>;
@@ -48,20 +43,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Int32 presentFamily = -1;
};
struct SwapchainCapabilities {
VkSurfaceCapabilitiesKHR capabilities;
Vector<VkSurfaceFormatKHR> surfaceFormats;
Vector<VkPresentModeKHR> 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<VkImage> m_swapchainImages;
Vector<VkImageLayout> m_swapchainImageLayouts;
VkExtent2D m_swapchainExtent;
Vector<VkImageView> m_swapchainImageViews;
SwapchainObject m_swapchainObject;
// Vector<VkQueueFamilyProperties> 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<VkQueueFamilyProperties>& queueFamilies, VkQueueFlagBits flag);
static Vector<VkExtensionProperties> 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<VkSurfaceFormatKHR>& availableFormats);
static VkPresentModeKHR ChooseSwapchainPresentMode(const Vector<VkPresentModeKHR>& 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"
};
@@ -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;