Vulkan: динамический рендеринг изображения перехода

Я перемещаю свой рендерер с проходов рендеринга на динамический рендеринг и вижу сообщения проверки в 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, но, конечно, мне хотелось бы понять, что не завершено, а что нет.

И, конечно же, любые структурные улучшения для лучшей обработки переходов изображений будут очень приветствоваться. "="

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
94
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Что касается образа цепочки обмена, вы, вероятно, не ждете, пока он станет готовым (его получение завершится). Используйте ограждение для ожидания на стороне процессора или используйте семафор с 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.

Я использовал семафор ожидания, но, по-видимому, он сам по себе имел непредвиденный эффект. Однако я совершенно забыл об этом как о необходимом этапе синхронизации, поэтому было полезно напомнить об этом. Спасибо =)

pdm 27.05.2024 20:41
Ответ принят как подходящий

Настоящий «ответ» на этот вопрос заключается в том, что мне все еще нужно больше изучать синхронизацию. Ничего удивительного.

Однако мне удалось устранить ошибки проверки синхронизации, которые я видел, переместив существующий код на использование расширения 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 «кувалда» для синхронизации перехода макета.

Мне все еще приходится работать над этим, чтобы получить некоторое понимание, а не копировать/вставлять/следовать подсказкам, которые я делал до сих пор, но приятно иметь для этого проверенный трамплин.

Другие вопросы по теме