c++glslshadervulkan

How do I pass a 3D VkImage to a compute shader without the data changing?


I'm trying to pass a randomized 3D image of voxels to a compute shader, but when I run the shader the entire shader results in this: Compute Shader Output

As you can see, this does not look anything like randomly generated voxels, except for the first and half of the second voxels. I honestly have absolutely no clue as to what is happening to the data. I know its not a problem with my compute shader output to the swapchain images because I checked if other compute shaders (like just a screen of noise etc.) would work, and they did. I've narrowed down the location of the bug to the copying of data from my std::vector<std::vector<std::vector<glm::vec4>>> to the voxelImage or it could be in the passing of the voxelImage to the compute shader.

Also I have checked that the generation of the voxels is not just generating what is seen on the screen. The vector of vec4s is indeed randomized, I guarantee the error is in either of the two places I have narrowed it down to.

I will only post the code for the voxelImage and voxelImageView creation, as well as the descriptors as that is likely where the bug is occuring. (posting the rest of the code would be unnecessarily excessive and it would be far too long)

Voxel Image Creation:

void createVoxelImage() {

        VkDeviceSize imageSize = voxelDataInit.size();



        VkBuffer stagingBuffer;
        VkDeviceMemory stagingBufferMemory;
        VmaAllocation stagingAllocation;
        createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VMA_MEMORY_USAGE_GPU_ONLY, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, stagingBuffer, stagingAllocation, stagingBufferMemory);

        void* data;
        vmaMapMemory(allocator, stagingAllocation, &data);
        memcpy(data, &voxelDataInit, imageSize);
        vmaUnmapMemory(allocator, stagingAllocation);

        
        
       
        VkDeviceMemory temp; 
        createImage(voxWidth, voxHeight, voxDepth, VK_IMAGE_TYPE_3D, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, voxelImage, voxelAllocation, VMA_MEMORY_USAGE_GPU_ONLY, temp, 5);
        //vkFreeMemory(device, temp, NULL);

        //VkDeviceMemory temp;
        //createImage(voxWidth, voxHeight, voxDepth, VK_IMAGE_TYPE_3D, VK_FORMAT_B8G8R8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_STORAGE_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, voxelImage, voxelAllocation, VMA_MEMORY_USAGE_CPU_TO_GPU, temp, 5);

        transitionImageLayout(voxelImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT);
        copyBufferToImage(stagingBuffer, voxelImage, static_cast<uint32_t>(voxWidth), static_cast<uint32_t>(voxHeight), static_cast<uint32_t>(voxDepth));
        transitionImageLayout(voxelImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);

        vmaDestroyBuffer(allocator, stagingBuffer, stagingAllocation);

    }

Voxel Image View Creation:

void createVoxelImageView() {
        VkImageViewCreateInfo viewInfo{};
        viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
        viewInfo.image = voxelImage;
        viewInfo.viewType = VK_IMAGE_VIEW_TYPE_3D;
        viewInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
        //viewInfo.flags = VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT;
        viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        viewInfo.subresourceRange.baseMipLevel = 0;
        viewInfo.subresourceRange.levelCount = 1;
        viewInfo.subresourceRange.baseArrayLayer = 0;
        viewInfo.subresourceRange.layerCount = 1;

        
        
        if (vkCreateImageView(device, &viewInfo, nullptr, &voxelImageView) != VK_SUCCESS) {
            throw std::runtime_error("Failed to create image view! (voxel)");
        }
    }

Input of Data Into Shader

layout(binding = 4, rgba8) uniform image3D voxels;

Def of createImage :

