Virtual texture (and virtual page) class for doing partial resident allocations

This commit is contained in:
saschawillems 2016-09-14 19:01:44 +02:00
parent 2de2012bac
commit 13eed42bfb

View file

@ -20,6 +20,7 @@ todos:
#include <assert.h> #include <assert.h>
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include <random>
#define GLM_FORCE_RADIANS #define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE #define GLM_FORCE_DEPTH_ZERO_TO_ONE
@ -41,13 +42,151 @@ struct Vertex {
float normal[3]; float normal[3];
}; };
// Virtual texture page as a part of the partially resident texture
// Contains memory bindings, offsets and status information
struct VirtualTexturePage
{
VkOffset3D offset;
VkExtent3D extent;
VkSparseImageMemoryBind imageMemoryBind; // Sparse image memory bind for this page
VkDeviceSize size; // Page (memory) size in bytes
uint32_t mipLevel; // Mip level that this page belongs to
uint32_t layer; // Array layer that this page belongs to
uint32_t index;
VirtualTexturePage()
{
imageMemoryBind.memory = VK_NULL_HANDLE; // Page initially not backed up by memory
}
// Allocate Vulkan memory for the virtual page
void allocate(VkDevice device, uint32_t memoryTypeIndex)
{
if (imageMemoryBind.memory != VK_NULL_HANDLE)
{
//std::cout << "Page " << index << " already allocated" << std::endl;
return;
};
imageMemoryBind = {};
VkMemoryAllocateInfo allocInfo = vkTools::initializers::memoryAllocateInfo();
allocInfo.allocationSize = size;
allocInfo.memoryTypeIndex = memoryTypeIndex;
VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &imageMemoryBind.memory));
VkImageSubresource subResource{};
subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
subResource.mipLevel = mipLevel;
subResource.arrayLayer = layer;
// Sparse image memory binding
imageMemoryBind.subresource = subResource;
imageMemoryBind.extent = extent;
imageMemoryBind.offset = offset;
}
// Release Vulkan memory allocated for this page
void release(VkDevice device)
{
if (imageMemoryBind.memory != VK_NULL_HANDLE)
{
vkFreeMemory(device, imageMemoryBind.memory, nullptr);
imageMemoryBind.memory = VK_NULL_HANDLE;
//std::cout << "Page " << index << " released" << std::endl;
}
}
};
// Virtual texture object containing all pages
struct VirtualTexture
{
VkDevice device;
VkImage image; // Texture image handle
VkBindSparseInfo bindSparseInfo; // Sparse queue binding information
std::vector<VirtualTexturePage> pages; // Contains all virtual pages of the texture
std::vector<VkSparseImageMemoryBind> sparseImageMemoryBinds; // Sparse image memory bindings of all memory-backed virtual tables
std::vector<VkSparseMemoryBind> opaqueMemoryBinds; // Sparse ópaque memory bindings for the mip tail (if present)
VkSparseImageMemoryBindInfo imageMemoryBindInfo; // Sparse image memory bind info
VkSparseImageOpaqueMemoryBindInfo opaqueMemoryBindInfo; // Sparse image opaque memory bind info (mip tail)
VirtualTexturePage* addPage(VkOffset3D offset, VkExtent3D extent, const VkDeviceSize size, const uint32_t mipLevel, uint32_t layer)
{
VirtualTexturePage newPage;
newPage.offset = offset;
newPage.extent = extent;
newPage.size = size;
newPage.mipLevel = mipLevel;
newPage.layer = layer;
newPage.index = static_cast<uint32_t>(pages.size());
newPage.imageMemoryBind.offset = offset;
newPage.imageMemoryBind.extent = extent;
pages.push_back(newPage);
return &pages.back();
}
// Call before sparse binding to update memory bind list etc.
void updateSparseBindInfo()
{
// Update list of memory-backed sparse image memory binds
sparseImageMemoryBinds.clear();
for (auto page : pages)
{
sparseImageMemoryBinds.push_back(page.imageMemoryBind);
}
// Update sparse bind info
bindSparseInfo = vkTools::initializers::bindSparseInfo();
// todo: Semaphore for queue submission
// bindSparseInfo.signalSemaphoreCount = 1;
// bindSparseInfo.pSignalSemaphores = &bindSparseSemaphore;
// Image memory binds
imageMemoryBindInfo.image = image;
imageMemoryBindInfo.bindCount = static_cast<uint32_t>(sparseImageMemoryBinds.size());
imageMemoryBindInfo.pBinds = sparseImageMemoryBinds.data();
bindSparseInfo.imageBindCount = (imageMemoryBindInfo.bindCount > 0) ? 1 : 0;
bindSparseInfo.pImageBinds = &imageMemoryBindInfo;
// Opaque image memory binds (mip tail)
opaqueMemoryBindInfo.image = image;
opaqueMemoryBindInfo.bindCount = static_cast<uint32_t>(opaqueMemoryBinds.size());
opaqueMemoryBindInfo.pBinds = opaqueMemoryBinds.data();
bindSparseInfo.imageOpaqueBindCount = (opaqueMemoryBindInfo.bindCount > 0) ? 1 : 0;
bindSparseInfo.pImageOpaqueBinds = &opaqueMemoryBindInfo;
uint32_t memBackedPages(0);
for (auto page : pages)
{
if (page.imageMemoryBind.memory != VK_NULL_HANDLE)
{
memBackedPages++;
}
}
std::cout << "Bound " << memBackedPages << " memory backed virtual pages " << std::endl;
}
// Release all Vulkan resources
void destroy()
{
for (auto page : pages)
{
page.release(device);
}
for (auto bind : opaqueMemoryBinds)
{
vkFreeMemory(device, bind.memory, nullptr);
}
}
};
uint32_t memoryTypeIndex;
class VulkanExample : public VulkanExampleBase class VulkanExample : public VulkanExampleBase
{ {
public: public:
//todo: comments //todo: comments
struct SparseTexture { struct SparseTexture : VirtualTexture {
VkSampler sampler; VkSampler sampler;
VkImage image;
VkImageLayout imageLayout; VkImageLayout imageLayout;
VkImageView view; VkImageView view;
VkDescriptorImageInfo descriptor; VkDescriptorImageInfo descriptor;
@ -55,10 +194,6 @@ public:
uint32_t width, height; uint32_t width, height;
uint32_t mipLevels; uint32_t mipLevels;
uint32_t layerCount; uint32_t layerCount;
std::vector<VkSparseImageMemoryBind> residencyMemoryBinds; // Sparse image mempory bindings for the resident part of the image
std::vector<VkSparseMemoryBind> opaqueMemoryBinds; // Sparse memory bindings for the mip tail (if present)
VkSparseImageMemoryBindInfo imageMemoryBindInfo; // Bind info for queue
VkSparseImageOpaqueMemoryBindInfo opaqueMemoryBindInfo; // Opaque bind info for queue
} texture; } texture;
struct { struct {
@ -89,7 +224,6 @@ public:
VkDescriptorSetLayout descriptorSetLayout; VkDescriptorSetLayout descriptorSetLayout;
//todo: comment //todo: comment
VkBindSparseInfo bindSparseInfo;
VkSemaphore bindSparseSemaphore = VK_NULL_HANDLE; VkSemaphore bindSparseSemaphore = VK_NULL_HANDLE;
// Device features to be enabled for this example // Device features to be enabled for this example
@ -129,74 +263,6 @@ public:
uniformBufferVS.destroy(); uniformBufferVS.destroy();
} }
// Create an image memory barrier for changing the layout of
// an image and put it into an active command buffer
void setImageLayout(VkCommandBuffer cmdBuffer, VkImage image, VkImageAspectFlags aspectMask, VkImageLayout oldImageLayout, VkImageLayout newImageLayout, VkImageSubresourceRange subresourceRange)
{
// Create an image barrier object
VkImageMemoryBarrier imageMemoryBarrier = vkTools::initializers::imageMemoryBarrier();;
imageMemoryBarrier.oldLayout = oldImageLayout;
imageMemoryBarrier.newLayout = newImageLayout;
imageMemoryBarrier.image = image;
imageMemoryBarrier.subresourceRange = subresourceRange;
// Only sets masks for layouts used in this example
// For a more complete version that can be used with other layouts see vkTools::setImageLayout
// Source layouts (old)
switch (oldImageLayout)
{
case VK_IMAGE_LAYOUT_UNDEFINED:
// Only valid as initial layout, memory contents are not preserved
// Can be accessed directly, no source dependency required
imageMemoryBarrier.srcAccessMask = 0;
break;
case VK_IMAGE_LAYOUT_PREINITIALIZED:
// Only valid as initial layout for linear images, preserves memory contents
// Make sure host writes to the image have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
// Old layout is transfer destination
// Make sure any writes to the image have been finished
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
break;
}
// Target layouts (new)
switch (newImageLayout)
{
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
// Transfer source (copy, blit)
// Make sure any reads from the image have been finished
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
// Transfer destination (copy, blit)
// Make sure any writes to the image have been finished
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
// Shader read (sampler, input attachment)
imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
break;
}
// Put barrier on top of pipeline
VkPipelineStageFlags srcStageFlags = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkPipelineStageFlags destStageFlags = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
// Put barrier inside setup command buffer
vkCmdPipelineBarrier(
cmdBuffer,
srcStageFlags,
destStageFlags,
VK_FLAGS_NONE,
0, nullptr,
0, nullptr,
1, &imageMemoryBarrier);
}
glm::uvec3 alignedDivision(const VkExtent3D& extent, const VkExtent3D& granularity) glm::uvec3 alignedDivision(const VkExtent3D& extent, const VkExtent3D& granularity)
{ {
glm::uvec3 res; glm::uvec3 res;
@ -208,6 +274,7 @@ public:
void prepareSparseTexture(uint32_t width, uint32_t height, uint32_t layerCount, VkFormat format) void prepareSparseTexture(uint32_t width, uint32_t height, uint32_t layerCount, VkFormat format)
{ {
texture.device = vulkanDevice->logicalDevice;
texture.width = width; texture.width = width;
texture.height = height; texture.height = height;
texture.mipLevels = floor(log2(std::max(width, height))) + 1; //todo texture.mipLevels = floor(log2(std::max(width, height))) + 1; //todo
@ -323,7 +390,7 @@ public:
// todo: // todo:
// Calculate number of required sparse memory bindings by alignment // Calculate number of required sparse memory bindings by alignment
assert((sparseImageMemoryReqs.size % sparseImageMemoryReqs.alignment) == 0); assert((sparseImageMemoryReqs.size % sparseImageMemoryReqs.alignment) == 0);
uint32_t memoryTypeIndex = vulkanDevice->getMemoryType(sparseImageMemoryReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); memoryTypeIndex = vulkanDevice->getMemoryType(sparseImageMemoryReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
// Get sparse bindings // Get sparse bindings
uint32_t sparseBindsCount = static_cast<uint32_t>(sparseImageMemoryReqs.size / sparseImageMemoryReqs.alignment); uint32_t sparseBindsCount = static_cast<uint32_t>(sparseImageMemoryReqs.size / sparseImageMemoryReqs.alignment);
@ -365,36 +432,28 @@ public:
{ {
for (uint32_t x = 0; x < sparseBindCounts.x; x++) for (uint32_t x = 0; x < sparseBindCounts.x; x++)
{ {
// Offset
if ((x % 2 == 1) || (y % 2 == 1))
continue;
VkOffset3D offset; VkOffset3D offset;
offset.x = x * imageGranularity.width; offset.x = x * imageGranularity.width;
offset.y = y * imageGranularity.height; offset.y = y * imageGranularity.height;
offset.z = z * imageGranularity.depth; offset.z = z * imageGranularity.depth;
// Size of the page
VkExtent3D extent; VkExtent3D extent;
extent.width = (x == sparseBindCounts.x - 1) ? lastBlockExtent.x : imageGranularity.width; extent.width = (x == sparseBindCounts.x - 1) ? lastBlockExtent.x : imageGranularity.width;
extent.height = (y == sparseBindCounts.y - 1) ? lastBlockExtent.y : imageGranularity.height; extent.height = (y == sparseBindCounts.y - 1) ? lastBlockExtent.y : imageGranularity.height;
extent.depth = (z == sparseBindCounts.z - 1) ? lastBlockExtent.z : imageGranularity.depth; extent.depth = (z == sparseBindCounts.z - 1) ? lastBlockExtent.z : imageGranularity.depth;
// Allocate memory for this sparse block // Add new virtual page
VkMemoryAllocateInfo allocInfo = vkTools::initializers::memoryAllocateInfo(); VirtualTexturePage *newPage = texture.addPage(offset, extent, sparseImageMemoryReqs.alignment, mipLevel, layer);
allocInfo.allocationSize = sparseImageMemoryReqs.alignment; newPage->imageMemoryBind.subresource = subResource;
allocInfo.memoryTypeIndex = memoryTypeIndex;
VkDeviceMemory deviceMemory; if ((x % 2 == 1) || (y % 2 == 1))
VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &deviceMemory)); {
// Allocate memory for this virtual page
newPage->allocate(device, memoryTypeIndex);
}
// Sparse image memory binding index++;
VkSparseImageMemoryBind sparseImageMemoryBind{};
sparseImageMemoryBind.subresource = subResource;
sparseImageMemoryBind.extent = extent;
sparseImageMemoryBind.offset = offset;
sparseImageMemoryBind.memory = deviceMemory;
texture.residencyMemoryBinds.push_back(sparseImageMemoryBind);
} }
} }
} }
@ -421,6 +480,10 @@ public:
} }
} // end layers and mips } // end layers and mips
std::cout << "Texture info:" << std::endl;
std::cout << "\tDim: " << texture.width << " x " << texture.height << std::endl;
std::cout << "\tVirtual pages: " << texture.pages.size() << std::endl;
// Check if format has one mip tail for all layers // Check if format has one mip tail for all layers
if ((sparseMemoryReq.formatProperties.flags & VK_SPARSE_IMAGE_FORMAT_SINGLE_MIPTAIL_BIT) && (sparseMemoryReq.imageMipTailFirstLod < texture.mipLevels)) if ((sparseMemoryReq.formatProperties.flags & VK_SPARSE_IMAGE_FORMAT_SINGLE_MIPTAIL_BIT) && (sparseMemoryReq.imageMipTailFirstLod < texture.mipLevels))
{ {
@ -446,31 +509,11 @@ public:
VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &bindSparseSemaphore)); VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &bindSparseSemaphore));
// Prepare bind sparse info for reuse in queue submission // Prepare bind sparse info for reuse in queue submission
bindSparseInfo = vkTools::initializers::bindSparseInfo(); texture.updateSparseBindInfo();
//bindSparseInfo.signalSemaphoreCount = 1;
//bindSparseInfo.pSignalSemaphores = &bindSparseSemaphore;
if (texture.residencyMemoryBinds.size() > 0)
{
texture.imageMemoryBindInfo.image = texture.image;
texture.imageMemoryBindInfo.bindCount = static_cast<uint32_t>(texture.residencyMemoryBinds.size());
texture.imageMemoryBindInfo.pBinds = texture.residencyMemoryBinds.data();
bindSparseInfo.imageBindCount = 1;
bindSparseInfo.pImageBinds = &texture.imageMemoryBindInfo;
}
if (texture.opaqueMemoryBinds.size() > 0)
{
texture.opaqueMemoryBindInfo.image = texture.image;
texture.opaqueMemoryBindInfo.bindCount = static_cast<uint32_t>(texture.opaqueMemoryBinds.size());
texture.opaqueMemoryBindInfo.pBinds = texture.opaqueMemoryBinds.data();
bindSparseInfo.imageOpaqueBindCount = 1;
bindSparseInfo.pImageOpaqueBinds = &texture.opaqueMemoryBindInfo;
}
// Bind to queue // Bind to queue
// todo: in draw? // todo: in draw?
vkQueueBindSparse(queue, 1, &bindSparseInfo, VK_NULL_HANDLE); vkQueueBindSparse(queue, 1, &texture.bindSparseInfo, VK_NULL_HANDLE);
//todo: use sparse bind semaphore //todo: use sparse bind semaphore
vkQueueWaitIdle(queue); vkQueueWaitIdle(queue);
@ -517,16 +560,7 @@ public:
vkDestroyImageView(device, texture.view, nullptr); vkDestroyImageView(device, texture.view, nullptr);
vkDestroyImage(device, texture.image, nullptr); vkDestroyImage(device, texture.image, nullptr);
vkDestroySampler(device, texture.sampler, nullptr); vkDestroySampler(device, texture.sampler, nullptr);
// Release sparse image memory texture.destroy();
for (auto residencyBind : texture.residencyMemoryBinds)
{
vkFreeMemory(device, residencyBind.memory, nullptr);
}
// Release sparse opqaue memory (mip tail)
for (auto opaqueBind : texture.opaqueMemoryBinds)
{
vkFreeMemory(device, opaqueBind.memory, nullptr);
}
} }
void buildCommandBuffers() void buildCommandBuffers()
@ -897,6 +931,41 @@ public:
updateTextOverlay(); updateTextOverlay();
} }
// Clear all pages of the virtual texture
// todo: just for testing
void flushVirtualTexture()
{
vkDeviceWaitIdle(device);
for (auto& page : texture.pages)
{
page.release(device);
}
texture.updateSparseBindInfo();
vkQueueBindSparse(queue, 1, &texture.bindSparseInfo, VK_NULL_HANDLE);
//todo: use sparse bind semaphore
vkQueueWaitIdle(queue);
}
// Randomly fill pages
// todo: just for testing
void fillVirtualTexture()
{
vkDeviceWaitIdle(device);
std::default_random_engine rndEngine(std::random_device{}());
std::uniform_real_distribution<float> rndDist(0.0f, 1.0f);
for (auto& page : texture.pages)
{
if (rndDist(rndEngine) < 0.5f)
{
page.allocate(device, memoryTypeIndex);
}
}
texture.updateSparseBindInfo();
vkQueueBindSparse(queue, 1, &texture.bindSparseInfo, VK_NULL_HANDLE);
//todo: use sparse bind semaphore
vkQueueWaitIdle(queue);
}
virtual void keyPressed(uint32_t keyCode) virtual void keyPressed(uint32_t keyCode)
{ {
switch (keyCode) switch (keyCode)
@ -909,6 +978,12 @@ public:
case GAMEPAD_BUTTON_L1: case GAMEPAD_BUTTON_L1:
changeLodBias(-0.1f); changeLodBias(-0.1f);
break; break;
case KEY_F:
flushVirtualTexture();
break;
case KEY_N:
fillVirtualTexture();
break;
} }
} }