Есть ли способ обновить текстуру без использования промежуточного буфера? - PullRequest
3 голосов
/ 14 апреля 2020

Я работаю с https://vulkan-tutorial.com/ кодом буферизации глубины в качестве базы. Сделано несколько изменений для обновления буфера команд каждый кадр.

Я использую грубый способ проверки fps. Не уверен, насколько он точен на самом деле, но я использую эту проверку для fps.

            static auto startTime = std::chrono::high_resolution_clock::now();

            auto currentTime = std::chrono::high_resolution_clock::now();
            float time = std::chrono::duration<float, std::chrono::seconds::period>(currentTime - startTime).count();

            if (time < 1)
            {
                counter++;
            }
            else
            {
                int a = 34; //breakpoint put here to check the counter fps.
            }

В любом случае без текстуры на кадр (буфер команд все еще обновляется на кадр.) Fps около 3500 кадров в секунду. Если я пытаюсь обновить текстуру для каждого кадра, частота кадров падает до 350i sh fps.

Это просто тестовый код с пустой текстурой, но это процесс, который я использую для загрузки текстуры в первый раз и чтобы обновить его.

void createTextureImage()
{
    int Width = 1024;
    int Height = 1024;

    VkDeviceSize imageSize = Width * Height * sizeof(Pixel);
    PixelImage.resize(Width * Height, Pixel(0xFF, 0x00, 0x00));

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

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

    createImage(Width, Height, 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>(Width), static_cast<uint32_t>(Height));
    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);
}

void UpdateTexture()
{
    VkDeviceSize imageSize = 1024 * 1024 * sizeof(Pixel);
    memset(&PixelImage[0], 0xFF, imageSize);

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

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

    transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
    copyBufferToImage(stagingBuffer, textureImage, static_cast<uint32_t>(1024), static_cast<uint32_t>(1024));
    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);

    vkDestroyImageView(device, textureImageView, nullptr);
    CreateImageView();
}

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

Для большего контекста, это оставшаяся часть процесса обновления текстур.

        UpdateTexture();
    for (size_t i = 0; i < vulkanFrame.size(); i++)
    {
        VkDescriptorBufferInfo bufferInfo = {};
        bufferInfo.buffer = uniformBuffers[i];
        bufferInfo.offset = 0;
        bufferInfo.range = sizeof(UniformBufferObject);

        VkDescriptorImageInfo imageInfo = {};
        imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
        imageInfo.imageView = textureImageView;
        imageInfo.sampler = textureSampler;

        std::array<VkWriteDescriptorSet, 2> descriptorWrites = {};

        descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
        descriptorWrites[0].dstSet = descriptorSets[i];
        descriptorWrites[0].dstBinding = 0;
        descriptorWrites[0].dstArrayElement = 0;
        descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
        descriptorWrites[0].descriptorCount = 1;
        descriptorWrites[0].pBufferInfo = &bufferInfo;

        descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
        descriptorWrites[1].dstSet = descriptorSets[i];
        descriptorWrites[1].dstBinding = 1;
        descriptorWrites[1].dstArrayElement = 0;
        descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
        descriptorWrites[1].descriptorCount = 1;
        descriptorWrites[1].pImageInfo = &imageInfo;

        vkUpdateDescriptorSets(device, static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);
    }

Кроме того, какова хорошая базовая частота кадров для пустого экрана обновления для 2D-игры. Я также использую vulkan для 3d, но я также хочу делать ретро 2d с ним тоже.

1 Ответ

3 голосов
/ 14 апреля 2020

Вы отправляете 4 МБ данных из ЦП в ГП каждый кадр. При скорости 350 кадров в секунду это ~ 1,4 ГБ / с c скорости передачи данных. Это вполне прилично, учитывая все обстоятельства.

Буфер промежуточного уровня на самом деле не проблема. Как только вы решите отправлять данные из ЦП в графический процессор, вы потеряли некоторое количество производительности.

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

Что-то более эффективное, что вы могли бы сделать, это перестать делать бессмысленные вещи. Вам нужно остановить:

  1. Выделение и освобождение места для вашего промежуточного буфера на при каждой загрузке . Создайте достаточное количество промежуточной памяти и буферного пространства в начале вашего приложения и просто сохраняйте его.
  2. Распаковка памяти; в Вулкане нет особого смысла, если только вы не собираетесь удалить указанную память. Что опять-таки не то, что вы должны делать.
  3. Отправка операции переноса в тот момент, когда вы заканчиваете sh сборку. Я не вижу работы вашего CB / очереди, поэтому я представляю, что transitionImageLayout и copyBufferToImage не просто создают информацию CB, но и отправляют ее. Это убийственная производительность (, особенно , если transitionImageLayout также отправляет работу). Вы хотите иметь как можно меньше отправок на кадр, в идеале только одну на очередь, которую вы фактически используете.

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

...