SwapChain
Bigfoot / Sonarqube (game) (push) Skipped
Bigfoot / Sonarqube (toolmode) (push) Skipped
Bigfoot / Build & Test Debug with clang (Unity Build: OFF, Variant: game) (push) Successful in 1m8s
Bigfoot / Build & Test Debug with clang (Unity Build: OFF, Variant: toolmode) (push) Successful in 1m12s
Bigfoot / Build & Test Debug with clang (Unity Build: ON, Variant: game) (push) Successful in 42s
Bigfoot / Build & Test Debug with clang (Unity Build: ON, Variant: toolmode) (push) Successful in 46s
Bigfoot / Build & Test Debug with clang_asan (Unity Build: OFF, Variant: game) (push) Successful in 2m17s
Bigfoot / Build & Test Release with clang (Unity Build: OFF, Variant: game) (push) Successful in 1m8s
Bigfoot / Build & Test Release with clang (Unity Build: OFF, Variant: toolmode) (push) Successful in 1m30s
Bigfoot / Build & Test Release with clang_asan (Unity Build: OFF, Variant: game) (push) Successful in 1m47s
Bigfoot / Build & Test Release with clang_asan (Unity Build: ON, Variant: game) (push) Successful in 1m28s
Bigfoot / Build & Test Release with clang_asan (Unity Build: ON, Variant: toolmode) (push) Successful in 1m35s
Bigfoot / Build & Test RelWithDebInfo with clang (Unity Build: ON, Variant: game) (push) Successful in 46s
Bigfoot / Build & Test RelWithDebInfo with clang (Unity Build: ON, Variant: toolmode) (push) Successful in 49s
Bigfoot / Build & Test RelWithDebInfo with clang_asan (Unity Build: ON, Variant: toolmode) (push) Successful in 1m55s
Bigfoot / Clang Format Checks (push) Successful in 10s
Bigfoot / Build & Test Debug with clang_asan (Unity Build: OFF, Variant: toolmode) (push) Successful in 2m17s
Bigfoot / Build & Test Debug with clang_asan (Unity Build: ON, Variant: game) (push) Successful in 1m42s
Bigfoot / Build & Test Debug with clang_asan (Unity Build: ON, Variant: toolmode) (push) Successful in 1m50s
Bigfoot / Build & Test Release with clang (Unity Build: ON, Variant: game) (push) Successful in 51s
Bigfoot / Build & Test Release with clang (Unity Build: ON, Variant: toolmode) (push) Successful in 1m11s
Bigfoot / Build & Test Release with clang_asan (Unity Build: OFF, Variant: toolmode) (push) Successful in 1m49s
Bigfoot / Build & Test RelWithDebInfo with clang (Unity Build: OFF, Variant: game) (push) Successful in 1m16s
Bigfoot / Build & Test RelWithDebInfo with clang (Unity Build: OFF, Variant: toolmode) (push) Successful in 1m18s
Bigfoot / Build & Test RelWithDebInfo with clang_asan (Unity Build: OFF, Variant: game) (push) Successful in 2m23s
Bigfoot / Build & Test RelWithDebInfo with clang_asan (Unity Build: OFF, Variant: toolmode) (push) Successful in 2m24s
Bigfoot / Build & Test RelWithDebInfo with clang_asan (Unity Build: ON, Variant: game) (push) Successful in 1m43s

