Vulkan: vkCmdPipelineBarrier для согласованности данных

Мой вопрос состоит из 2 частей:

  1. В чем разница между доступной / видимой памятью?
  2. Я изучаю Vulkan по этому руководству (https://vulkan-tutorial.com) и сейчас крадусь в поисках другого подход для загрузки унифицированных данных (простых матриц модели / вида / проекции) в локальную память устройства. Матрицы используются в вершинном шейдере.

    В этом руководстве матрицы обновляются и копируются в промежуточный буфер (vkMapMemory и т. Д.), А затем копируются в конечный локальный буфер устройства путем создания командного буфера, записи vkCmdCopy, отправки и уничтожения буфера. Я стараюсь сделать последний шаг в пределах обязательных командных буферов для рисования.

    Хотя учебный метод приводит к плавной анимации, в моем эксперименте эта функция отсутствует. Я попытался установить 2 bufferBarrier, чтобы гарантировать, что копии сделаны (что кажется проблемой), но это не помогло. Ресурсы правильно созданы и привязаны - все работает нормально.

    //update uniform buffer and copy it to the staging buffer
    //(called every frame)
    Tools::UniformBufferObject ubo;
    //set the matrices
    void* data;
    data = device.mapMemory( uniformStagingMemory, 0, sizeof( ubo ), (vk::MemoryMapFlagBits) 0 );
      memcpy( data, &ubo, sizeof( ubo ));
    device.unmapMemory( uniformStagingMemory );
    
    
    //once: create a command buffer for each framebuffer of the swapchain
    //queueFamily struct members set properly
    //1st barrier: make transfer from host memory to staging buffer available / visible
    vk::BufferMemoryBarrier bufMemBarrierStaging;
    bufMemBarrierStaging.srcAccessMask = vk::AccessFlagBits::eHostWrite;
    bufMemBarrierStaging.dstAccessMask = vk::AccessFlagBits::eTransferRead;
    bufMemBarrierStaging.buffer = uniformStagingBuffer;
    bufMemBarrierStaging.offset = 0;
    bufMemBarrierStaging.size = sizeof( Tools::UniformBufferObject );
    
    //2nd barrier: make transfer from staging buffer to device local buffer available / visible
    vk::BufferMemoryBarrier bufMemBarrier;
    bufMemBarrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
    bufMemBarrier.dstAccessMask = vk::AccessFlagBits::eUniformRead | vk::AccessFlagBits::eShaderRead;
    bufMemBarrier.buffer = dataBuffer;
    bufMemBarrier.offset = dataBufferOffsets[2];
    bufMemBarrier.size = sizeof( Tools::UniformBufferObject );
    
    for( size_t i = 0; i < cmdBuffers.size(); i++ ) {
        //begin command buffer
    
        cmdBuffers[i].pipelineBarrier(
            vk::PipelineStageFlagBits::eHost, //srcPipelineStage
            vk::PipelineStageFlagBits::eTransfer, //dstPipelineStage
            (vk::DependencyFlagBits) 0,
            nullptr, //memBarrier
            bufMemBarrierStaging,
            nullptr //imgBarrier
        );
        vk::BufferCopy copyRegion; //filled appropriate
        cmdBuffers[i].copyBuffer( uniformStagingBuffer, dataBuffer, copyRegion );
    
        cmdBuffers[i].pipelineBarrier(
            vk::PipelineStageFlagBits::eTransfer, //srcPipelineStage
            vk::PipelineStageFlagBits::eVertexShader, //dstPipelineStage
            (vk::DependencyFlagBits) 0,
            nullptr, //memBarrier
            bufMemBarrier,
            nullptr //imgBarrier
        );
        //renderpass stuff and drawing etc.
    }
    

    с участием

    namespace Tools {
      struct UniformBufferObject {
        glm::mat4 model;
        glm::mat4 view;
        glm::mat4 proj;
      };
    };
    vk::Buffer uniformStagingBuffer;
    vk::DeviceMemory uniformStagingMemory;
    //dataBuffer also contains the vertex and index data, is device local
    vk::Buffer dataBuffer;
    vk::DeviceMemory dataBufferMemory;
    vk::vector<vk::DeviceSize> dataBufferOffsets;
    
    std::vector<vk::CommandBuffer> cmdBuffers;
    

    Я использую vkcpp (https://github.com/KhronosGroup/Vulkan-Hpp).

    Является ли причиной этой не текучей анимации отсутствие согласованности данных - и не ошибся ли я, пытаясь этого добиться?

Заранее спасибо!

Изменить: проблема части 2 заключалась в отсутствии синхронизации; Промежуточный буфер был (частично) обновлен до того, как он был прочитан во время рендеринга кадра ранее. (Спасибо, что разъяснили разницу между доступной / видимой памятью).


person camelCase    schedule 13.11.2016    source источник
comment
В чем разница между доступной / видимой памятью? Непонятно, что вы имеете в виду, говоря о доступности. и уничтожение буфера Зачем вам уничтожать буфер? Что именно вы имеете в виду?   -  person Nicol Bolas    schedule 13.11.2016
comment
Кстати, вам не нужен первый барьер памяти. Барьер памяти HOST_WRITE_BIT / HOST_STAGE неявно создается путем подачи буфера команд.   -  person Antoine Morrier    schedule 14.11.2016
comment
@NicolBolas Первый вопрос относится к разделу 6.4 спецификации, параграфу о зависимостях памяти. Я не видел разницы. Уничтожение буфера означает, что я вызываю vkFreeCommandBuffers. Поскольку это код из учебного пособия, выбран самый простой способ, а не лучший.   -  person camelCase    schedule 14.11.2016


Ответы (2)


Если промежуточная буферная память не является согласованной с хостом, вам дополнительно необходимо vkFlushMappedMemoryRanges после memcpy (память может оставаться отображенной). Если вы этого не сделаете, то нет никакой гарантии, что данные действительно видны графическому процессору.

Первый барьер (хост для передачи) на самом деле не нужен; есть неявный барьер для отправки.

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

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

person ratchet freak    schedule 13.11.2016

ad 1.

Доступный и видимый - это две половины зависимости от памяти. Практически оба должны произойти, чтобы это была действительная зависимость от памяти.

Вы можете рассматривать это как последовательность состояний:

Ресурс написан src → сделан доступным для src → сделан видимым для dst → используется dst.

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

Вы отвечаете за то, чтобы сделать их доступными и видимыми. Операции, которые могут выполнять некоторые из них: барьеры, события, согласованная отображаемая память или flush, invalidate и некоторые другие ...

AFAIK планируется переписать спецификацию синхронизации, и номенклатура может измениться.

person krOoze    schedule 13.11.2016