Split into header and implementation

This commit is contained in:
Sascha Willems 2020-07-14 20:17:48 +02:00
parent 77322190ea
commit 3b117fd2dc
2 changed files with 932 additions and 872 deletions

View file

@ -10,53 +10,27 @@
* Note : This sample is work-in-progress and works basically, but it's not yet finished
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <vector>
#include <algorithm>
#include <random>
#include <chrono>
#include "texturesparseresidency.h"
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
/*
Virtual texture page
Contains all functions and objects for a single page of a virtual texture
*/
#include <vulkan/vulkan.h>
#include "vulkanexamplebase.h"
#include "VulkanDevice.hpp"
#include "VulkanBuffer.hpp"
#include "VulkanModel.hpp"
#define ENABLE_VALIDATION false
// Virtual texture page as a part of the partially resident texture
// Contains memory bindings, offsets and status information
struct VirtualTexturePage
VirtualTexturePage::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;
// Pages are initially not backed up by memory (non-resident)
imageMemoryBind.memory = VK_NULL_HANDLE;
}
VirtualTexturePage()
{
imageMemoryBind.memory = VK_NULL_HANDLE; // Page initially not backed up by memory
}
bool resident()
{
bool VirtualTexturePage::resident()
{
return (imageMemoryBind.memory != VK_NULL_HANDLE);
}
}
// Allocate Vulkan memory for the virtual page
void allocate(VkDevice device, uint32_t memoryTypeIndex)
{
// Allocate Vulkan memory for the virtual page
void VirtualTexturePage::allocate(VkDevice device, uint32_t memoryTypeIndex)
{
if (imageMemoryBind.memory != VK_NULL_HANDLE)
{
return;
@ -78,42 +52,26 @@ struct VirtualTexturePage
imageMemoryBind.subresource = subResource;
imageMemoryBind.extent = extent;
imageMemoryBind.offset = offset;
}
}
// Release Vulkan memory allocated for this page
void release(VkDevice device)
{
// Release Vulkan memory allocated for this page
void VirtualTexturePage::release(VkDevice device)
{
if (imageMemoryBind.memory != VK_NULL_HANDLE)
{
vkFreeMemory(device, imageMemoryBind.memory, nullptr);
imageMemoryBind.memory = VK_NULL_HANDLE;
}
}
};
}
// Virtual texture object containing all pages
struct VirtualTexture
/*
Virtual texture
Contains the virtual pages and memory binding information for a whole virtual texture
*/
VirtualTexturePage* VirtualTexture::addPage(VkOffset3D offset, VkExtent3D extent, const VkDeviceSize size, const uint32_t mipLevel, uint32_t layer)
{
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)
uint32_t mipTailStart; // First mip level in mip tail
VkSparseImageMemoryRequirements sparseImageMemoryRequirements; // @todo: Comment
// @todo: comment
struct MipTailInfo {
bool singleMipTail;
bool alingedMipSize;
} mipTailInfo;
VirtualTexturePage* addPage(VkOffset3D offset, VkExtent3D extent, const VkDeviceSize size, const uint32_t mipLevel, uint32_t layer)
{
VirtualTexturePage newPage;
VirtualTexturePage newPage{};
newPage.offset = offset;
newPage.extent = extent;
newPage.size = size;
@ -125,11 +83,11 @@ struct VirtualTexture
newPage.imageMemoryBind.extent = extent;
pages.push_back(newPage);
return &pages.back();
}
}
// Call before sparse binding to update memory bind list etc.
void updateSparseBindInfo()
{
// Call before sparse binding to update memory bind list etc.
void VirtualTexture::updateSparseBindInfo()
{
// Update list of memory-backed sparse image memory binds
//sparseImageMemoryBinds.resize(pages.size());
sparseImageMemoryBinds.clear();
@ -157,11 +115,11 @@ struct VirtualTexture
opaqueMemoryBindInfo.pBinds = opaqueMemoryBinds.data();
bindSparseInfo.imageOpaqueBindCount = (opaqueMemoryBindInfo.bindCount > 0) ? 1 : 0;
bindSparseInfo.pImageOpaqueBinds = &opaqueMemoryBindInfo;
}
}
// Release all Vulkan resources
void destroy()
{
// Release all Vulkan resources
void VirtualTexture::destroy()
{
for (auto page : pages)
{
page.release(device);
@ -170,51 +128,13 @@ struct VirtualTexture
{
vkFreeMemory(device, bind.memory, nullptr);
}
}
};
}
uint32_t memoryTypeIndex;
class VulkanExample : public VulkanExampleBase
/*
Vulkan Example class
*/
VulkanExample::VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
{
public:
//todo: comments
struct SparseTexture : VirtualTexture {
VkSampler sampler;
VkImageLayout imageLayout;
VkImageView view;
VkDescriptorImageInfo descriptor;
VkFormat format;
uint32_t width, height;
uint32_t mipLevels;
uint32_t layerCount;
} texture;
vks::VertexLayout vertexLayout = vks::VertexLayout({
vks::VERTEX_COMPONENT_POSITION,
vks::VERTEX_COMPONENT_NORMAL,
vks::VERTEX_COMPONENT_UV,
});
vks::Model plane;
struct UboVS {
glm::mat4 projection;
glm::mat4 model;
glm::vec4 viewPos;
float lodBias = 0.0f;
} uboVS;
vks::Buffer uniformBufferVS;
VkPipeline pipeline;
VkPipelineLayout pipelineLayout;
VkDescriptorSet descriptorSet;
VkDescriptorSetLayout descriptorSetLayout;
//todo: comment
VkSemaphore bindSparseSemaphore = VK_NULL_HANDLE;
VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION)
{
title = "Sparse texture residency";
std::cout.imbue(std::locale(""));
camera.type = Camera::CameraType::lookat;
@ -222,10 +142,10 @@ public:
camera.setRotation(glm::vec3(0.0f, 180.0f, 0.0f));
camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
settings.overlay = true;
}
}
~VulkanExample()
{
VulkanExample::~VulkanExample()
{
// Clean up used Vulkan resources
// Note : Inherited destructor cleans up resources stored in base class
destroyTextureImage(texture);
@ -235,10 +155,10 @@ public:
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
plane.destroy();
uniformBufferVS.destroy();
}
}
virtual void getEnabledFeatures()
{
void VulkanExample::getEnabledFeatures()
{
if (deviceFeatures.sparseBinding && deviceFeatures.sparseResidencyImage2D) {
enabledFeatures.shaderResourceResidency = VK_TRUE;
enabledFeatures.shaderResourceMinLod = VK_TRUE;
@ -248,19 +168,19 @@ public:
else {
std::cout << "Sparse binding not supported" << std::endl;
}
}
}
glm::uvec3 alignedDivision(const VkExtent3D& extent, const VkExtent3D& granularity)
{
glm::uvec3 VulkanExample::alignedDivision(const VkExtent3D& extent, const VkExtent3D& granularity)
{
glm::uvec3 res;
res.x = extent.width / granularity.width + ((extent.width % granularity.width) ? 1u : 0u);
res.y = extent.height / granularity.height + ((extent.height % granularity.height) ? 1u : 0u);
res.z = extent.depth / granularity.depth + ((extent.depth % granularity.depth) ? 1u : 0u);
return res;
}
}
void prepareSparseTexture(uint32_t width, uint32_t height, uint32_t layerCount, VkFormat format)
{
void VulkanExample::prepareSparseTexture(uint32_t width, uint32_t height, uint32_t layerCount, VkFormat format)
{
texture.device = vulkanDevice->logicalDevice;
texture.width = width;
texture.height = height;
@ -380,10 +300,10 @@ public:
return;
}
// todo:
// @todo: proper comment
// Calculate number of required sparse memory bindings by alignment
assert((sparseImageMemoryReqs.size % sparseImageMemoryReqs.alignment) == 0);
memoryTypeIndex = vulkanDevice->getMemoryType(sparseImageMemoryReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
texture.memoryTypeIndex = vulkanDevice->getMemoryType(sparseImageMemoryReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
// Get sparse bindings
uint32_t sparseBindsCount = static_cast<uint32_t>(sparseImageMemoryReqs.size / sparseImageMemoryReqs.alignment);
@ -458,7 +378,7 @@ public:
// Allocate memory for the mip tail
VkMemoryAllocateInfo allocInfo = vks::initializers::memoryAllocateInfo();
allocInfo.allocationSize = sparseMemoryReq.imageMipTailSize;
allocInfo.memoryTypeIndex = memoryTypeIndex;
allocInfo.memoryTypeIndex = texture.memoryTypeIndex;
VkDeviceMemory deviceMemory;
VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &deviceMemory));
@ -483,7 +403,7 @@ public:
// Allocate memory for the mip tail
VkMemoryAllocateInfo allocInfo = vks::initializers::memoryAllocateInfo();
allocInfo.allocationSize = sparseMemoryReq.imageMipTailSize;
allocInfo.memoryTypeIndex = memoryTypeIndex;
allocInfo.memoryTypeIndex = texture.memoryTypeIndex;
VkDeviceMemory deviceMemory;
VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &deviceMemory));
@ -514,7 +434,7 @@ public:
VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
sampler.magFilter = VK_FILTER_LINEAR;
sampler.minFilter = VK_FILTER_LINEAR;
sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
@ -524,7 +444,7 @@ public:
sampler.maxLod = static_cast<float>(texture.mipLevels);
sampler.maxAnisotropy = vulkanDevice->features.samplerAnisotropy ? vulkanDevice->properties.limits.maxSamplerAnisotropy : 1.0f;
sampler.anisotropyEnable = false;
sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
sampler.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &texture.sampler));
// Create image view
@ -545,19 +465,19 @@ public:
texture.descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
texture.descriptor.imageView = texture.view;
texture.descriptor.sampler = texture.sampler;
}
}
// Free all Vulkan resources used a texture object
void destroyTextureImage(SparseTexture texture)
{
// Free all Vulkan resources used a texture object
void VulkanExample::destroyTextureImage(SparseTexture texture)
{
vkDestroyImageView(device, texture.view, nullptr);
vkDestroyImage(device, texture.image, nullptr);
vkDestroySampler(device, texture.sampler, nullptr);
texture.destroy();
}
}
void buildCommandBuffers()
{
void VulkanExample::buildCommandBuffers()
{
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
VkClearValue clearValues[2];
@ -601,24 +521,24 @@ public:
VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
}
}
}
void draw()
{
void VulkanExample::draw()
{
VulkanExampleBase::prepareFrame();
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
VulkanExampleBase::submitFrame();
}
}
void loadAssets()
{
void VulkanExample::loadAssets()
{
plane.loadFromFile(getAssetPath() + "models/plane_z.obj", vertexLayout, 1.0f, vulkanDevice, queue);
}
}
void setupDescriptorPool()
{
void VulkanExample::setupDescriptorPool()
{
// Example uses one ubo and one image sampler
std::vector<VkDescriptorPoolSize> poolSizes =
{
@ -633,10 +553,10 @@ public:
2);
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
}
}
void setupDescriptorSetLayout()
{
void VulkanExample::setupDescriptorSetLayout()
{
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings =
{
// Binding 0 : Vertex shader uniform buffer
@ -664,10 +584,10 @@ public:
1);
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout));
}
}
void setupDescriptorSet()
{
void VulkanExample::setupDescriptorSet()
{
VkDescriptorSetAllocateInfo allocInfo =
vks::initializers::descriptorSetAllocateInfo(
descriptorPool,
@ -693,10 +613,10 @@ public:
};
vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL);
}
}
void preparePipelines()
{
void VulkanExample::preparePipelines()
{
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
@ -738,11 +658,11 @@ public:
shaderStages[0] = loadShader(getShadersPath() + "texturesparseresidency/sparseresidency.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
shaderStages[1] = loadShader(getShadersPath() + "texturesparseresidency/sparseresidency.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipeline));
}
}
// Prepare and initialize uniform buffer containing shader uniforms
void prepareUniformBuffers()
{
// Prepare and initialize uniform buffer containing shader uniforms
void VulkanExample::prepareUniformBuffers()
{
// Vertex shader uniform buffer block
VK_CHECK_RESULT(vulkanDevice->createBuffer(
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
@ -752,10 +672,10 @@ public:
&uboVS));
updateUniformBuffers();
}
}
void updateUniformBuffers()
{
void VulkanExample::updateUniformBuffers()
{
uboVS.projection = camera.matrices.perspective;
uboVS.model = camera.matrices.view;
uboVS.viewPos = camera.viewPos;
@ -763,10 +683,10 @@ public:
VK_CHECK_RESULT(uniformBufferVS.map());
memcpy(uniformBufferVS.mapped, &uboVS, sizeof(uboVS));
uniformBufferVS.unmap();
}
}
void prepare()
{
void VulkanExample::prepare()
{
VulkanExampleBase::prepare();
// Check if the GPU supports sparse residency for 2D images
if (!vulkanDevice->features.sparseResidencyImage2D) {
@ -782,20 +702,20 @@ public:
setupDescriptorSet();
buildCommandBuffers();
prepared = true;
}
}
virtual void render()
{
void VulkanExample::render()
{
if (!prepared)
return;
draw();
if (camera.updated) {
updateUniformBuffers();
}
}
}
void uploadContent(VirtualTexturePage page, VkImage image)
{
void VulkanExample::uploadContent(VirtualTexturePage page, VkImage image)
{
// Generate some random image data and upload as a buffer
const size_t bufferSize = 4 * page.extent.width * page.extent.height;
@ -844,10 +764,10 @@ public:
vulkanDevice->flushCommandBuffer(copyCmd, queue);
imageBuffer.destroy();
}
}
void fillRandomPages()
{
void VulkanExample::fillRandomPages()
{
vkDeviceWaitIdle(device);
std::default_random_engine rndEngine(std::random_device{}());
@ -858,7 +778,7 @@ public:
if (rndDist(rndEngine) < 0.5f) {
continue;
}
page.allocate(device, memoryTypeIndex);
page.allocate(device, texture.memoryTypeIndex);
updatedPages.push_back(page);
}
@ -873,10 +793,10 @@ public:
for (auto &page: updatedPages) {
uploadContent(page, texture.image);
}
}
}
void fillMipTail()
{
void VulkanExample::fillMipTail()
{
//@todo: WIP
VkDeviceSize imageMipTailSize = texture.sparseImageMemoryRequirements.imageMipTailSize;
VkDeviceSize imageMipTailOffset = texture.sparseImageMemoryRequirements.imageMipTailOffset;
@ -887,7 +807,7 @@ public:
VkMemoryAllocateInfo allocInfo = vks::initializers::memoryAllocateInfo();
allocInfo.allocationSize = imageMipTailSize;
allocInfo.memoryTypeIndex = memoryTypeIndex;
allocInfo.memoryTypeIndex = texture.memoryTypeIndex;
VK_CHECK_RESULT(vkAllocateMemory(device, &allocInfo, nullptr, &mipTailimageMemoryBind.memory));
uint32_t mipLevel = texture.sparseImageMemoryRequirements.imageMipTailFirstLod;
@ -962,10 +882,10 @@ public:
imageBuffer.destroy();
}
}
}
void flushRandomPages()
{
void VulkanExample::flushRandomPages()
{
vkDeviceWaitIdle(device);
std::default_random_engine rndEngine(std::random_device{}());
@ -987,10 +907,10 @@ public:
VK_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &fence));
vkQueueBindSparse(queue, 1, &texture.bindSparseInfo, fence);
vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
}
}
virtual void OnUpdateUIOverlay(vks::UIOverlay* overlay)
{
void VulkanExample::OnUpdateUIOverlay(vks::UIOverlay* overlay)
{
if (overlay->header("Settings")) {
if (overlay->sliderFloat("LOD bias", &uboVS.lodBias, -(float)texture.mipLevels, (float)texture.mipLevels)) {
updateUniformBuffers();
@ -1012,7 +932,6 @@ public:
overlay->text("Mip tail starts at: %d", texture.mipTailStart);
}
}
};
}
VULKAN_EXAMPLE_MAIN()

