Как вы должны обновить текстуру на кадр в Vulkan? - PullRequest
1 голос
/ 08 марта 2020

Я пытаюсь работать с 2D в Vulkan вместе с 3D. Итак, прямо сейчас тестируем обновление текстуры для каждого кадра в зависимости от того, что происходит в 2D. Я получил кое-что из работающего средства обновления текстур, проблема в том, что он очень медленный и, вероятно, не так, как предполагалось. Есть ли лучший способ сделать это? Код основан на https://vulkan-tutorial.com/ коде.

https://vulkan-tutorial.com/code/26_depth_buffering.cpp

    void UpdateTexture()
{
    vkDeviceWaitIdle(device);
    vkFreeMemory(device, textureImageMemory, nullptr);

    VkBuffer stagingBuffer;
    VkDeviceMemory stagingBufferMemory;
    createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);

    void* data;
    vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data);
    memcpy(data, pixel2.data(), static_cast<size_t>(imageSize));
    vkUnmapMemory(device, stagingBufferMemory);

    createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory);

    transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
    copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(texWidth), static_cast<uint32_t>(texHeight));
    transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);

    vkDestroyBuffer(device, stagingBuffer, nullptr);
    vkFreeMemory(device, stagingBufferMemory, nullptr);

    createTextureImageView();
    createDescriptorPool();
    createDescriptorSets();
    createCommandBuffers();
}

1 Ответ

1 голос
/ 08 марта 2020

Этот код выглядит как прямой перевод некоторого кода OpenGL, и не очень хороший / современный код OpenGL.

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

Во-первых, вы всегда должны рассматривать любой вызов vkDeviceWaitIdle как неправильную вещь. Единственное исключение - когда вы готовитесь уничтожить сам VkDevice. Нет никакой другой причины делать полную синхронизацию CPU / GPU c подобным образом.

Предположительно, эта синхронизация существует, так что вы можете быть уверены, что GPU завершил использование изображения перед его изменением. Это неправильная вещь. Вместо этого вы должны использовать множественную буферизацию. То есть у вас должно быть два изображения, которые вы используете. Один в настоящее время используется в процессе рендеринга, а другой передается в.

Вместо полной синхронизации устройства c, вы вместо этого синхронизируете с пакетом, который вы отправили два кадра go. То есть, если вы хотите передать данные для использования в кадре 10, сначала необходимо выполнить операцию fence-syn c с пакетом, отправленным в кадре 8. Кадр 9 все еще обрабатывается, но кадр 8 вероятно, уже сделано. Поэтому синхронизация не должна причинять слишком много вреда.

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

Независимо от того, что делает ваш вызов createBuffer, это очень похоже на плохую идею. Vulkan не является OpenGL; Vulkan отделил память от буферов / текстур, которые используют ее по определенной причине. Создание API, которые скрывают это разделение, в основном отбрасывает все это.

Точно так же никогда не снимайте карту памяти, если только вы не собираетесь уничтожить этот объект памяти. В Vulkan ( или OpenGL ) нет проблем с бесконечным отображением фрагмента памяти. Просто отобразите весь диапазон памяти и оставьте его на карте. В самом деле, вы можете просто передать сопоставленный указатель непосредственно в ваш загрузчик изображений, в зависимости от того, как память записывается кодом загрузки изображения (если он пытается прочитать данные из этого указателя, это может быть проблемой).

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

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

Использование всего вышеперечисленного значительно усложнит ваше приложение. Но вот как это должно работать.

...