Suslik
> при расстановке барьеров я автоматически меняю лейаут и передаю владение, если необходимо.
То есть сабмитишь командный буфер, где ресурс освобождается из очереди?
> однако, ремарка: у меня сейчас compute и graphics queue используют один и тот же queue family index,
Вообще не факт, что они будут параллельно выполняться. Я думаю 2 очереди с одним queue family используют один и тот же командный процессор, просто он будет чередовать эти команды, но не может их распараллеливать. А если разные queue family, то и командные процессоры разные, тем более для async compute там намного больше блоков которые обрабатывают команды.
> поэтому я пока не тестировал, если они действительно разные. может быть какой-то баг, о котором я пока не догадываюсь.
queue ownership transfer нужен только для разных queue family, а если ты не производишь корректную передачу владения, то это UB и содержимое ресурса может быть утеряно.
/A\
> Проблема в том, что не удобно руками указывать все эти атласы.
всё равно не понимаю, в чём проблема. сами юникодные страницы с глифами (или откуда ты там копируешь глифы?) вообще нигде указывать не надо, так как они всегда в одном лейауте для чтения. атласы же, в которые дописываешь глифы, указываешь как рендертаргет в пассе, который в них глифы добавляет, и как ресурс на чтение в пассе, который рендерит текст. что здесь именно неудобно?
> То есть сабмитишь командный буфер, где ресурс освобождается из очереди?
в принципе, как я ищу последний пасс, использовавший ресурс, можно точно так же найти и следующий пасс, который будет использовать этот ресурс. сейчас я ставлю барьер непосредственно перед использованием, который забирает ownership себе в том командном буфере, где он будет использоваться. но точно так же можно поставить другой барьер, который передаст ownership другому queue сразу после использования.
> queue ownership transfer нужен только для разных queue family, а если ты не производишь корректную передачу владения, то это UB и содержимое ресурса может быть утеряно.
насколько я помню, это не UB, а разрешено спекой и содержимое ресурса автоматически обнуляется, если не передать владение явно. я в курсе, что передача нужна только для разных family index — она у меня реализована, но я её по факту не тестил, так как индекс получается всегда один.
Suslik
> указываешь как рендертаргет в пассе, который в них глифы добавляет, и как
> ресурс на чтение в пассе, который рендерит текст. что здесь именно неудобно?
Неудобно то, что пасс, который грузит новые глифы не знает ни про какие другие пассы.
> но точно так же можно поставить другой барьер, который передаст ownership другому queue сразу после использования.
А как ты передаешь ownership если у тебя один и тот же queue family ?
/A\
> Неудобно то, что пасс, который грузит новые глифы не знает ни про какие другие
> пассы.
а, ну так это проблема установки зависимостей между пассами. у меня-то такой проблемы вообще нет, потому что пассы сабмитятся последовательно, гарантированно один за другим. зависимостей между пассами вообще нет, зависимости есть только через доступ к одним и тем же ресурсам. поэтому несмотря на то, что сабмитятся пассы последовательно, исполняться они могут всё равно параллельно, если между ними нет зависимостей.
> А как ты передаешь ownership если у тебя один и тот же queue family ?
if (srcImageAccessPattern.queueFamilyIndex == dstImageAccessPattern.queueFamilyIndex) { imageBarrier .setSrcQueueFamilyIndex( VK_QUEUE_FAMILY_IGNORED) .setDstQueueFamilyIndex( VK_QUEUE_FAMILY_IGNORED); } else { imageBarrier .setSrcQueueFamilyIndex( srcImageAccessPattern.queueFamilyIndex) .setDstQueueFamilyIndex( dstImageAccessPattern.queueFamilyIndex); }
короче, вот так я расставляю барьеры для image'ей перед исполнением таска dstTaskIndex:
for (uint32_t mipLevel = imageView->GetBaseMipLevel( ); mipLevel < imageView->GetBaseMipLevel( ) + imageView->GetMipLevelsCount( ); mipLevel++) { auto lastUsageType = GetLastImageSubresourceUsageType( dstTaskIndex, imageView->GetImageData( ), mipLevel, arrayLayer); if ( prevSubresourceUsageType != lastUsageType) { FlushImageTransitionBarriers( imageView->GetImageData( ), range, prevSubresourceUsageType, dstUsageType, srcStage, dstStage, imageBarriers); range .setBaseMipLevel( mipLevel) .setLevelCount( 0); prevSubresourceUsageType = lastUsageType; } range.levelCount++; }
> В спеке сказано, что содержимое ресурса может быть утеряно, но у меня оно не терялось. Поэтому я и сказал что UB, так как оно может работать, а может и нет и ты не сразу увидишь ошибку, а слой валидации это не отслеживает.
окей, приму к сведенью. но точно помню, что это — не запрещённая операция. то есть если тебе всё равно на содержимое ресурса, ты можешь не указывать явно барьер для передачи его владения. это может быть очень полезно для случая, когда ресурс в кадре не использовался (неизвестно, кому он принадлежит), но его всё равно очищать надо. поэтому оно, наверное, и не репортится validation layer'ом.
Suslik
> насколько я помню, это не UB, а разрешено спекой и содержимое ресурса автоматически обнуляется, если не передать владение явно.
В спеке сказано, что содержимое ресурса может быть утеряно, но у меня оно не терялось. Поэтому я и сказал что UB, так как оно может работать, а может и нет и ты не сразу увидишь ошибку, а слой валидации это не отслеживает.
вот так у меня в последней ревизии выглядит запись таска:
frameInfo.renderGraph->AddPass(legit::RenderGraph::ComputePassDesc( ) .SetStorageBuffers( { frameResources->indirectLightBuffer }) .SetInputImages( { frameResources->blurredDirectLight.imageViewProxy, frameResources->blurredDepthMoments.imageViewProxy, frameResources->normal.imageViewProxy, frameResources->depthStencil.imageViewProxy, }) .SetRecordFunc( [this, passData]( legit::RenderGraph::PassContext passContext) { auto pipeineInfo = pipelineCache->BindComputePipeline( passContext.GetCommandBuffer( ), indirectLightingShader.compute.get( )); { const legit::DescriptorSetLayoutKey *shaderDataSetInfo = indirectLightingShader.compute->GetSetInfo( ShaderDataSetIndex); auto shaderData = passData.memoryPool->BeginSet( shaderDataSetInfo); { auto shaderDataBuffer = passData.memoryPool->GetUniformBufferData<IndirectLightingShader::DataBuffer>( "IndirectLightingData"); shaderDataBuffer->viewMatrix = passData.viewMatrix; shaderDataBuffer->projMatrix = passData.projMatrix; shaderDataBuffer->viewportSize = glm::vec4( passData.viewportSize.width, passData.viewportSize.height, 0.0f, 0.0f); } passData.memoryPool->EndSet( ); std::vector<legit::ImageSamplerBinding> imageSamplerBindings; auto blurredDirectLightImageView = passContext.GetImageView( passData.frameResources->blurredDirectLight.imageViewProxy); imageSamplerBindings.push_back( shaderDataSetInfo->MakeImageSamplerBinding( "blurredDirectLightSampler", blurredDirectLightImageView, screenspaceSampler.get( ))); auto blurredDepthMomentsImageView = passContext.GetImageView( passData.frameResources->blurredDepthMoments.imageViewProxy); imageSamplerBindings.push_back( shaderDataSetInfo->MakeImageSamplerBinding( "blurredDepthMomentsSampler", blurredDepthMomentsImageView, screenspaceSampler.get( ))); auto normalImageView = passContext.GetImageView( passData.frameResources->normal.imageViewProxy); imageSamplerBindings.push_back( shaderDataSetInfo->MakeImageSamplerBinding( "normalSampler", normalImageView, screenspaceSampler.get( ))); auto depthStencilImageView = passContext.GetImageView( passData.frameResources->depthStencil.imageViewProxy); imageSamplerBindings.push_back( shaderDataSetInfo->MakeImageSamplerBinding( "depthStencilSampler", depthStencilImageView, screenspaceSampler.get( ))); std::vector<legit::StorageBufferBinding> storageBufferBindings; auto pixelBuffer = passContext.GetBuffer( passData.frameResources->indirectLightBuffer); storageBufferBindings.push_back( shaderDataSetInfo->MakeStorageBufferBinding( "PixelData", pixelBuffer)); auto shaderDataSet = descriptorSetCache->GetDescriptorSet( *shaderDataSetInfo, shaderData.uniformBufferBindings, storageBufferBindings, imageSamplerBindings); passContext.GetCommandBuffer( ).bindDescriptorSets( vk::PipelineBindPoint::eCompute, pipeineInfo.pipelineLayout, ShaderDataSetIndex, { shaderDataSet }, { shaderData.dynamicOffset }); size_t workGroupSize = 32; passContext.GetCommandBuffer( ).dispatch( passData.viewportSize.width / workGroupSize + 1, passData.viewportSize.height / workGroupSize + 1, 1); } }));
Suslik
> std::vector
Не боишься выделять память каждый раз? ))
/A\
да это всё можно легко заменить либо на кешированный вектор, либо на small vector (который меньше определённого размера выделяется на стеке). там под капотом гораздо более криминальные вещи с точки зрения микрооптимизаций происходят, например, у меня все кеши построены на std::map'ах вместо хотя бы std::unordered_map. это всё можно исправить позже, когда я уверен, что меня устраивает вся архитектура.
Suslik
> там под капотом гораздо более криминальные вещи с точки зрения микрооптимизаций
> происходят, например, у меня все кеши построены на std::map'ах вместо хотя бы std::unordered_map.
По сравнению с глобальным мютексом на алокаторе, это не так страшно.
А у меня бывали случаи, когда unordered из-за хэшей работала в 10 раз медленее обычной мапы.
У меня вообще линейный поиск был для проверки пересечений диапазонов, профайлер говорил, что это далеко не самое медленное место)
/A\
> По сравнению с глобальным мютексом на алокаторе, это не так страшно.
> А у меня бывали случаи, когда unordered из-за хэшей работала в 10 раз медленее
> обычной мапы.
это всё микрооптимизации, которые всегда можно разрулить позже. для текущей реализации я просто определяю operator < () и могу использовать структуру как ключ для кеша. у меня даже хешей нигде нету, потому что не до них. во всех примерах выше std::vector<> можно заменить как минимум на std::array<>, но суть вообще не в этом, потому что сейчас я занимаюсь обкаткой гораздо более глобальных алгоритмов и более высокоуровневой архитектуры.
Раз эта тема перешла в обсуждение фреймграфов, то вот вариант от юбисофт еще от 2017 года.
https://developer.download.nvidia.com/assets/gameworks/downloads/… 017_FINAL.pdf
Интересно насколько быстро эта их система работает.
на пути к лайтмаперу
/A\
лул, уже близко
Какой-то странный баг появился: добавил поддержку VR для рейтрейса как в шейдертое, но вот некоторые шейдеры рисуются с шумом.
* Воспроизводится на реальном VR, эмуляторе и на самописном эмуляторе.
* Воспроизводится на нвидиа и интел.
* В обычном режиме все работает.
* Слои валидации молчат.
* Пробовал играться с синхронизациями - не помогло.
* Смотрел в рендер доке, тот же шум воспроизводится,
* Запускал свой дебагер шейдеров и черных пикселей не обнаружил, то есть проблема где-то позже.
* А вот что помогло - убрал макрос VR_MODE, все равно у меня правильно рассчитываются лучи для VR. Но вот почему небольшая разница в коде дает такой результат я не понимаю. С макросом VR_MODE но с рисованием в одну текстуру баг воспроизводится.
Тема в архиве.