Reputation: 722
I'm having a memory issue in my Vulkan application. Loading a 10mb PNG image is using almost 500mb of ram.
TextureObject* createTextureImage(const char* File) {
auto Tex = Textures.emplace_back(new TextureObject(_Driver));
//decode
unsigned error = lodepng::decode(Tex->Pixels, Tex->Width, Tex->Height, File);
//if there's an error, display it
if (error) printf("PNG Decoder error: (%i) %s", error, lodepng_error_text(error));
Tex->Empty = false;
VkDeviceSize imageSize = Tex->Width * Tex->Height * 4;
//
// Image Staging Buffer
VkBufferCreateInfo stagingBufferInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
stagingBufferInfo.size = imageSize;
stagingBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
VmaAllocationCreateInfo allocInfo = {};
allocInfo.usage = VMA_MEMORY_USAGE_CPU_ONLY;
allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
VkBuffer stagingImageBuffer = VK_NULL_HANDLE;
VmaAllocation stagingImageBufferAlloc = VK_NULL_HANDLE;
vmaCreateBuffer(_Driver->allocator, &stagingBufferInfo, &allocInfo, &stagingImageBuffer, &stagingImageBufferAlloc, nullptr);
memcpy(stagingImageBufferAlloc->GetMappedData(), Tex->Pixels.data(), static_cast<size_t>(imageSize));
Tex->Pixels.clear();
VkImageCreateInfo imageInfo = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.width = static_cast<uint32_t>(Tex->Width);
imageInfo.extent.height = static_cast<uint32_t>(Tex->Height);
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.format = VK_FORMAT_B8G8R8A8_SRGB;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
allocInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
VmaAllocationInfo imageBufferAllocInfo = {};
vmaCreateImage(_Driver->allocator, &imageInfo, &allocInfo, &Tex->Image, &Tex->Allocation, nullptr);
//
// CPU->GPU Copy
VkCommandBuffer commandBuffer = _Driver->_SceneGraph->beginSingleTimeCommands();
VkImageMemoryBarrier imgMemBarrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
imgMemBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imgMemBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
imgMemBarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imgMemBarrier.subresourceRange.baseMipLevel = 0;
imgMemBarrier.subresourceRange.levelCount = 1;
imgMemBarrier.subresourceRange.baseArrayLayer = 0;
imgMemBarrier.subresourceRange.layerCount = 1;
imgMemBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imgMemBarrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
imgMemBarrier.image = Tex->Image;
imgMemBarrier.srcAccessMask = 0;
imgMemBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(
commandBuffer,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0,
0, nullptr,
0, nullptr,
1, &imgMemBarrier);
VkBufferImageCopy region = {};
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
region.imageExtent.width = static_cast<uint32_t>(Tex->Width);
region.imageExtent.height = static_cast<uint32_t>(Tex->Height);
region.imageExtent.depth = 1;
vkCmdCopyBufferToImage(commandBuffer, stagingImageBuffer, Tex->Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
imgMemBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
imgMemBarrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
imgMemBarrier.image = Tex->Image;
imgMemBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
imgMemBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(
commandBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0,
0, nullptr,
0, nullptr,
1, &imgMemBarrier);
_Driver->_SceneGraph->endSingleTimeCommands(commandBuffer);
vmaDestroyBuffer(_Driver->allocator, stagingImageBuffer, stagingImageBufferAlloc);
VkImageViewCreateInfo textureImageViewInfo = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
textureImageViewInfo.image = Tex->Image;
textureImageViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
textureImageViewInfo.format = VK_FORMAT_B8G8R8A8_SRGB;
textureImageViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
textureImageViewInfo.subresourceRange.baseMipLevel = 0;
textureImageViewInfo.subresourceRange.levelCount = 1;
textureImageViewInfo.subresourceRange.baseArrayLayer = 0;
textureImageViewInfo.subresourceRange.layerCount = 1;
vkCreateImageView(_Driver->device, &textureImageViewInfo, nullptr, &Tex->ImageView);
VkSamplerCreateInfo samplerInfo = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO };
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.anisotropyEnable = VK_TRUE;
samplerInfo.maxAnisotropy = 16;
samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
samplerInfo.unnormalizedCoordinates = VK_FALSE;
samplerInfo.compareEnable = VK_FALSE;
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerInfo.mipLodBias = 0.0f;
samplerInfo.minLod = 0.0f;
samplerInfo.maxLod = 0.0f;
if (vkCreateSampler(_Driver->device, &samplerInfo, nullptr, &Tex->Sampler) != VK_SUCCESS) {
#ifdef _DEBUG
throw std::runtime_error("failed to create texture sampler!");
#endif
}
return Tex;
}
I've narrowed the allocation down to happening in this function. Simply swapping the image being loaded from the 10mb png over to a 1kb png drastically reduces memory consumption during this function call.
Am I doing something wrong here causing massive amounts of memory to be allocated and not freed?
Placing breakpoints on every line shows the huge allocation occurs right after the call to:
unsigned error = lodepng::decode(Tex->Pixels, Tex->Width, Tex->Height, File);
How can loading a 10mb png file use upwards of 500mb to load?
lodepng::decode
fills the following 3 variables inside Tex
unsigned int Width = 0;
unsigned int Height = 0;
std::vector<unsigned char> Pixels = {};
Even the 1kb png uses around 5mb to load which seems awfully high.
I have also tried using stb_image to load the png files and it gave the same result.
EDIT2: The 10mb png is 4096x4096 and the 1kb png is 16x16.
EDIT2: After some memory profiling, 58,000 instances of void accounting for 563,000,000 bytes get are allocated after checking one of the memory spikes and over 500,000,000 of that is sitting inside the vector I called .clear()
on. I'm not sure how to really deallocate this or if it's even that big of an issue?
Upvotes: 0
Views: 255
Reputation: 12069
How can loading a 10mb png file use upwards of 500mb to load?
Well, its 10MB of compressed data. All of the buffers you are throwing around here are after decompression, so 64MB each ...
One possible leak is that vector.clear()
isn't guaranteed to reallocate, so while you call clear()
here to free the objects it's possible that you are still sitting on 64MB of backing storage which is not released until the vector is destroyed.
Upvotes: 1