This commit is contained in:
2026-09-13 17:55:54 +02:00
parent df985d0397
commit 2fcb2fd64c
7 changed files with 529 additions and 2 deletions
@@ -32,6 +32,13 @@ GraphicsContext::GraphicsContext(const GraphicsContextCreateInfo& p_createInfo)
m_device != nullptr,
"No graphics device registered for GraphicsAPI '{}'",
EnumNameGraphicsAPI(p_createInfo.m_api));
const SwapchainCreateInfo swapchainCreateInfo {m_device.get(), m_surface.get(), p_createInfo.m_extent};
m_swapchain = g_graphicsSwapchainFactory->Create(p_createInfo.m_api, swapchainCreateInfo);
CRITICAL_ASSERT(GraphicsCoreAssertHandler,
m_swapchain != nullptr,
"No graphics swapchain registered for GraphicsAPI '{}'",
EnumNameGraphicsAPI(p_createInfo.m_api));
}
/****************************************************************************************/
@@ -54,4 +61,11 @@ GraphicsDevice& GraphicsContext::GetDevice() const
{
return *m_device;
}
/****************************************************************************************/
GraphicsSwapchain& GraphicsContext::GetSwapchain() const
{
return *m_swapchain;
}
} // namespace Bigfoot
@@ -9,6 +9,7 @@
#include <GraphicsInterface/GraphicsDevice.hpp>
#include <GraphicsInterface/GraphicsInstance.hpp>
#include <GraphicsInterface/GraphicsSurface.hpp>
#include <GraphicsInterface/GraphicsSwapchain.hpp>
#include <Utils/Version.hpp>
@@ -16,6 +17,7 @@
#include <EASTL/string_view.h>
#include <EASTL/unique_ptr.h>
#include <glm/glm.hpp>
namespace Bigfoot
{
@@ -45,6 +47,11 @@ struct GraphicsContextCreateInfo
* Pick a specific physical device by name.
*/
eastl::string_view m_deviceNameHint;
/**
* The desired size of the swapchain images.
*/
glm::uvec2 m_extent = {};
};
class GraphicsContext
@@ -87,6 +94,14 @@ class GraphicsContext
[[nodiscard]]
GraphicsDevice& GetDevice() const;
/**
* Get the graphics swapchain
*
* \return The graphics swapchain
*/
[[nodiscard]]
GraphicsSwapchain& GetSwapchain() const;
private:
/**
* The graphics instance.
@@ -102,6 +117,11 @@ class GraphicsContext
* The graphics device.
*/
eastl::unique_ptr<GraphicsDevice> m_device;
/**
* The graphics swapchain.
*/
eastl::unique_ptr<GraphicsSwapchain> m_swapchain;
};
} // namespace Bigfoot
@@ -0,0 +1,89 @@
/*********************************************************************
* \file GraphicsSwapchain.hpp
*
* \author Romain BOULLARD
* \date September 2026
*********************************************************************/
#ifndef BIGFOOT_GRAPHICSINTERFACE_GRAPHICSSWAPCHAIN_HPP
#define BIGFOOT_GRAPHICSINTERFACE_GRAPHICSSWAPCHAIN_HPP
#include <GraphicsInterface/GraphicsDevice.hpp>
#include <GraphicsInterface/GraphicsImage.hpp>
#include <GraphicsInterface/GraphicsInstance.hpp>
#include <GraphicsInterface/GraphicsSurface.hpp>
#include <Utils/Factory.hpp>
#include <Utils/LazyGlobal.hpp>
#include <GraphicsInterface/GraphicsAPI_generated.hpp>
#include <EASTL/functional.h>
#include <EASTL/unique_ptr.h>
#include <glm/glm.hpp>
#include <cstdint>
namespace Bigfoot
{
struct SwapchainCreateInfo
{
/**
* The device to create the swapchain from. Must be able to present to m_surface.
*/
const GraphicsDevice* m_device = nullptr;
/**
* The surface the swapchain will present to.
*/
const GraphicsSurface* m_surface = nullptr;
/**
* The desired size of the swapchain images.
*/
glm::uvec2 m_extent = {};
};
class GraphicsSwapchain
{
public:
virtual ~GraphicsSwapchain() = default;
/**
* Get the number of images owned by the swapchain
*
* \return The image count
*/
[[nodiscard]]
virtual std::uint32_t GetImageCount() const = 0;
/**
* Get one of the images owned by the swapchain
*
* \param p_index The index of the image to get.
* \return The image
*/
[[nodiscard]]
virtual const GraphicsImage& GetImage(std::uint32_t p_index) const = 0;
GraphicsSwapchain(const GraphicsSwapchain& p_swapchain) = delete;
GraphicsSwapchain(GraphicsSwapchain&& p_swapchain) = delete;
GraphicsSwapchain& operator=(const GraphicsSwapchain& p_swapchain) = delete;
GraphicsSwapchain& operator=(GraphicsSwapchain&& p_swapchain) = delete;
protected:
GraphicsSwapchain() = default;
};
/****************************************************************************************/
using GraphicsSwapchainFactory =
Factory<GraphicsSwapchain,
GraphicsAPI,
eastl::function<eastl::unique_ptr<GraphicsSwapchain>(const SwapchainCreateInfo&)>>;
/****************************************************************************************/
inline LazyGlobal<GraphicsSwapchainFactory> g_graphicsSwapchainFactory;
} // namespace Bigfoot
#endif
@@ -0,0 +1,88 @@
/*********************************************************************
* \file VulkanSwapchain.hpp
*
* \author Romain BOULLARD
* \date September 2026
*********************************************************************/
#ifndef BIGFOOT_GRAPHICSVULKAN_VULKANSWAPCHAIN_HPP
#define BIGFOOT_GRAPHICSVULKAN_VULKANSWAPCHAIN_HPP
#include <GraphicsVulkan/VulkanDevice.hpp>
#include <GraphicsVulkan/VulkanImage.hpp>
#include <GraphicsVulkan/VulkanInclude.hpp>
#include <GraphicsVulkan/VulkanSurface.hpp>
#include <GraphicsInterface/GraphicsSwapchain.hpp>
#include <EASTL/span.h>
#include <EASTL/vector.h>
#include <cstdint>
namespace Bigfoot::Vulkan
{
class VulkanSwapchain final: public GraphicsSwapchain
{
public:
/**
* Constructor.
*
* \param p_device The device to create the swapchain from.
* \param p_surface The surface the swapchain will present to.
* \param p_extent The desired size of the swapchain images.
*/
VulkanSwapchain(const VulkanDevice& p_device, const VulkanSurface& p_surface, glm::uvec2 p_extent);
~VulkanSwapchain() override;
/**
* Get the number of images owned by the swapchain
*
* \return The image count
*/
[[nodiscard]]
std::uint32_t GetImageCount() const override;
/**
* Get one of the images owned by the swapchain
*
* \param p_index The index of the image to get. Must be less than GetImageCount()
* \return The image
*/
[[nodiscard]]
const GraphicsImage& GetImage(std::uint32_t p_index) const override;
/**
* Get the underlying Vulkan swapchain handle
*
* \return The vk::SwapchainKHR handle
*/
[[nodiscard]]
vk::SwapchainKHR GetHandle() const;
/**
* Get the swapchain images
*
* \return The images owned by the swapchain
*/
[[nodiscard]]
eastl::span<const VulkanImage> GetImages() const;
private:
/**
* The device the swapchain was created from
*/
const VulkanDevice& m_device;
/**
* The swapchain
*/
vk::SwapchainKHR m_swapchain;
/**
* The images owned by the swapchain
*/
eastl::vector<VulkanImage> m_images;
};
} // namespace Bigfoot::Vulkan
#endif
@@ -0,0 +1,235 @@
/*********************************************************************
* \file VulkanSwapchain.cpp
*
* \author Romain BOULLARD
* \date September 2026
*********************************************************************/
#include <GraphicsVulkan/VulkanSwapchain.hpp>
#include <GraphicsVulkan/GraphicsVulkanAssertHandler.hpp>
#include <GraphicsVulkan/VulkanFormatters.hpp>
#include <GraphicsVulkan/VulkanPixelFormat.hpp>
#include <GraphicsVulkan/VulkanQueue.hpp>
#include <EASTL/algorithm.h>
#include <EASTL/vector.h>
#include <cstdint>
#include <limits>
#include <span>
#include <vector>
namespace Bigfoot::Vulkan
{
[[nodiscard]]
vk::SurfaceFormatKHR SelectSurfaceFormat(const std::span<const vk::SurfaceFormatKHR> p_formats)
{
const auto it = eastl::find_if(p_formats.begin(),
p_formats.end(),
[](const vk::SurfaceFormatKHR& p_format)
{
return p_format.format == vk::Format::eB8G8R8A8Srgb &&
p_format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear;
});
return it != p_formats.end() ? *it : p_formats.front();
}
/****************************************************************************************/
[[nodiscard]]
vk::PresentModeKHR SelectPresentMode(const std::span<const vk::PresentModeKHR> p_presentModes)
{
const auto it = eastl::find(p_presentModes.begin(), p_presentModes.end(), vk::PresentModeKHR::eMailbox);
// eFifo is guaranteed by the spec to always be supported
return it != p_presentModes.end() ? *it : vk::PresentModeKHR::eFifo;
}
/****************************************************************************************/
[[nodiscard]]
vk::Extent2D SelectExtent(const vk::SurfaceCapabilitiesKHR& p_capabilities, const glm::uvec2 p_extent)
{
constexpr std::uint32_t undefinedExtent = std::numeric_limits<std::uint32_t>::max();
if (p_capabilities.currentExtent.width != undefinedExtent)
{
return p_capabilities.currentExtent;
}
return {eastl::clamp(p_extent.x, p_capabilities.minImageExtent.width, p_capabilities.maxImageExtent.width),
eastl::clamp(p_extent.y, p_capabilities.minImageExtent.height, p_capabilities.maxImageExtent.height)};
}
/****************************************************************************************/
[[nodiscard]]
vk::CompositeAlphaFlagBitsKHR SelectCompositeAlpha(const vk::CompositeAlphaFlagsKHR p_supportedCompositeAlpha)
{
for (const vk::CompositeAlphaFlagBitsKHR candidate: {vk::CompositeAlphaFlagBitsKHR::eOpaque,
vk::CompositeAlphaFlagBitsKHR::ePreMultiplied,
vk::CompositeAlphaFlagBitsKHR::ePostMultiplied,
vk::CompositeAlphaFlagBitsKHR::eInherit})
{
if (p_supportedCompositeAlpha & candidate)
{
return candidate;
}
}
return vk::CompositeAlphaFlagBitsKHR::eOpaque;
}
/****************************************************************************************/
VulkanSwapchain::VulkanSwapchain(const VulkanDevice& p_device,
const VulkanSurface& p_surface,
const glm::uvec2 p_extent):
m_device(p_device)
{
const vk::PhysicalDevice physicalDevice = p_device.GetPhysicalDeviceHandle();
const vk::SurfaceKHR surface = p_surface.GetHandle();
const vk::ResultValue<vk::SurfaceCapabilitiesKHR> capabilities = physicalDevice.getSurfaceCapabilitiesKHR(surface);
CRITICAL_ASSERT(GraphicsVulkanAssertHandler,
capabilities.result == vk::Result::eSuccess,
"({}) Failed to get Vulkan surface capabilities",
capabilities.result);
const vk::ResultValue<std::vector<vk::SurfaceFormatKHR>> formats = physicalDevice.getSurfaceFormatsKHR(surface);
CRITICAL_ASSERT(GraphicsVulkanAssertHandler,
formats.result == vk::Result::eSuccess && !formats.value.empty(),
"({}) Failed to get Vulkan surface formats",
formats.result);
const vk::ResultValue<std::vector<vk::PresentModeKHR>> presentModes = physicalDevice.getSurfacePresentModesKHR(
surface);
CRITICAL_ASSERT(GraphicsVulkanAssertHandler,
presentModes.result == vk::Result::eSuccess && !presentModes.value.empty(),
"({}) Failed to get Vulkan surface present modes",
presentModes.result);
const vk::SurfaceFormatKHR surfaceFormat = SelectSurfaceFormat(formats.value);
const vk::PresentModeKHR presentMode = SelectPresentMode(presentModes.value);
const vk::Extent2D extent = SelectExtent(capabilities.value, p_extent);
std::uint32_t imageCount = capabilities.value.minImageCount + 1;
if (capabilities.value.maxImageCount > 0)
{
imageCount = eastl::min(imageCount, capabilities.value.maxImageCount);
}
const auto* const graphicsQueue = static_cast<const VulkanQueue*>(p_device.GetQueue(GraphicsQueueType::Graphics));
const auto* const presentQueue = static_cast<const VulkanQueue*>(p_device.GetQueue(GraphicsQueueType::Present));
CRITICAL_ASSERT(GraphicsVulkanAssertHandler,
graphicsQueue != nullptr && presentQueue != nullptr,
"Cannot create a swapchain on a device with no graphics or present queue");
const bool sameQueueFamily = graphicsQueue->GetFamilyIndex() == presentQueue->GetFamilyIndex();
eastl::vector<std::uint32_t> queueFamilyIndices;
if (!sameQueueFamily)
{
queueFamilyIndices = {graphicsQueue->GetFamilyIndex(), presentQueue->GetFamilyIndex()};
}
const vk::SwapchainCreateInfoKHR swapchainCreateInfo {
{},
surface,
imageCount,
surfaceFormat.format,
surfaceFormat.colorSpace,
extent,
1,
vk::ImageUsageFlagBits::eColorAttachment,
sameQueueFamily ? vk::SharingMode::eExclusive : vk::SharingMode::eConcurrent,
queueFamilyIndices,
capabilities.value.currentTransform,
SelectCompositeAlpha(capabilities.value.supportedCompositeAlpha),
presentMode,
VK_TRUE};
const vk::ResultValue<vk::SwapchainKHR> swapchain = p_device.GetHandle().createSwapchainKHR(swapchainCreateInfo);
CRITICAL_ASSERT(GraphicsVulkanAssertHandler,
swapchain.result == vk::Result::eSuccess,
"({}) Failed to create Vulkan swapchain",
swapchain.result);
m_swapchain = swapchain.value;
const vk::ResultValue<std::vector<vk::Image>> images = p_device.GetHandle().getSwapchainImagesKHR(m_swapchain);
CRITICAL_ASSERT(GraphicsVulkanAssertHandler,
images.result == vk::Result::eSuccess,
"({}) Failed to get Vulkan swapchain images",
images.result);
const PixelFormat imageFormat = FromVulkanFormat(surfaceFormat.format);
m_images.reserve(images.value.size());
for (const vk::Image image: images.value)
{
m_images.emplace_back(image, imageFormat, extent);
}
}
/****************************************************************************************/
VulkanSwapchain::~VulkanSwapchain()
{
m_device.GetHandle().destroySwapchainKHR(m_swapchain);
}
/****************************************************************************************/
std::uint32_t VulkanSwapchain::GetImageCount() const
{
return static_cast<std::uint32_t>(m_images.size());
}
/****************************************************************************************/
const GraphicsImage& VulkanSwapchain::GetImage(const std::uint32_t p_index) const
{
CRITICAL_ASSERT(GraphicsVulkanAssertHandler,
p_index < m_images.size(),
"Swapchain image index {} out of range (swapchain has {} images)",
p_index,
m_images.size());
return m_images[p_index];
}
/****************************************************************************************/
vk::SwapchainKHR VulkanSwapchain::GetHandle() const
{
return m_swapchain;
}
/****************************************************************************************/
eastl::span<const VulkanImage> VulkanSwapchain::GetImages() const
{
return {m_images.data(), m_images.size()};
}
/****************************************************************************************/
[[nodiscard]]
eastl::unique_ptr<GraphicsSwapchain> CreateVulkanSwapchain(const SwapchainCreateInfo& p_createInfo)
{
return eastl::make_unique<VulkanSwapchain>(static_cast<const VulkanDevice&>(*p_createInfo.m_device),
static_cast<const VulkanSurface&>(*p_createInfo.m_surface),
p_createInfo.m_extent);
}
/****************************************************************************************/
[[nodiscard]]
bool RegisterVulkanSwapchainFactory()
{
const bool inserted = g_graphicsSwapchainFactory->Register(GraphicsAPI::Vulkan, &CreateVulkanSwapchain);
CRITICAL_ASSERT(GraphicsVulkanAssertHandler, inserted, "Failed to register the Vulkan graphics swapchain factory");
return inserted;
}
/****************************************************************************************/
const bool g_vulkanSwapchainFactoryRegistered = RegisterVulkanSwapchainFactory();
} // namespace Bigfoot::Vulkan
@@ -20,12 +20,16 @@ class GraphicsContextFixture: public ::testing::Test
{
SDL_Init(SDL_INIT_VIDEO);
m_window = SDL_CreateWindow("GraphicsContextTests", 64, 64, SDL_WINDOW_HIDDEN | SDL_WINDOW_VULKAN);
{
BIGFOOT_LSAN_ONLY(const __lsan::ScopedDisabler lsanDisabler;)
m_window = SDL_CreateWindow("GraphicsContextTests", 64, 64, SDL_WINDOW_HIDDEN | SDL_WINDOW_VULKAN);
}
const SDL_PropertiesID windowProperties = SDL_GetWindowProperties(m_window);
m_createInfo.m_applicationName = "GraphicsContextTests";
m_createInfo.m_applicationVersion = Version {1, 0, 0};
m_createInfo.m_extent = {64, 64};
#ifdef BIGFOOT_WINDOWS
m_createInfo.m_surfaceCreateInfo.m_windowHandle = static_cast<HWND>(
SDL_GetPointerProperty(windowProperties, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr));
@@ -50,7 +54,7 @@ class GraphicsContextFixture: public ::testing::Test
/****************************************************************************************/
TEST_F(GraphicsContextFixture, Constructor_ShouldCreateAValidInstanceSurfaceAndDevice)
TEST_F(GraphicsContextFixture, Constructor_ShouldCreateAValidContext)
{
const GraphicsContext context {m_createInfo};
@@ -58,5 +62,6 @@ TEST_F(GraphicsContextFixture, Constructor_ShouldCreateAValidInstanceSurfaceAndD
EXPECT_NE(context.GetDevice().GetQueue(GraphicsQueueType::Compute), nullptr);
EXPECT_NE(context.GetDevice().GetQueue(GraphicsQueueType::Transfer), nullptr);
EXPECT_NE(context.GetDevice().GetQueue(GraphicsQueueType::Present), nullptr);
EXPECT_GT(context.GetSwapchain().GetImageCount(), 0u);
}
} // namespace Bigfoot
@@ -0,0 +1,76 @@
/*********************************************************************
* \file VulkanSwapchain.cpp
*
* \author Romain BOULLARD
* \date September 2026
*********************************************************************/
#include <GraphicsVulkan/VulkanSwapchain.hpp>
#include <GraphicsVulkan/VulkanDevice.hpp>
#include <GraphicsVulkan/VulkanImage.hpp>
#include <GraphicsVulkan/VulkanInstance.hpp>
#include <GraphicsVulkan/VulkanSurface.hpp>
#include <Utils/TargetMacros.h>
#include <SDL3/SDL.h>
#include <gtest/gtest.h>
namespace Bigfoot::Vulkan
{
class VulkanSwapchainFixture: public ::testing::Test
{
protected:
void SetUp() override
{
SDL_Init(SDL_INIT_VIDEO);
{
BIGFOOT_LSAN_ONLY(const __lsan::ScopedDisabler lsanDisabler;)
m_window = SDL_CreateWindow("VulkanSwapchainTests", 64, 64, SDL_WINDOW_HIDDEN | SDL_WINDOW_VULKAN);
}
const SDL_PropertiesID windowProperties = SDL_GetWindowProperties(m_window);
#ifdef BIGFOOT_WINDOWS
m_surfaceCreateInfo.m_windowHandle = static_cast<HWND>(
SDL_GetPointerProperty(windowProperties, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr));
m_surfaceCreateInfo.m_windowInstance = GetModuleHandle(nullptr);
#elif defined BIGFOOT_LINUX
m_surfaceCreateInfo.m_display = static_cast<wl_display*>(
SDL_GetPointerProperty(windowProperties, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr));
m_surfaceCreateInfo.m_surface = static_cast<wl_surface*>(
SDL_GetPointerProperty(windowProperties, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr));
#endif
}
void TearDown() override
{
SDL_DestroyWindow(m_window);
SDL_Quit();
}
VulkanInstance m_instance {"VulkanSwapchainTests", Version {1, 0, 0}};
SDL_Window* m_window = nullptr;
SurfaceCreateInfo m_surfaceCreateInfo;
};
/****************************************************************************************/
TEST_F(VulkanSwapchainFixture, Constructor_ShouldCreateAValidSwapchainWithAtLeastOneImage)
{
const VulkanSurface surface {m_instance, m_surfaceCreateInfo};
const VulkanDevice device {m_instance, &surface, {}};
const VulkanSwapchain swapchain {device, surface, glm::uvec2 {64, 64}};
EXPECT_TRUE(swapchain.GetHandle());
ASSERT_FALSE(swapchain.GetImages().empty());
const VulkanImage& image = swapchain.GetImages().front();
EXPECT_TRUE(image.GetHandle());
EXPECT_NE(image.GetFormat(), PixelFormat::Undefined);
EXPECT_GT(image.GetExtent().x, 0u);
EXPECT_GT(image.GetExtent().y, 0u);
}
} // namespace Bigfoot::Vulkan