325 lines
11 KiB
C++
325 lines
11 KiB
C++
|
|
#include "iwa/swapchain.hpp"
|
|
|
|
#include <mijin/debug/assert.hpp>
|
|
#include <SDL_vulkan.h>
|
|
#include <vulkan/vulkan_to_string.hpp>
|
|
#include "iwa/device.hpp"
|
|
#include "iwa/log.hpp"
|
|
|
|
namespace iwa
|
|
{
|
|
namespace
|
|
{
|
|
std::pair<vk::PresentModeKHR, std::uint32_t> detectPresentMode(const Device& device, const Window& window, const vk::SurfaceCapabilitiesKHR& surfaceCapabilities)
|
|
{
|
|
std::vector<vk::PresentModeKHR> presentModes = device.getVkPhysicalDevice().getSurfacePresentModesKHR(window.getVkSurface());
|
|
|
|
vk::PresentModeKHR presentMode = vk::PresentModeKHR::eImmediate;
|
|
std::uint32_t imageCount = 2;
|
|
if (std::ranges::find(presentModes, vk::PresentModeKHR::eMailbox) != presentModes.end())
|
|
{
|
|
presentMode = vk::PresentModeKHR::eMailbox;
|
|
imageCount = 3;
|
|
}
|
|
imageCount = std::max(imageCount, surfaceCapabilities.minImageCount);
|
|
if (surfaceCapabilities.maxImageCount > 0)
|
|
{
|
|
imageCount = std::min(imageCount, surfaceCapabilities.maxImageCount);
|
|
}
|
|
return std::make_pair(presentMode, imageCount);
|
|
}
|
|
|
|
vk::SurfaceFormatKHR detectImageFormat(const Device& device, const Window& window, const vk::ImageUsageFlags imageUsage)
|
|
{
|
|
std::vector<vk::SurfaceFormatKHR> surfaceFormats = device.getVkPhysicalDevice().getSurfaceFormatsKHR(window.getVkSurface());
|
|
|
|
int resultIdx = -1;
|
|
for (std::size_t idx = 0; idx < surfaceFormats.size(); ++idx)
|
|
{
|
|
const vk::SurfaceFormatKHR surfaceFormat = surfaceFormats[idx];
|
|
vk::ImageFormatProperties formatProperties;
|
|
|
|
try
|
|
{
|
|
formatProperties = device.getVkPhysicalDevice().getImageFormatProperties(
|
|
/* format = */ surfaceFormat.format,
|
|
/* type = */ vk::ImageType::e2D,
|
|
/* tiling = */ vk::ImageTiling::eOptimal,
|
|
/* usage = */ imageUsage
|
|
);
|
|
}
|
|
catch(vk::FormatNotSupportedError&)
|
|
{
|
|
continue; // not supported
|
|
}
|
|
|
|
if (resultIdx < 0)
|
|
{
|
|
resultIdx = static_cast<int>(idx);
|
|
}
|
|
else if (surfaceFormat.format == vk::Format::eB8G8R8A8Srgb
|
|
&& surfaceFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear)
|
|
{
|
|
resultIdx = static_cast<int>(idx);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (resultIdx < 0)
|
|
{
|
|
logAndDie("Error creating Vulkan swapchain, no compatible image format found.");
|
|
}
|
|
|
|
return surfaceFormats[resultIdx];
|
|
}
|
|
|
|
vk::Extent2D detectImageExtent(const Window& window, const vk::SurfaceCapabilitiesKHR& surfaceCapabilities)
|
|
{
|
|
// default to current extent (chosen by the driver)
|
|
vk::Extent2D extent = surfaceCapabilities.currentExtent;
|
|
|
|
// alternatively ask SDL about it
|
|
if (extent.width == 0xFFFFFFFF && extent.height == 0xFFFFFFFF)
|
|
{
|
|
int width, height; // NOLINT(readability-isolate-declaration, cppcoreguidelines-init-variables)
|
|
SDL_Vulkan_GetDrawableSize(window.getSDLWindow(), &width, &height);
|
|
|
|
extent.width = std::clamp(static_cast<std::uint32_t>(width), surfaceCapabilities.minImageExtent.width, surfaceCapabilities.maxImageExtent.width);
|
|
extent.height = std::clamp(static_cast<std::uint32_t>(height), surfaceCapabilities.minImageExtent.height, surfaceCapabilities.maxImageExtent.height);
|
|
}
|
|
|
|
return extent;
|
|
}
|
|
} // namespace
|
|
|
|
Swapchain::Swapchain(ObjectPtr<Device> owner, SwapchainCreationArgs args)
|
|
: super_t(std::move(owner)), mWindow(std::move(args.window)), mImageUsage(args.imageUsage)
|
|
{
|
|
MIJIN_ASSERT(mWindow, "Invalid SwapchainCreationArgs: window cannot be NULL.");
|
|
|
|
mImageAvailableSemaphores.resize(args.parallelFrames);
|
|
recreate();
|
|
}
|
|
Swapchain::~Swapchain() noexcept
|
|
{
|
|
IWA_DELETE_DEVICE_OBJECT(getOwner(), mHandle, destroySwapchainKHR)
|
|
}
|
|
|
|
mijin::Task<> Swapchain::c_present(const PresentArgs& args)
|
|
{
|
|
while (!mWindow->isVisible()) {
|
|
co_await mijin::c_suspend();
|
|
}
|
|
|
|
const vk::PresentInfoKHR presentInfo =
|
|
{
|
|
.waitSemaphoreCount = static_cast<std::uint32_t>(args.waitSemaphores.size()),
|
|
.pWaitSemaphores = args.waitSemaphores.data(),
|
|
.swapchainCount = 1,
|
|
.pSwapchains = &mHandle,
|
|
.pImageIndices = &mCurrentImageIdx
|
|
};
|
|
|
|
vk::Result result; // NOLINT(cppcoreguidelines-init-variables)
|
|
try {
|
|
result = args.queue.presentKHR(presentInfo);
|
|
} catch(vk::OutOfDateKHRError&) {
|
|
result = vk::Result::eErrorOutOfDateKHR;
|
|
}
|
|
mCurrentImageIdx = INVALID_IMAGE_INDEX;
|
|
|
|
// next image, please
|
|
mCurrentFrameIdx = (mCurrentFrameIdx + 1) % static_cast<int>(getNumParallelFrames());
|
|
if (result == vk::Result::eSuccess) {
|
|
co_await c_acquireImage();
|
|
}
|
|
else {
|
|
recreate();
|
|
}
|
|
}
|
|
|
|
void Swapchain::recreate()
|
|
{
|
|
const vk::SurfaceCapabilitiesKHR surfaceCapabilities = getOwner()->getVkPhysicalDevice().getSurfaceCapabilitiesKHR(mWindow->getVkSurface());
|
|
const bool firstCreate = !mHandle;
|
|
|
|
// doing this here seems to fix the device loss during recreation
|
|
getOwner()->getVkHandle().waitIdle();
|
|
|
|
vk::SwapchainCreateInfoKHR createInfo;
|
|
|
|
// surface
|
|
createInfo.surface = mWindow->getVkSurface();
|
|
|
|
// old swapchain
|
|
createInfo.oldSwapchain = mHandle;
|
|
|
|
// some defaults
|
|
createInfo.imageSharingMode = vk::SharingMode::eExclusive;
|
|
createInfo.clipped = VK_TRUE;
|
|
createInfo.preTransform = surfaceCapabilities.currentTransform;
|
|
|
|
// queue families
|
|
createInfo.queueFamilyIndexCount = 1;
|
|
createInfo.pQueueFamilyIndices = &getOwner()->getDeviceInfo().graphicsQueueFamily;
|
|
|
|
// present mode and image count
|
|
std::tie(createInfo.presentMode, createInfo.minImageCount) = detectPresentMode(*getOwner(), *mWindow, surfaceCapabilities);
|
|
|
|
// image usage
|
|
createInfo.imageUsage = mImageUsage;
|
|
MIJIN_ASSERT((surfaceCapabilities.supportedUsageFlags & createInfo.imageUsage) == createInfo.imageUsage, "Invalid image usage flags when creating surface.");
|
|
|
|
// image format and color space
|
|
const vk::SurfaceFormatKHR surfaceFormat = detectImageFormat(*getOwner(), *mWindow, createInfo.imageUsage);
|
|
createInfo.imageFormat = surfaceFormat.format;
|
|
createInfo.imageColorSpace = surfaceFormat.colorSpace;
|
|
|
|
// image extent
|
|
createInfo.imageExtent = detectImageExtent(*mWindow, surfaceCapabilities);
|
|
createInfo.imageArrayLayers = 1;
|
|
|
|
// composite alpha
|
|
createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque;
|
|
MIJIN_ASSERT((surfaceCapabilities.supportedCompositeAlpha & createInfo.compositeAlpha) == createInfo.compositeAlpha, "Invalid composite alpha when creating surface.");
|
|
|
|
mHandle = getOwner()->getVkHandle().createSwapchainKHR(createInfo);
|
|
mFormat = createInfo.imageFormat;
|
|
mExtent = createInfo.imageExtent;
|
|
|
|
//for (ObjectPtr<Image>& image : mImages)
|
|
//{
|
|
// image->unwrap();
|
|
// unregisterImage(&image);
|
|
//}
|
|
mImages.clear();
|
|
|
|
// retrieve swapchain images
|
|
const std::vector<vk::Image> imageHandles = getOwner()->getVkHandle().getSwapchainImagesKHR(mHandle);
|
|
mImages.reserve(imageHandles.size());
|
|
|
|
// and create image views
|
|
// destroyImageViews();
|
|
// imageViews.reserve(imageHandles.size());
|
|
|
|
for (const vk::Image image : imageHandles)
|
|
{
|
|
mImages.emplace_back(getOwner()->createChild<Image>(ImageWrapArgs{
|
|
.handle = image,
|
|
.type = vk::ImageType::e2D,
|
|
.format = createInfo.imageFormat,
|
|
.usage = createInfo.imageUsage,
|
|
.size = {
|
|
createInfo.imageExtent.width,
|
|
createInfo.imageExtent.height,
|
|
1
|
|
}
|
|
}));
|
|
|
|
|
|
// const ImageViewCreateInfo ivCreateInfo =
|
|
// {
|
|
// .nativeImage = image,
|
|
// .viewType = vk::ImageViewType::e2D,
|
|
// .format = createInfo.imageFormat,
|
|
// .components =
|
|
// {
|
|
// .r = vk::ComponentSwizzle::eIdentity,
|
|
// .g = vk::ComponentSwizzle::eIdentity,
|
|
// .b = vk::ComponentSwizzle::eIdentity,
|
|
// .a = vk::ComponentSwizzle::eIdentity
|
|
// },
|
|
// .subresourceRange =
|
|
// {
|
|
// .aspectMask = vk::ImageAspectFlagBits::eColor,
|
|
// .baseMipLevel = 0,
|
|
// .levelCount = 1,
|
|
// .baseArrayLayer = 0,
|
|
// .layerCount = 1
|
|
// }
|
|
// };
|
|
// imageViews.emplace_back().create(ivCreateInfo);
|
|
|
|
// images.back().setDebugName(fmt::format("Swapchain Image #{}", images.size() - 1));
|
|
// registerImage(&images.back());
|
|
}
|
|
|
|
// create "image available" semaphores
|
|
for (ObjectPtr<Semaphore>& semaphore : mImageAvailableSemaphores)
|
|
{
|
|
semaphore = getOwner()->createChild<Semaphore>();
|
|
}
|
|
|
|
if (!firstCreate)
|
|
{
|
|
getOwner()->getVkHandle().destroySwapchainKHR(createInfo.oldSwapchain);
|
|
}
|
|
|
|
logMsg("Swapchain (re)created:");
|
|
logMsg(" Extent: {}x{}", mExtent.width, mExtent.height);
|
|
logMsg(" Image Usage: {}", vk::to_string(createInfo.imageUsage));
|
|
logMsg(" Image count: {}", mImages.size());
|
|
logMsg(" Format: {}", vk::to_string(mFormat));
|
|
logMsg(" Color Space: {}", vk::to_string(createInfo.imageColorSpace));
|
|
|
|
// update size
|
|
// imageSize.width = createInfo.imageExtent.width;
|
|
// imageSize.height = createInfo.imageExtent.height;
|
|
// imageFormat = createInfo.imageFormat;
|
|
|
|
// already request the first frame
|
|
acquireImage();
|
|
|
|
recreated.emit();
|
|
}
|
|
|
|
void Swapchain::acquireImage()
|
|
{
|
|
MIJIN_ASSERT(mCurrentImageIdx == INVALID_IMAGE_INDEX, "Attempting to acquire a new image before presenting.");
|
|
|
|
try
|
|
{
|
|
const vk::ResultValue<std::uint32_t> result = getOwner()->getVkHandle().acquireNextImageKHR(mHandle, std::numeric_limits<std::uint64_t>::max(), *mImageAvailableSemaphores[mCurrentFrameIdx]);
|
|
if (result.result != vk::Result::eSuccess)
|
|
{
|
|
assert(result.result == vk::Result::eSuboptimalKHR);
|
|
recreate();
|
|
return;
|
|
}
|
|
mCurrentImageIdx = result.value;
|
|
mImages[mCurrentImageIdx]->resetUsage(ResetLayout::YES);
|
|
}
|
|
catch(vk::OutOfDateKHRError&) {
|
|
recreate();
|
|
}
|
|
}
|
|
|
|
mijin::Task<> Swapchain::c_acquireImage()
|
|
{
|
|
try
|
|
{
|
|
vk::ResultValue<std::uint32_t> result = vk::ResultValue<std::uint32_t>(vk::Result::eTimeout, 0);
|
|
while(true)
|
|
{
|
|
result = getOwner()->getVkHandle().acquireNextImageKHR(mHandle, 0, *mImageAvailableSemaphores[mCurrentFrameIdx]);
|
|
if (result.result != vk::Result::eTimeout && result.result != vk::Result::eNotReady) {
|
|
break;
|
|
}
|
|
co_await mijin::c_suspend();
|
|
}
|
|
if (result.result != vk::Result::eSuccess)
|
|
{
|
|
MIJIN_ASSERT(result.result == vk::Result::eSuboptimalKHR, "Error acquiring swapchain image.");
|
|
recreate();
|
|
co_return;
|
|
}
|
|
mCurrentImageIdx = result.value;
|
|
mImages[mCurrentImageIdx]->resetUsage(ResetLayout::YES);
|
|
}
|
|
catch(vk::OutOfDateKHRError&) {
|
|
recreate();
|
|
}
|
|
}
|
|
}
|