Рендеринг целевой записи / синхронизация чтения шейдера между различными проходами рендеринга - PullRequest
1 голос
/ 07 апреля 2020

Вот настройка визуализации Vulkan:

  1. Submit CB1 - содержит проход рендеринга A с X вызовами отрисовки.
  2. Submit CB2 - содержит проход рендеринга B с Y вызовами отрисовки, где один из рисования вызывает образцы из изображения, которое является целью рендеринга прохода рендеринга A.
  3. Во время передачи CB2, семафор, который используется совместно с некоторым внешним API GPU, вставляет сигнализацию, чтобы убедиться, что выполнение CB2 выполнено до цель визуализации прохода рендеринга B используется далее (в данном случае CUDA). Этот шаг установлен правильно, и мне ясно, как он работает.

Все это происходит в одной и той же очереди и в вышеуказанный порядок. Проходы рендеринга A и B совместно используют образ MSAA, но у каждого есть свое уникальное цветовое вложение, в котором разрешается MSAA.

Мой вопрос заключается в том, каков наилучший способ синхронизации между завершающей записью CB1 для рендеринга прохода A RT и одним или больше вызовов отрисовки в выборке CB2 из этой RT в шейдере во время выполнения прохода рендеринга B?

На основании некоторого предложения, которое я получил в группе Khronos Vulkan Slack, я попытался выполнить синхронизацию через зависимости subpass.

Проход рендеринга Настройка зависимости:

    VkSubpassDependency dependencies[2] = {};

    dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
    dependencies[0].dstSubpass = 0;
    dependencies[0].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependencies[0].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; 
    dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
    dependencies[0].dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
    dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
    VkAttachmentReference colorReferences[2] = {};
    colorReferences[0] = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; //this one for MSAA attachment
    colorReferences[1] = { 1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL };

Настройка рендеринга прохода B:

    VkSubpassDependency dependencies[2] = {};

    dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
    dependencies[0].dstSubpass = 0;
    dependencies[0].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
    dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
    VkAttachmentReference colorReferences[2] = {};
    colorReferences[0] = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL }; 
    colorReferences[1] = { 1, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };

Представленное выше решение, похоже, работает. Но я не уверен, что у меня есть гарантия неявной синхронизации об этом. В этом обсуждении один из ответов состояния

Единственные изображения, где это не так, - это зависимости для целей рендеринга. И те, и другие обрабатываются специально в любом случае. Каждый подпроцесс говорит, что он пишет и куда, поэтому у системы есть информация для явного построения этих зависимостей памяти.

Также в этой статье автор пишет:

ПРИМЕЧАНИЕ: Операции с буфером кадра внутри прохода рендеринга, конечно, выполняются в порядке API. Это специальное исключение, которое вызывает spe c.

Но в этом SO вопросе ответ заключается в том, что необходимо выполнить синхронизацию c между буферами команд , с событиями в этом случае.

Так что другой вариант, который я попробовал, состоял в том, чтобы вставить барьер памяти конвейера во время записи CB2 для изображений, которые являются целями рендеринга в ранее представленном CB (проход рендеринга A), до начала прохода рендеринга B запись:

запись CB2 :

  BeginCommandBuffer(commandBuffer);
   ...
  if (vulkanTex->isRenderTarget)//for each sampler in this pass
  {
    VkImageMemoryBarrier imageMemoryBarrier = CreateImageBarrier();
    imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
    imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
    imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    imageMemoryBarrier.image = vulkanTex->image;
    VkImageSubresourceRange range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 };
    imageMemoryBarrier.subresourceRange = range;

    vkCmdPipelineBarrier(
        commandBuffer,
        VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
        VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
        0,
        0, nullptr,
        0, nullptr,
        1, &imageMemoryBarrier);
   }

 VkRenderPassBeginInfo renderPassBeginInfo = {};
 ...
 .....

Конечно, в этом сценарии я могу установить одинаковые зависимости для всех проходов рендеринга, чтобы они были аналогичны настройке зависимости прохода рендеринга B .Это тоже работает. Но это требует от меня записи большего количества команд в CB. Итак, какой путь является правильным (данная опция зависимостей подпроцесса действительна) и наиболее оптимальным с точки зрения эффективности аппаратного обеспечения?

1 Ответ

1 голос
/ 07 апреля 2020

Представленное выше решение работает.

Ваши зависимости не имеют смысла. В частности, зависимость renderpass B определенно странна, учитывая ваше описание фактической зависимости: «где один из отрисовок вызывает сэмплы из изображения, которое является целью рендеринга прохода рендеринга A.» Это представляло бы зависимость между записями кадрового буфера внутри A и текстурой с чтением в B. Ваша зависимость в вашем примере создает зависимость между записями кадрового буфера внутри A и записями кадрового буфера в B. не имеет смысла и не имеет отношения к тому, что вы говорите, что на самом деле делаете.

Кроме того, ваш srcAccessMask не имеет смысла, поскольку вы утверждаете, что предыдущий источник читает память, когда вы пытаетесь синхронизироваться с запись памяти.

Теперь может случиться так, что ваша семафорная зависимость покрывает ее, или что семафор мешает попыткам слоев Vulkan обнаружить проблемы с зависимостями (вы использует слои, верно?). Но код, который вы показали, просто не имеет смысла.

Внешняя зависимость от renderpass B - верный путь к go (не ясно, зачем renderpass A нужна здесь внешняя зависимость), но это должен действительно иметь смысл. Если renderpass B действительно выполняет выборку из изображения, записанного в renderpass A, то это будет выглядеть примерно так:

    dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
    dependencies[0].dstSubpass = 0;
    dependencies[0].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependencies[0].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; //Assuming you're reading the image in the fragment shader. Insert other shader stage(s) if otherwise.
    dependencies[0].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; //renderpass A wrote to the image as an attachment.
    dependencies[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT; //renderpass B is reading from the image in a shader.
    dependencies[0].dependencyFlags = 0; //By region dependencies make no sense here, since they're not part of the same renderpass.
...