Я перемещаю свой рендерер с проходов рендеринга на динамический рендеринг и вижу сообщения проверки в Linux (точнее, Nvidia 550.78), которых я не вижу в Windows (точнее, AMD 24.3.1). Вот несколько строк, которые я отформатировал для удобства чтения:
[17:01:00.273][8314]: Swapchain image 0xcad092000000000d: 'ColorAttachmentOptimal'->'PresentSrcKHR'
[17:01:00.273][8314]:
Validation Error: [ SYNC-HAZARD-WRITE-AFTER-READ ]
Object 0: handle = 0x555555b41130, type = VK_OBJECT_TYPE_QUEUE; |
MessageID = 0x376bc9df |
vkQueueSubmit():
Hazard WRITE_AFTER_READ for entry 0,
VkCommandBuffer 0x5555582fad00[],
Submitted access info (submitted_usage: SYNC_IMAGE_LAYOUT_TRANSITION,
command: vkCmdPipelineBarrier,
seq_no: 1,
VkImage 0xcad092000000000d[],
reset_no: 30).
Access info (prior_usage: SYNC_PRESENT_ENGINE_SYNCVAL_PRESENT_ACQUIRE_READ_SYNCVAL,
read_barriers: VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT|VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT,
,
batch_tag: 677,
vkAcquireNextImageKHR aquire_tag:677
: VkSwapchainKHR 0xe88693000000000c[],
image_index: 0
image: VkImage 0xcad092000000000d[]).
[17:01:00.273][8314]:
Validation Error: [ SYNC-HAZARD-WRITE-AFTER-WRITE ]
Object 0: handle = 0x555555b41130, type = VK_OBJECT_TYPE_QUEUE; |
MessageID = 0x5c0ec5d6 |
vkQueueSubmit():
Hazard WRITE_AFTER_WRITE for entry 0,
VkCommandBuffer 0x5555582fad00[],
Submitted access info (submitted_usage: SYNC_IMAGE_LAYOUT_TRANSITION,
command: vkCmdPipelineBarrier,
seq_no: 2,
VkImage 0x2e2941000000001f[],
reset_no: 30).
Access info (prior_usage: SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE,
write_barriers: 0,
queue: VkQueue 0x555555b41130[],
submit: 88,
batch: 0,
batch_tag: 663,
command: vkCmdEndRenderingKHR,
command_buffer: VkCommandBuffer 0x555558313000[],
seq_no: 11,
reset_no: 28).
[17:01:00.289][8314]: Swapchain image 0x967dd1000000000e: 'Undefined'->'ColorAttachmentOptimal'
[17:01:00.289][8314]: Image 0x2e2941000000001f: 'Undefined'->'DepthAttachmentOptimal'
Первая и последние две строки — это мои собственные записи, а «две средние строки» — это разбитые сообщения проверки. Похоже, что происходит какая-то ерунда с форматированием, но я сохранил недостающую информацию и придерживался максимально последовательного форматирования.
В любом случае, после того как я отправлю командный буфер, в котором есть барьер конвейера перехода, я получаю ошибку записи после чтения. Вот код, который фактически контролирует создание барьеров. Непосредственно перед вызовом beginRenderingKHR()
я перемещаю буферы изображения и глубины в цепочке обмена:
auto &color_buffer = *Renderer::swapchain().images()[Renderer::image_index()];
color_buffer.transition_layout(Renderer::cmd_buffer(),
vk::ImageLayout::eUndefined,
vk::ImageLayout::eColorAttachmentOptimal);
_depth_buffer.transition_layout(Renderer::cmd_buffer(),
vk::ImageLayout::eUndefined,
vk::ImageLayout::eDepthAttachmentOptimal);
Затем, сразу после вызова endRenderingKHR()
, я перемещаю буфер цвета:
auto &color_buffer = *Renderer::swapchain().images()[Renderer::image_index()];
color_buffer.transition_layout(Renderer::cmd_buffer(),
vk::ImageLayout::eColorAttachmentOptimal,
vk::ImageLayout::ePresentSrcKHR);
А вот код перехода изображения (vkSwapchainImage
не относится к буферу глубины, но логика та же):
void vkSwapchainImage::transition_layout(vkCmdBuffer const &cmd_buffer,
vk::ImageLayout const old_layout,
vk::ImageLayout const new_layout)
{
BTX_TRACE("Swapchain image {}: '{:s}'->'{:s}'",
_handle,
vk::to_string(old_layout),
vk::to_string(new_layout));
vk::ImageMemoryBarrier barrier {
.srcAccessMask = { },
.dstAccessMask = { },
.oldLayout = old_layout,
.newLayout = new_layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = _handle,
.subresourceRange {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0u,
.levelCount = 1u,
.baseArrayLayer = 0u,
.layerCount = 1u,
}
};
vk::PipelineStageFlags src_stage = vk::PipelineStageFlagBits::eNone;
vk::PipelineStageFlags dst_stage = vk::PipelineStageFlagBits::eNone;
if (old_layout == vk::ImageLayout::eUndefined) {
barrier.srcAccessMask = vk::AccessFlagBits::eNone;
if (new_layout == vk::ImageLayout::eColorAttachmentOptimal) {
barrier.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead
| vk::AccessFlagBits::eColorAttachmentWrite;
src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
dst_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
}
else if (new_layout == vk::ImageLayout::eDepthAttachmentOptimal) {
barrier.dstAccessMask =
vk::AccessFlagBits::eDepthStencilAttachmentRead
| vk::AccessFlagBits::eDepthStencilAttachmentWrite;
src_stage = vk::PipelineStageFlagBits::eTopOfPipe;
dst_stage = vk::PipelineStageFlagBits::eEarlyFragmentTests
| vk::PipelineStageFlagBits::eLateFragmentTests;
}
else {
BTX_CRITICAL("Unsupported image layout transition");
return;
}
}
else if (old_layout == vk::ImageLayout::eColorAttachmentOptimal) {
barrier.srcAccessMask = vk::AccessFlagBits::eColorAttachmentRead
| vk::AccessFlagBits::eColorAttachmentWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eNone;
src_stage = vk::PipelineStageFlagBits::eColorAttachmentOutput;
dst_stage = vk::PipelineStageFlagBits::eBottomOfPipe;
}
else {
BTX_CRITICAL("Unsupported image layout transition");
return;
}
cmd_buffer.native().pipelineBarrier(
src_stage, // Source stage
dst_stage, // Destination stage
{ }, // Dependency flags
nullptr, // Memory barriers
nullptr, // Buffer memory barriers
{ barrier } // Image memory barriers
);
_layout = barrier.newLayout;
}
Я постарался скопировать переходы изображений в точности из официального примера кода динамического рендеринга, и они кажутся нормальными в Windows/AMD, но, конечно, мне хотелось бы понять, что не завершено, а что нет.
И, конечно же, любые структурные улучшения для лучшей обработки переходов изображений будут очень приветствоваться. "="
Что касается образа цепочки обмена, вы, вероятно, не ждете, пока он станет готовым (его получение завершится).
Используйте ограждение для ожидания на стороне процессора или используйте семафор с VkSubmitInfo::pWaitSemaphores
.
Для изображения глубины: VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT
не указывает этап выполнения, если он указан в первой области синхронизации (src_stage
).
т. е. вы не сообщаете графическому процессору, что его предыдущее использование завершено, а кеши очищены/недействительны.
Вы, вероятно, тоже хотите VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT
для src_stage
, с соответствующим VkAccessFlagBits
.
Настоящий «ответ» на этот вопрос заключается в том, что мне все еще нужно больше изучать синхронизацию. Ничего удивительного.
Однако мне удалось устранить ошибки проверки синхронизации, которые я видел, переместив существующий код на использование расширения sync2. Я использовал новую настройку vkguide.dev в качестве основы и пошел дальше. Сейчас цепочка событий выглядит следующим образом.
Я начинаю запись буфера команд однократной отправки. Затем я перемещаю буфер цвета и буфер глубины и начинаю рендеринг.
void vkColorDepth::begin() {
auto &color_buffer = *Renderer::swapchain().images()[Renderer::image_index()];
color_buffer.transition_layout(Renderer::cmd_buffer(),
vk::ImageLayout::eUndefined,
vk::ImageLayout::eColorAttachmentOptimal);
_depth_buffer.transition_layout(Renderer::cmd_buffer(),
vk::ImageLayout::eUndefined,
vk::ImageLayout::eDepthAttachmentOptimal);
vk::Rect2D const render_area = {
.offset { .x = 0u, .y = 0u },
.extent {
.width = Renderer::swapchain().size().width,
.height = Renderer::swapchain().size().height,
},
};
Renderer::cmd_buffer().begin_rendering(
vk::RenderingInfoKHR {
.pNext = nullptr,
.flags = { },
.renderArea = render_area,
.layerCount = 1u,
.viewMask = 0u,
.colorAttachmentCount = 1u,
.pColorAttachments = &_color_attachments[Renderer::image_index()],
.pDepthAttachment = &_depth_attachment,
.pStencilAttachment = nullptr,
}
);
}
Команды записываются, затем я снова перемещаю буфер цвета:
void vkColorDepth::end() {
Renderer::cmd_buffer().end_rendering();
auto &color_buffer = *Renderer::swapchain().images()[Renderer::image_index()];
color_buffer.transition_layout(Renderer::cmd_buffer(),
vk::ImageLayout::eColorAttachmentOptimal,
vk::ImageLayout::ePresentSrcKHR);
}
И, наконец, команды отправляются:
void Renderer::_submit_commands() {
auto const &frame_sync = _frame_sync[_image_index];
auto const cmd_submit_info = vk::CommandBufferSubmitInfo {
.pNext = nullptr,
.commandBuffer = frame_sync.cmd_buffer().native(),
.deviceMask = { },
};
auto const wait_info = vk::SemaphoreSubmitInfoKHR {
.pNext = nullptr,
.semaphore = frame_sync.present_semaphore(),
.value = { },
.stageMask = vk::PipelineStageFlagBits2KHR::eColorAttachmentOutput,
.deviceIndex = { },
};
auto const signal_info = vk::SemaphoreSubmitInfoKHR {
.pNext = nullptr,
.semaphore = frame_sync.queue_semaphore(),
.value = { },
.stageMask = vk::PipelineStageFlagBits2KHR::eAllGraphics,
.deviceIndex = { },
};
auto const queue_submit_info = vk::SubmitInfo2KHR {
.pNext = nullptr,
.flags = { },
.waitSemaphoreInfoCount = 1u,
.pWaitSemaphoreInfos = &wait_info,
.commandBufferInfoCount = 1u,
.pCommandBufferInfos = &cmd_submit_info,
.signalSemaphoreInfoCount = 1u,
.pSignalSemaphoreInfos = &signal_info,
};
auto const result = _device.graphics_queue().native().submit2KHR(
1u,
&queue_submit_info,
frame_sync.queue_fence()
);
if (result != vk::Result::eSuccess) {
BTX_CRITICAL("Failed to submit commands to device queue: '{}'",
vk::to_string(result));
}
}
Теперь вот как выглядит код перехода изображения цепочки обмена:
void vkSwapchainImage::transition_layout(vkCmdBuffer const &cmd_buffer,
vk::ImageLayout const old_layout,
vk::ImageLayout const new_layout)
{
// BTX_TRACE("Swapchain image {}: '{:s}'->'{:s}'",
// _handle,
// vk::to_string(old_layout),
// vk::to_string(new_layout));
vk::ImageMemoryBarrier2KHR barrier {
.pNext = nullptr,
.srcStageMask = vk::PipelineStageFlagBits2KHR::eAllCommands,
.srcAccessMask = vk::AccessFlagBits2KHR::eMemoryWrite,
.dstStageMask = vk::PipelineStageFlagBits2KHR::eAllCommands,
.dstAccessMask = vk::AccessFlagBits2KHR::eMemoryRead
| vk::AccessFlagBits2KHR::eMemoryWrite,
.oldLayout = old_layout,
.newLayout = new_layout,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = _handle,
.subresourceRange {
.aspectMask = vk::ImageAspectFlagBits::eColor,
.baseMipLevel = 0u,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0u,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
}
};
if (old_layout == vk::ImageLayout::eUndefined) {
if (new_layout == vk::ImageLayout::eColorAttachmentOptimal) {
barrier.srcAccessMask = vk::AccessFlagBits2KHR::eColorAttachmentRead
| vk::AccessFlagBits2KHR::eColorAttachmentWrite;
barrier.dstAccessMask = vk::AccessFlagBits2KHR::eColorAttachmentRead
| vk::AccessFlagBits2KHR::eColorAttachmentWrite;
}
else {
BTX_CRITICAL("Image {}: unsupported swapchain image layout "
"transition: '{:s}'->'{:s}'",
_handle,
vk::to_string(old_layout),
vk::to_string(new_layout));
return;
}
}
else if (old_layout == vk::ImageLayout::eColorAttachmentOptimal) {
if (new_layout == vk::ImageLayout::ePresentSrcKHR) {
barrier.srcAccessMask = vk::AccessFlagBits2KHR::eColorAttachmentRead
| vk::AccessFlagBits2KHR::eColorAttachmentWrite;
barrier.dstAccessMask = { };
}
else {
BTX_CRITICAL("Image {}: unsupported swapchain image layout "
"transition: '{:s}'->'{:s}'",
_handle,
vk::to_string(old_layout),
vk::to_string(new_layout));
return;
}
}
else {
BTX_CRITICAL("Image {}: unsupported swapchain image layout transition: "
"'{:s}'->'{:s}'",
_handle,
vk::to_string(old_layout),
vk::to_string(new_layout));
return;
}
auto dep_info = vk::DependencyInfoKHR {
.pNext = nullptr,
.dependencyFlags = { },
.memoryBarrierCount = { },
.pMemoryBarriers = { },
.bufferMemoryBarrierCount = { },
.pBufferMemoryBarriers = { },
.imageMemoryBarrierCount = 1u,
.pImageMemoryBarriers = &barrier,
};
cmd_buffer.native().pipelineBarrier2KHR(dep_info);
}
Я пропущу публикацию кода перехода к буферу глубины, потому что структурно он практически такой же.
Удивительно полезным в этом упражнении было то, что предупреждения проверки sync2 намного более информативны, чем предупреждения sync1. Помимо полного игнорирования предупреждения об использовании маски этапа ALL_COMMANDS, я фактически смог перейти от одного предупреждения к другому после первоначальной настройки использования подхода vkguide.dev «кувалда» для синхронизации перехода макета.
Мне все еще приходится работать над этим, чтобы получить некоторое понимание, а не копировать/вставлять/следовать подсказкам, которые я делал до сих пор, но приятно иметь для этого проверенный трамплин.
Я использовал семафор ожидания, но, по-видимому, он сам по себе имел непредвиденный эффект. Однако я совершенно забыл об этом как о необходимом этапе синхронизации, поэтому было полезно напомнить об этом. Спасибо =)