init
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
# Copyright (c) 2023-2024, Mobica Limited
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 the "License";
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
|
||||
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
|
||||
|
||||
add_sample(
|
||||
ID ${FOLDER_NAME}
|
||||
CATEGORY ${CATEGORY_NAME}
|
||||
AUTHOR "Mobica"
|
||||
NAME "Sparse image binding and residency"
|
||||
DESCRIPTION "This sample is showcasing the potential usage of the sparse-image-binding and sparse-image-residency features. It works with the concept of Virtual Textures, allowing textures to be rendered without being entirely allocated in the memory."
|
||||
SHADER_FILES_GLSL
|
||||
"sparse_image/glsl/sparse.vert"
|
||||
"sparse_image/glsl/sparse.frag"
|
||||
SHADER_FILES_HLSL
|
||||
"sparse_image/hlsl/sparse.vert.hlsl"
|
||||
"sparse_image/hlsl/sparse.frag.hlsl")
|
||||
@@ -0,0 +1,161 @@
|
||||
////
|
||||
- Copyright (c) 2023, Mobica Limited
|
||||
-
|
||||
- SPDX-License-Identifier: Apache-2.0
|
||||
-
|
||||
- Licensed under the Apache License, Version 2.0 the "License";
|
||||
- you may not use this file except in compliance with the License.
|
||||
- You may obtain a copy of the License at
|
||||
-
|
||||
- http://www.apache.org/licenses/LICENSE-2.0
|
||||
-
|
||||
- Unless required by applicable law or agreed to in writing, software
|
||||
- distributed under the License is distributed on an "AS IS" BASIS,
|
||||
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
- See the License for the specific language governing permissions and
|
||||
- limitations under the License.
|
||||
-
|
||||
////
|
||||
|
||||
== Sparse image
|
||||
|
||||
ifdef::site-gen-antora[]
|
||||
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/sparse_image[Khronos Vulkan samples github repository].
|
||||
endif::[]
|
||||
|
||||
image::./images/sparse_image_screenshot.png[Sample]
|
||||
|
||||
== Overview
|
||||
|
||||
The usage of
|
||||
https://registry.khronos.org/vulkan/site/spec/latest/chapters/sparsemem.html[Sparse
|
||||
Resources] allows for less restrict memory binding in comparison to a
|
||||
standard resource.
|
||||
|
||||
The key differences between standard and sparse resources, showcased in
|
||||
this sample are:
|
||||
|
||||
* Sparse resources can be bound non-contiguously to one or more
|
||||
VkDeviceMemory allocations;
|
||||
* Sparse resources can be re-bound to different memory allocations over
|
||||
the lifetime of the resource;
|
||||
|
||||
The sample demonstrates usage of the Sparse Image feature by rendering a
|
||||
high-resolution texture with only a fraction of the total image size
|
||||
actually allocated on the device's memory. This is possible by
|
||||
dynamically loading required memory areas, generating mip levels for
|
||||
outer parts, removing unused memory and finally: binding an image in
|
||||
real-time.
|
||||
|
||||
== Enabling features
|
||||
|
||||
There are 3 features to be enabled:
|
||||
|
||||
* sparseBinding;
|
||||
* sparseResidencyImage2D;
|
||||
* shaderResourceResidency;
|
||||
|
||||
First two, are the key features required for the usage of the sparse
|
||||
image resources. The last one - shaderResourceResidency, is required for
|
||||
the fragment shader to be able to detect which parts of the image are
|
||||
allocated in the memory.
|
||||
|
||||
[source,c++]
|
||||
----
|
||||
void SparseImage::request_gpu_features(vkb::PhysicalDevice &gpu)
|
||||
{
|
||||
if (gpu.get_features().sparseBinding && gpu.get_features().sparseResidencyImage2D && gpu.get_features().shaderResourceResidency)
|
||||
{
|
||||
gpu.get_mutable_requested_features().sparseBinding = VK_TRUE;
|
||||
gpu.get_mutable_requested_features().sparseResidencyImage2D = VK_TRUE;
|
||||
gpu.get_mutable_requested_features().shaderResourceResidency = VK_TRUE;
|
||||
}
|
||||
----
|
||||
|
||||
== Enabling extensions
|
||||
|
||||
There is a single extensions used in this sample:
|
||||
|
||||
* GL_ARB_sparse_texture2;
|
||||
|
||||
This extension is used only by the fragment shader, but requires
|
||||
shaderResourceResidency feature to be enabled first. What this extension
|
||||
does, is allowing the fragment to check if the memory for the particular
|
||||
fragment is actually allocated or not. Because of this extension, it is
|
||||
possible to keep checking the residency from the fragment shader, and
|
||||
basically use the most detailed data available.
|
||||
|
||||
[source,glsl]
|
||||
----
|
||||
#extension GL_ARB_sparse_texture2 : enable
|
||||
----
|
||||
|
||||
[source,glsl]
|
||||
----
|
||||
for(; (lod <= maxLOD) && !sparseTexelsResidentARB(residencyCode); lod += 1)
|
||||
{
|
||||
residencyCode = sparseTextureLodARB(texSampler, fragTexCoord, lod, color);
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
== How is required LOD calculated?
|
||||
|
||||
The whole method is well-described in the source file. In general, the
|
||||
value of LOD is obtained by calculating: What is the ratio between x or y
|
||||
movement on the screen, to the u or v movement on the texture?
|
||||
|
||||
The idea is, that when moving pixel-by-pixel along the x or y axis
|
||||
on-screen, if the small on-screen step causes a significant step
|
||||
on-texture, then the area is far away from the observer and
|
||||
a less-detailed mip-level is required.
|
||||
|
||||
The formula used for those calculations is:
|
||||
|
||||
LOD = log2 (max(dT / dx, dT / dy)); where:
|
||||
|
||||
* dT is an on-texture-step in texels,
|
||||
* dx, dy are on-screen-steps in pixels.
|
||||
|
||||
|
||||
== User Interface
|
||||
|
||||
The user can alter the application by using the GUI.
|
||||
|
||||
These are available options:
|
||||
|
||||
* Color highlight - if enabled, areas of a particular LOD usage are
|
||||
color-highlighted.
|
||||
* Memory defragmentation - if enabled, memory pages are reallocated from
|
||||
low-occupied sectors to higher-occupied (but available) sectors to keep the
|
||||
overall number of allocations as low as possible.
|
||||
* Update prioritization - if enabled, the application is focused on
|
||||
processing the most actual requests and discards remainings from the
|
||||
previous requests. This can be observed when dynamically moving the
|
||||
camera around.
|
||||
* Blocks per cycle - describes up to how many blocks can be updated per
|
||||
a single render cycle. The total number of blocks is defined as: (Vertical
|
||||
blocks) * (Horizontal blocks).
|
||||
* Vertical blocks - describes the number of columns the texture is
|
||||
divided into.
|
||||
* Horizontal blocks - describes the number of rows the texture is
|
||||
divided into.
|
||||
|
||||
Additionally, GUI contains memory usage data. It describes (in pages)
|
||||
what are the virtual requirements (what if the whole image was allocated
|
||||
in the memory) and what is the actual, current allocation on the
|
||||
device.
|
||||
|
||||
|
||||
== Conclusion
|
||||
|
||||
The primary usage of the sparse image feature is generally speaking
|
||||
dedicated for cases where too much device's memory is occupied. Keeping
|
||||
a low-detailed mip-level constantly in the memory and dynamically
|
||||
loading required areas when the camera changes, is the way to handle
|
||||
terrain mega-textures. The downside of these solution is that there is a
|
||||
possibility of a bottleneck problem when constantly transferring
|
||||
required memory chunks from the CPU to the device. The other downside is
|
||||
that since the application decides what memory is going to be allocated,
|
||||
it must take care of the calculations such as: "`what level of detail is
|
||||
required?`". This creates an unwanted CPU overhead.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 280 KiB |
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,400 @@
|
||||
/* Copyright (c) 2023-2025, Mobica Limited
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 the "License";
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "api_vulkan_sample.h"
|
||||
#include <list>
|
||||
|
||||
class SparseImage : public ApiVulkanSample
|
||||
{
|
||||
public:
|
||||
enum class Stages
|
||||
{
|
||||
Idle,
|
||||
CalculateMipsTable,
|
||||
CompareMipsTable,
|
||||
FreeMemory,
|
||||
ProcessTextureBlocks,
|
||||
UpdateAndGenerate,
|
||||
};
|
||||
|
||||
struct MVP
|
||||
{
|
||||
alignas(16) glm::mat4 model;
|
||||
alignas(16) glm::mat4 view;
|
||||
alignas(16) glm::mat4 proj;
|
||||
};
|
||||
|
||||
struct FragSettingsData
|
||||
{
|
||||
bool color_highlight;
|
||||
int minLOD;
|
||||
int maxLOD;
|
||||
};
|
||||
|
||||
struct SimpleVertex
|
||||
{
|
||||
glm::vec2 norm;
|
||||
glm::vec2 uv;
|
||||
};
|
||||
|
||||
struct MipProperties
|
||||
{
|
||||
size_t num_rows;
|
||||
size_t num_columns;
|
||||
size_t mip_num_pages;
|
||||
size_t mip_base_page_index;
|
||||
size_t width;
|
||||
size_t height;
|
||||
};
|
||||
|
||||
struct TextureBlock
|
||||
{
|
||||
bool operator<(TextureBlock const &other) const
|
||||
{
|
||||
if (this->new_mip_level == other.new_mip_level)
|
||||
{
|
||||
if (this->column == other.column)
|
||||
{
|
||||
return this->row < other.row;
|
||||
}
|
||||
else
|
||||
{
|
||||
return this->column < other.column;
|
||||
}
|
||||
}
|
||||
return this->new_mip_level < other.new_mip_level;
|
||||
};
|
||||
|
||||
size_t row;
|
||||
size_t column;
|
||||
double old_mip_level;
|
||||
double new_mip_level;
|
||||
bool on_screen;
|
||||
};
|
||||
|
||||
struct MemPageDescription
|
||||
{
|
||||
size_t x;
|
||||
size_t y;
|
||||
uint8_t mip_level;
|
||||
};
|
||||
|
||||
struct Point
|
||||
{
|
||||
double x;
|
||||
double y;
|
||||
bool on_screen;
|
||||
};
|
||||
|
||||
struct MipBlock
|
||||
{
|
||||
double mip_level;
|
||||
bool on_screen;
|
||||
};
|
||||
|
||||
struct MemSector;
|
||||
|
||||
struct PageInfo
|
||||
{
|
||||
std::shared_ptr<MemSector> memory_sector = nullptr;
|
||||
uint32_t offset = 0U;
|
||||
};
|
||||
|
||||
struct PageTable
|
||||
{
|
||||
bool valid = false; // bound via vkQueueBindSparse() and contains valid data
|
||||
bool gen_mip_required = false; // required for the mip generation
|
||||
bool fixed = false; // not freed from the memory at any cases
|
||||
PageInfo page_memory_info; // memory-related info
|
||||
|
||||
std::set<std::tuple<uint8_t, size_t, size_t>> render_required_set; // set holding information on what BLOCKS require this particular memory page to be valid for rendering
|
||||
};
|
||||
|
||||
struct MemAllocInfo
|
||||
{
|
||||
VkDevice device = VK_NULL_HANDLE;
|
||||
uint64_t page_size = 0U;
|
||||
uint32_t memory_type_index = 0U;
|
||||
size_t pages_per_allocation = 0U;
|
||||
|
||||
void get_allocation(PageInfo &page_memory_info, size_t page_index)
|
||||
{
|
||||
if (memory_sectors.empty() || memory_sectors.front().expired() || memory_sectors.front().lock()->available_offsets.empty())
|
||||
{
|
||||
page_memory_info.memory_sector = std::make_shared<MemSector>(*this);
|
||||
page_memory_info.offset = *(page_memory_info.memory_sector->available_offsets.begin());
|
||||
|
||||
page_memory_info.memory_sector->available_offsets.erase(page_memory_info.offset);
|
||||
page_memory_info.memory_sector->virt_page_indices.insert(page_index);
|
||||
|
||||
memory_sectors.push_front(page_memory_info.memory_sector);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto ptr = memory_sectors.front().lock();
|
||||
|
||||
page_memory_info.memory_sector = ptr;
|
||||
page_memory_info.offset = *(page_memory_info.memory_sector->available_offsets.begin());
|
||||
|
||||
page_memory_info.memory_sector->available_offsets.erase(page_memory_info.offset);
|
||||
page_memory_info.memory_sector->virt_page_indices.insert(page_index);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t get_size()
|
||||
{
|
||||
return static_cast<uint32_t>(memory_sectors.size());
|
||||
}
|
||||
|
||||
std::list<std::weak_ptr<MemSector>> &get_memory_sectors()
|
||||
{
|
||||
return memory_sectors;
|
||||
}
|
||||
|
||||
private:
|
||||
std::list<std::weak_ptr<MemSector>> memory_sectors;
|
||||
};
|
||||
|
||||
struct MemSector : public MemAllocInfo
|
||||
{
|
||||
VkDeviceMemory memory = VK_NULL_HANDLE;
|
||||
|
||||
std::set<uint32_t> available_offsets;
|
||||
std::set<size_t> virt_page_indices;
|
||||
|
||||
MemSector(MemAllocInfo &mem_alloc_info) :
|
||||
MemAllocInfo(mem_alloc_info)
|
||||
{
|
||||
VkMemoryAllocateInfo memory_allocate_info{};
|
||||
memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||
memory_allocate_info.allocationSize = page_size * pages_per_allocation;
|
||||
memory_allocate_info.memoryTypeIndex = memory_type_index;
|
||||
|
||||
VkDeviceMemory memory;
|
||||
VK_CHECK(vkAllocateMemory(device, &memory_allocate_info, nullptr, &memory));
|
||||
this->memory = memory;
|
||||
|
||||
for (size_t i = 0U; i < pages_per_allocation; i++)
|
||||
{
|
||||
available_offsets.insert(static_cast<uint32_t>(page_size * i));
|
||||
}
|
||||
}
|
||||
|
||||
~MemSector()
|
||||
{
|
||||
vkDeviceWaitIdle(device);
|
||||
vkFreeMemory(device, memory, nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
struct MemSectorCompare
|
||||
{
|
||||
bool operator()(const std::weak_ptr<MemSector> &left, const std::weak_ptr<MemSector> &right)
|
||||
{
|
||||
if (left.expired())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (right.expired())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return left.lock()->available_offsets.size() > right.lock()->available_offsets.size();
|
||||
};
|
||||
};
|
||||
|
||||
struct VirtualTexture
|
||||
{
|
||||
VkImage texture_image = VK_NULL_HANDLE;
|
||||
VkImageView texture_image_view = VK_NULL_HANDLE;
|
||||
MemAllocInfo memory_allocations;
|
||||
|
||||
// Dimensions
|
||||
size_t width = 0U;
|
||||
size_t height = 0U;
|
||||
|
||||
// Number of bytes per page
|
||||
size_t page_size = 0U;
|
||||
|
||||
uint8_t base_mip_level = 0U;
|
||||
uint8_t mip_levels = 0U;
|
||||
std::vector<MipProperties> mip_properties;
|
||||
|
||||
std::vector<std::vector<MipBlock>> current_mip_table;
|
||||
std::vector<std::vector<MipBlock>> new_mip_table;
|
||||
|
||||
// Image containing a single, most detailed mip, allocated in the CPU memory, coppied to VRAM via staging buffer in update_and_generate()
|
||||
std::unique_ptr<vkb::sg::Image> raw_data_image;
|
||||
|
||||
// Key table that includes data on which page is allocated to what memory block from the textureMemory vector
|
||||
std::vector<PageTable> page_table;
|
||||
|
||||
// Set containing BLOCKS for which the required mip level has changed or/and its on-screen visibility changed
|
||||
std::set<TextureBlock> texture_block_update_set;
|
||||
|
||||
// Set containing information which pages from the page_table should be updated (either loaded from CPU memory or blitted)
|
||||
std::set<size_t> update_set;
|
||||
|
||||
// Sparse-image-related format and memory properties
|
||||
VkSparseImageFormatProperties format_properties{};
|
||||
|
||||
std::vector<VkSparseImageMemoryBind> sparse_image_memory_bind;
|
||||
};
|
||||
|
||||
struct CalculateMipLevelData
|
||||
{
|
||||
std::vector<std::vector<Point>> mesh;
|
||||
std::vector<std::vector<MipBlock>> mip_table;
|
||||
|
||||
uint32_t vertical_num_blocks;
|
||||
uint32_t horizontal_num_blocks;
|
||||
|
||||
uint8_t mip_levels;
|
||||
|
||||
std::vector<float> ax_vertical;
|
||||
std::vector<float> ax_horizontal;
|
||||
|
||||
glm::mat4 mvp_transform;
|
||||
|
||||
VkExtent2D texture_base_dim;
|
||||
VkExtent2D screen_base_dim;
|
||||
|
||||
CalculateMipLevelData(const glm::mat4 &mvp_transform, const VkExtent2D &texture_base_dim, const VkExtent2D &screen_base_dim, uint32_t vertical_num_blocks, uint32_t horizontal_num_blocks, uint8_t mip_levels) :
|
||||
mesh(vertical_num_blocks + 1U), vertical_num_blocks(vertical_num_blocks), horizontal_num_blocks(horizontal_num_blocks), mip_levels(mip_levels), ax_vertical(horizontal_num_blocks + 1U), ax_horizontal(vertical_num_blocks + 1U), mvp_transform(mvp_transform), texture_base_dim(texture_base_dim), screen_base_dim(screen_base_dim)
|
||||
{
|
||||
for (auto &row : mesh)
|
||||
{
|
||||
row.resize(horizontal_num_blocks + 1U);
|
||||
}
|
||||
}
|
||||
|
||||
CalculateMipLevelData() :
|
||||
mvp_transform(glm::mat4(0)), texture_base_dim(VkExtent2D{0U, 0U}), screen_base_dim(VkExtent2D{0U, 0U}), mesh{0}, vertical_num_blocks(0U), horizontal_num_blocks(0U), mip_levels(0U)
|
||||
{}
|
||||
void calculate_mesh_coordinates();
|
||||
void calculate_mip_levels();
|
||||
};
|
||||
|
||||
// UI related
|
||||
bool color_highlight = true;
|
||||
bool color_highlight_changed = false;
|
||||
bool memory_defragmentation = true;
|
||||
bool frame_counter_feature = true;
|
||||
|
||||
size_t blocks_to_update_per_cycle = 25U;
|
||||
|
||||
size_t num_vertical_blocks = 50U;
|
||||
size_t num_horizontal_blocks = 50U;
|
||||
|
||||
size_t num_vertical_blocks_upd = 50U;
|
||||
size_t num_horizontal_blocks_upd = 50U;
|
||||
|
||||
bool update_required = false;
|
||||
|
||||
uint8_t frame_counter_per_transfer = 0U;
|
||||
|
||||
const uint8_t FRAME_COUNTER_CAP = 10U;
|
||||
const uint8_t MEMORY_FRAGMENTATION_CAP = 20U;
|
||||
const uint8_t PAGES_PER_ALLOC = 50U;
|
||||
const double FOV_DEGREES = 60.0;
|
||||
|
||||
Stages next_stage = Stages::Idle;
|
||||
|
||||
const VkFormat image_format = VK_FORMAT_R8G8B8A8_SRGB;
|
||||
const VkImageUsageFlags image_usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
||||
|
||||
VirtualTexture virtual_texture;
|
||||
|
||||
CalculateMipLevelData mesh_data;
|
||||
|
||||
VkQueue sparse_queue;
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> vertex_buffer;
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> index_buffer;
|
||||
size_t index_count;
|
||||
|
||||
std::unique_ptr<vkb::core::BufferC> mvp_buffer;
|
||||
std::unique_ptr<vkb::core::BufferC> frag_settings_data_buffer;
|
||||
|
||||
glm::mat4 current_mvp_transform;
|
||||
|
||||
VkPipeline sample_pipeline;
|
||||
VkPipelineLayout sample_pipeline_layout;
|
||||
|
||||
VkDescriptorSetLayout descriptor_set_layout;
|
||||
VkDescriptorSet descriptor_set;
|
||||
VkSampler texture_sampler;
|
||||
|
||||
VkSemaphore bound_semaphore;
|
||||
VkSemaphore submit_semaphore;
|
||||
|
||||
//==================================================================================================
|
||||
SparseImage();
|
||||
virtual ~SparseImage();
|
||||
|
||||
void setup_camera();
|
||||
void load_assets();
|
||||
|
||||
void prepare_pipelines();
|
||||
|
||||
void create_sparse_bind_queue();
|
||||
|
||||
void create_vertex_buffer();
|
||||
void create_index_buffer();
|
||||
|
||||
void create_uniform_buffers();
|
||||
void create_texture_sampler();
|
||||
|
||||
void create_descriptor_set_layout();
|
||||
void create_descriptor_pool();
|
||||
void create_descriptor_sets();
|
||||
|
||||
void create_sparse_texture_image();
|
||||
|
||||
void draw();
|
||||
|
||||
void update_mvp();
|
||||
void process_stage(enum Stages next_stage);
|
||||
void free_unused_memory();
|
||||
void update_and_generate();
|
||||
void process_texture_blocks();
|
||||
struct MemPageDescription get_mem_page_description(size_t page_index);
|
||||
void calculate_mips_table();
|
||||
void compare_mips_table();
|
||||
void process_texture_block(const TextureBlock &on_screen_block);
|
||||
std::vector<size_t> get_memory_dependency_for_the_block(size_t column, size_t row, uint8_t mip_level);
|
||||
void check_mip_page_requirements(std::vector<MemPageDescription> &mipgen_required_vec, MemPageDescription mip_dependency);
|
||||
void bind_sparse_image();
|
||||
void load_least_detailed_level();
|
||||
void set_least_detailed_level();
|
||||
void update_frag_settings();
|
||||
uint8_t get_mip_level(size_t page_index);
|
||||
size_t get_page_index(MemPageDescription mem_page_desc);
|
||||
void reset_mip_table();
|
||||
|
||||
// Override basic framework functionalities
|
||||
void build_command_buffers() override;
|
||||
void render(float delta_time) override;
|
||||
bool prepare(const vkb::ApplicationOptions &options) override;
|
||||
void request_gpu_features(vkb::PhysicalDevice &gpu) override;
|
||||
virtual void on_update_ui_overlay(vkb::Drawer &drawer) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<vkb::VulkanSampleC> create_sparse_image();
|
||||
Reference in New Issue
Block a user