View file

@ -0,0 +1,141 @@
/*
* Vulkan Example - Sparse texture residency example
*
* Copyright (C) 2016-2020 by Sascha Willems - www.saschawillems.de
*
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
*/
/*
* Note : This sample is work-in-progress and works basically, but it's not yet finished
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <vector>
#include <algorithm>
#include <random>
#include <chrono>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <vulkan/vulkan.h>
#include "vulkanexamplebase.h"
#include "VulkanDevice.hpp"
#include "VulkanBuffer.hpp"
#include "VulkanModel.hpp"
#define ENABLE_VALIDATION false
// 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();
bool resident();
void allocate(VkDevice device, uint32_t memoryTypeIndex);
void release(VkDevice device);
};
// 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)
uint32_t mipTailStart; // First mip level in mip tail
VkSparseImageMemoryRequirements sparseImageMemoryRequirements; // @todo: Comment
uint32_t memoryTypeIndex; // @todo: Comment
// @todo: comment
struct MipTailInfo {
bool singleMipTail;
bool alingedMipSize;
} mipTailInfo;
VirtualTexturePage *addPage(VkOffset3D offset, VkExtent3D extent, const VkDeviceSize size, const uint32_t mipLevel, uint32_t layer);
void updateSparseBindInfo();
// @todo: replace with dtor?
void destroy();
};
class VulkanExample : public VulkanExampleBase
{
public:
//todo: comments
struct SparseTexture : VirtualTexture {
VkSampler sampler;
VkImageLayout imageLayout;
VkImageView view;
VkDescriptorImageInfo descriptor;
VkFormat format;
uint32_t width, height;
uint32_t mipLevels;
uint32_t layerCount;
} texture;
vks::VertexLayout vertexLayout = vks::VertexLayout({
vks::VERTEX_COMPONENT_POSITION,
vks::VERTEX_COMPONENT_NORMAL,
vks::VERTEX_COMPONENT_UV,
});
vks::Model plane;
struct UboVS {
glm::mat4 projection;
glm::mat4 model;
glm::vec4 viewPos;
float lodBias = 0.0f;
} uboVS;
vks::Buffer uniformBufferVS;
VkPipeline pipeline;
VkPipelineLayout pipelineLayout;
VkDescriptorSet descriptorSet;
VkDescriptorSetLayout descriptorSetLayout;
//todo: comment
VkSemaphore bindSparseSemaphore = VK_NULL_HANDLE;
VulkanExample();
~VulkanExample();
virtual void getEnabledFeatures();
glm::uvec3 alignedDivision(const VkExtent3D& extent, const VkExtent3D& granularity);
void prepareSparseTexture(uint32_t width, uint32_t height, uint32_t layerCount, VkFormat format);
// @todo: move to dtor of texture
void destroyTextureImage(SparseTexture texture);
void buildCommandBuffers();
void draw();
void loadAssets();
void setupDescriptorPool();
void setupDescriptorSetLayout();
void setupDescriptorSet();
void preparePipelines();
void prepareUniformBuffers();
void updateUniformBuffers();
void prepare();
virtual void render();
void uploadContent(VirtualTexturePage page, VkImage image);
void fillRandomPages();
void fillMipTail();
void flushRandomPages();
virtual void OnUpdateUIOverlay(vks::UIOverlay* overlay);
};