void createImage(uint32_t width, uint32_t height, uint32_t depth, VkImageType imgType, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkImageLayout layout, VkMemoryPropertyFlags properties, VkImage& image, VmaAllocation& allocation, VmaMemoryUsage memUsage, VkDeviceMemory& imageMemory, int callNum) {
        VkImageCreateInfo imageInfo{};
        imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
        imageInfo.imageType = imgType;
        imageInfo.extent.width = width;
        imageInfo.extent.height = height;
        imageInfo.extent.depth = depth;
        imageInfo.mipLevels = 1;
        
        imageInfo.arrayLayers = 1;
        imageInfo.format = format;
        imageInfo.tiling = tiling;
        imageInfo.initialLayout = layout;
        imageInfo.usage = usage;
        imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
        imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
        imageInfo.flags = VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT;

        VmaAllocationCreateInfo vmaAllocInfo = {};
        vmaAllocInfo.usage = memUsage;
        vmaAllocInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;


        if (vmaCreateImage(allocator, &imageInfo, &vmaAllocInfo, &image, &allocation, nullptr) != VK_SUCCESS) {
            throw std::runtime_error(std::to_string(callNum));
            throw std::runtime_error("failed to create image!");
        }
    }

Def of transitionImageLayout :

void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, VkAccessFlagBits srcAccess, VkAccessFlagBits dstAccess, VkPipelineStageFlagBits srcStage, VkPipelineStageFlagBits dstStage) {
        VkCommandBuffer commandBuffer = beginSingleTimeCommands();

        VkImageMemoryBarrier barrier{};
        barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
        barrier.oldLayout = oldLayout;
        barrier.newLayout = newLayout;
        barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
        barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
        barrier.image = image;
        barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        barrier.subresourceRange.baseMipLevel = 0;
        barrier.subresourceRange.levelCount = 1;
        barrier.subresourceRange.baseArrayLayer = 0;
        barrier.subresourceRange.layerCount = 1;
        

        VkPipelineStageFlags sourceStage;
        VkPipelineStageFlags destinationStage;

        if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
            barrier.srcAccessMask = 0;
            barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;

            sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
            destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
        }
        
        else {
            if (srcStage == VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT) {
                barrier.srcAccessMask = 0;
            }
            else {
                barrier.srcAccessMask = srcAccess;
            }
            barrier.dstAccessMask = dstAccess;

            sourceStage = srcStage;
            destinationStage = dstStage;
            //throw std::runtime_error("Unsupported layout transition.");
        }

        vkCmdPipelineBarrier(commandBuffer, sourceStage, destinationStage, 0, 0, nullptr, 0, nullptr, 1, &barrier);

        endSingleTimeCommands(commandBuffer);
    }

Def of copyBufferToImage :

void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height, uint32_t depth){
    VkCommandBuffer commandBuffer = beginSingleTimeCommands();

    VkBufferImageCopy region{};
    region.bufferOffset = 0;
    region.bufferRowLength = 0;
    region.bufferImageHeight = 0;
    
    region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    region.imageSubresource.mipLevel = 0;
    region.imageSubresource.baseArrayLayer = 0;
    region.imageSubresource.layerCount = 1;

    region.imageOffset = { 0, 0, 0 };
    region.imageExtent = {
        width,
        height,
        depth
    };

    

    vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);

    endSingleTimeCommands(commandBuffer);
}

Solution

  • If voxelDataInit truly is a variable of type std::vector<std::vector<std::vector<glm::vec4>>>, then memcpy(data, &voxelDataInit, imageSize); is never going to work. &voxelDataInit is a pointer to a vector. And pointers to vector<T>s are always the same size (ignoring the allocator itself): the size of 3 pointers.

    Remember: a vector<T> is a pointer to an array of Ts. Or rather, it's 3 pointers into that array. But regardless, a vector<T> is not itself an array of Ts; it merely owns one. As such, copying the bytes of a vector will not copy the array itself.

    Also, the size of the vector is just the number of elements, not the number of bytes in the array.

    The best way to copy such a data structure is to stop using such a data structure. If you want a 3D array, then what you want is a one-dimensional array of size width × height × depth. You can index any particular X,Y,Z component of the array by using length,width,height to convert a 3D coordinate into a 1D coordinate.