我在我的Vulkan应用程序中偶然发现了一个奇怪的行为(基于Qt,但它没关系)。
当我的Vulkan窗口调整大小时,我需要重新创建交换链。我使用vkGetPhysicalDeviceSurfaceCapabilitiesKHR()
来获取有关窗口表面的新信息(未重新创建,可能会或可能不会出现问题)并接收新的表面范围:currentExtent = { 46, 0 }
minImageExtent = { 1, 1 }
。换句话说,Vulkan希望我创建一个0高度的交换链,同时1x1是允许的最小值。
假设驱动程序知道它正在做什么,我继续进行,之后我得到一个调试报告,说明某些内部“vkCreateImage失败”,之后Vulkan崩溃只留下死亡和破坏。怎么处理?我应该将currentExtent限制为min和max吗?或者我错过了什么?
编辑:
正如评论所示,我正在复制粘贴部分代码。我注意到的是,由于某些原因,操作系统实际上在某个时刻将窗口大小调整为46x0。
void QtWindow::resizeEvent(QResizeEvent *event)
{
Q_UNUSED(event);
scheduleSwapchainRecreation();
}
void QtWindow::scheduleSwapchainRecreation()
{
std::lock_guard<std::mutex> lock{mSwapchainRecreationMutex};
if (mSwapchainRecreationScheduled)
return;
mSwapchainRecreationScheduled = true;
QMetaObject::invokeMethod(this, "recreateSwapchain", Qt::QueuedConnection);
}
void QtWindow::recreateSwapchain()
{
std::lock_guard<std::mutex> lock{mStateMutex};
checkVkResult(vkDeviceWaitIdle(mDevice));
createSwapchain();
// state mutex should also cover swapchain recreation
mSwapchainRecreationScheduled = false;
}
void QtWindow::createSwapchain()
{
mSwapchain = makeUnique<VulkanSwapchain>(mMemoryManager,
width(),
height(),
mInstance,
mPhysicalDevice,
mDevice,
mSurface,
mPresentationQueueFamily,
mPresentationQueue,
mMemoryManager,
mSwapchain.get());
const auto min = mSwapchain->getMinExtent();
const auto max = mSwapchain->getMaxExtent();
setMinimumSize({ static_cast<int>(min.width), static_cast<int>(min.height) });
setMaximumSize({ static_cast<int>(max.width), static_cast<int>(max.height) });
}
交换链类:
VulkanSwapchain::VulkanSwapchain(uint32_t width,
uint32_t height,
VkInstance instance,
VkPhysicalDevice physicalDevice,
const VulkanDevice &device,
VkSurfaceKHR surface,
uint32_t presentationQueueFamily,
VkQueue presentationQueue,
const MemoryManagerPtr &memoryManager,
VulkanSwapchain *oldSwapchain)
: mInstance{instance}
, mDevice{device}
, mSurface{surface}
, mPresentQueue{presentationQueue}
, mDetails{{},{{}, memoryManager},{{}, memoryManager}}
, mSwapchainImages{memoryManager}
, mSwapchainImageViews{memoryManager}
, mMainCommandBuffers{memoryManager}
{
createSwapchain(width, height, physicalDevice, presentationQueueFamily, memoryManager, oldSwapchain);
createSwapchainImageViews();
createSemaphores();
createMainCommandPool();
}
VulkanSwapchain::~VulkanSwapchain()
{
if (mMainCommandPool != VK_NULL_HANDLE)
vkDestroyCommandPool(mDevice, mMainCommandPool, nullptr);
vkDestroySemaphore(mDevice, mAcquireSemaphore, nullptr);
for (const auto view : mSwapchainImageViews)
vkDestroyImageView(mDevice, view, nullptr);
if (mSwapchain != VK_NULL_HANDLE)
vkDestroySwapchainKHR(mDevice, mSwapchain, nullptr);
if (mSurface != VK_NULL_HANDLE)
vkDestroySurfaceKHR(mInstance, mSurface, nullptr);
}
VkResult VulkanSwapchain::acquire() noexcept
{
return vkAcquireNextImageKHR(mDevice, mSwapchain, UINT64_MAX, mAcquireSemaphore, VK_NULL_HANDLE, &mAcquiredImageIndex);
}
VkResult VulkanSwapchain::present(VkSemaphore renderingFinishedSemaphore) noexcept
{
VkPresentInfoKHR presentInfo = {};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &renderingFinishedSemaphore;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &mSwapchain;
presentInfo.pImageIndices = &mAcquiredImageIndex;
return vkQueuePresentKHR(mPresentQueue, &presentInfo);
}
void VulkanSwapchain::createSwapchain(uint32_t width,
uint32_t height,
VkPhysicalDevice physicalDevice,
uint32_t presentationQueueFamily,
const MemoryManagerPtr &memoryManager,
VulkanSwapchain *oldSwapchain)
{
GE_LOG << L"Creating swapchain..." << std::endl;
querySwapchainDetails(physicalDevice);
chooseSwapchainSurfaceFormat();
const auto chosenPresentMode = chooseSwapchainPresentMode();
chooseSwapchainExtents(width, height);
// triple or double buffering
auto imageCount = (chosenPresentMode == VK_PRESENT_MODE_MAILBOX_KHR) ? (3u) : (2u);
if (imageCount < mDetails.mCapabilities.minImageCount)
imageCount = mDetails.mCapabilities.minImageCount;
else if (mDetails.mCapabilities.maxImageCount > 0 && imageCount > mDetails.mCapabilities.maxImageCount)
imageCount = mDetails.mCapabilities.maxImageCount;
GE_LOG << L"Chosen surface format: " << mSwapchainFormat.format << L' ' << mSwapchainFormat.colorSpace << std::endl;
GE_LOG << L"Chosen present mode: " << chosenPresentMode << std::endl;
GE_LOG << L"Chosen extents: " << mExtent.width << L'x' << mExtent.height << std::endl;
GE_LOG << L"Image count: " << imageCount << std::endl;
VkSwapchainCreateInfoKHR createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = mSurface;
createInfo.minImageCount = imageCount;
createInfo.imageFormat = mSwapchainFormat.format;
createInfo.imageColorSpace = mSwapchainFormat.colorSpace;
createInfo.imageExtent = mExtent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
createInfo.preTransform = mDetails.mCapabilities.currentTransform;
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createInfo.presentMode = chosenPresentMode;
createInfo.clipped = VK_TRUE;
if (oldSwapchain != nullptr)
createInfo.oldSwapchain = *oldSwapchain;
const auto graphicsQueueFamily = mDevice.getGraphicsQueueFamily();
if (graphicsQueueFamily != presentationQueueFamily)
{
uint32_t queueFamilyIndices[] = { graphicsQueueFamily, presentationQueueFamily };
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
createInfo.queueFamilyIndexCount = 2;
createInfo.pQueueFamilyIndices = queueFamilyIndices;
}
else
{
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
}
checkVkResult(vkCreateSwapchainKHR(mDevice, &createInfo, nullptr, &mSwapchain));
uint32_t swapchainImageCount = 0;
checkVkResult(vkGetSwapchainImagesKHR(mDevice, mSwapchain, &swapchainImageCount, nullptr));
mSwapchainImages.resize(swapchainImageCount);
checkVkResult(vkGetSwapchainImagesKHR(mDevice, mSwapchain, &swapchainImageCount, mSwapchainImages.data()));
}
void VulkanSwapchain::querySwapchainDetails(VkPhysicalDevice physicalDevice)
{
checkVkResult(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, mSurface, &mDetails.mCapabilities));
uint32_t numSurfaceFormats = 0;
checkVkResult(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, mSurface, &numSurfaceFormats, nullptr));
if (numSurfaceFormats == 0)
BOOST_THROW_EXCEPTION(RenderingModule::Exception{"Cannot find surface formats."});
mDetails.mSurfaceFormats.resize(numSurfaceFormats);
checkVkResult(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, mSurface, &numSurfaceFormats, mDetails.mSurfaceFormats.data()));
uint32_t numPresentModes = 0;
checkVkResult(vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, mSurface, &numPresentModes, nullptr));
if (numPresentModes == 0)
BOOST_THROW_EXCEPTION(RenderingModule::Exception{"Cannot find presentation modes."});
mDetails.mPresentModes.resize(numPresentModes);
checkVkResult(vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, mSurface, &numPresentModes, mDetails.mPresentModes.data()));
}
void VulkanSwapchain::chooseSwapchainExtents(uint32_t width, uint32_t height)
{
if (mDetails.mCapabilities.currentExtent.width != std::numeric_limits<uint32_t>::max())
mExtent.width = mDetails.mCapabilities.currentExtent.width;
else
mExtent.width = std::min(std::max(mDetails.mCapabilities.minImageExtent.width, width), mDetails.mCapabilities.maxImageExtent.width);
if (mDetails.mCapabilities.currentExtent.height != std::numeric_limits<uint32_t>::max())
mExtent.height = mDetails.mCapabilities.currentExtent.height;
else
mExtent.height = std::min(std::max(mDetails.mCapabilities.minImageExtent.height, height), mDetails.mCapabilities.maxImageExtent.height);
}
void VulkanSwapchain::chooseSwapchainSurfaceFormat()
{
VkSurfaceFormatKHR desiredSurfaceFormat = { VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR };
if (mDetails.mSurfaceFormats.size() == 1 && mDetails.mSurfaceFormats[0].format == VK_FORMAT_UNDEFINED)
{
mSwapchainFormat = desiredSurfaceFormat;
}
else
{
auto found = false;
for (const auto &format : mDetails.mSurfaceFormats)
{
if (format.format == desiredSurfaceFormat.format && format.colorSpace == desiredSurfaceFormat.colorSpace)
{
mSwapchainFormat = format;
found = true;
break;
}
}
if (!found)
{
// try without color space
for (const auto &format : mDetails.mSurfaceFormats)
{
if (format.format == desiredSurfaceFormat.format)
{
mSwapchainFormat = format;
found = true;
break;
}
}
if (!found)
mSwapchainFormat = mDetails.mSurfaceFormats.front();
}
}
}
void VulkanSwapchain::createSwapchainImageViews()
{
mSwapchainImageViews.resize(mSwapchainImages.size());
VkImageViewCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
createInfo.format = mSwapchainFormat.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.levelCount = 1;
createInfo.subresourceRange.layerCount = 1;
for (auto i = 0u; i < mSwapchainImages.size(); ++i)
{
createInfo.image = mSwapchainImages[i];
checkVkResult(vkCreateImageView(mDevice, &createInfo, nullptr, &mSwapchainImageViews[i]));
}
}
void VulkanSwapchain::createSemaphores()
{
VkSemaphoreCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
checkVkResult(vkCreateSemaphore(mDevice, &createInfo, nullptr, &mAcquireSemaphore));
}
void VulkanSwapchain::createMainCommandPool()
{
VkCommandPoolCreateInfo createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
createInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
createInfo.queueFamilyIndex = mDevice.getGraphicsQueueFamily();
checkVkResult(vkCreateCommandPool(mDevice, &createInfo, nullptr, &mMainCommandPool));
mMainCommandBuffers.resize(mSwapchainImageViews.size());
VkCommandBufferAllocateInfo allocateInfo = {};
allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocateInfo.commandPool = mMainCommandPool;
allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocateInfo.commandBufferCount = static_cast<uint32_t>(mMainCommandBuffers.size());
checkVkResult(vkAllocateCommandBuffers(mDevice, &allocateInfo, mMainCommandBuffers.data()));
}
VkPresentModeKHR VulkanSwapchain::chooseSwapchainPresentMode() const
{
VkPresentModeKHR chosenPresentMode = VK_PRESENT_MODE_FIFO_KHR;
QSettings settings;
if (!settings.value(swapIntervalSettingName, true).toBool())
{
for (const auto mode : mDetails.mPresentModes)
{
if (mode == VK_PRESENT_MODE_IMMEDIATE_KHR)
{
chosenPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
break;
}
}
}
#if (defined(Q_OS_WIN) && !defined(Q_OS_WINPHONE)) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
else if (settings.value(tripleBufferingSettingName, true).toBool())
{
// reduce latency on desktops, but preserve battery life on mobile
for (const auto mode : mDetails.mPresentModes)
{
if (mode == VK_PRESENT_MODE_MAILBOX_KHR)
{
chosenPresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
break;
}
}
}
#endif
return chosenPresentMode;
}