Is there a way update a texture with out using a staging buffer?
Asked Answered
K

1

5

I'm working with the https://vulkan-tutorial.com/ Depth Buffering code as a base. Made a few changes to update the command buffer every frame.

I'm using a crude way of checking fps. Not sure how accurate it really is, but I'm using this check to the 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.
            }

Any way without the texture per frame(The command buffer is still being updated per frame.) the fps is around 3500 fps. If I try to update the texture per frame the fps goes down to 350ish fps.

This is just test code with a blank texture, but this is process I'm using upload the texture the first time and to update it.

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();
}

I've been playing around with it a little, and it seems that all writing to the buffer and transitioning the layout multiple times what's really slowing things down.

For a bit more of context this the rest of the update texture process.

        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);
    }

Also what's a good base fps to have for blank updating screen for a 2d game. I'm also using vulkan for 3d, but I also want to do retro 2d stuff with it too.

Kiyohara answered 14/4, 2020 at 12:57 Comment(1)
"Also what's a good base fps to have for blank updating screen for a 2d game." You shouldn't measure performance in FPS; measure performance in time per frame. When you do, you'll find that 2.9ms is hardly unreasonable when doing a DMA transfer from the CPU every frame.Schutz
S
6

You're sending 4MB of data from the CPU to the GPU every frame. At 350 fps, that's ~1.4GB/sec of data transfer speed. That's pretty decent, all things considered.

The staging buffer isn't really the problem. Once you decide that you're going to be sending data from the CPU to the GPU, then you've forfeited some quantity of performance.

If you're really insistent on avoiding staging, you could check to see if your implementation allows linear textures to be sampled from by the shader. In that case, you can write data directly into the texture's memory. However, you'd need to double-buffer your textures, so that you're not writing to a texture that is currently in use by the GPU. But you'd need to do that anyway even with staging.

Something more effective you could do is stop doing pointless things. You need to stop:

  1. Allocating and freeing the space for your staging buffer on every upload. Create enough staging memory and buffer space at the start of your application and just keep it around.
  2. Unmapping the memory; there's pretty much no point to that in Vulkan unless you're about to delete said memory. Which again is not something you ought to be doing.
  3. Submitting a transfer operation the moment you finish building it. I don't see your CB/queue work, so I imagine that transitionImageLayout and copyBufferToImage are not merely building CB information but also submitting it. That's killing performance (especially if transitionImageLayout also submits work). You want to have as few submits per-frame as possible, ideally only one per queue that you actually use.

All of these things hurt the CPU performance of your code. They don't change the actual time of the GPU transfer, but they make the code that causes that transfer run much slower.

Schutz answered 14/4, 2020 at 14:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.