[英](Vulkan) vkAcquireNextImageKHR takes so long when VK_PRESENT_MODE_FIFO_KHR choosen for present mode
[英]Why I got white blinking while resizing resizing the images of the swapchain with VK_PRESENT_MODE_FIFO_KHR present mode?
调整交换链的图像大小时(因为 window 大小发生了变化)我得到了一些白色的闪烁。 我真的不明白为什么是这个问题的根源。 我只有在将 VK_PRESENT_MODE_FIFO_KHR 呈现模式与集成的 Intel(R) UHD Graphics 630 GPU 一起使用时才会遇到这个问题,我在 GeForce GTX 1050 上没有这个问题。我发现根据 Z52F9EC21735243AD9917CDA3CA077D32 有不同的行为.
也许我试图实现的理想解决方案是有一个交换链,它总是做屏幕的大小,如果可能的话,只对可见部分进行 blit?
这是我的交换链调整大小代码(远非最佳,因为我重做了一些可以避免的操作)。
bool resize_swapchain(VK_Renderer* renderer, Window* window) {
assert(renderer);
VkResult res;
clear_swapchain(renderer);
// Build the swapchain
// Get the list of VkFormats that are supported:
get_enumeration(vkGetPhysicalDeviceSurfaceFormatsKHR,
VkSurfaceFormatKHR,
surface_formats,
"Failed to get physical device surface formats.\n",
"Found %d surface formats.\n",
renderer->physical_device,
renderer->surface);
// If the format list includes just one entry of VK_FORMAT_UNDEFINED,
// the surface has no preferred format. Otherwise, at least one
// supported format will be returned.
if (surface_formats.size() == 1 && surface_formats[0].format == VK_FORMAT_UNDEFINED) {
renderer->surface_format = VK_FORMAT_B8G8R8A8_UNORM;
} else {
renderer->surface_format = surface_formats[0].format;
}
VkSurfaceCapabilitiesKHR surface_capabilities;
res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(renderer->physical_device, renderer->surface, &surface_capabilities);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to get physical device surface capabilities.\n");
clear_swapchain(renderer);
return false;
}
get_enumeration(vkGetPhysicalDeviceSurfacePresentModesKHR,
VkPresentModeKHR,
present_modes,
"Failed to get physical device surface present modes.\n",
"Found %d present modes.\n",
renderer->physical_device,
renderer->surface);
// width and height are either both 0xFFFFFFFF, or both not 0xFFFFFFFF.
if (surface_capabilities.currentExtent.width == 0xFFFFFFFF) {
// If the surface size is undefined, the size is set to
// the size of the images requested.
renderer->swapchain_extent.width = window->size.x;
renderer->swapchain_extent.height = window->size.y;
if (renderer->swapchain_extent.width < surface_capabilities.minImageExtent.width) {
renderer->swapchain_extent.width = surface_capabilities.minImageExtent.width;
} else if (renderer->swapchain_extent.width > surface_capabilities.maxImageExtent.width) {
renderer->swapchain_extent.width = surface_capabilities.maxImageExtent.width;
}
if (renderer->swapchain_extent.height < surface_capabilities.minImageExtent.height) {
renderer->swapchain_extent.height = surface_capabilities.minImageExtent.height;
} else if (renderer->swapchain_extent.height > surface_capabilities.maxImageExtent.height) {
renderer->swapchain_extent.height = surface_capabilities.maxImageExtent.height;
}
} else {
// If the surface size is defined, the swap chain size must match
renderer->swapchain_extent = surface_capabilities.currentExtent;
}
// The FIFO present mode is guaranteed by the spec to be supported
#if defined(FL_PROFILING_MODE)
VkPresentModeKHR swapchain_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
#else
VkPresentModeKHR swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR;
#endif
// Determine the number of VkImage's to use in the swap chain.
// We need to acquire only 1 presentable image at at time.
// Asking for minImageCount images ensures that we can acquire
// 1 presentable image as long as we present it before attempting
// to acquire another.
uint32_t desired_number_of_swapchain_images = surface_capabilities.minImageCount;
VkSurfaceTransformFlagBitsKHR surface_transform;
if (surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
surface_transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
} else {
surface_transform = surface_capabilities.currentTransform;
}
// Find a supported composite alpha mode - one of these is guaranteed to be set
VkCompositeAlphaFlagBitsKHR composite_alpha_flag = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
// TODO change the order if we want to be able to blend the window of our application with the Windows Desktop
VkCompositeAlphaFlagBitsKHR composite_alpha_flags[4] = {
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
};
for (uint32_t i = 0; i < sizeof(composite_alpha_flags); i++) {
if (surface_capabilities.supportedCompositeAlpha & composite_alpha_flags[i]) {
composite_alpha_flag = composite_alpha_flags[i];
break;
}
}
VkSwapchainCreateInfoKHR swapchain_info = {};
swapchain_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchain_info.pNext = nullptr;
swapchain_info.surface = renderer->surface;
swapchain_info.minImageCount = desired_number_of_swapchain_images;
swapchain_info.imageFormat = renderer->surface_format;
swapchain_info.imageExtent.width = renderer->swapchain_extent.width;
swapchain_info.imageExtent.height = renderer->swapchain_extent.height;
swapchain_info.preTransform = surface_transform;
swapchain_info.compositeAlpha = composite_alpha_flag;
swapchain_info.imageArrayLayers = 1;
swapchain_info.presentMode = swapchain_present_mode;
swapchain_info.oldSwapchain = nullptr;
swapchain_info.clipped = true;
swapchain_info.imageColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
swapchain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
swapchain_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapchain_info.queueFamilyIndexCount = 0;
swapchain_info.pQueueFamilyIndices = nullptr;
uint32_t queue_family_indices[2] = {(uint32_t)renderer->graphics_queue_family_index, (uint32_t)renderer->present_queue_family_index};
if (renderer->graphics_queue_family_index != renderer->present_queue_family_index) {
// If the graphics and present queues are from different queue families,
// we either have to explicitly transfer ownership of images between
// the queues, or we have to create the swapchain with imageSharingMode
// as VK_SHARING_MODE_CONCURRENT
swapchain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swapchain_info.queueFamilyIndexCount = 2;
swapchain_info.pQueueFamilyIndices = queue_family_indices;
// TODO @Speedup We may want optimize this by using VK_SHARING_MODE_EXCLUSIVE and be explicit about transfert ownership
}
res = vkCreateSwapchainKHR(renderer->device, &swapchain_info, nullptr, &renderer->swapchain);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to create the swapchain.\n");
clear_swapchain(renderer);
return false;
}
log(globals.logger, Log_Level::verbose, "Swapchain created with size (%d, %d).\n",
swapchain_info.imageExtent.width,
swapchain_info.imageExtent.height);
get_enumeration(vkGetSwapchainImagesKHR,
VkImage,
swapchain_images,
"Failed to get swapchain images.\n",
"Found %d swapchain images.\n",
renderer->device,
renderer->swapchain);
renderer->swapchain_buffers.resize(swapchain_images.size());
for (uint32_t i = 0; i < swapchain_images.size(); i++) {
renderer->swapchain_buffers[i].image = swapchain_images[i];
}
for (uint32_t i = 0; i < swapchain_images.size(); i++) {
VkImageViewCreateInfo color_image_view = {};
color_image_view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
color_image_view.pNext = nullptr;
color_image_view.flags = 0;
color_image_view.image = renderer->swapchain_buffers[i].image;
color_image_view.viewType = VK_IMAGE_VIEW_TYPE_2D;
color_image_view.format = renderer->surface_format;
color_image_view.components.r = VK_COMPONENT_SWIZZLE_R;
color_image_view.components.g = VK_COMPONENT_SWIZZLE_G;
color_image_view.components.b = VK_COMPONENT_SWIZZLE_B;
color_image_view.components.a = VK_COMPONENT_SWIZZLE_A;
color_image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
color_image_view.subresourceRange.baseMipLevel = 0;
color_image_view.subresourceRange.levelCount = 1;
color_image_view.subresourceRange.baseArrayLayer = 0;
color_image_view.subresourceRange.layerCount = 1;
res = vkCreateImageView(renderer->device, &color_image_view, nullptr, &renderer->swapchain_buffers[i].view);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to create image view.\n");
clear_swapchain(renderer);
return false;
}
log(globals.logger, Log_Level::verbose, "Image view %d created.\n", i);
}
// Build the depth buffer
VkImageCreateInfo image_info = {};
const VkFormat depth_format = VK_FORMAT_D32_SFLOAT;
VkFormatProperties format_properties;
bool found_memory_type_index;
vkGetPhysicalDeviceFormatProperties(renderer->physical_device, depth_format, &format_properties);
if (format_properties.linearTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
image_info.tiling = VK_IMAGE_TILING_LINEAR;
} else if (format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
} else {
// @TODO choose an other format?
log(globals.logger, Log_Level::error, "VK_FORMAT_D32_SFLOAT Unsupported.\n");
clear_swapchain(renderer);
return false;
}
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.pNext = nullptr;
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.format = depth_format;
image_info.extent.width = renderer->swapchain_extent.width;
image_info.extent.height = renderer->swapchain_extent.height;
image_info.extent.depth = 1;
image_info.mipLevels = 1;
image_info.arrayLayers = 1;
image_info.samples = renderer->sample_count_flag;
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
image_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
image_info.queueFamilyIndexCount = 0;
image_info.pQueueFamilyIndices = nullptr;
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
image_info.flags = 0;
VkMemoryAllocateInfo memory_allocation_info = {};
memory_allocation_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_allocation_info.pNext = nullptr;
memory_allocation_info.allocationSize = 0;
memory_allocation_info.memoryTypeIndex = 0;
VkImageViewCreateInfo view_info = {};
view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_info.pNext = nullptr;
view_info.image = nullptr;
view_info.format = depth_format;
view_info.components.r = VK_COMPONENT_SWIZZLE_R;
view_info.components.g = VK_COMPONENT_SWIZZLE_G;
view_info.components.b = VK_COMPONENT_SWIZZLE_B;
view_info.components.a = VK_COMPONENT_SWIZZLE_A;
view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
view_info.subresourceRange.baseMipLevel = 0;
view_info.subresourceRange.levelCount = 1;
view_info.subresourceRange.baseArrayLayer = 0;
view_info.subresourceRange.layerCount = 1;
view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_info.flags = 0;
VkMemoryRequirements memory_requirements;
renderer->depth_buffer.format = depth_format;
/* Create image */
res = vkCreateImage(renderer->device, &image_info, nullptr, &renderer->depth_buffer.image);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to create the depth image.\n");
clear_swapchain(renderer);
return false;
}
vkGetImageMemoryRequirements(renderer->device, renderer->depth_buffer.image, &memory_requirements);
memory_allocation_info.allocationSize = memory_requirements.size;
/* Use the memory properties to determine the type of memory required */
found_memory_type_index = memory_type_from_properties(renderer, memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memory_allocation_info.memoryTypeIndex);
if (!found_memory_type_index) {
log(globals.logger, Log_Level::error, "Failed to find memory type to allocate the depth image.\n");
clear_swapchain(renderer);
return false;
}
/* Allocate memory */
res = vkAllocateMemory(renderer->device, &memory_allocation_info, nullptr, &renderer->depth_buffer.memory);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to create memory for depth image.\n");
clear_swapchain(renderer);
return false;
}
/* Bind memory */
res = vkBindImageMemory(renderer->device, renderer->depth_buffer.image, renderer->depth_buffer.memory, 0);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to bind the depth image memory.\n");
clear_swapchain(renderer);
return false;
}
/* Create image view */
view_info.image = renderer->depth_buffer.image;
res = vkCreateImageView(renderer->device, &view_info, nullptr, &renderer->depth_buffer.view);
if (res != VK_SUCCESS) {
log(globals.logger, Log_Level::error, "Failed to create the depth image view.\n");
clear_swapchain(renderer);
return false;
}
log(globals.logger, Log_Level::verbose, "Depth buffer created.\n");
for (size_t i = 0; i < renderer->scenes.size(); i++) {
swapchain_resized(renderer->scenes[i], renderer->swapchain_extent.width, renderer->swapchain_extent.height);
}
return true;
}
编辑:也许我的问题与我如何将渲染图像提交到交换链或图像采集有关。
for (size_t i = 0; i < scene->meshes.size(); i++) {
draw_mesh(scene->meshes[i]);
}
// End the Render pass
vkCmdEndRenderPass(scene->renderer->graphical_command_buffer);
// End command buffer
{
res = vkEndCommandBuffer(scene->renderer->graphical_command_buffer);
}
// Execute queue command buffer
{
/* Queue the command buffer for execution */
const VkCommandBuffer command_buffers[] = {scene->renderer->graphical_command_buffer};
VkFenceCreateInfo fence_create_info;
VkFence draw_fence;
fence_create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_create_info.pNext = nullptr;
fence_create_info.flags = 0;
vkCreateFence(scene->renderer->device, &fence_create_info, nullptr, &draw_fence);
VkPipelineStageFlags pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submit_info[1] = {};
submit_info[0].pNext = nullptr;
submit_info[0].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info[0].waitSemaphoreCount = 1;
submit_info[0].pWaitSemaphores = &scene->image_acquired_semaphore;
submit_info[0].pWaitDstStageMask = &pipe_stage_flags;
submit_info[0].commandBufferCount = 1;
submit_info[0].pCommandBuffers = command_buffers;
submit_info[0].signalSemaphoreCount = 0;
submit_info[0].pSignalSemaphores = nullptr;
/* Queue the command buffer for execution */
res = vkQueueSubmit(scene->renderer->graphics_queue, 1, submit_info, draw_fence);
assert(res == VK_SUCCESS);
/* Now present the image in the window */
VkPresentInfoKHR present;
present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present.pNext = nullptr;
present.swapchainCount = 1;
present.pSwapchains = &scene->renderer->swapchain;
present.pImageIndices = &scene->current_buffer;
present.pWaitSemaphores = nullptr;
present.waitSemaphoreCount = 0;
present.pResults = nullptr;
/* Make sure command buffer is finished before presenting */
do {
res = vkWaitForFences(scene->renderer->device, 1, &draw_fence, VK_TRUE, scene->renderer->draw_fence_timeout_us);
} while (res == VK_TIMEOUT);
assert(res == VK_SUCCESS);
res = vkQueuePresentKHR(scene->renderer->present_queue, &present);
assert(res == VK_SUCCESS);
vkDestroyFence(scene->renderer->device, draw_fence, nullptr);
}
在 vulkan-tutorial.com 上写道,我们还应该使用交换链( https://vulkan-tutorial.com/Drawing_a_triangle/Swap_chain_recreation )重新创建命令缓冲区,这真的是强制性的吗?
我是那个reddit线程的人。 如果我们有完全相同的问题,我不确定 100%,但我可以解释我正在处理什么以及我是如何解决它的。
所以这里的问题有几个层面。 第一个是 window 在 Windows 上调整大小会阻塞消息队列,因为它需要为自己捕获所有输入事件。 因此,要解决此问题,您需要使 window 更新异步,例如通过线程。
现在您的渲染和 window 调整大小异步工作,这很棒,直到有人调整 windows 的大小,而您正在渲染新帧的一半。 这立即导致交换链为VK_ERROR_OUT_OF_DATE_KHR
,使您无法将渲染结果呈现到屏幕上。 这可能会导致表面上出现各种不同的伪影,具体取决于 GPU 供应商、驱动程序版本,甚至同一供应商的不同 GPU 之间。 这是字面上未定义的行为。 但是闪烁绝对是常见的结果之一,在新的成功挂起之前,它根本不会在表面上显示任何内容。 到目前为止,我还没有找到一个支持VK_SUBOPTIMAL_KHR
以允许您继续渲染的供应商。
一个天真的解决方案是让 window 完全控制帧速率,但会给出非常差且不一致的帧时序,尤其是在超过 60hz 时。 您希望渲染以尽可能快的速度运行,并尽可能减少延迟。
所以在我go进入解决方案之前,让我们总结一下需求:
* 不调整大小时
您可能已经注意到最后一个要求上的星号。 这是因为我们将不得不做出一个小小的妥协。 我们的想法是,我们只让 window 在调整大小时控制帧时序。 除此之外,我们可以尽可能快地绘制,因为没有其他东西可以使中间的交换链失效。
为此,我使用了Fibers 。 您可以将纤维视为没有线程的堆栈。 然后,您可以从光纤跳到不同的光纤并返回。 还记得消息队列(特别是GetMessage
/ PeekMessage
调用)在调整大小时不会返回吗? 好吧,您可以跳出该循环并使用光纤返回,并结合导致切换的计时器。 我们可以同步更新 window 以及渲染帧:这是我的代码示例:
LRESULT Window::Impl::WndProc(HWND a_HWND, UINT a_Message, WPARAM a_WParam, LPARAM a_LParam)
{
switch (a_Message)
{
case WM_ENTERSIZEMOVE:
SetTimer(a_HWND, 0, 1, NULL);
break;
case WM_EXITSIZEMOVE:
KillTimer(a_HWND, 0);
break;
case WM_TIMER:
m_MainFiber.Switch();
break;
case WM_MOVE:
if (m_MoveCallback)
{
m_MoveCallback(m_This, Vector2i(static_cast<int16_t>(LOWORD(a_LParam)), static_cast<int16_t>(HIWORD(a_LParam))));
}
break;
case WM_SIZE:
switch (a_WParam)
{
case SIZE_MINIMIZED:
if (m_MinimizeCallback)
{
m_MinimizeCallback(m_This);
}
break;
case SIZE_MAXIMIZED:
if (m_MaximizeCallback)
{
m_MaximizeCallback(m_This);
}
break;
}
if (m_ResizeCallback)
{
m_ResizeCallback(m_This, Vector2i(static_cast<int16_t>(LOWORD(a_LParam)), static_cast<int16_t>(HIWORD(a_LParam))));
}
break;
case WM_CLOSE:
if (m_CloseCallback)
{
m_CloseCallback(m_This);
}
break;
}
if (a_Message == WM_CLOSE)
{
return 0;
}
return DefWindowProcW(a_HWND, a_Message, a_WParam, a_LParam);
}
如您所见,它实际上非常简单。 调整大小开始时启动计时器,调整大小结束时停止计时器,并在触发时切换回原始光纤。
这是光纤回调本身:
void Window::Impl::FiberCallback()
{
MSG msg;
for (;;)
{
if (PeekMessageW(&msg, m_Window, 0, 0, PM_REMOVE) != 0)
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
else
{
m_MainFiber.Switch();
}
}
}
然后实际的轮询就这么简单:
void Window::PollEvents()
{
m_Impl->m_MessageFiber.Switch();
}
这应该使PollEvents
在不调整大小时总是立即返回,并且在您调整大小时计时器到期后。 它还完全避免了线程,因为它都在同一个线程上运行,它只是在堆栈之间切换。
如果有不清楚的地方发表评论,我希望它能解决你的问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.