有没有一种方法可以在不使用暂存缓冲区的情况下更新纹理?

dhz*_*dhz 5 c++ textures game-engine vulkan

我正在使用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.
            }
Run Code Online (Sandbox Code Playgroud)

任何没有每帧纹理的方式(命令缓冲区仍在每帧更新。) fps 约为 3500 fps。如果我尝试更新每帧的纹理,fps 会下降到 350 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();
}
Run Code Online (Sandbox Code Playgroud)

我已经尝试了一下,似乎所有写入缓冲区并多次转换布局的操作都真正减慢了速度。

对于更多上下文,这是更新纹理过程的其余部分。

        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);
    }
Run Code Online (Sandbox Code Playgroud)

另外,对于 2D 游戏的空白更新屏幕来说,良好的基本 fps 是多少。我还使用 vulkan 进行 3d 制作,但我也想用它来做复古 2d 的东西。

Nic*_*las 5

每帧从 CPU 向 GPU 发送 4MB 的数据。在 350 fps 下,数据传输速度约为 1.4GB/秒。考虑到所有因素,这相当不错。

暂存缓冲区并不是真正的问题。一旦您决定将数据从 CPU 发送到 GPU,那么您就会丧失一定的性能。

如果您确实坚持要避免分段,则可以检查您的实现是否允许着色器对线性纹理进行采样。在这种情况下,您可以将数据直接写入纹理的内存中。但是,您需要对纹理进行双缓冲,这样您就不会写入 GPU 当前正在使用的纹理。但无论如何,即使有分期,你也需要这样做。

你可以做的更有效的事情就是停止做毫无意义的事情。你需要停止:

  1. 每次上传时分配和释放暂存缓冲区的空间。在应用程序启动时创建足够的暂存内存和缓冲区空间并保留它。
  2. 取消内存映射;在 Vulkan 中,这几乎没有任何意义,除非你要删除所述内存。这又不是你应该做的事情。
  3. 构建完成后立即提交传输操作。我没有看到你的 CB/队列工作,所以我想这transitionImageLayout不仅仅是copyBufferToImage构建 CB 信息,而且还提交它。这就是扼杀性能的表现(特别是如果transitionImageLayout还提交工作的话)。您希望每帧的提交次数尽可能少,理想情况下您实际使用的每个队列只有一个提交。

所有这些都会损害代码的 CPU 性能。它们不会改变 GPU 传输的实际时间,但会使导致传输的代码运行速度变慢。