/* 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 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 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> 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(*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(memory_sectors.size()); } std::list> &get_memory_sectors() { return memory_sectors; } private: std::list> memory_sectors; }; struct MemSector : public MemAllocInfo { VkDeviceMemory memory = VK_NULL_HANDLE; std::set available_offsets; std::set 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(page_size * i)); } } ~MemSector() { vkDeviceWaitIdle(device); vkFreeMemory(device, memory, nullptr); } }; struct MemSectorCompare { bool operator()(const std::weak_ptr &left, const std::weak_ptr &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 mip_properties; std::vector> current_mip_table; std::vector> 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 raw_data_image; // Key table that includes data on which page is allocated to what memory block from the textureMemory vector std::vector page_table; // Set containing BLOCKS for which the required mip level has changed or/and its on-screen visibility changed std::set 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 update_set; // Sparse-image-related format and memory properties VkSparseImageFormatProperties format_properties{}; std::vector sparse_image_memory_bind; }; struct CalculateMipLevelData { std::vector> mesh; std::vector> mip_table; uint32_t vertical_num_blocks; uint32_t horizontal_num_blocks; uint8_t mip_levels; std::vector ax_vertical; std::vector 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 vertex_buffer; std::unique_ptr index_buffer; size_t index_count; std::unique_ptr mvp_buffer; std::unique_ptr 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 get_memory_dependency_for_the_block(size_t column, size_t row, uint8_t mip_level); void check_mip_page_requirements(std::vector &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 create_sparse_image();