This commit is contained in:
xsl
2025-09-04 10:54:47 +08:00
commit 6bc8f61b18
1808 changed files with 208268 additions and 0 deletions
+529
View File
@@ -0,0 +1,529 @@
# Copyright (c) 2019-2025, Arm Limited and Contributors
# Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
#
# 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.
#
cmake_minimum_required(VERSION 3.16)
project(framework LANGUAGES C CXX)
set(FRAMEWORK_FILES
# Header Files
gui.h
drawer.h
spirv_reflection.h
gltf_loader.h
buffer_pool.h
debug_info.h
fence_pool.h
heightmap.h
semaphore_pool.h
resource_binding_state.h
resource_cache.h
resource_record.h
resource_replay.h
vulkan_sample.h
api_vulkan_sample.h
timer.h
camera.h
builder_base.h
vulkan_type_mapping.h
hpp_api_vulkan_sample.h
hpp_fence_pool.h
hpp_gltf_loader.h
hpp_resource_binding_state.h
hpp_resource_cache.h
hpp_resource_record.h
hpp_resource_replay.h
hpp_semaphore_pool.h
# Source Files
drawer.cpp
spirv_reflection.cpp
gltf_loader.cpp
debug_info.cpp
fence_pool.cpp
heightmap.cpp
semaphore_pool.cpp
resource_binding_state.cpp
resource_cache.cpp
resource_record.cpp
resource_replay.cpp
api_vulkan_sample.cpp
timer.cpp
camera_core.cpp
hpp_api_vulkan_sample.cpp
hpp_resource_cache.cpp)
set(COMMON_FILES
# Header Files
common/ktx_common.h
common/vk_common.h
common/vk_initializers.h
common/glm_common.h
common/resource_caching.h
common/helpers.h
common/error.h
common/utils.h
common/strings.h
common/tags.h
common/hpp_error.h
common/hpp_resource_caching.h
common/hpp_strings.h
common/hpp_utils.h
common/hpp_vk_common.h
# Source Files
common/error.cpp
common/ktx_common.cpp
common/vk_common.cpp
common/utils.cpp
common/strings.cpp)
set(GEOMETRY_FILES
# Header Files
geometry/frustum.h
# Source Files
geometry/frustum.cpp)
set(RENDERING_FILES
# Header files
rendering/pipeline_state.h
rendering/postprocessing_pipeline.h
rendering/postprocessing_pass.h
rendering/postprocessing_renderpass.h
rendering/postprocessing_computepass.h
rendering/render_context.h
rendering/render_frame.h
rendering/render_pipeline.h
rendering/render_target.h
rendering/subpass.h
rendering/hpp_pipeline_state.h
rendering/hpp_render_context.h
rendering/hpp_render_pipeline.h
rendering/hpp_render_target.h
# Source files
rendering/pipeline_state.cpp
rendering/postprocessing_pipeline.cpp
rendering/postprocessing_pass.cpp
rendering/postprocessing_renderpass.cpp
rendering/postprocessing_computepass.cpp
rendering/render_context.cpp
rendering/render_pipeline.cpp
rendering/render_target.cpp
rendering/hpp_render_context.cpp
rendering/hpp_render_target.cpp)
set(RENDERING_SUBPASSES_FILES
# Header files
rendering/subpasses/forward_subpass.h
rendering/subpasses/lighting_subpass.h
rendering/subpasses/geometry_subpass.h
rendering/subpasses/hpp_forward_subpass.h
# Source files
rendering/subpasses/forward_subpass.cpp
rendering/subpasses/lighting_subpass.cpp
rendering/subpasses/geometry_subpass.cpp)
set(SCENE_GRAPH_FILES
# Header Files
scene_graph/component.h
scene_graph/node.h
scene_graph/scene.h
scene_graph/script.h
scene_graph/hpp_scene.h
# Source Files
scene_graph/component.cpp
scene_graph/node.cpp
scene_graph/scene.cpp
scene_graph/script.cpp)
set(SCENE_GRAPH_COMPONENT_FILES
# Header Files
scene_graph/components/aabb.h
scene_graph/components/camera.h
scene_graph/components/perspective_camera.h
scene_graph/components/orthographic_camera.h
scene_graph/components/image.h
scene_graph/components/light.h
scene_graph/components/material.h
scene_graph/components/mesh.h
scene_graph/components/pbr_material.h
scene_graph/components/sampler.h
scene_graph/components/sub_mesh.h
scene_graph/components/texture.h
scene_graph/components/transform.h
scene_graph/components/image/astc.h
scene_graph/components/image/ktx.h
scene_graph/components/image/stb.h
scene_graph/components/hpp_image.h
scene_graph/components/hpp_material.h
scene_graph/components/hpp_mesh.h
scene_graph/components/hpp_sub_mesh.h
scene_graph/components/hpp_texture.h
# Source Files
scene_graph/components/aabb.cpp
scene_graph/components/camera.cpp
scene_graph/components/perspective_camera.cpp
scene_graph/components/orthographic_camera.cpp
scene_graph/components/image.cpp
scene_graph/components/light.cpp
scene_graph/components/material.cpp
scene_graph/components/mesh.cpp
scene_graph/components/pbr_material.cpp
scene_graph/components/sampler.cpp
scene_graph/components/sub_mesh.cpp
scene_graph/components/texture.cpp
scene_graph/components/transform.cpp
scene_graph/components/image/astc.cpp
scene_graph/components/image/ktx.cpp
scene_graph/components/image/stb.cpp
scene_graph/components/hpp_image.cpp)
set(SCENE_GRAPH_SCRIPTS_FILES
# Header Files
scene_graph/scripts/free_camera.h
scene_graph/scripts/node_animation.h
scene_graph/scripts/animation.h
# Source Files
scene_graph/scripts/free_camera.cpp
scene_graph/scripts/node_animation.cpp
scene_graph/scripts/animation.cpp)
set(STATS_FILES
# Header Files
stats/stats.h
stats/stats_common.h
stats/stats_provider.h
stats/frame_time_stats_provider.h
stats/vulkan_stats_provider.h
stats/hpp_stats.h
# Source Files
stats/stats.cpp
stats/stats_provider.cpp
stats/frame_time_stats_provider.cpp
stats/vulkan_stats_provider.cpp)
set(CORE_FILES
# Header Files
core/instance.h
core/physical_device.h
core/device.h
core/debug.h
core/shader_module.h
core/pipeline_layout.h
core/pipeline.h
core/descriptor_set_layout.h
core/descriptor_pool.h
core/descriptor_set.h
core/queue.h
core/command_pool.h
core/command_pool_base.h
core/swapchain.h
core/command_buffer.h
core/allocated.h
core/buffer.h
core/image.h
core/image_view.h
core/sampled_image.h
core/sampler.h
core/framebuffer.h
core/render_pass.h
core/query_pool.h
core/acceleration_structure.h
core/hpp_debug.h
core/hpp_descriptor_pool.h
core/hpp_descriptor_set.h
core/hpp_descriptor_set_layout.h
core/hpp_framebuffer.h
core/hpp_image.h
core/hpp_image_view.h
core/hpp_physical_device.h
core/hpp_pipeline.h
core/hpp_pipeline_layout.h
core/hpp_query_pool.h
core/hpp_queue.h
core/hpp_render_pass.h
core/hpp_sampler.h
core/hpp_shader_module.h
core/hpp_swapchain.h
core/vulkan_resource.h
# Source Files
core/command_pool_base.cpp
core/physical_device.cpp
core/debug.cpp
core/image_core.cpp
core/shader_module.cpp
core/pipeline_layout.cpp
core/pipeline.cpp
core/descriptor_set_layout.cpp
core/descriptor_pool.cpp
core/descriptor_set.cpp
core/queue.cpp
core/swapchain.cpp
core/allocated.cpp
core/image_core.cpp
core/image_view.cpp
core/sampled_image.cpp
core/sampler_core.cpp
core/framebuffer.cpp
core/render_pass.cpp
core/query_pool.cpp
core/acceleration_structure.cpp
core/hpp_debug.cpp
core/hpp_image_core.cpp
core/hpp_image_view.cpp
core/hpp_physical_device.cpp
core/hpp_pipeline_layout.cpp
core/hpp_queue.cpp
core/hpp_sampler.cpp
core/hpp_swapchain.cpp
)
set(PLATFORM_FILES
# Header Files
platform/application.h
platform/platform.h
platform/window.h
platform/input_events.h
platform/configuration.h
platform/headless_window.h
platform/plugins/plugin.h
platform/plugins/plugin_base.h
# Source Files
platform/headless_window.cpp
platform/application.cpp
platform/platform.cpp
platform/window.cpp
platform/input_events.cpp
platform/configuration.cpp
platform/plugins/plugin.cpp)
set(GLFW_FILES
# Header Files
platform/glfw_window.h
# Source Files
platform/glfw_window.cpp
)
set(ANDROID_FILES
# Header Files
platform/android/android_platform.h
platform/android/android_window.h
stats/hwcpipe_stats_provider.h
# Source Files
platform/android/android_platform.cpp
platform/android/android_window.cpp
stats/hwcpipe_stats_provider.cpp)
set(IOS_FILES
# Header Files
platform/ios/ios_platform.h
platform/ios/ios_window.h
# Source Files
platform/ios/ios_platform.mm
platform/ios/ios_window.mm)
set(WINDOWS_FILES
# Header Files
platform/windows/windows_platform.h
# Source Files
platform/windows/windows_platform.cpp)
set(UNIX_FILES
# Header Files
platform/unix/unix_platform.h
# Source Files
platform/unix/unix_platform.cpp)
set(LINUX_D2D_FILES
# Header Files
platform/unix/unix_d2d_platform.h
platform/unix/direct_window.h
# Source Files
platform/unix/unix_d2d_platform.cpp
platform/unix/direct_window.cpp)
source_group("\\" FILES ${FRAMEWORK_FILES})
source_group("common\\" FILES ${COMMON_FILES})
source_group("platform\\" FILES ${PLATFORM_FILES})
source_group("platform\\" FILES ${GLFW_FILES})
source_group("platform\\windows" FILES ${WINDOWS_FILES})
source_group("platform\\android" FILES ${ANDROID_FILES})
source_group("platform\\ios" FILES ${IOS_FILES})
source_group("platform\\unix" FILES ${UNIX_FILES})
source_group("platform\\unix" FILES ${LINUX_D2D_FILES})
source_group("core\\" FILES ${CORE_FILES})
source_group("geometry\\" FILES ${GEOMETRY_FILES})
source_group("rendering\\" FILES ${RENDERING_FILES})
source_group("rendering\\subpasses" FILES ${RENDERING_SUBPASSES_FILES})
source_group("scene_graph\\" FILES ${SCENE_GRAPH_FILES})
source_group("scene_graph\\components\\" FILES ${SCENE_GRAPH_COMPONENT_FILES})
source_group("scene_graph\\scripts\\" FILES ${SCENE_GRAPH_SCRIPTS_FILES})
source_group("stats\\" FILES ${STATS_FILES})
set(PROJECT_FILES
${PLATFORM_FILES}
${COMMON_FILES}
${FRAMEWORK_FILES}
${CORE_FILES}
${GEOMETRY_FILES}
${RENDERING_FILES}
${RENDERING_SUBPASSES_FILES}
${SCENE_GRAPH_FILES}
${SCENE_GRAPH_COMPONENT_FILES}
${SCENE_GRAPH_SCRIPTS_FILES}
${STATS_FILES})
# No need for explicit casts from vk::HandleType to VkHandleType on ANDROID
if(ANDROID)
add_definitions(-DVULKAN_HPP_TYPESAFE_CONVERSION=1)
endif()
# Add files based on platform
if(ANDROID)
list(APPEND PROJECT_FILES ${ANDROID_FILES})
message(STATUS "Android platform detected")
else()
if(DIRECT_TO_DISPLAY)
list(APPEND PROJECT_FILES ${LINUX_D2D_FILES})
message(STATUS "Unix d2d platform detected")
else()
if(IOS)
enable_language(OBJCXX)
list(APPEND PROJECT_FILES ${IOS_FILES})
message(STATUS "iOS platform detected")
else()
list(APPEND PROJECT_FILES ${GLFW_FILES})
if(WIN32)
list(APPEND PROJECT_FILES ${WINDOWS_FILES})
message(STATUS "Windows platform detected")
else()
list(APPEND PROJECT_FILES ${UNIX_FILES})
message(STATUS "Unix platform detected")
endif()
endif ()
endif()
endif()
# mask out the min/max macros from minwindef.h
if(MSVC)
add_definitions(-DNOMINMAX)
endif()
#NB: switch this to shared library and things stop working. (there is likely two copies of volk somewhere.
add_library(${PROJECT_NAME} OBJECT ${PROJECT_FILES})
set_target_properties(${PROJECT_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON)
# compiler flags based on compiler type
if(NOT MSVC)
target_compile_options(${PROJECT_NAME} PUBLIC -fexceptions)
endif()
if(MSVC)
target_compile_options(${PROJECT_NAME} PUBLIC /MP)
endif()
if(${VKB_VALIDATION_LAYERS})
target_compile_definitions(${PROJECT_NAME} PUBLIC VKB_VALIDATION_LAYERS)
endif()
# GPU assisted validation layers are not available on macOS.
if(${VKB_VALIDATION_LAYERS_GPU_ASSISTED})
if (APPLE)
message(WARNING "GPU assisted validation layers are not currently available on macOS.")
else()
target_compile_definitions(${PROJECT_NAME} PUBLIC VKB_VALIDATION_LAYERS)
target_compile_definitions(${PROJECT_NAME} PUBLIC VKB_VALIDATION_LAYERS_GPU_ASSISTED)
endif()
endif()
if(${VKB_VALIDATION_LAYERS_BEST_PRACTICES})
target_compile_definitions(${PROJECT_NAME} PUBLIC VKB_VALIDATION_LAYERS)
target_compile_definitions(${PROJECT_NAME} PUBLIC VKB_VALIDATION_LAYERS_BEST_PRACTICES)
endif()
if(${VKB_VALIDATION_LAYERS_SYNCHRONIZATION})
target_compile_definitions(${PROJECT_NAME} PUBLIC VKB_VALIDATION_LAYERS)
target_compile_definitions(${PROJECT_NAME} PUBLIC VKB_VALIDATION_LAYERS_SYNCHRONIZATION)
endif()
if(${VKB_VULKAN_DEBUG})
target_compile_definitions(${PROJECT_NAME} PUBLIC VKB_VULKAN_DEBUG)
endif()
if(${VKB_ENABLE_PORTABILITY})
message(STATUS "Vulkan Portability Enumeration and Portability Subset extensions are enabled")
target_compile_definitions(${PROJECT_NAME} PUBLIC VKB_ENABLE_PORTABILITY)
endif()
if(${VKB_WARNINGS_AS_ERRORS})
message(STATUS "Warnings as Errors Enabled")
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
target_compile_options(${PROJECT_NAME} PRIVATE -Werror)
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
target_compile_options(${PROJECT_NAME} PRIVATE /W3 /WX)
endif()
endif()
target_include_directories(${PROJECT_NAME} PUBLIC $<TARGET_PROPERTY:plugins,INCLUDE_DIRECTORIES> ${CMAKE_CURRENT_SOURCE_DIR})
# Link third party libraries
target_link_libraries(${PROJECT_NAME} PUBLIC
vkb__core
vkb__filesystem
volk
ktx
stb
${ASTC_TARGET}
imgui
tinygltf
glm
vma
spirv-cross-glsl
spdlog
plugins)
if(${NEED_LINK_ATOMIC})
target_link_libraries(${PROJECT_NAME} PUBLIC atomic)
endif()
# Link platform specific libraries
if(ANDROID)
# Import game-activity static lib inside the game-activity_static prefab module.
find_package(game-activity REQUIRED CONFIG)
target_link_libraries(${PROJECT_NAME} PUBLIC hwcpipe log android game-activity::game-activity_static)
else()
if (DIRECT_TO_DISPLAY)
target_link_libraries(${PROJECT_NAME} PRIVATE dl)
else()
if (NOT IOS)
target_link_libraries(${PROJECT_NAME} PRIVATE glfw)
endif ()
endif()
endif()
if(VKB_DO_CLANG_TIDY)
set_target_properties(framework PROPERTIES CXX_CLANG_TIDY "${VKB_DO_CLANG_TIDY}")
endif()
if (VKB_PROFILING)
## Enable profling
target_compile_definitions(${PROJECT_NAME} PUBLIC VKB_PROFILING=1)
else()
## Disable profiling
target_compile_definitions(${PROJECT_NAME} PUBLIC VKB_PROFILING=0)
endif()
+231
View File
@@ -0,0 +1,231 @@
////
- Copyright (c) 2023-2025, Sascha Willems
-
- 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.
-
////
= Framework
// omit in toc
:pp: {plus}{plus}
This folder contains the base framework used by the samples.
It offers sample base classes, encapsulates common functionality for e.g.
loading assets (images, models, shaders), wraps common Vulkan objects and implements frequently used concepts like a cache and a scene graph.
The framework also implements platform support for Windows, Linux, MacOS and Android.
It can be used as a guideline for writing advanced Vulkan applications.
Before trying to implement common functions, consider checking if the framework doesn't already provides what you are looking for.
== Sample base classes
The framework provides two different sample base classes.
When xref:../scripts/README.adoc[creating new samples], you can choose between one of them:
=== High level base sample class
This base class abstracts away most of the Vulkan API calls and as such makes heavy use of the Vulkan object wrapper classes of the framework.
Writing samples with the base class is less verbose.
See link:./vulkan_sample.h[vulkan_sample.h] and link:./vulkan_sample.cpp[vulkan_sample.cpp].
=== API sample base class
This base class uses less abstraction, letting you work more explicitly with the api.
See link:./api_vulkan_sample.h[api_vulkan_sample.h] and link:./api_vulkan_sample.cpp[api_vulkan_sample.cpp].
=== Support for Vulkan-Hpp
While the framework itself primarily uses the C-Interface for Vulkan, both the high level and the API sample base class also come with https://github.com/KhronosGroup/Vulkan-Hpp[Vulkan-Hpp] variants, letting you write samples using the C{pp} Vulkan language bindings instead.
See link:./hpp_vulkan_sample.h[hpp_vulkan_sample.h] / link:./hpp_vulkan_sample.cpp[hpp_vulkan_sample.cpp] and link:./hpp_api_vulkan_sample.h[hpp_api_vulkan_sample.h] / link:./hpp_api_vulkan_sample.cpp[hpp_api_vulkan_sample.cpp].
== Commonly used framework concepts
=== Enabling extensions
Vulkan is an extensible api.
New features are usually exposed through either instance or device extensions.
Extensions can be enabled in the constructor of both the high level and api base sample class:
[,cpp]
----
MySample::MySample()
{
add_instance_extension(VK_SOME_INSTANCE_EXTENSION_NAME);
add_device_extension(VK_SOME_DEVICE_EXTENSION_NAME);
}
----
NOTE: The framework uses the https://github.com/zeux/volk[Volk] meta-loader, which will automatically load extension function pointers for all enabled extensions.
There is no need to manually get extension pointer functions.
=== Changing the Vulkan api version
By default all samples create a Vulkan 1.1 instance.
Higher versions can be requested in the constructor of a sample:
[,cpp]
----
MySample::MySample()
{
set_api_version(VK_API_VERSION_1_2);
}
----
=== Requesting GPU features
Most extensions also require enabling corresponding feature flags.
This can be done by overriding the `request_gpu_features` function of the base class:
[,cpp]
----
void MySample::request_gpu_features(vkb::PhysicalDevice &gpu)
{
// Get a reference to the feature structure required for an extension
auto &requested_extension_feature = gpu
request_extension_features<VkPhysicalDeviceSomeExtensionFeaturesKHR>(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SOME_EXTENSION_FEATURES_KHR);
// Enable a selected feature
requested_extension_feature.featureName = VK_TRUE;
}
----
=== Extending the graphical user interface
The framework includes a graphical user interface based on https://github.com/ocornut/imgui[Dear ImGui].
This can be used by samples to display values and add controls like buttons, dropdowns, etc.
To add additional elements to the UI of a sample, you override the respective function from the base class:
Samples based on the high level base class need to override the `draw_gui` function:
[,cpp]
----
void MySample::draw_gui()
{
if (ImGui::Checkbox("Enable Option", &option_enabled))
{
...
}
}
----
Samples based on the api base class need to override the `on_update_ui_overlay` function:
[,cpp]
----
void MyApiSample::on_update_ui_overlay(vkb::Drawer &drawer)
{
if (drawer.checkbox("Enable option", &option_enabled))
{
...
}
}
----
=== Loading models
The framework supports https://www.khronos.org/gltf/[glTF models] and includes a loader for this format.
The high level base class works with a single glTF scene loaded at startup.
The scene is part of the base class, and there is no need to explicitly draw it:
[,cpp]
----
bool MySample::prepare(const vkb::ApplicationOptions &options)
{
scene = load_scene("filename.gltf");
}
----
With the api base class are explicitly declared, loaded and rendered:
[,cpp]
----
// my_sample.h
class MyApiSample : public ApiVulkanSample
{
std::unique_ptr<vkb::sg::SubMesh> modelA;
std::unique_ptr<vkb::sg::SubMesh> modelB;
...
}
// my_sample.cpp
bool MyApiSample::prepare(const vkb::ApplicationOptions &options)
{
modelA = load_model("filenameA.gltf");
modelB = load_model("filenameB.gltf");
}
void MyApiSample::build_command_buffers()
{
vkBeginCommandBuffer(...);
...
draw_model(modelA, draw_cmd_buffers[i]);
...
draw_model(modelB, draw_cmd_buffers[i]);
...
vkEndCommandBufer(...);
}
----
=== Loading images
The framework supports the https://www.khronos.org/ktx/[KTX] GPU container format and includes a loader for this format.
As a container format, KTX supports different image formats ranging from basic RGBA images to compressed formats.
[,cpp]
----
texture = load_texture("rgba_texture.ktx", vkb::sg::Image::Color);
----
Images (textures) loaded like this can then be used as descriptors later on:
[,cpp]
----
VkDescriptorImageInfo image_descriptor = create_descriptor(texture);
----
=== Loading shaders
The framework supports loading textual GLSL shaders.
These shaders are then compiled to https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html[SPIR-V] at runtime so Vulkan can ingest them.
With the high level base class, shaders are attached to the scene graphics' render pipeline:
[,cpp]
----
vkb::ShaderSource vert_shader("vs.vert");
vkb::ShaderSource frag_shader("fs.frag");
auto scene_subpass = std::make_unique<vkb::ForwardSubpass>(get_render_context(), std::move(vert_shader), std::move(frag_shader), *scene, *camera);
auto render_pipeline = vkb::RenderPipeline();
render_pipeline.add_subpass(std::move(scene_subpass));
set_render_pipeline(std::move(render_pipeline));
----
While in the api base class, this is again more explicitly by creating shader modules used at pipeline creation time:
[,cpp]
----
std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages;
...
shader_stages[0] = load_shader("vs.vert", VK_SHADER_STAGE_VERTEX_BIT);
shader_stages[1] = load_shader("fs.frag", VK_SHADER_STAGE_FRAGMENT_BIT);
VK_CHECK(vkCreateGraphicsPipelines(get_device().get_handle(), pipeline_cache, 1, &pipeline_create_info, nullptr, &pipeline));
----
File diff suppressed because it is too large Load Diff
+466
View File
@@ -0,0 +1,466 @@
/* Copyright (c) 2019-2025, Sascha Willems
*
* 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
#ifdef _WIN32
# pragma comment(linker, "/subsystem:windows")
# include <ShellScalingAPI.h>
# include <fcntl.h>
# include <io.h>
# include <windows.h>
#endif
#include <chrono>
#include <iostream>
#include <random>
#include <sys/stat.h>
#include "camera.h"
#include "common/vk_common.h"
#include "common/vk_initializers.h"
#include "core/buffer.h"
#include "core/swapchain.h"
#include "gui.h"
#include "platform/platform.h"
#include "rendering/render_context.h"
#include "scene_graph/components/image.h"
#include "scene_graph/components/sampler.h"
#include "scene_graph/components/texture.h"
#include "vulkan_sample.h"
/**
* @brief A swapchain buffer
*/
struct SwapchainBuffer
{
VkImage image;
VkImageView view;
};
/**
* @brief A texture wrapper that owns its image data and links it with a sampler
*/
struct Texture
{
std::unique_ptr<vkb::sg::Image> image;
VkSampler sampler;
};
/**
* @brief The structure of a vertex
*/
struct Vertex
{
glm::vec3 pos;
glm::vec3 normal;
glm::vec2 uv;
glm::vec4 joint0;
glm::vec4 weight0;
glm::vec3 color;
};
/**
* @brief The structure of a vertex for storage buffer
* Simplified to position and normal for easier alignment
*/
struct AlignedVertex
{
glm::vec4 pos;
glm::vec4 normal;
};
/**
* @brief The structure of a meshlet for mesh shader
*/
struct Meshlet
{
uint32_t vertices[64];
uint32_t indices[126];
uint32_t vertex_count;
uint32_t index_count;
};
/**
* @brief Sascha Willems base class for use in his ported samples into the framework
*
* See vkb::VulkanSample for documentation
*/
class ApiVulkanSample : public vkb::VulkanSampleC
{
public:
ApiVulkanSample() = default;
virtual ~ApiVulkanSample();
virtual bool prepare(const vkb::ApplicationOptions &options) override;
virtual void input_event(const vkb::InputEvent &input_event) override;
virtual void update(float delta_time) override;
virtual bool resize(const uint32_t width, const uint32_t height) override;
virtual void render(float delta_time) = 0;
enum RenderPassCreateFlags
{
ColorAttachmentLoad = 0x00000001
};
protected:
/// Stores the swapchain image buffers
std::vector<SwapchainBuffer> swapchain_buffers;
virtual void create_render_context() override;
// Handle to the device graphics queue that command buffers are submitted to
VkQueue queue;
// Depth buffer format (selected during Vulkan initialization)
VkFormat depth_format;
// Command buffer pool
VkCommandPool cmd_pool;
/** @brief Pipeline stages used to wait at for graphics queue submissions */
VkPipelineStageFlags submit_pipeline_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
// Contains command buffers and semaphores to be presented to the queue
VkSubmitInfo submit_info;
// Command buffers used for rendering
std::vector<VkCommandBuffer> draw_cmd_buffers;
// Global render pass for frame buffer writes
VkRenderPass render_pass;
// List of available frame buffers (same as number of swap chain images)
std::vector<VkFramebuffer> framebuffers;
// Active frame buffer index
uint32_t current_buffer = 0;
// Descriptor set pool
VkDescriptorPool descriptor_pool = VK_NULL_HANDLE;
// List of shader modules created (stored for cleanup)
std::vector<VkShaderModule> shader_modules;
// Pipeline cache object
VkPipelineCache pipeline_cache;
// Synchronization semaphores
struct
{
// Swap chain image presentation
VkSemaphore acquired_image_ready;
// Command buffer submission and execution
VkSemaphore render_complete;
} semaphores;
// Synchronization fences
std::vector<VkFence> wait_fences;
/**
* @brief Populates the swapchain_buffers vector with the image and imageviews
*/
void create_swapchain_buffers();
/**
* @brief Updates the swapchains image usage, if a swapchain exists and recreates all resources based on swapchain images
* @param image_usage_flags The usage flags the new swapchain images will have
*/
void update_swapchain_image_usage_flags(std::set<VkImageUsageFlagBits> image_usage_flags);
/**
* @brief Handles changes to the surface, e.g. on resize
*/
void handle_surface_changes();
/**
* @brief Creates a buffer descriptor
* @param buffer The buffer from which to create the descriptor from
* @param size The size of the descriptor (default: VK_WHOLE_SIZE)
* @param offset The offset of the descriptor (default: 0)
*/
VkDescriptorBufferInfo create_descriptor(vkb::core::BufferC &buffer, VkDeviceSize size = VK_WHOLE_SIZE, VkDeviceSize offset = 0);
/**
* @brief Creates an image descriptor
* @param texture The texture from which to create the descriptor from
* @param descriptor_type The type of image descriptor (default: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER)
*/
VkDescriptorImageInfo create_descriptor(Texture &texture, VkDescriptorType descriptor_type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
/**
* @brief Loads in a ktx 2D texture
* @param file The filename of the texture to load
* @param content_type The type of content in the image file
*/
Texture load_texture(const std::string &file, vkb::sg::Image::ContentType content_type);
/**
* @brief Loads in a ktx 2D texture array
* @param file The filename of the texture to load
* @param content_type The type of content in the image file
*/
Texture load_texture_array(const std::string &file, vkb::sg::Image::ContentType content_type);
/**
* @brief Loads in a ktx 2D texture cubemap
* @param file The filename of the texture to load
* @param content_type The type of content in the image file
*/
Texture load_texture_cubemap(const std::string &file, vkb::sg::Image::ContentType content_type);
/**
* @brief Loads in a single model from a GLTF file
* @param file The filename of the model to load
* @param index The index of the model to load from the GLTF file (default: 0)
* @param storage_buffer Set true to store model in SSBO
* @param additional_buffer_usage_flags Additional buffer usage flags to be applied to vertex and index buffers
*/
std::unique_ptr<vkb::sg::SubMesh> load_model(const std::string &file, uint32_t index = 0, bool storage_buffer = false, VkBufferUsageFlags additional_buffer_usage_flags = 0);
/**
* @brief Records the necessary drawing commands to a command buffer
* @param model The model to draw
* @param command_buffer The command buffer to record to
* @param instance_count The number of instances (default: 1)
*/
void draw_model(std::unique_ptr<vkb::sg::SubMesh> &model, VkCommandBuffer command_buffer, uint32_t instance_count = 1);
/**
* @brief Synchronously execute a block code within a command buffer, then submit the command buffer and wait for completion.
* @param f a block of code which is passed a command buffer which is already in the begin state.
* @param signalSemaphore An optional semaphore to signal when the commands have completed execution.
*/
void with_command_buffer(const std::function<void(VkCommandBuffer command_buffer)> &f, VkSemaphore signalSemaphore = VK_NULL_HANDLE);
/**
* @brief Synchronously execute a block code within a command buffer vkb wrapper, then submit the command buffer and wait for completion.
* @param f a block of code which is passed a command buffer which is already in the begin state.
*/
void with_vkb_command_buffer(const std::function<void(vkb::core::CommandBufferC &command_buffer)> &f);
public:
/**
* @brief Called when a view change occurs, can be overriden in derived samples to handle updating uniforms
*/
virtual void view_changed();
/**
* @brief Called after the mouse cursor is moved and before internal events (like camera rotation) is handled
* @param x The width from the origin
* @param y The height from the origin
* @param handled Whether the event was handled
*/
virtual void mouse_moved(double x, double y, bool &handled);
/**
* @brief To be overridden by the derived class. Records the relevant commands to the rendering command buffers
* Called when the framebuffers need to be rebuilt
*/
virtual void build_command_buffers() = 0;
/**
* @brief Rebuild the command buffers by first resetting the corresponding command pool and then building the command buffers.
*/
void rebuild_command_buffers();
/**
* @brief Creates the fences for rendering
*/
void create_synchronization_primitives();
/**
* @brief Creates a new (graphics) command pool object storing command buffers
*/
void create_command_pool();
/**
* @brief Setup default depth and stencil views
*/
virtual void setup_depth_stencil();
/**
* @brief Create framebuffers for all requested swap chain images
* Can be overriden in derived class to setup a custom framebuffer (e.g. for MSAA)
*/
virtual void setup_framebuffer();
/**
* @brief Setup a default render pass
* Can be overriden in derived class to setup a custom render pass (e.g. for MSAA)
*/
virtual void setup_render_pass();
/**
* @brief Update flags for the default render pass and recreate it
* @param flags Optional flags for render pass creation
*/
void update_render_pass_flags(uint32_t flags = 0);
/**
* @brief Check if command buffers are valid (!= VK_NULL_HANDLE)
*/
bool check_command_buffers();
/**
* @brief Create command buffers for drawing commands
*/
void create_command_buffers();
/**
* @brief Destroy all command buffers, may be necessary during runtime if options are toggled
*/
void destroy_command_buffers();
/**
* @brief Recreate the current command buffer draw_cmd_buffer[current_buffer]
*/
void recreate_current_command_buffer();
/**
* @brief Create a cache pool for rendering pipelines
*/
void create_pipeline_cache();
/**
* @brief Load a SPIR-V shader
* @param file The file location of the shader relative to the shaders folder
* @param stage The shader stage
*/
VkPipelineShaderStageCreateInfo load_shader(const std::string &file, VkShaderStageFlagBits stage);
/**
* @brief Load a SPIR-V shader based on current shader language selection
* @param sample_folder_name Base folder where the shaders are located (without GLSL/HLSL sub folder)
* @param shader_filename Base name of the shader file
* @param stage The shader stage
*/
VkPipelineShaderStageCreateInfo load_shader(const std::string &sample_folder_name, const std::string &shader_filename, VkShaderStageFlagBits stage);
/**
* @brief Updates the overlay
* @param delta_time The time taken since the last frame
* @param additional_ui Function that implements an additional Gui
*/
void update_overlay(float delta_time, const std::function<void()> &additional_ui) override;
/**
* @brief If the gui is enabled, then record the drawing commands to a command buffer
* @param command_buffer A valid command buffer that is ready to be recorded to
*/
void draw_ui(const VkCommandBuffer command_buffer);
/**
* @brief Prepare the frame for workload submission, acquires the next image from the swap chain and
* sets the default wait and signal semaphores
*/
void prepare_frame();
/**
* @brief Submit the frames' workload
*/
void submit_frame();
/**
* @brief Called when the UI overlay is updating, can be used to add custom elements to the overlay
* @param drawer The drawer from the gui to draw certain elements
*/
virtual void on_update_ui_overlay(vkb::Drawer &drawer);
/**
* @brief Initializes the UI. Can be overridden to customize the way it is displayed.
*/
virtual void prepare_gui();
private:
/** brief Indicates that the view (position, rotation) has changed and buffers containing camera matrices need to be updated */
bool view_updated = false;
// Destination dimensions for resizing the window
uint32_t dest_width;
uint32_t dest_height;
bool resizing = false;
void handle_mouse_move(int32_t x, int32_t y);
#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)
/// The debug report callback
VkDebugReportCallbackEXT debug_report_callback{VK_NULL_HANDLE};
#endif
public:
bool prepared = false;
uint32_t width = 1280;
uint32_t height = 720;
VkClearColorValue default_clear_color = {{0.002f, 0.002f, 0.002f, 1.0f}};
float zoom = 0;
// Defines a frame rate independent timer value clamped from -1.0...1.0
// For use in animations, rotations, etc.
float timer = 0.0f;
// Multiplier for speeding up (or slowing down) the global timer
float timer_speed = 0.0025f;
bool paused = false;
// Use to adjust mouse rotation speed
float rotation_speed = 1.0f;
// Use to adjust mouse zoom speed
float zoom_speed = 1.0f;
vkb::Camera camera;
glm::vec3 rotation = glm::vec3();
glm::vec3 camera_pos = glm::vec3();
glm::vec2 mouse_pos;
std::string title = "Vulkan Example";
std::string name = "vulkanExample";
struct ImageData
{
VkImage image{VK_NULL_HANDLE};
VkDeviceMemory mem{VK_NULL_HANDLE};
VkImageView view{VK_NULL_HANDLE};
} depth_stencil;
struct
{
bool left = false;
bool right = false;
bool middle = false;
} mouse_buttons;
struct TouchPos
{
int32_t x;
int32_t y;
} touch_pos;
bool touch_down = false;
double touch_timer = 0.0;
uint32_t frame_count = 0;
float accumulated_time = 0.0f;
uint32_t fps = 1; // to prevent division by zero on first frame
};
+377
View File
@@ -0,0 +1,377 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
* Copyright (c) 2024-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "common/helpers.h"
#include "core/buffer.h"
#include "core/device.h"
namespace vkb
{
/**
* @brief An allocation of vulkan memory; different buffer allocations,
* with different offset and size, may come from the same Vulkan buffer
*/
template <vkb::BindingType bindingType>
class BufferAllocation
{
public:
using DeviceSizeType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DeviceSize, VkDeviceSize>::type;
public:
BufferAllocation() = default;
BufferAllocation(const BufferAllocation &) = default;
BufferAllocation(BufferAllocation &&) = default;
BufferAllocation &operator=(const BufferAllocation &) = default;
BufferAllocation &operator=(BufferAllocation &&) = default;
BufferAllocation(vkb::core::Buffer<bindingType> &buffer, DeviceSizeType size, DeviceSizeType offset);
bool empty() const;
vkb::core::Buffer<bindingType> &get_buffer();
DeviceSizeType get_offset() const;
DeviceSizeType get_size() const;
void update(const std::vector<uint8_t> &data, uint32_t offset = 0);
template <typename T>
void update(const T &value, uint32_t offset = 0);
private:
vkb::core::BufferCpp *buffer = nullptr;
vk::DeviceSize offset = 0;
vk::DeviceSize size = 0;
};
using BufferAllocationC = BufferAllocation<vkb::BindingType::C>;
using BufferAllocationCpp = BufferAllocation<vkb::BindingType::Cpp>;
template <>
inline BufferAllocation<vkb::BindingType::Cpp>::BufferAllocation(vkb::core::BufferCpp &buffer, vk::DeviceSize size, vk::DeviceSize offset) :
buffer(&buffer),
offset(offset),
size(size)
{}
template <>
inline BufferAllocation<vkb::BindingType::C>::BufferAllocation(vkb::core::BufferC &buffer, VkDeviceSize size, VkDeviceSize offset) :
buffer(reinterpret_cast<vkb::core::BufferCpp *>(&buffer)),
offset(static_cast<vk::DeviceSize>(offset)),
size(static_cast<vk::DeviceSize>(size))
{}
template <vkb::BindingType bindingType>
bool BufferAllocation<bindingType>::empty() const
{
return size == 0 || buffer == nullptr;
}
template <vkb::BindingType bindingType>
typename vkb::core::Buffer<bindingType> &BufferAllocation<bindingType>::get_buffer()
{
assert(buffer && "Invalid buffer pointer");
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return *buffer;
}
else
{
return reinterpret_cast<vkb::core::BufferC &>(*buffer);
}
}
template <vkb::BindingType bindingType>
typename BufferAllocation<bindingType>::DeviceSizeType BufferAllocation<bindingType>::get_offset() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return offset;
}
else
{
return static_cast<VkDeviceSize>(offset);
}
}
template <vkb::BindingType bindingType>
typename BufferAllocation<bindingType>::DeviceSizeType BufferAllocation<bindingType>::get_size() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return size;
}
else
{
return static_cast<VkDeviceSize>(size);
}
}
template <vkb::BindingType bindingType>
void BufferAllocation<bindingType>::update(const std::vector<uint8_t> &data, uint32_t offset)
{
assert(buffer && "Invalid buffer pointer");
if (offset + data.size() <= size)
{
buffer->update(data, to_u32(this->offset) + offset);
}
else
{
LOGE("Ignore buffer allocation update");
}
}
template <vkb::BindingType bindingType>
template <typename T>
void BufferAllocation<bindingType>::update(const T &value, uint32_t offset)
{
update(to_bytes(value), offset);
}
/**
* @brief Helper class which handles multiple allocation from the same underlying Vulkan buffer.
*/
template <vkb::BindingType bindingType>
class BufferBlock
{
public:
using BufferUsageFlagsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::BufferUsageFlags, VkBufferUsageFlags>::type;
using DeviceSizeType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DeviceSize, VkDeviceSize>::type;
public:
BufferBlock() = delete;
BufferBlock(BufferBlock const &rhs) = delete;
BufferBlock(BufferBlock &&rhs) = default;
BufferBlock &operator=(BufferBlock const &rhs) = delete;
BufferBlock &operator=(BufferBlock &&rhs) = default;
BufferBlock(vkb::core::Device<bindingType> &device, DeviceSizeType size, BufferUsageFlagsType usage, VmaMemoryUsage memory_usage);
/**
* @return An usable view on a portion of the underlying buffer
*/
BufferAllocation<bindingType> allocate(DeviceSizeType size);
/**
* @brief check if this BufferBlock can allocate a given amount of memory
* @param size the number of bytes to check
* @return \c true if \a size bytes can be allocated from this \c BufferBlock, otherwise \c false.
*/
bool can_allocate(DeviceSizeType size) const;
DeviceSizeType get_size() const;
void reset();
private:
/**
* @ brief Determine the current aligned offset.
* @return The current aligned offset.
*/
vk::DeviceSize aligned_offset() const;
vk::DeviceSize determine_alignment(vk::BufferUsageFlags usage, vk::PhysicalDeviceLimits const &limits) const;
private:
vkb::core::BufferCpp buffer;
vk::DeviceSize alignment = 0; // Memory alignment, it may change according to the usage
vk::DeviceSize offset = 0; // Current offset, it increases on every allocation
};
using BufferBlockC = BufferBlock<vkb::BindingType::C>;
using BufferBlockCpp = BufferBlock<vkb::BindingType::Cpp>;
template <vkb::BindingType bindingType>
BufferBlock<bindingType>::BufferBlock(vkb::core::Device<bindingType> &device, DeviceSizeType size, BufferUsageFlagsType usage, VmaMemoryUsage memory_usage) :
buffer{device, size, usage, memory_usage}
{
if constexpr (bindingType == BindingType::Cpp)
{
alignment = determine_alignment(usage, device.get_gpu().get_properties().limits);
}
else
{
alignment =
determine_alignment(static_cast<vk::BufferUsageFlags>(usage), static_cast<vk::PhysicalDeviceLimits>(device.get_gpu().get_properties().limits));
}
}
template <vkb::BindingType bindingType>
BufferAllocation<bindingType> BufferBlock<bindingType>::allocate(DeviceSizeType size)
{
if (can_allocate(size))
{
// Move the current offset and return an allocation
auto aligned = aligned_offset();
offset = aligned + size;
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return BufferAllocationCpp{buffer, size, aligned};
}
else
{
return BufferAllocationC{reinterpret_cast<vkb::core::BufferC &>(buffer), static_cast<VkDeviceSize>(size), static_cast<VkDeviceSize>(aligned)};
}
}
// No more space available from the underlying buffer, return empty allocation
return BufferAllocation<bindingType>{};
}
template <vkb::BindingType bindingType>
bool BufferBlock<bindingType>::can_allocate(DeviceSizeType size) const
{
assert(size > 0 && "Allocation size must be greater than zero");
return (aligned_offset() + size <= buffer.get_size());
}
template <vkb::BindingType bindingType>
typename BufferBlock<bindingType>::DeviceSizeType BufferBlock<bindingType>::get_size() const
{
return buffer.get_size();
}
template <vkb::BindingType bindingType>
void BufferBlock<bindingType>::reset()
{
offset = 0;
}
template <vkb::BindingType bindingType>
vk::DeviceSize BufferBlock<bindingType>::aligned_offset() const
{
return (offset + alignment - 1) & ~(alignment - 1);
}
template <vkb::BindingType bindingType>
vk::DeviceSize BufferBlock<bindingType>::determine_alignment(vk::BufferUsageFlags usage, vk::PhysicalDeviceLimits const &limits) const
{
if (usage == vk::BufferUsageFlagBits::eUniformBuffer)
{
return limits.minUniformBufferOffsetAlignment;
}
else if (usage == vk::BufferUsageFlagBits::eStorageBuffer)
{
return limits.minStorageBufferOffsetAlignment;
}
else if (usage == vk::BufferUsageFlagBits::eUniformTexelBuffer)
{
return limits.minTexelBufferOffsetAlignment;
}
else if (usage == vk::BufferUsageFlagBits::eIndexBuffer || usage == vk::BufferUsageFlagBits::eVertexBuffer ||
usage == vk::BufferUsageFlagBits::eIndirectBuffer)
{
// Used to calculate the offset, required when allocating memory (its value should be power of 2)
return 16;
}
else
{
throw std::runtime_error("Usage not recognised");
}
}
/**
* @brief A pool of buffer blocks for a specific usage.
* It may contain inactive blocks that can be recycled.
*
* BufferPool is a linear allocator for buffer chunks, it gives you a view of the size you want.
* A BufferBlock is the corresponding VkBuffer and you can get smaller offsets inside it.
* Since a shader cannot specify dynamic UBOs, it has to be done from the code
* (set_resource_dynamic).
*
* When a new frame starts, buffer blocks are returned: the offset is reset and contents are
* overwritten. The minimum allocation size is 256 kb, if you ask for more you get a dedicated
* buffer allocation.
*
* We re-use descriptor sets: we only need one for the corresponding buffer infos (and we only
* have one VkBuffer per BufferBlock), then it is bound and we use dynamic offsets.
*/
template <vkb::BindingType bindingType>
class BufferPool
{
public:
using BufferUsageFlagsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::BufferUsageFlags, VkBufferUsageFlags>::type;
using DeviceSizeType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DeviceSize, VkDeviceSize>::type;
public:
BufferPool(
vkb::core::Device<bindingType> &device, DeviceSizeType block_size, BufferUsageFlagsType usage, VmaMemoryUsage memory_usage = VMA_MEMORY_USAGE_CPU_TO_GPU);
BufferBlock<bindingType> &request_buffer_block(DeviceSizeType minimum_size, bool minimal = false);
void reset();
private:
vkb::core::DeviceCpp &device;
std::vector<std::unique_ptr<BufferBlockCpp>> buffer_blocks; /// List of blocks requested (need to be pointers in order to keep their address constant on vector resizing)
vk::DeviceSize block_size = 0; /// Minimum size of the blocks
vk::BufferUsageFlags usage;
VmaMemoryUsage memory_usage{};
};
using BufferPoolC = BufferPool<vkb::BindingType::C>;
using BufferPoolCpp = BufferPool<vkb::BindingType::Cpp>;
template <vkb::BindingType bindingType>
BufferPool<bindingType>::BufferPool(vkb::core::Device<bindingType> &device,
DeviceSizeType block_size,
BufferUsageFlagsType usage,
VmaMemoryUsage memory_usage) :
device{reinterpret_cast<vkb::core::DeviceCpp &>(device)}, block_size{block_size}, usage{usage}, memory_usage{memory_usage}
{
}
template <vkb::BindingType bindingType>
BufferBlock<bindingType> &BufferPool<bindingType>::request_buffer_block(DeviceSizeType minimum_size, bool minimal)
{
// Find a block in the range of the blocks which can fit the minimum size
auto it = minimal ? std::ranges::find_if(buffer_blocks,
[&minimum_size](auto const &buffer_block) { return (buffer_block->get_size() == minimum_size) && buffer_block->can_allocate(minimum_size); }) :
std::ranges::find_if(buffer_blocks,
[&minimum_size](auto const &buffer_block) { return buffer_block->can_allocate(minimum_size); });
if (it == buffer_blocks.end())
{
LOGD("Building #{} buffer block ({})", buffer_blocks.size(), vk::to_string(usage));
vk::DeviceSize new_block_size = minimal ? minimum_size : std::max(block_size, minimum_size);
// Create a new block and get the iterator on it
it = buffer_blocks.emplace(buffer_blocks.end(), std::make_unique<BufferBlockCpp>(device, new_block_size, usage, memory_usage));
}
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return *it->get();
}
else
{
return reinterpret_cast<BufferBlockC &>(*it->get());
}
}
template <vkb::BindingType bindingType>
void BufferPool<bindingType>::reset()
{
// Attention: Resetting the BufferPool is not supposed to clear the BufferBlocks, but just reset them!
// The actual VkBuffers are used to hash the DescriptorSet in RenderFrame::request_descriptor_set.
// Don't know (for now) how that works with resetted buffers!
for (auto &buffer_block : buffer_blocks)
{
buffer_block->reset();
}
}
} // namespace vkb
+263
View File
@@ -0,0 +1,263 @@
/* Copyright (c) 2021-2025, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2025, Bradley Austin Davis. All rights reserved.
*
* 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 "common/vk_common.h"
#include "vulkan_type_mapping.h"
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace allocated
{
/**
* @brief Many Vulkan resource types (most notably Images and to a lesser extent Buffers)
* and and their corresponding memory allocations have many parameters that need to be setup
* when creating them. Although many of these have reasonable defaults, constructors with
* numerous arguments, some or all of which may have default arguments, aren't well suited
* to partial customization. This is a common failing of languages that don't support named
* arguments and has led to the common use of the [builder pattern](https://en.wikipedia.org/wiki/Builder_pattern),
* where a helper class is used to store all the options that can be tweaked for an object
* when it's created. A builder class will have reasonable defaults where appropriate and only
* require arguments for the builder constructor when a value is always required for creation to occur
* (for example, the size of a buffer or the extent of an image). Remaining parameters can be set
* with methods on the builder class, which return a reference to the builder object, allowing
* chaining of the method calls.
*
* This builder class serves as a base containing options that are common to all
* [VMA](https://gpuopen.com/vulkan-memory-allocator/) allocated and managed resources.
* For instance, the VMA create and usage flags are set here, but the image or buffer
* usage flags are handled in the derived builder classes specific to those types.
*
* The following is an example of how the builder pattern is used in the codebase:
```cpp
vkb::core::ImageBuilder(VkExtent3D{grid_width, grid_height, 1})
.with_format(VK_FORMAT_R8G8B8A8_UNORM)
.with_usage(VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT)
.with_vma_usage(VMA_MEMORY_USAGE_GPU_ONLY)
.with_sample_count(VK_SAMPLE_COUNT_1_BIT)
.with_mip_levels(1)
.with_array_layers(1)
.with_tiling(VK_IMAGE_TILING_OPTIMAL)
.with_queue_families(static_cast<uint32_t>(queue_families.size()), queue_families.data())
.with_sharing_mode(sharing_mode));
```
* The actual image can be created with `build()` which returns a `vkb::core::Image` or `buildPtr` which returns a `std::unique_ptr<vkb::core::Image>`.
* Alternatively, the builder can be used as an argument to the `Image` constructor, which will build the image for you in place.
* @note The builder pattern is intended to displace the currently used `vkb::core::Image` and `vkb::core::Buffer` constructors with numerous
* arguments, but this is a work in progress and not currently in wide use in the codebase.
*
* @tparam BuilderType Allow the same builder base class to be used
* with a variety of subclasses while using casting to return the corect dervied type
* from the modifier methods.
* @tparam bindingType A flag indicating whether this is being used with the C or C++ API
* @tparam CreateInfoType The type of the Vulkan create info structure. Either a `VkSomethingCreateInfo`
* or `vk::SomethingCreateInfo` for the C or C++ API respectively.
*/
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
class BuilderBase
{
public:
using MemoryPropertyFlagsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::MemoryPropertyFlags, VkMemoryPropertyFlags>::type;
using SharingModeType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::SharingMode, VkSharingMode>::type;
public:
VmaAllocationCreateInfo const &get_allocation_create_info() const;
CreateInfoType const &get_create_info() const;
std::string const &get_debug_name() const;
BuilderType &with_debug_name(const std::string &name);
BuilderType &with_implicit_sharing_mode();
BuilderType &with_memory_type_bits(uint32_t type_bits);
BuilderType &with_queue_families(uint32_t count, const uint32_t *family_indices);
BuilderType &with_queue_families(std::vector<uint32_t> const &queue_families);
BuilderType &with_sharing_mode(SharingModeType sharing_mode);
BuilderType &with_vma_flags(VmaAllocationCreateFlags flags);
BuilderType &with_vma_pool(VmaPool pool);
BuilderType &with_vma_preferred_flags(MemoryPropertyFlagsType flags);
BuilderType &with_vma_required_flags(MemoryPropertyFlagsType flags);
BuilderType &with_vma_usage(VmaMemoryUsage usage);
protected:
BuilderBase(const BuilderBase &other) = delete;
BuilderBase(const CreateInfoType &create_info);
CreateInfoType &get_create_info();
private:
// we always want to store a vk::CreateInfo, so we have to figure out that type, depending on the BindingType!
using HPPCreateInfoType = typename vkb::VulkanTypeMapping<bindingType, CreateInfoType>::Type;
protected:
VmaAllocationCreateInfo alloc_create_info = {};
HPPCreateInfoType create_info = {};
std::string debug_name = {};
};
template <typename BuilderType, typename CreateInfoType>
using BuilderBaseC = BuilderBase<vkb::BindingType::C, BuilderType, CreateInfoType>;
template <typename BuilderType, typename CreateInfoType>
using BuilderBaseCpp = BuilderBase<vkb::BindingType::Cpp, BuilderType, CreateInfoType>;
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline BuilderBase<bindingType, BuilderType, CreateInfoType>::BuilderBase(const CreateInfoType &create_info_) :
create_info{reinterpret_cast<HPPCreateInfoType const &>(create_info_)}
{
alloc_create_info.usage = VMA_MEMORY_USAGE_AUTO;
};
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline VmaAllocationCreateInfo const &BuilderBase<bindingType, BuilderType, CreateInfoType>::get_allocation_create_info() const
{
return alloc_create_info;
}
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline CreateInfoType const &BuilderBase<bindingType, BuilderType, CreateInfoType>::get_create_info() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return create_info;
}
else
{
return *reinterpret_cast<typename HPPCreateInfoType::NativeType const *>(&create_info);
}
}
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline CreateInfoType &BuilderBase<bindingType, BuilderType, CreateInfoType>::get_create_info()
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return create_info;
}
else
{
return *reinterpret_cast<typename HPPCreateInfoType::NativeType *>(&create_info);
}
}
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline std::string const &BuilderBase<bindingType, BuilderType, CreateInfoType>::get_debug_name() const
{
return debug_name;
}
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline BuilderType &BuilderBase<bindingType, BuilderType, CreateInfoType>::with_debug_name(const std::string &name)
{
debug_name = name;
return *static_cast<BuilderType *>(this);
}
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline BuilderType &BuilderBase<bindingType, BuilderType, CreateInfoType>::with_implicit_sharing_mode()
{
create_info.sharingMode = (1 < create_info.queueFamilyIndexCount) ? vk::SharingMode::eConcurrent : vk::SharingMode::eExclusive;
return *static_cast<BuilderType *>(this);
}
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline BuilderType &BuilderBase<bindingType, BuilderType, CreateInfoType>::with_memory_type_bits(uint32_t type_bits)
{
alloc_create_info.memoryTypeBits = type_bits;
return *static_cast<BuilderType *>(this);
}
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline BuilderType &BuilderBase<bindingType, BuilderType, CreateInfoType>::with_queue_families(uint32_t count, const uint32_t *family_indices)
{
create_info.queueFamilyIndexCount = count;
create_info.pQueueFamilyIndices = family_indices;
return *static_cast<BuilderType *>(this);
}
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline BuilderType &BuilderBase<bindingType, BuilderType, CreateInfoType>::with_queue_families(std::vector<uint32_t> const &queue_families)
{
return with_queue_families(static_cast<uint32_t>(queue_families.size()), queue_families.data());
}
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline BuilderType &BuilderBase<bindingType, BuilderType, CreateInfoType>::with_sharing_mode(SharingModeType sharing_mode)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
create_info.sharingMode = sharing_mode;
}
else
{
create_info.sharingMode = static_cast<vk::SharingMode>(sharing_mode);
}
return *static_cast<BuilderType *>(this);
}
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline BuilderType &BuilderBase<bindingType, BuilderType, CreateInfoType>::with_vma_flags(VmaAllocationCreateFlags flags)
{
alloc_create_info.flags = flags;
return *static_cast<BuilderType *>(this);
}
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline BuilderType &BuilderBase<bindingType, BuilderType, CreateInfoType>::with_vma_pool(VmaPool pool)
{
alloc_create_info.pool = pool;
return *static_cast<BuilderType *>(this);
}
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline BuilderType &BuilderBase<bindingType, BuilderType, CreateInfoType>::with_vma_preferred_flags(MemoryPropertyFlagsType flags)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
alloc_create_info.preferredFlags = static_cast<VkMemoryPropertyFlags>(flags);
}
else
{
alloc_create_info.preferredFlags = flags;
}
return *static_cast<BuilderType *>(this);
}
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline BuilderType &BuilderBase<bindingType, BuilderType, CreateInfoType>::with_vma_required_flags(MemoryPropertyFlagsType flags)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
alloc_create_info.requiredFlags = static_cast<VkMemoryPropertyFlags>(flags);
}
else
{
alloc_create_info.requiredFlags = flags;
}
return *static_cast<BuilderType *>(this);
}
template <vkb::BindingType bindingType, typename BuilderType, typename CreateInfoType>
inline BuilderType &BuilderBase<bindingType, BuilderType, CreateInfoType>::with_vma_usage(VmaMemoryUsage usage)
{
alloc_create_info.usage = usage;
return *static_cast<BuilderType *>(this);
}
} // namespace allocated
} // namespace vkb
+93
View File
@@ -0,0 +1,93 @@
/* Copyright (c) 2019-2024, Sascha Willems
*
* 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 "common/error.h"
#include "common/glm_common.h"
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/quaternion.hpp>
namespace vkb
{
enum CameraType
{
LookAt,
FirstPerson
};
class Camera
{
public:
void update(float deltaTime);
// Update camera passing separate axis data (gamepad)
// Returns true if view or position has been changed
bool update_gamepad(glm::vec2 axis_left, glm::vec2 axis_right, float delta_time);
CameraType type = CameraType::LookAt;
glm::vec3 rotation = glm::vec3();
glm::vec3 position = glm::vec3();
float rotation_speed = 1.0f;
float translation_speed = 1.0f;
bool updated = false;
struct
{
glm::mat4 perspective;
glm::mat4 view;
} matrices;
struct
{
bool left = false;
bool right = false;
bool up = false;
bool down = false;
} keys;
bool moving();
float get_near_clip();
float get_far_clip();
void set_perspective(float fov, float aspect, float znear, float zfar);
void update_aspect_ratio(float aspect);
void set_position(const glm::vec3 &position);
void set_rotation(const glm::vec3 &rotation);
void rotate(const glm::vec3 &delta);
void set_translation(const glm::vec3 &translation);
void translate(const glm::vec3 &delta);
private:
float fov;
float znear, zfar;
void update_view_matrix();
};
} // namespace vkb
+203
View File
@@ -0,0 +1,203 @@
/* Copyright (c) 2019-2024, Sascha Willems
*
* 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.
*/
#include "camera.h"
namespace vkb
{
void Camera::update_view_matrix()
{
glm::mat4 rotation_matrix = glm::mat4(1.0f);
glm::mat4 transformation_matrix;
rotation_matrix = glm::rotate(rotation_matrix, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
rotation_matrix = glm::rotate(rotation_matrix, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
rotation_matrix = glm::rotate(rotation_matrix, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));
transformation_matrix = glm::translate(glm::mat4(1.0f), position);
if (type == CameraType::FirstPerson)
{
matrices.view = rotation_matrix * transformation_matrix;
}
else
{
matrices.view = transformation_matrix * rotation_matrix;
}
updated = true;
}
bool Camera::moving()
{
return keys.left || keys.right || keys.up || keys.down;
}
float Camera::get_near_clip()
{
return znear;
}
float Camera::get_far_clip()
{
return zfar;
}
void Camera::set_perspective(float fov, float aspect, float znear, float zfar)
{
this->fov = fov;
this->znear = znear;
this->zfar = zfar;
matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar);
updated = true;
}
void Camera::update_aspect_ratio(float aspect)
{
matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar);
updated = true;
}
void Camera::set_position(const glm::vec3 &position)
{
this->position = position;
update_view_matrix();
}
void Camera::set_rotation(const glm::vec3 &rotation)
{
this->rotation = rotation;
update_view_matrix();
}
void Camera::rotate(const glm::vec3 &delta)
{
this->rotation += delta;
update_view_matrix();
}
void Camera::set_translation(const glm::vec3 &translation)
{
this->position = translation;
update_view_matrix();
}
void Camera::translate(const glm::vec3 &delta)
{
this->position += delta;
update_view_matrix();
}
void Camera::update(float deltaTime)
{
updated = false;
if (type == CameraType::FirstPerson)
{
if (moving())
{
glm::vec3 front;
front.x = -cos(glm::radians(rotation.x)) * sin(glm::radians(rotation.y));
front.y = sin(glm::radians(rotation.x));
front.z = cos(glm::radians(rotation.x)) * cos(glm::radians(rotation.y));
front = glm::normalize(front);
float move_speed = deltaTime * translation_speed;
if (keys.up)
{
position += front * move_speed;
}
if (keys.down)
{
position -= front * move_speed;
}
if (keys.left)
{
position -= glm::normalize(glm::cross(front, glm::vec3(0.0f, 1.0f, 0.0f))) * move_speed;
}
if (keys.right)
{
position += glm::normalize(glm::cross(front, glm::vec3(0.0f, 1.0f, 0.0f))) * move_speed;
}
update_view_matrix();
}
}
}
bool Camera::update_gamepad(glm::vec2 axis_left, glm::vec2 axis_right, float delta_time)
{
bool changed = false;
if (type == CameraType::FirstPerson)
{
// Use the common console thumbstick layout
// Left = view, right = move
const float dead_zone = 0.0015f;
const float range = 1.0f - dead_zone;
glm::vec3 front;
front.x = -cos(glm::radians(rotation.x)) * sin(glm::radians(rotation.y));
front.y = sin(glm::radians(rotation.x));
front.z = cos(glm::radians(rotation.x)) * cos(glm::radians(rotation.y));
front = glm::normalize(front);
float move_speed = delta_time * translation_speed * 2.0f;
float new_rotation_speed = delta_time * rotation_speed * 50.0f;
// Move
if (fabsf(axis_left.y) > dead_zone)
{
float pos = (fabsf(axis_left.y) - dead_zone) / range;
position -= front * pos * ((axis_left.y < 0.0f) ? -1.0f : 1.0f) * move_speed;
changed = true;
}
if (fabsf(axis_left.x) > dead_zone)
{
float pos = (fabsf(axis_left.x) - dead_zone) / range;
position += glm::normalize(glm::cross(front, glm::vec3(0.0f, 1.0f, 0.0f))) * pos * ((axis_left.x < 0.0f) ? -1.0f : 1.0f) * move_speed;
changed = true;
}
// Rotate
if (fabsf(axis_right.x) > dead_zone)
{
float pos = (fabsf(axis_right.x) - dead_zone) / range;
rotation.y += pos * ((axis_right.x < 0.0f) ? -1.0f : 1.0f) * new_rotation_speed;
changed = true;
}
if (fabsf(axis_right.y) > dead_zone)
{
float pos = (fabsf(axis_right.y) - dead_zone) / range;
rotation.x -= pos * ((axis_right.y < 0.0f) ? -1.0f : 1.0f) * new_rotation_speed;
changed = true;
}
}
else
{
// todo: move code from example base class for look-at
}
if (changed)
{
update_view_matrix();
}
return changed;
}
} // namespace vkb
+35
View File
@@ -0,0 +1,35 @@
/* Copyright (c) 2018-2020, Arm Limited and Contributors
*
* 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.
*/
#include "error.h"
#include "helpers.h"
namespace vkb
{
VulkanException::VulkanException(const VkResult result, const std::string &msg) :
result{result},
std::runtime_error{msg}
{
error_message = std::string(std::runtime_error::what()) + std::string{" : "} + to_string(result);
}
const char *VulkanException::what() const noexcept
{
return error_message.c_str();
}
} // namespace vkb
+75
View File
@@ -0,0 +1,75 @@
/* Copyright (c) 2018-2024, Arm Limited and Contributors
*
* 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 <cassert>
#include <stdexcept>
#include <string>
#include "core/util/error.hpp"
#include "common/strings.h"
#include "core/util/logging.hpp"
#include "vk_common.h"
namespace vkb
{
/**
* @brief Vulkan exception structure
*/
class VulkanException : public std::runtime_error
{
public:
/**
* @brief Vulkan exception constructor
*/
VulkanException(VkResult result, const std::string &msg = "Vulkan error");
/**
* @brief Returns the Vulkan error code as string
* @return String message of exception
*/
const char *what() const noexcept override;
VkResult result;
private:
std::string error_message;
};
} // namespace vkb
/// @brief Helper macro to test the result of Vulkan calls which can return an error.
#define VK_CHECK(x) \
do \
{ \
VkResult err = x; \
if (err) \
{ \
throw std::runtime_error("Detected Vulkan error: " + vkb::to_string(err)); \
} \
} while (0)
#define ASSERT_VK_HANDLE(handle) \
do \
{ \
if ((handle) == VK_NULL_HANDLE) \
{ \
LOGE("Handle is NULL"); \
abort(); \
} \
} while (0)
+28
View File
@@ -0,0 +1,28 @@
/* Copyright (c) 2019, Arm Limited and Contributors
*
* 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
#ifndef GLM_FORCE_RADIANS
# define GLM_FORCE_RADIANS
#endif
#ifndef GLM_FORCE_DEPTH_ZERO_TO_ONE
# define GLM_FORCE_DEPTH_ZERO_TO_ONE
#endif
#include <glm/glm.hpp>
#include <glm/gtx/rotate_vector.hpp>
#include <glm/gtx/transform.hpp>
+215
View File
@@ -0,0 +1,215 @@
/* Copyright (c) 2018-2024, Arm Limited and Contributors
*
* 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 <algorithm>
#include <array>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <functional>
#include <iterator>
#include <map>
#include <memory>
#include <queue>
#include <set>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "common/error.h"
#include "common/glm_common.h"
#include <glm/gtx/hash.hpp>
namespace vkb
{
template <typename T>
inline void read(std::istringstream &is, T &value)
{
is.read(reinterpret_cast<char *>(&value), sizeof(T));
}
inline void read(std::istringstream &is, std::string &value)
{
std::size_t size;
read(is, size);
value.resize(size);
is.read(const_cast<char *>(value.data()), size);
}
template <class T>
inline void read(std::istringstream &is, std::set<T> &value)
{
std::size_t size;
read(is, size);
for (uint32_t i = 0; i < size; i++)
{
T item;
is.read(reinterpret_cast<char *>(&item), sizeof(T));
value.insert(std::move(item));
}
}
template <class T>
inline void read(std::istringstream &is, std::vector<T> &value)
{
std::size_t size;
read(is, size);
value.resize(size);
is.read(reinterpret_cast<char *>(value.data()), value.size() * sizeof(T));
}
template <class T, class S>
inline void read(std::istringstream &is, std::map<T, S> &value)
{
std::size_t size;
read(is, size);
for (uint32_t i = 0; i < size; i++)
{
std::pair<T, S> item;
read(is, item.first);
read(is, item.second);
value.insert(std::move(item));
}
}
template <class T, uint32_t N>
inline void read(std::istringstream &is, std::array<T, N> &value)
{
is.read(reinterpret_cast<char *>(value.data()), N * sizeof(T));
}
template <typename T, typename... Args>
inline void read(std::istringstream &is, T &first_arg, Args &...args)
{
read(is, first_arg);
read(is, args...);
}
template <typename T>
inline void write(std::ostringstream &os, const T &value)
{
os.write(reinterpret_cast<const char *>(&value), sizeof(T));
}
inline void write(std::ostringstream &os, const std::string &value)
{
write(os, value.size());
os.write(value.data(), value.size());
}
template <class T>
inline void write(std::ostringstream &os, const std::set<T> &value)
{
write(os, value.size());
for (const T &item : value)
{
os.write(reinterpret_cast<const char *>(&item), sizeof(T));
}
}
template <class T>
inline void write(std::ostringstream &os, const std::vector<T> &value)
{
write(os, value.size());
os.write(reinterpret_cast<const char *>(value.data()), value.size() * sizeof(T));
}
template <class T, class S>
inline void write(std::ostringstream &os, const std::map<T, S> &value)
{
write(os, value.size());
for (const std::pair<T, S> &item : value)
{
write(os, item.first);
write(os, item.second);
}
}
template <class T, uint32_t N>
inline void write(std::ostringstream &os, const std::array<T, N> &value)
{
os.write(reinterpret_cast<const char *>(value.data()), N * sizeof(T));
}
template <typename T, typename... Args>
inline void write(std::ostringstream &os, const T &first_arg, const Args &...args)
{
write(os, first_arg);
write(os, args...);
}
/**
* @brief Helper function to combine a given hash
* with a generated hash for the input param.
*/
template <class T>
inline void hash_combine(size_t &seed, const T &v)
{
std::hash<T> hasher;
glm::detail::hash_combine(seed, hasher(v));
}
/**
* @brief Helper function to convert a data type
* to string using output stream operator.
* @param value The object to be converted to string
* @return String version of the given object
*/
template <class T>
inline std::string to_string(const T &value)
{
std::stringstream ss;
ss << std::fixed << value;
return ss.str();
}
/**
* @brief Helper function to check size_t is correctly converted to uint32_t
* @param value Value of type size_t to convert
* @return An uint32_t representation of the same value
*/
template <class T>
uint32_t to_u32(T value)
{
static_assert(std::is_arithmetic<T>::value, "T must be numeric");
if (static_cast<uintmax_t>(value) > static_cast<uintmax_t>(std::numeric_limits<uint32_t>::max()))
{
throw std::runtime_error("to_u32() failed, value is too big to be converted to uint32_t");
}
return static_cast<uint32_t>(value);
}
template <typename T>
inline std::vector<uint8_t> to_bytes(const T &value)
{
return std::vector<uint8_t>{reinterpret_cast<const uint8_t *>(&value),
reinterpret_cast<const uint8_t *>(&value) + sizeof(T)};
}
} // namespace vkb
+41
View File
@@ -0,0 +1,41 @@
/* Copyright (c) 2022-2024, NVIDIA CORPORATION. All rights reserved.
*
* 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 <common/error.h>
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace common
{
/**
* @brief facade class around vkb::VulkanException, providing a vulkan.hpp-based interface
*
* See vkb::VulkanException for documentation
*/
class HPPVulkanException : public vkb::VulkanException
{
public:
HPPVulkanException(vk::Result result, std::string const &msg = "Vulkan error") :
vkb::VulkanException(static_cast<VkResult>(result), msg)
{}
};
} // namespace common
} // namespace vkb
+414
View File
@@ -0,0 +1,414 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "common/hpp_vk_common.h"
#include "core/hpp_descriptor_set.h"
#include "core/hpp_image_view.h"
#include "core/hpp_render_pass.h"
#include "core/hpp_shader_module.h"
#include "hpp_resource_record.h"
#include "rendering/hpp_render_target.h"
#include "resource_caching.h"
#include <vulkan/vulkan_hash.hpp>
namespace std
{
template <typename Key, typename Value>
struct hash<std::map<Key, Value>>
{
size_t operator()(std::map<Key, Value> const &bindings) const
{
size_t result = 0;
vkb::hash_combine(result, bindings.size());
for (auto const &binding : bindings)
{
vkb::hash_combine(result, binding.first);
vkb::hash_combine(result, binding.second);
}
return result;
}
};
template <typename T>
struct hash<std::vector<T>>
{
size_t operator()(std::vector<T> const &values) const
{
size_t result = 0;
vkb::hash_combine(result, values.size());
for (auto const &value : values)
{
vkb::hash_combine(result, value);
}
return result;
}
};
template <>
struct hash<vkb::common::HPPLoadStoreInfo>
{
size_t operator()(vkb::common::HPPLoadStoreInfo const &lsi) const
{
size_t result = 0;
vkb::hash_combine(result, lsi.load_op);
vkb::hash_combine(result, lsi.store_op);
return result;
}
};
template <vkb::BindingType bindingType, typename T>
struct hash<vkb::core::VulkanResource<bindingType, T>>
{
size_t operator()(const vkb::core::VulkanResource<bindingType, T> &vulkan_resource) const
{
return std::hash<T>()(vulkan_resource.get_handle());
}
};
template <>
struct hash<vkb::core::HPPDescriptorPool>
{
size_t operator()(const vkb::core::HPPDescriptorPool &descriptor_pool) const
{
return std::hash<vkb::DescriptorPool>()(reinterpret_cast<vkb::DescriptorPool const &>(descriptor_pool));
}
};
template <>
struct hash<vkb::core::HPPDescriptorSet>
{
size_t operator()(vkb::core::HPPDescriptorSet &descriptor_set) const
{
size_t result = 0;
vkb::hash_combine(result, descriptor_set.get_layout());
// descriptor_pool ?
vkb::hash_combine(result, descriptor_set.get_buffer_infos());
vkb::hash_combine(result, descriptor_set.get_image_infos());
vkb::hash_combine(result, descriptor_set.get_handle());
// write_descriptor_sets ?
return result;
}
};
template <>
struct hash<vkb::core::HPPDescriptorSetLayout>
{
size_t operator()(const vkb::core::HPPDescriptorSetLayout &descriptor_set_layout) const
{
return std::hash<vkb::DescriptorSetLayout>()(reinterpret_cast<vkb::DescriptorSetLayout const &>(descriptor_set_layout));
}
};
template <>
struct hash<vkb::core::HPPImage>
{
size_t operator()(const vkb::core::HPPImage &image) const
{
size_t result = 0;
vkb::hash_combine(result, image.get_memory());
vkb::hash_combine(result, image.get_type());
vkb::hash_combine(result, image.get_extent());
vkb::hash_combine(result, image.get_format());
vkb::hash_combine(result, image.get_usage());
vkb::hash_combine(result, image.get_sample_count());
vkb::hash_combine(result, image.get_tiling());
vkb::hash_combine(result, image.get_subresource());
vkb::hash_combine(result, image.get_array_layer_count());
return result;
}
};
template <>
struct hash<vkb::core::HPPImageView>
{
size_t operator()(const vkb::core::HPPImageView &image_view) const
{
size_t result = std::hash<vkb::core::VulkanResourceCpp<vk::ImageView>>()(image_view);
vkb::hash_combine(result, image_view.get_image());
vkb::hash_combine(result, image_view.get_format());
vkb::hash_combine(result, image_view.get_subresource_range());
return result;
}
};
template <>
struct hash<vkb::core::HPPRenderPass>
{
size_t operator()(const vkb::core::HPPRenderPass &render_pass) const
{
return std::hash<vkb::RenderPass>()(reinterpret_cast<vkb::RenderPass const &>(render_pass));
}
};
template <>
struct hash<vkb::core::HPPShaderModule>
{
size_t operator()(const vkb::core::HPPShaderModule &shader_module) const
{
return std::hash<vkb::ShaderModule>()(reinterpret_cast<vkb::ShaderModule const &>(shader_module));
}
};
template <>
struct hash<vkb::core::HPPShaderResource>
{
size_t operator()(vkb::core::HPPShaderResource const &shader_resource) const
{
size_t result = 0;
vkb::hash_combine(result, shader_resource.stages);
vkb::hash_combine(result, shader_resource.type);
vkb::hash_combine(result, shader_resource.mode);
vkb::hash_combine(result, shader_resource.set);
vkb::hash_combine(result, shader_resource.binding);
vkb::hash_combine(result, shader_resource.location);
vkb::hash_combine(result, shader_resource.input_attachment_index);
vkb::hash_combine(result, shader_resource.vec_size);
vkb::hash_combine(result, shader_resource.columns);
vkb::hash_combine(result, shader_resource.array_size);
vkb::hash_combine(result, shader_resource.offset);
vkb::hash_combine(result, shader_resource.size);
vkb::hash_combine(result, shader_resource.constant_id);
vkb::hash_combine(result, shader_resource.qualifiers);
vkb::hash_combine(result, shader_resource.name);
return result;
}
};
template <>
struct hash<vkb::core::HPPShaderSource>
{
size_t operator()(const vkb::core::HPPShaderSource &shader_source) const
{
return std::hash<vkb::ShaderSource>()(reinterpret_cast<vkb::ShaderSource const &>(shader_source));
}
};
template <>
struct hash<vkb::core::HPPShaderVariant>
{
size_t operator()(const vkb::core::HPPShaderVariant &shader_variant) const
{
return std::hash<vkb::ShaderVariant>()(reinterpret_cast<vkb::ShaderVariant const &>(shader_variant));
}
};
template <>
struct hash<vkb::core::HPPSubpassInfo>
{
size_t operator()(vkb::core::HPPSubpassInfo const &subpass_info) const
{
size_t result = 0;
vkb::hash_combine(result, subpass_info.input_attachments);
vkb::hash_combine(result, subpass_info.output_attachments);
vkb::hash_combine(result, subpass_info.color_resolve_attachments);
vkb::hash_combine(result, subpass_info.disable_depth_stencil_attachment);
vkb::hash_combine(result, subpass_info.depth_stencil_resolve_attachment);
vkb::hash_combine(result, subpass_info.depth_stencil_resolve_mode);
vkb::hash_combine(result, subpass_info.debug_name);
return result;
}
};
template <>
struct hash<vkb::rendering::HPPAttachment>
{
size_t operator()(const vkb::rendering::HPPAttachment &attachment) const
{
size_t result = 0;
vkb::hash_combine(result, attachment.format);
vkb::hash_combine(result, attachment.samples);
vkb::hash_combine(result, attachment.usage);
vkb::hash_combine(result, attachment.initial_layout);
return result;
}
};
template <>
struct hash<vkb::rendering::HPPPipelineState>
{
size_t operator()(const vkb::rendering::HPPPipelineState &pipeline_state) const
{
return std::hash<vkb::PipelineState>()(reinterpret_cast<vkb::PipelineState const &>(pipeline_state));
}
};
template <>
struct hash<vkb::rendering::HPPRenderTarget>
{
size_t operator()(const vkb::rendering::HPPRenderTarget &render_target) const
{
size_t result = 0;
vkb::hash_combine(result, render_target.get_extent());
for (auto const &view : render_target.get_views())
{
vkb::hash_combine(result, view);
}
for (auto const &attachment : render_target.get_attachments())
{
vkb::hash_combine(result, attachment);
}
for (auto const &input : render_target.get_input_attachments())
{
vkb::hash_combine(result, input);
}
for (auto const &output : render_target.get_output_attachments())
{
vkb::hash_combine(result, output);
}
return result;
}
};
} // namespace std
namespace vkb
{
namespace common
{
/**
* @brief facade helper functions and structs around the functions and structs in common/resource_caching, providing a vulkan.hpp-based interface
*/
namespace
{
template <class T, class... A>
struct HPPRecordHelper
{
size_t record(HPPResourceRecord & /*recorder*/, A &.../*args*/)
{
return 0;
}
void index(HPPResourceRecord & /*recorder*/, size_t /*index*/, T & /*resource*/)
{}
};
template <class... A>
struct HPPRecordHelper<vkb::core::HPPShaderModule, A...>
{
size_t record(HPPResourceRecord &recorder, A &...args)
{
return recorder.register_shader_module(args...);
}
void index(HPPResourceRecord &recorder, size_t index, vkb::core::HPPShaderModule &shader_module)
{
recorder.set_shader_module(index, shader_module);
}
};
template <class... A>
struct HPPRecordHelper<vkb::core::HPPPipelineLayout, A...>
{
size_t record(HPPResourceRecord &recorder, A &...args)
{
return recorder.register_pipeline_layout(args...);
}
void index(HPPResourceRecord &recorder, size_t index, vkb::core::HPPPipelineLayout &pipeline_layout)
{
recorder.set_pipeline_layout(index, pipeline_layout);
}
};
template <class... A>
struct HPPRecordHelper<vkb::core::HPPRenderPass, A...>
{
size_t record(HPPResourceRecord &recorder, A &...args)
{
return recorder.register_render_pass(args...);
}
void index(HPPResourceRecord &recorder, size_t index, vkb::core::HPPRenderPass &render_pass)
{
recorder.set_render_pass(index, render_pass);
}
};
template <class... A>
struct HPPRecordHelper<vkb::core::HPPGraphicsPipeline, A...>
{
size_t record(HPPResourceRecord &recorder, A &...args)
{
return recorder.register_graphics_pipeline(args...);
}
void index(HPPResourceRecord &recorder, size_t index, vkb::core::HPPGraphicsPipeline &graphics_pipeline)
{
recorder.set_graphics_pipeline(index, graphics_pipeline);
}
};
} // namespace
template <class T, class... A>
T &request_resource(vkb::core::DeviceCpp &device, vkb::HPPResourceRecord *recorder, std::unordered_map<size_t, T> &resources, A &...args)
{
HPPRecordHelper<T, A...> record_helper;
size_t hash{0U};
hash_param(hash, args...);
auto res_it = resources.find(hash);
if (res_it != resources.end())
{
return res_it->second;
}
// If we do not have it already, create and cache it
const char *res_type = typeid(T).name();
size_t res_id = resources.size();
LOGD("Building #{} cache object ({})", res_id, res_type);
// Only error handle in release
#ifndef DEBUG
try
{
#endif
T resource(device, args...);
auto res_ins_it = resources.emplace(hash, std::move(resource));
if (!res_ins_it.second)
{
throw std::runtime_error{std::string{"Insertion error for #"} + std::to_string(res_id) + "cache object (" + res_type + ")"};
}
res_it = res_ins_it.first;
if (recorder)
{
size_t index = record_helper.record(*recorder, args...);
record_helper.index(*recorder, index, res_it->second);
}
#ifndef DEBUG
}
catch (const std::exception &e)
{
LOGE("Creation error for #{} cache object ({})", res_id, res_type);
throw e;
}
#endif
return res_it->second;
}
} // namespace common
} // namespace vkb
+36
View File
@@ -0,0 +1,36 @@
/* Copyright (c) 2021-2022, NVIDIA CORPORATION. All rights reserved.
*
* 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 <common/strings.h>
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace common
{
/**
* @brief facade helper functions around the functions in common/strings.h, providing a vulkan.hpp-based interface
*/
std::string to_string(vk::Extent2D const &extent)
{
return vkb::to_string(static_cast<VkExtent2D const &>(extent));
}
} // namespace common
} // namespace vkb
+42
View File
@@ -0,0 +1,42 @@
/* Copyright (c) 2021-2024, NVIDIA CORPORATION. All rights reserved.
*
* 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 <common/utils.h>
#include <rendering/hpp_render_context.h>
#include <scene_graph/hpp_scene.h>
/**
* @brief facade helper functions around the functions in common/utils.h, providing a vulkan.hpp-based interface
*/
namespace vkb
{
namespace common
{
inline sg::Node &add_free_camera(vkb::scene_graph::HPPScene &scene, const std::string &node_name, vk::Extent2D const &extent)
{
return vkb::add_free_camera(reinterpret_cast<vkb::sg::Scene &>(scene), node_name, static_cast<VkExtent2D>(extent));
}
inline void screenshot(vkb::rendering::HPPRenderContext &render_context, const std::string &filename)
{
vkb::screenshot(reinterpret_cast<vkb::RenderContext &>(render_context), filename);
}
} // namespace common
} // namespace vkb
+453
View File
@@ -0,0 +1,453 @@
/* Copyright (c) 2021-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "common/vk_common.h"
#include "core/util/logging.hpp"
#include "vulkan/vulkan.hpp"
#include "vulkan/vulkan_format_traits.hpp"
namespace vkb
{
namespace common
{
/**
* @brief facade helper functions and structs around the functions and structs in common/vk_common, providing a vulkan.hpp-based interface
*/
struct HPPBufferMemoryBarrier
{
vk::PipelineStageFlags src_stage_mask = vk::PipelineStageFlagBits::eBottomOfPipe;
vk::PipelineStageFlags dst_stage_mask = vk::PipelineStageFlagBits::eTopOfPipe;
vk::AccessFlags src_access_mask = {};
vk::AccessFlags dst_access_mask = {};
};
struct HPPImageMemoryBarrier
{
vk::PipelineStageFlags src_stage_mask = vk::PipelineStageFlagBits::eBottomOfPipe;
vk::PipelineStageFlags dst_stage_mask = vk::PipelineStageFlagBits::eTopOfPipe;
vk::AccessFlags src_access_mask;
vk::AccessFlags dst_access_mask;
vk::ImageLayout old_layout = vk::ImageLayout::eUndefined;
vk::ImageLayout new_layout = vk::ImageLayout::eUndefined;
uint32_t src_queue_family = VK_QUEUE_FAMILY_IGNORED;
uint32_t dst_queue_family = VK_QUEUE_FAMILY_IGNORED;
};
struct HPPLoadStoreInfo
{
vk::AttachmentLoadOp load_op = vk::AttachmentLoadOp::eClear;
vk::AttachmentStoreOp store_op = vk::AttachmentStoreOp::eStore;
};
inline int32_t get_bits_per_pixel(vk::Format format)
{
return vkb::get_bits_per_pixel(static_cast<VkFormat>(format));
}
inline vk::Format get_suitable_depth_format(vk::PhysicalDevice physical_device,
bool depth_only = false,
const std::vector<vk::Format> &depth_format_priority_list = {
vk::Format::eD32Sfloat, vk::Format::eD24UnormS8Uint, vk::Format::eD16Unorm})
{
return static_cast<vk::Format>(
vkb::get_suitable_depth_format(physical_device, depth_only, reinterpret_cast<std::vector<VkFormat> const &>(depth_format_priority_list)));
}
inline bool is_buffer_descriptor_type(vk::DescriptorType descriptor_type)
{
return vkb::is_buffer_descriptor_type(static_cast<VkDescriptorType>(descriptor_type));
}
inline bool is_depth_only_format(vk::Format format)
{
assert(vkb::is_depth_only_format(static_cast<VkFormat>(format)) ==
((vk::componentCount(format) == 1) && (std::string(vk::componentName(format, 0)) == "D")));
return vkb::is_depth_only_format(static_cast<VkFormat>(format));
}
inline bool is_depth_stencil_format(vk::Format format)
{
assert(vkb::is_depth_stencil_format(static_cast<VkFormat>(format)) ==
((vk::componentCount(format) == 2) && (std::string(vk::componentName(format, 0)) == "D") &&
(std::string(vk::componentName(format, 1)) == "S")));
return vkb::is_depth_stencil_format(static_cast<VkFormat>(format));
}
inline bool is_depth_format(vk::Format format)
{
assert(vkb::is_depth_format(static_cast<VkFormat>(format)) == (std::string(vk::componentName(format, 0)) == "D"));
return vkb::is_depth_format(static_cast<VkFormat>(format));
}
inline bool is_dynamic_buffer_descriptor_type(vk::DescriptorType descriptor_type)
{
return vkb::is_dynamic_buffer_descriptor_type(static_cast<VkDescriptorType>(descriptor_type));
}
inline vk::ShaderModule load_shader(const std::string &filename, vk::Device device, vk::ShaderStageFlagBits stage)
{
return static_cast<vk::ShaderModule>(vkb::load_shader(filename, device, static_cast<VkShaderStageFlagBits>(stage)));
}
inline void image_layout_transition(vk::CommandBuffer command_buffer,
vk::Image image,
vk::ImageLayout old_layout,
vk::ImageLayout new_layout)
{
vkb::image_layout_transition(static_cast<VkCommandBuffer>(command_buffer),
static_cast<VkImage>(image),
static_cast<VkImageLayout>(old_layout),
static_cast<VkImageLayout>(new_layout));
}
inline void image_layout_transition(vk::CommandBuffer command_buffer,
vk::Image image,
vk::ImageLayout old_layout,
vk::ImageLayout new_layout,
vk::ImageSubresourceRange subresource_range)
{
vkb::image_layout_transition(static_cast<VkCommandBuffer>(command_buffer),
static_cast<VkImage>(image),
static_cast<VkImageLayout>(old_layout),
static_cast<VkImageLayout>(new_layout),
static_cast<VkImageSubresourceRange>(subresource_range));
}
inline void image_layout_transition(vk::CommandBuffer command_buffer,
vk::Image image,
vk::PipelineStageFlags src_stage_mask,
vk::PipelineStageFlags dst_stage_mask,
vk::AccessFlags src_access_mask,
vk::AccessFlags dst_access_mask,
vk::ImageLayout old_layout,
vk::ImageLayout new_layout,
vk::ImageSubresourceRange const &subresource_range)
{
vkb::image_layout_transition(static_cast<VkCommandBuffer>(command_buffer),
static_cast<VkImage>(image),
static_cast<VkPipelineStageFlags>(src_stage_mask),
static_cast<VkPipelineStageFlags>(dst_stage_mask),
static_cast<VkAccessFlags>(src_access_mask),
static_cast<VkAccessFlags>(dst_access_mask),
static_cast<VkImageLayout>(old_layout),
static_cast<VkImageLayout>(new_layout),
static_cast<VkImageSubresourceRange const &>(subresource_range));
}
inline void make_filters_valid(vk::PhysicalDevice physical_device, vk::Format format, vk::Filter *filter, vk::SamplerMipmapMode *mipmapMode = nullptr)
{
// Not all formats support linear filtering, so we need to adjust them if they don't
if (*filter == vk::Filter::eNearest && (mipmapMode == nullptr || *mipmapMode == vk::SamplerMipmapMode::eNearest))
{
return; // These must already be valid
}
vk::FormatProperties properties = physical_device.getFormatProperties(format);
if (!(properties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear))
{
*filter = vk::Filter::eNearest;
if (mipmapMode)
{
*mipmapMode = vk::SamplerMipmapMode::eNearest;
}
}
}
inline vk::SurfaceFormatKHR select_surface_format(vk::PhysicalDevice gpu,
vk::SurfaceKHR surface,
std::vector<vk::Format> const &preferred_formats = {
vk::Format::eR8G8B8A8Srgb, vk::Format::eB8G8R8A8Srgb, vk::Format::eA8B8G8R8SrgbPack32})
{
std::vector<vk::SurfaceFormatKHR> supported_surface_formats = gpu.getSurfaceFormatsKHR(surface);
assert(!supported_surface_formats.empty());
auto it = std::ranges::find_if(supported_surface_formats,
[&preferred_formats](vk::SurfaceFormatKHR surface_format) {
return std::ranges::any_of(preferred_formats,
[&surface_format](vk::Format format) { return format == surface_format.format; });
});
// We use the first supported format as a fallback in case none of the preferred formats is available
return it != supported_surface_formats.end() ? *it : supported_surface_formats[0];
}
inline vk::Format choose_blendable_format(vk::PhysicalDevice gpu, const std::vector<vk::Format> &format_priority_list)
{
for (const auto &format : format_priority_list)
{
vk::FormatProperties fmt_props = gpu.getFormatProperties(format);
if (fmt_props.optimalTilingFeatures & vk::FormatFeatureFlagBits::eColorAttachmentBlend)
return format;
}
throw std::runtime_error("No suitable blendable format could be determined");
}
// helper functions not backed by vk_common.h
inline vk::CommandBuffer
allocate_command_buffer(vk::Device device, vk::CommandPool command_pool, vk::CommandBufferLevel level = vk::CommandBufferLevel::ePrimary)
{
vk::CommandBufferAllocateInfo command_buffer_allocate_info{.commandPool = command_pool, .level = level, .commandBufferCount = 1};
return device.allocateCommandBuffers(command_buffer_allocate_info).front();
}
inline vk::DescriptorSet allocate_descriptor_set(vk::Device device, vk::DescriptorPool descriptor_pool, vk::DescriptorSetLayout descriptor_set_layout)
{
vk::DescriptorSetAllocateInfo descriptor_set_allocate_info{.descriptorPool = descriptor_pool,
.descriptorSetCount = 1,
.pSetLayouts = &descriptor_set_layout};
return device.allocateDescriptorSets(descriptor_set_allocate_info).front();
}
inline vk::Framebuffer
create_framebuffer(vk::Device device, vk::RenderPass render_pass, std::vector<vk::ImageView> const &attachments, vk::Extent2D const &extent)
{
vk::FramebufferCreateInfo framebuffer_create_info{.renderPass = render_pass,
.attachmentCount = static_cast<uint32_t>(attachments.size()),
.pAttachments = attachments.data(),
.width = extent.width,
.height = extent.height,
.layers = 1};
return device.createFramebuffer(framebuffer_create_info);
}
inline vk::Pipeline create_graphics_pipeline(vk::Device device,
vk::PipelineCache pipeline_cache,
std::vector<vk::PipelineShaderStageCreateInfo> const &shader_stages,
vk::PipelineVertexInputStateCreateInfo const &vertex_input_state,
vk::PrimitiveTopology primitive_topology,
uint32_t patch_control_points,
vk::PolygonMode polygon_mode,
vk::CullModeFlags cull_mode,
vk::FrontFace front_face,
std::vector<vk::PipelineColorBlendAttachmentState> const &blend_attachment_states,
vk::PipelineDepthStencilStateCreateInfo const &depth_stencil_state,
vk::PipelineLayout pipeline_layout,
vk::RenderPass render_pass)
{
vk::PipelineInputAssemblyStateCreateInfo input_assembly_state{.topology = primitive_topology};
vk::PipelineTessellationStateCreateInfo tessellation_state{.patchControlPoints = patch_control_points};
vk::PipelineViewportStateCreateInfo viewport_state{.viewportCount = 1, .scissorCount = 1};
vk::PipelineRasterizationStateCreateInfo rasterization_state{
.polygonMode = polygon_mode, .cullMode = cull_mode, .frontFace = front_face, .lineWidth = 1.0f};
vk::PipelineMultisampleStateCreateInfo multisample_state{.rasterizationSamples = vk::SampleCountFlagBits::e1};
vk::PipelineColorBlendStateCreateInfo color_blend_state{.attachmentCount = static_cast<uint32_t>(blend_attachment_states.size()),
.pAttachments = blend_attachment_states.data()};
std::array<vk::DynamicState, 2> dynamic_state_enables = {vk::DynamicState::eViewport, vk::DynamicState::eScissor};
vk::PipelineDynamicStateCreateInfo dynamic_state{.dynamicStateCount = static_cast<uint32_t>(dynamic_state_enables.size()),
.pDynamicStates = dynamic_state_enables.data()};
// Final fullscreen composition pass pipeline
vk::GraphicsPipelineCreateInfo pipeline_create_info{.stageCount = static_cast<uint32_t>(shader_stages.size()),
.pStages = shader_stages.data(),
.pVertexInputState = &vertex_input_state,
.pInputAssemblyState = &input_assembly_state,
.pTessellationState = &tessellation_state,
.pViewportState = &viewport_state,
.pRasterizationState = &rasterization_state,
.pMultisampleState = &multisample_state,
.pDepthStencilState = &depth_stencil_state,
.pColorBlendState = &color_blend_state,
.pDynamicState = &dynamic_state,
.layout = pipeline_layout,
.renderPass = render_pass,
.basePipelineIndex = -1};
vk::Result result;
vk::Pipeline pipeline;
std::tie(result, pipeline) = device.createGraphicsPipeline(pipeline_cache, pipeline_create_info);
assert(result == vk::Result::eSuccess);
return pipeline;
}
inline vk::ImageView create_image_view(vk::Device device,
vk::Image image,
vk::ImageViewType view_type,
vk::Format format,
vk::ImageAspectFlags aspect_mask = vk::ImageAspectFlagBits::eColor,
uint32_t base_mip_level = 0,
uint32_t level_count = 1,
uint32_t base_array_layer = 0,
uint32_t layer_count = 1)
{
vk::ImageViewCreateInfo image_view_create_info{.image = image,
.viewType = view_type,
.format = format,
.subresourceRange = {.aspectMask = aspect_mask,
.baseMipLevel = base_mip_level,
.levelCount = level_count,
.baseArrayLayer = base_array_layer,
.layerCount = layer_count}};
return device.createImageView(image_view_create_info);
}
inline vk::QueryPool create_query_pool(vk::Device device, vk::QueryType query_type, uint32_t query_count, vk::QueryPipelineStatisticFlags pipeline_statistics = {})
{
vk::QueryPoolCreateInfo query_pool_create_info{.queryType = query_type, .queryCount = query_count, .pipelineStatistics = pipeline_statistics};
return device.createQueryPool(query_pool_create_info);
}
inline vk::Sampler create_sampler(vk::Device device,
vk::Filter mag_filter,
vk::Filter min_filter,
vk::SamplerMipmapMode mipmap_mode,
vk::SamplerAddressMode sampler_address_mode,
float max_anisotropy,
float max_LOD)
{
vk::SamplerCreateInfo sampler_create_info{.magFilter = mag_filter,
.minFilter = min_filter,
.mipmapMode = mipmap_mode,
.addressModeU = sampler_address_mode,
.addressModeV = sampler_address_mode,
.addressModeW = sampler_address_mode,
.anisotropyEnable = (1.0f < max_anisotropy),
.maxAnisotropy = max_anisotropy,
.compareOp = vk::CompareOp::eNever,
.minLod = 0.0f,
.maxLod = max_LOD,
.borderColor = vk::BorderColor::eFloatOpaqueWhite};
return device.createSampler(sampler_create_info);
}
inline vk::Sampler create_sampler(vk::PhysicalDevice gpu,
vk::Device device,
vk::Format format,
vk::Filter filter,
vk::SamplerAddressMode sampler_address_mode,
float max_anisotropy,
float max_LOD)
{
const vk::FormatProperties fmt_props = gpu.getFormatProperties(format);
bool has_linear_filter = !!(fmt_props.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear);
return create_sampler(device,
has_linear_filter ? filter : vk::Filter::eNearest,
has_linear_filter ? filter : vk::Filter::eNearest,
has_linear_filter ? vk::SamplerMipmapMode::eLinear : vk::SamplerMipmapMode::eNearest,
sampler_address_mode,
max_anisotropy,
max_LOD);
}
inline vk::ImageAspectFlags get_image_aspect_flags(vk::ImageUsageFlagBits usage, vk::Format format)
{
vk::ImageAspectFlags image_aspect_flags;
switch (usage)
{
case vk::ImageUsageFlagBits::eColorAttachment:
assert(!vkb::common::is_depth_format(format));
image_aspect_flags = vk::ImageAspectFlagBits::eColor;
break;
case vk::ImageUsageFlagBits::eDepthStencilAttachment:
assert(vkb::common::is_depth_format(format));
image_aspect_flags = vk::ImageAspectFlagBits::eDepth;
// Stencil aspect should only be set on depth + stencil formats
if (vkb::common::is_depth_stencil_format(format))
{
image_aspect_flags |= vk::ImageAspectFlagBits::eStencil;
}
break;
default:
assert(false);
}
return image_aspect_flags;
}
inline void submit_and_wait(vk::Device device, vk::Queue queue, std::vector<vk::CommandBuffer> command_buffers, std::vector<vk::Semaphore> semaphores = {})
{
// Submit command_buffer
vk::SubmitInfo submit_info{.commandBufferCount = static_cast<uint32_t>(command_buffers.size()),
.pCommandBuffers = command_buffers.data(),
.signalSemaphoreCount = static_cast<uint32_t>(semaphores.size()),
.pSignalSemaphores = semaphores.data()};
// Create fence to ensure that command_buffer has finished executing
vk::Fence fence = device.createFence({});
// Submit to the queue
queue.submit(submit_info, fence);
// Wait for the fence to signal that command_buffer has finished executing
vk::Result result = device.waitForFences(fence, true, DEFAULT_FENCE_TIMEOUT);
if (result != vk::Result::eSuccess)
{
LOGE("Vulkan error on waitForFences: {}", vk::to_string(result));
abort();
}
// Destroy the fence
device.destroyFence(fence);
}
inline uint32_t get_queue_family_index(std::vector<vk::QueueFamilyProperties> const &queue_family_properties, vk::QueueFlagBits queue_flag)
{
// Dedicated queue for compute
// Try to find a queue family index that supports compute but not graphics
if (queue_flag & vk::QueueFlagBits::eCompute)
{
auto propertyIt = std::ranges::find_if(queue_family_properties,
[queue_flag](const vk::QueueFamilyProperties &property) { return (property.queueFlags & queue_flag) && !(property.queueFlags & vk::QueueFlagBits::eGraphics); });
if (propertyIt != queue_family_properties.end())
{
return static_cast<uint32_t>(std::distance(queue_family_properties.begin(), propertyIt));
}
}
// Dedicated queue for transfer
// Try to find a queue family index that supports transfer but not graphics and compute
if (queue_flag & vk::QueueFlagBits::eTransfer)
{
auto propertyIt = std::ranges::find_if(queue_family_properties,
[queue_flag](const vk::QueueFamilyProperties &property) {
return (property.queueFlags & queue_flag) && !(property.queueFlags & vk::QueueFlagBits::eGraphics) &&
!(property.queueFlags & vk::QueueFlagBits::eCompute);
});
if (propertyIt != queue_family_properties.end())
{
return static_cast<uint32_t>(std::distance(queue_family_properties.begin(), propertyIt));
}
}
// For other queue types or if no separate compute queue is present, return the first one to support the requested flags
auto propertyIt = std::ranges::find_if(
queue_family_properties, [queue_flag](const vk::QueueFamilyProperties &property) { return (property.queueFlags & queue_flag) == queue_flag; });
if (propertyIt != queue_family_properties.end())
{
return static_cast<uint32_t>(std::distance(queue_family_properties.begin(), propertyIt));
}
throw std::runtime_error("Could not find a matching queue family index");
}
} // namespace common
} // namespace vkb
+36
View File
@@ -0,0 +1,36 @@
/* Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
*
* 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.
*/
#include "ktx_common.h"
#include <stdexcept>
namespace vkb
{
namespace ktx
{
ktxTexture *load_texture(std::string const &filename)
{
ktxTexture *ktx_texture;
KTX_error_code result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktx_texture);
if ((result != KTX_SUCCESS) || (ktx_texture == nullptr))
{
throw std::runtime_error("Couldn't load texture");
}
return ktx_texture;
}
} // namespace ktx
} // namespace vkb
+29
View File
@@ -0,0 +1,29 @@
/* Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
*
* 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 <ktx.h>
#include <string>
namespace vkb
{
namespace ktx
{
ktxTexture *load_texture(std::string const &filename);
}
} // namespace vkb
+133
View File
@@ -0,0 +1,133 @@
/* Copyright (c) 2021, Arm Limited and Contributors
*
* 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 <cassert>
namespace vkb
{
// Simple optional class for pre c++17 std::optional
// May be replaced by std::optional in the future
template <typename Type>
class Optional
{
public:
virtual ~Optional() = default;
Optional() = default;
Optional(const Optional &optional);
template <typename U = Type>
Optional(const U &value);
bool has_value() const;
const Type &value() const;
const Type value_or(Type &&alternative) const;
const Type value_or(const Type &alternative = {}) const;
Optional &operator=(Optional &&opt)
{
_has_value = opt._has_value;
_value = opt._value;
return *this;
}
template <typename U = Type>
Optional &operator=(U &&value)
{
_has_value = true;
_value = value;
return *this;
}
Optional &operator=(const Optional &value)
{
_has_value = value._has_value;
_value = value._value;
return *this;
}
template <typename U = Type>
Optional &operator=(U *value)
{
if (value == nullptr)
{
_has_value = false;
return *this;
}
_has_value = true;
_value = *value;
return *this;
}
private:
bool _has_value = false;
Type _value;
};
template <typename Type>
Optional<Type>::Optional(const Optional &optional)
{
_has_value = optional._has_value;
_value = optional._value;
}
template <typename Type>
template <typename U>
Optional<Type>::Optional(const U &value)
{
_has_value = true;
_value = value;
}
template <typename Type>
bool Optional<Type>::has_value() const
{
return _has_value;
}
template <typename Type>
const Type &Optional<Type>::value() const
{
assert(_has_value && "Value does not exist");
return _value;
}
template <typename Type>
const Type Optional<Type>::value_or(Type &&alternative) const
{
if (has_value())
{
return _value;
}
return alternative;
}
template <typename Type>
const Type Optional<Type>::value_or(const Type &alternative) const
{
if (has_value())
{
return _value;
}
return alternative;
}
} // namespace vkb
+781
View File
@@ -0,0 +1,781 @@
/* Copyright (c) 2018-2025, Arm Limited and Contributors
*
* 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 "core/descriptor_pool.h"
#include "core/descriptor_set.h"
#include "core/descriptor_set_layout.h"
#include "core/framebuffer.h"
#include "core/image.h"
#include "core/pipeline.h"
#include "rendering/pipeline_state.h"
#include "rendering/render_target.h"
#include "resource_record.h"
#include "common/helpers.h"
namespace std
{
template <>
struct hash<vkb::ShaderSource>
{
std::size_t operator()(const vkb::ShaderSource &shader_source) const
{
std::size_t result = 0;
vkb::hash_combine(result, shader_source.get_id());
return result;
}
};
template <>
struct hash<vkb::ShaderVariant>
{
std::size_t operator()(const vkb::ShaderVariant &shader_variant) const
{
std::size_t result = 0;
vkb::hash_combine(result, shader_variant.get_id());
return result;
}
};
template <>
struct hash<vkb::ShaderModule>
{
std::size_t operator()(const vkb::ShaderModule &shader_module) const
{
std::size_t result = 0;
vkb::hash_combine(result, shader_module.get_id());
return result;
}
};
template <>
struct hash<vkb::DescriptorSetLayout>
{
std::size_t operator()(const vkb::DescriptorSetLayout &descriptor_set_layout) const
{
std::size_t result = 0;
vkb::hash_combine(result, descriptor_set_layout.get_handle());
return result;
}
};
template <>
struct hash<vkb::DescriptorPool>
{
std::size_t operator()(const vkb::DescriptorPool &descriptor_pool) const
{
std::size_t result = 0;
vkb::hash_combine(result, descriptor_pool.get_descriptor_set_layout());
return result;
}
};
template <>
struct hash<vkb::PipelineLayout>
{
std::size_t operator()(const vkb::PipelineLayout &pipeline_layout) const
{
std::size_t result = 0;
vkb::hash_combine(result, pipeline_layout.get_handle());
return result;
}
};
template <>
struct hash<vkb::RenderPass>
{
std::size_t operator()(const vkb::RenderPass &render_pass) const
{
std::size_t result = 0;
vkb::hash_combine(result, render_pass.get_handle());
return result;
}
};
template <>
struct hash<vkb::Attachment>
{
std::size_t operator()(const vkb::Attachment &attachment) const
{
std::size_t result = 0;
vkb::hash_combine(result, static_cast<std::underlying_type<VkFormat>::type>(attachment.format));
vkb::hash_combine(result, static_cast<std::underlying_type<VkSampleCountFlagBits>::type>(attachment.samples));
vkb::hash_combine(result, attachment.usage);
vkb::hash_combine(result, static_cast<std::underlying_type<VkImageLayout>::type>(attachment.initial_layout));
return result;
}
};
template <>
struct hash<vkb::LoadStoreInfo>
{
std::size_t operator()(const vkb::LoadStoreInfo &load_store_info) const
{
std::size_t result = 0;
vkb::hash_combine(result, static_cast<std::underlying_type<VkAttachmentLoadOp>::type>(load_store_info.load_op));
vkb::hash_combine(result, static_cast<std::underlying_type<VkAttachmentStoreOp>::type>(load_store_info.store_op));
return result;
}
};
template <>
struct hash<vkb::SubpassInfo>
{
std::size_t operator()(const vkb::SubpassInfo &subpass_info) const
{
std::size_t result = 0;
for (uint32_t output_attachment : subpass_info.output_attachments)
{
vkb::hash_combine(result, output_attachment);
}
for (uint32_t input_attachment : subpass_info.input_attachments)
{
vkb::hash_combine(result, input_attachment);
}
for (uint32_t resolve_attachment : subpass_info.color_resolve_attachments)
{
vkb::hash_combine(result, resolve_attachment);
}
vkb::hash_combine(result, subpass_info.disable_depth_stencil_attachment);
vkb::hash_combine(result, subpass_info.depth_stencil_resolve_attachment);
vkb::hash_combine(result, subpass_info.depth_stencil_resolve_mode);
return result;
}
};
template <>
struct hash<vkb::SpecializationConstantState>
{
std::size_t operator()(const vkb::SpecializationConstantState &specialization_constant_state) const
{
std::size_t result = 0;
for (auto constants : specialization_constant_state.get_specialization_constant_state())
{
vkb::hash_combine(result, constants.first);
for (const auto data : constants.second)
{
vkb::hash_combine(result, data);
}
}
return result;
}
};
template <>
struct hash<vkb::ShaderResource>
{
std::size_t operator()(const vkb::ShaderResource &shader_resource) const
{
std::size_t result = 0;
if (shader_resource.type == vkb::ShaderResourceType::Input ||
shader_resource.type == vkb::ShaderResourceType::Output ||
shader_resource.type == vkb::ShaderResourceType::PushConstant ||
shader_resource.type == vkb::ShaderResourceType::SpecializationConstant)
{
return result;
}
vkb::hash_combine(result, shader_resource.set);
vkb::hash_combine(result, shader_resource.binding);
vkb::hash_combine(result, static_cast<std::underlying_type<vkb::ShaderResourceType>::type>(shader_resource.type));
vkb::hash_combine(result, shader_resource.mode);
return result;
}
};
template <>
struct hash<VkDescriptorBufferInfo>
{
std::size_t operator()(const VkDescriptorBufferInfo &descriptor_buffer_info) const
{
std::size_t result = 0;
vkb::hash_combine(result, descriptor_buffer_info.buffer);
vkb::hash_combine(result, descriptor_buffer_info.range);
vkb::hash_combine(result, descriptor_buffer_info.offset);
return result;
}
};
template <>
struct hash<VkDescriptorImageInfo>
{
std::size_t operator()(const VkDescriptorImageInfo &descriptor_image_info) const
{
std::size_t result = 0;
vkb::hash_combine(result, descriptor_image_info.imageView);
vkb::hash_combine(result, static_cast<std::underlying_type<VkImageLayout>::type>(descriptor_image_info.imageLayout));
vkb::hash_combine(result, descriptor_image_info.sampler);
return result;
}
};
template <>
struct hash<VkWriteDescriptorSet>
{
std::size_t operator()(const VkWriteDescriptorSet &write_descriptor_set) const
{
std::size_t result = 0;
vkb::hash_combine(result, write_descriptor_set.dstSet);
vkb::hash_combine(result, write_descriptor_set.dstBinding);
vkb::hash_combine(result, write_descriptor_set.dstArrayElement);
vkb::hash_combine(result, write_descriptor_set.descriptorCount);
vkb::hash_combine(result, write_descriptor_set.descriptorType);
switch (write_descriptor_set.descriptorType)
{
case VK_DESCRIPTOR_TYPE_SAMPLER:
case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
for (uint32_t i = 0; i < write_descriptor_set.descriptorCount; i++)
{
vkb::hash_combine(result, write_descriptor_set.pImageInfo[i]);
}
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
for (uint32_t i = 0; i < write_descriptor_set.descriptorCount; i++)
{
vkb::hash_combine(result, write_descriptor_set.pTexelBufferView[i]);
}
break;
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
for (uint32_t i = 0; i < write_descriptor_set.descriptorCount; i++)
{
vkb::hash_combine(result, write_descriptor_set.pBufferInfo[i]);
}
break;
default:
// Not implemented
break;
};
return result;
}
};
template <>
struct hash<VkVertexInputAttributeDescription>
{
std::size_t operator()(const VkVertexInputAttributeDescription &vertex_attrib) const
{
std::size_t result = 0;
vkb::hash_combine(result, vertex_attrib.binding);
vkb::hash_combine(result, static_cast<std::underlying_type<VkFormat>::type>(vertex_attrib.format));
vkb::hash_combine(result, vertex_attrib.location);
vkb::hash_combine(result, vertex_attrib.offset);
return result;
}
};
template <>
struct hash<VkVertexInputBindingDescription>
{
std::size_t operator()(const VkVertexInputBindingDescription &vertex_binding) const
{
std::size_t result = 0;
vkb::hash_combine(result, vertex_binding.binding);
vkb::hash_combine(result, static_cast<std::underlying_type<VkVertexInputRate>::type>(vertex_binding.inputRate));
vkb::hash_combine(result, vertex_binding.stride);
return result;
}
};
template <>
struct hash<vkb::StencilOpState>
{
std::size_t operator()(const vkb::StencilOpState &stencil) const
{
std::size_t result = 0;
vkb::hash_combine(result, static_cast<std::underlying_type<VkCompareOp>::type>(stencil.compare_op));
vkb::hash_combine(result, static_cast<std::underlying_type<VkStencilOp>::type>(stencil.depth_fail_op));
vkb::hash_combine(result, static_cast<std::underlying_type<VkStencilOp>::type>(stencil.fail_op));
vkb::hash_combine(result, static_cast<std::underlying_type<VkStencilOp>::type>(stencil.pass_op));
return result;
}
};
template <>
struct hash<VkExtent2D>
{
size_t operator()(const VkExtent2D &extent) const
{
size_t result = 0;
vkb::hash_combine(result, extent.width);
vkb::hash_combine(result, extent.height);
return result;
}
};
template <>
struct hash<VkOffset2D>
{
size_t operator()(const VkOffset2D &offset) const
{
size_t result = 0;
vkb::hash_combine(result, offset.x);
vkb::hash_combine(result, offset.y);
return result;
}
};
template <>
struct hash<VkRect2D>
{
size_t operator()(const VkRect2D &rect) const
{
size_t result = 0;
vkb::hash_combine(result, rect.extent);
vkb::hash_combine(result, rect.offset);
return result;
}
};
template <>
struct hash<VkViewport>
{
size_t operator()(const VkViewport &viewport) const
{
size_t result = 0;
vkb::hash_combine(result, viewport.width);
vkb::hash_combine(result, viewport.height);
vkb::hash_combine(result, viewport.maxDepth);
vkb::hash_combine(result, viewport.minDepth);
vkb::hash_combine(result, viewport.x);
vkb::hash_combine(result, viewport.y);
return result;
}
};
template <>
struct hash<vkb::ColorBlendAttachmentState>
{
std::size_t operator()(const vkb::ColorBlendAttachmentState &color_blend_attachment) const
{
std::size_t result = 0;
vkb::hash_combine(result, static_cast<std::underlying_type<VkBlendOp>::type>(color_blend_attachment.alpha_blend_op));
vkb::hash_combine(result, color_blend_attachment.blend_enable);
vkb::hash_combine(result, static_cast<std::underlying_type<VkBlendOp>::type>(color_blend_attachment.color_blend_op));
vkb::hash_combine(result, color_blend_attachment.color_write_mask);
vkb::hash_combine(result, static_cast<std::underlying_type<VkBlendFactor>::type>(color_blend_attachment.dst_alpha_blend_factor));
vkb::hash_combine(result, static_cast<std::underlying_type<VkBlendFactor>::type>(color_blend_attachment.dst_color_blend_factor));
vkb::hash_combine(result, static_cast<std::underlying_type<VkBlendFactor>::type>(color_blend_attachment.src_alpha_blend_factor));
vkb::hash_combine(result, static_cast<std::underlying_type<VkBlendFactor>::type>(color_blend_attachment.src_color_blend_factor));
return result;
}
};
template <>
struct hash<vkb::RenderTarget>
{
std::size_t operator()(const vkb::RenderTarget &render_target) const
{
std::size_t result = 0;
for (auto &view : render_target.get_views())
{
vkb::hash_combine(result, view.get_handle());
vkb::hash_combine(result, view.get_image().get_handle());
}
return result;
}
};
template <>
struct hash<vkb::PipelineState>
{
std::size_t operator()(const vkb::PipelineState &pipeline_state) const
{
std::size_t result = 0;
vkb::hash_combine(result, pipeline_state.get_pipeline_layout().get_handle());
// For graphics only
if (auto render_pass = pipeline_state.get_render_pass())
{
vkb::hash_combine(result, render_pass->get_handle());
}
vkb::hash_combine(result, pipeline_state.get_specialization_constant_state());
vkb::hash_combine(result, pipeline_state.get_subpass_index());
for (auto shader_module : pipeline_state.get_pipeline_layout().get_shader_modules())
{
vkb::hash_combine(result, shader_module->get_id());
}
// VkPipelineVertexInputStateCreateInfo
for (auto &attribute : pipeline_state.get_vertex_input_state().attributes)
{
vkb::hash_combine(result, attribute);
}
for (auto &binding : pipeline_state.get_vertex_input_state().bindings)
{
vkb::hash_combine(result, binding);
}
// VkPipelineInputAssemblyStateCreateInfo
vkb::hash_combine(result, pipeline_state.get_input_assembly_state().primitive_restart_enable);
vkb::hash_combine(result, static_cast<std::underlying_type<VkPrimitiveTopology>::type>(pipeline_state.get_input_assembly_state().topology));
// VkPipelineViewportStateCreateInfo
vkb::hash_combine(result, pipeline_state.get_viewport_state().viewport_count);
vkb::hash_combine(result, pipeline_state.get_viewport_state().scissor_count);
// VkPipelineRasterizationStateCreateInfo
vkb::hash_combine(result, pipeline_state.get_rasterization_state().cull_mode);
vkb::hash_combine(result, pipeline_state.get_rasterization_state().depth_bias_enable);
vkb::hash_combine(result, pipeline_state.get_rasterization_state().depth_clamp_enable);
vkb::hash_combine(result, static_cast<std::underlying_type<VkFrontFace>::type>(pipeline_state.get_rasterization_state().front_face));
vkb::hash_combine(result, static_cast<std::underlying_type<VkPolygonMode>::type>(pipeline_state.get_rasterization_state().polygon_mode));
vkb::hash_combine(result, pipeline_state.get_rasterization_state().rasterizer_discard_enable);
// VkPipelineMultisampleStateCreateInfo
vkb::hash_combine(result, pipeline_state.get_multisample_state().alpha_to_coverage_enable);
vkb::hash_combine(result, pipeline_state.get_multisample_state().alpha_to_one_enable);
vkb::hash_combine(result, pipeline_state.get_multisample_state().min_sample_shading);
vkb::hash_combine(result, static_cast<std::underlying_type<VkSampleCountFlagBits>::type>(pipeline_state.get_multisample_state().rasterization_samples));
vkb::hash_combine(result, pipeline_state.get_multisample_state().sample_shading_enable);
vkb::hash_combine(result, pipeline_state.get_multisample_state().sample_mask);
// VkPipelineDepthStencilStateCreateInfo
vkb::hash_combine(result, pipeline_state.get_depth_stencil_state().back);
vkb::hash_combine(result, pipeline_state.get_depth_stencil_state().depth_bounds_test_enable);
vkb::hash_combine(result, static_cast<std::underlying_type<VkCompareOp>::type>(pipeline_state.get_depth_stencil_state().depth_compare_op));
vkb::hash_combine(result, pipeline_state.get_depth_stencil_state().depth_test_enable);
vkb::hash_combine(result, pipeline_state.get_depth_stencil_state().depth_write_enable);
vkb::hash_combine(result, pipeline_state.get_depth_stencil_state().front);
vkb::hash_combine(result, pipeline_state.get_depth_stencil_state().stencil_test_enable);
// VkPipelineColorBlendStateCreateInfo
vkb::hash_combine(result, static_cast<std::underlying_type<VkLogicOp>::type>(pipeline_state.get_color_blend_state().logic_op));
vkb::hash_combine(result, pipeline_state.get_color_blend_state().logic_op_enable);
for (auto &attachment : pipeline_state.get_color_blend_state().attachments)
{
vkb::hash_combine(result, attachment);
}
return result;
}
};
} // namespace std
namespace vkb
{
namespace
{
template <typename T>
inline void hash_param(size_t &seed, const T &value)
{
hash_combine(seed, value);
}
template <>
inline void hash_param(size_t & /*seed*/, const VkPipelineCache & /*value*/)
{
}
template <>
inline void hash_param<std::vector<uint8_t>>(
size_t &seed,
const std::vector<uint8_t> &value)
{
hash_combine(seed, std::string{value.begin(), value.end()});
}
template <>
inline void hash_param<std::vector<Attachment>>(
size_t &seed,
const std::vector<Attachment> &value)
{
for (auto &attachment : value)
{
hash_combine(seed, attachment);
}
}
template <>
inline void hash_param<std::vector<LoadStoreInfo>>(
size_t &seed,
const std::vector<LoadStoreInfo> &value)
{
for (auto &load_store_info : value)
{
hash_combine(seed, load_store_info);
}
}
template <>
inline void hash_param<std::vector<SubpassInfo>>(
size_t &seed,
const std::vector<SubpassInfo> &value)
{
for (auto &subpass_info : value)
{
hash_combine(seed, subpass_info);
}
}
template <>
inline void hash_param<std::vector<ShaderModule *>>(
size_t &seed,
const std::vector<ShaderModule *> &value)
{
for (auto &shader_module : value)
{
hash_combine(seed, shader_module->get_id());
}
}
template <>
inline void hash_param<std::vector<ShaderResource>>(
size_t &seed,
const std::vector<ShaderResource> &value)
{
for (auto &resource : value)
{
hash_combine(seed, resource);
}
}
template <>
inline void hash_param<std::map<uint32_t, std::map<uint32_t, VkDescriptorBufferInfo>>>(
size_t &seed,
const std::map<uint32_t, std::map<uint32_t, VkDescriptorBufferInfo>> &value)
{
for (auto &binding_set : value)
{
hash_combine(seed, binding_set.first);
for (auto &binding_element : binding_set.second)
{
hash_combine(seed, binding_element.first);
hash_combine(seed, binding_element.second);
}
}
}
template <>
inline void hash_param<std::map<uint32_t, std::map<uint32_t, VkDescriptorImageInfo>>>(
size_t &seed,
const std::map<uint32_t, std::map<uint32_t, VkDescriptorImageInfo>> &value)
{
for (auto &binding_set : value)
{
hash_combine(seed, binding_set.first);
for (auto &binding_element : binding_set.second)
{
hash_combine(seed, binding_element.first);
hash_combine(seed, binding_element.second);
}
}
}
template <typename T, typename... Args>
inline void hash_param(size_t &seed, const T &first_arg, const Args &...args)
{
hash_param(seed, first_arg);
hash_param(seed, args...);
}
template <class T, class... A>
struct RecordHelper
{
size_t record(ResourceRecord & /*recorder*/, A &.../*args*/)
{
return 0;
}
void index(ResourceRecord & /*recorder*/, size_t /*index*/, T & /*resource*/)
{
}
};
template <class... A>
struct RecordHelper<ShaderModule, A...>
{
size_t record(ResourceRecord &recorder, A &...args)
{
return recorder.register_shader_module(args...);
}
void index(ResourceRecord &recorder, size_t index, ShaderModule &shader_module)
{
recorder.set_shader_module(index, shader_module);
}
};
template <class... A>
struct RecordHelper<PipelineLayout, A...>
{
size_t record(ResourceRecord &recorder, A &...args)
{
return recorder.register_pipeline_layout(args...);
}
void index(ResourceRecord &recorder, size_t index, PipelineLayout &pipeline_layout)
{
recorder.set_pipeline_layout(index, pipeline_layout);
}
};
template <class... A>
struct RecordHelper<RenderPass, A...>
{
size_t record(ResourceRecord &recorder, A &...args)
{
return recorder.register_render_pass(args...);
}
void index(ResourceRecord &recorder, size_t index, RenderPass &render_pass)
{
recorder.set_render_pass(index, render_pass);
}
};
template <class... A>
struct RecordHelper<GraphicsPipeline, A...>
{
size_t record(ResourceRecord &recorder, A &...args)
{
return recorder.register_graphics_pipeline(args...);
}
void index(ResourceRecord &recorder, size_t index, GraphicsPipeline &graphics_pipeline)
{
recorder.set_graphics_pipeline(index, graphics_pipeline);
}
};
} // namespace
template <class T, class... A>
T &request_resource(vkb::core::DeviceC &device, ResourceRecord *recorder, std::unordered_map<std::size_t, T> &resources, A &...args)
{
RecordHelper<T, A...> record_helper;
std::size_t hash{0U};
hash_param(hash, args...);
auto res_it = resources.find(hash);
if (res_it != resources.end())
{
return res_it->second;
}
// If we do not have it already, create and cache it
const char *res_type = typeid(T).name();
size_t res_id = resources.size();
LOGD("Building #{} cache object ({})", res_id, res_type);
// Only error handle in release
#ifndef DEBUG
try
{
#endif
T resource(device, args...);
auto res_ins_it = resources.emplace(hash, std::move(resource));
if (!res_ins_it.second)
{
throw std::runtime_error{std::string{"Insertion error for #"} + std::to_string(res_id) + "cache object (" + res_type + ")"};
}
res_it = res_ins_it.first;
if (recorder)
{
size_t index = record_helper.record(*recorder, args...);
record_helper.index(*recorder, index, res_it->second);
}
#ifndef DEBUG
}
catch (const std::exception &e)
{
LOGE("Creation error for #{} cache object ({})", res_id, res_type);
throw e;
}
#endif
return res_it->second;
}
} // namespace vkb
File diff suppressed because it is too large Load Diff
+304
View File
@@ -0,0 +1,304 @@
/* Copyright (c) 2018-2024, Arm Limited and Contributors
*
* 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 <map>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#include <volk.h>
namespace vkb
{
enum class ShaderResourceType;
namespace sg
{
enum class AlphaMode;
}
std::vector<std::string> split(const std::string &str, const std::string &delimiter);
std::string join(const std::vector<std::string> &str, const std::string &separator);
/**
* @brief Helper function to convert a VkFormat enum to a string
* @param format Vulkan format to convert.
* @return The string to return.
*/
const std::string to_string(VkFormat format);
/**
* @brief Helper function to convert a VkPresentModeKHR to a string
* @param present_mode Vulkan present mode to convert.
* @return The string to return.
*/
const std::string to_string(VkPresentModeKHR present_mode);
/**
* @brief Helper function to convert a VkResult enum to a string
* @param result Vulkan result to convert.
* @return The string to return.
*/
const std::string to_string(VkResult result);
/**
* @brief Helper function to convert a VkPhysicalDeviceType enum to a string
* @param type Vulkan physical device type to convert.
* @return The string to return.
*/
const std::string to_string(VkPhysicalDeviceType type);
/**
* @brief Helper function to convert a VkSurfaceTransformFlagBitsKHR flag to a string
* @param transform_flag Vulkan surface transform flag bit to convert.
* @return The string to return.
*/
const std::string to_string(VkSurfaceTransformFlagBitsKHR transform_flag);
/**
* @brief Helper function to convert a VkSurfaceFormatKHR format to a string
* @param surface_format Vulkan surface format to convert.
* @return The string to return.
*/
const std::string to_string(VkSurfaceFormatKHR surface_format);
/**
* @brief Helper function to convert a VkCompositeAlphaFlagBitsKHR flag to a string
* @param composite_alpha Vulkan composite alpha flag bit to convert.
* @return The string to return.
*/
const std::string to_string(VkCompositeAlphaFlagBitsKHR composite_alpha);
/**
* @brief Helper function to convert a VkImageUsageFlagBits flag to a string
* @param image_usage Vulkan image usage flag bit to convert.
* @return The string to return.
*/
const std::string to_string(VkImageUsageFlagBits image_usage);
/**
* @brief Helper function to convert a VkExtent2D flag to a string
* @param format Vulkan format to convert.
* @return The string to return.
*/
std::string to_string(VkExtent2D format);
/**
* @brief Helper function to convert VkSampleCountFlagBits to a string
* @param flags Vulkan sample count flags to convert
* @return const std::string
*/
const std::string to_string(VkSampleCountFlagBits flags);
/**
* @brief Helper function to convert VkImageTiling to a string
* @param tiling Vulkan VkImageTiling to convert
* @return The string to return
*/
const std::string to_string(VkImageTiling tiling);
/**
* @brief Helper function to convert VkImageType to a string
* @param type Vulkan VkImageType to convert
* @return The string to return
*/
const std::string to_string(VkImageType type);
/**
* @brief Helper function to convert VkBlendFactor to a string
* @param blend Vulkan VkBlendFactor to convert
* @return The string to return
*/
const std::string to_string(VkBlendFactor blend);
/**
* @brief Helper function to convert VkVertexInputRate to a string
* @param rate Vulkan VkVertexInputRate to convert
* @return The string to return
*/
const std::string to_string(VkVertexInputRate rate);
/**
* @brief Helper function to convert VkBool32 to a string
* @param state Vulkan VkBool32 to convert
* @return The string to return
*/
const std::string to_string_vk_bool(VkBool32 state);
/**
* @brief Helper function to convert VkPrimitiveTopology to a string
* @param topology Vulkan VkPrimitiveTopology to convert
* @return The string to return
*/
const std::string to_string(VkPrimitiveTopology topology);
/**
* @brief Helper function to convert VkFrontFace to a string
* @param face Vulkan VkFrontFace to convert
* @return The string to return
*/
const std::string to_string(VkFrontFace face);
/**
* @brief Helper function to convert VkPolygonMode to a string
* @param mode Vulkan VkPolygonMode to convert
* @return The string to return
*/
const std::string to_string(VkPolygonMode mode);
/**
* @brief Helper function to convert VkCompareOp to a string
* @param operation Vulkan VkCompareOp to convert
* @return The string to return
*/
const std::string to_string(VkCompareOp operation);
/**
* @brief Helper function to convert VkStencilOp to a string
* @param operation Vulkan VkStencilOp to convert
* @return The string to return
*/
const std::string to_string(VkStencilOp operation);
/**
* @brief Helper function to convert VkLogicOp to a string
* @param operation Vulkan VkLogicOp to convert
* @return The string to return
*/
const std::string to_string(VkLogicOp operation);
/**
* @brief Helper function to convert VkBlendOp to a string
* @param operation Vulkan VkBlendOp to convert
* @return The string to return
*/
const std::string to_string(VkBlendOp operation);
/**
* @brief Helper function to convert AlphaMode to a string
* @param mode Vulkan AlphaMode to convert
* @return The string to return
*/
const std::string to_string(sg::AlphaMode mode);
/**
* @brief Helper function to convert bool to a string
* @param flag Vulkan bool to convert (true/false)
* @return The string to return
*/
const std::string to_string(bool flag);
/**
* @brief Helper function to convert ShaderResourceType to a string
* @param type Vulkan ShaderResourceType to convert
* @return The string to return
*/
const std::string to_string(ShaderResourceType type);
/**
* @brief Helper generic function to convert a bitmask to a string of its components
* @param bitmask The bitmask to convert
* @param string_map A map of bitmask bits to the string that describe the Vulkan flag
* @returns A string of the enabled bits in the bitmask
*/
template <typename T>
inline const std::string to_string(uint32_t bitmask, const std::map<T, const char *> string_map)
{
std::stringstream result;
bool append = false;
for (const auto &s : string_map)
{
if (bitmask & s.first)
{
if (append)
{
result << " | ";
}
result << s.second;
append = true;
}
}
return result.str();
}
/**
* @brief Helper function to convert VkBufferUsageFlags to a string
* @param bitmask The buffer usage bitmask to convert to strings
* @return The converted string to return
*/
const std::string buffer_usage_to_string(VkBufferUsageFlags bitmask);
/**
* @brief Helper function to convert VkShaderStageFlags to a string
* @param bitmask The shader stage bitmask to convert
* @return The converted string to return
*/
const std::string shader_stage_to_string(VkShaderStageFlags bitmask);
/**
* @brief Helper function to convert VkImageUsageFlags to a string
* @param bitmask The image usage bitmask to convert
* @return The converted string to return
*/
const std::string image_usage_to_string(VkImageUsageFlags bitmask);
/**
* @brief Helper function to convert VkImageAspectFlags to a string
* @param bitmask The image aspect bitmask to convert
* @return The converted string to return
*/
const std::string image_aspect_to_string(VkImageAspectFlags bitmask);
/**
* @brief Helper function to convert VkCullModeFlags to a string
* @param bitmask The cull mode bitmask to convert
* @return The converted string to return
*/
const std::string cull_mode_to_string(VkCullModeFlags bitmask);
/**
* @brief Helper function to convert VkColorComponentFlags to a string
* @param bitmask The color component bitmask to convert
* @return The converted string to return
*/
const std::string color_component_to_string(VkColorComponentFlags bitmask);
/**
* @brief Helper function to convert VkImageCompressionFlagsEXT to a string
* @param flags The flags to convert
* @return The converted string to return
*/
const std::string image_compression_flags_to_string(VkImageCompressionFlagsEXT flags);
/**
* @brief Helper function to convert VkImageCompressionFixedRateFlagsEXT to a string
* @param flags The flags to convert
* @return The converted string to return
*/
const std::string image_compression_fixed_rate_flags_to_string(VkImageCompressionFixedRateFlagsEXT flags);
/**
* @brief Helper function to split a single string into a vector of strings by a delimiter
* @param input The input string to be split
* @param delim The character to delimit by
* @return The vector of tokenized strings
*/
std::vector<std::string> split(const std::string &input, char delim);
} // namespace vkb
+85
View File
@@ -0,0 +1,85 @@
/* Copyright (c) 2020-2025, Arm Limited and Contributors
*
* 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 <algorithm>
#include <vector>
/**
* @brief Used to represent a tag
*/
typedef void (*TagID)();
/**
* @brief Tag acts as a unique identifier to categories objects
*
* Tags are uniquely defined using different type names. The easiest way of creating a new tag is to use an empty struct
* struct TagName{};
* struct DifferentTag{};
* Tag<TagName>::ID == Tag<TagName>::member != Tag<DifferentTag>:ID
*
* @tparam TAGS A set of tags
*/
template <typename... TAGS>
class Tag
{
public:
Tag()
{
tags = {Tag<TAGS>::ID...};
}
static void member(){};
/**
* @brief Unique TagID for a given Tag<TagName>
*/
constexpr static TagID ID = &member;
static bool has_tag(TagID id)
{
return std::ranges::find(tags, id) != tags.end();
}
template <typename C>
static bool has_tag()
{
return has_tag(Tag<C>::ID);
}
template <typename... C>
static bool has_tags()
{
std::vector<TagID> query = {Tag<C>::ID...};
bool res = true;
for (auto id : query)
{
res &= has_tag(id);
}
return res;
}
private:
static std::vector<TagID> tags;
};
#ifndef DOXYGEN_SKIP
template <typename... TAGS>
std::vector<TagID> Tag<TAGS...>::tags;
#endif
+311
View File
@@ -0,0 +1,311 @@
/* Copyright (c) 2018-2025, Arm Limited and Contributors
*
* 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.
*/
#include "utils.h"
#include <queue>
#include <stdexcept>
#include "scene_graph/components/material.h"
#include "scene_graph/components/perspective_camera.h"
#include "scene_graph/components/sub_mesh.h"
#include "scene_graph/node.h"
#include "scene_graph/script.h"
#include "scene_graph/scripts/free_camera.h"
namespace vkb
{
std::string get_extension(const std::string &uri)
{
auto dot_pos = uri.find_last_of('.');
if (dot_pos == std::string::npos)
{
throw std::runtime_error{"Uri has no extension"};
}
return uri.substr(dot_pos + 1);
}
void screenshot(RenderContext &render_context, const std::string &filename)
{
assert(render_context.get_format() == VK_FORMAT_R8G8B8A8_UNORM ||
render_context.get_format() == VK_FORMAT_B8G8R8A8_UNORM ||
render_context.get_format() == VK_FORMAT_R8G8B8A8_SRGB ||
render_context.get_format() == VK_FORMAT_B8G8R8A8_SRGB);
// We want the last completed frame since we don't want to be reading from an incomplete framebuffer
auto &frame = render_context.get_last_rendered_frame();
assert(!frame.get_render_target().get_views().empty());
auto &src_image_view = frame.get_render_target().get_views()[0];
auto width = render_context.get_surface_extent().width;
auto height = render_context.get_surface_extent().height;
auto dst_size = width * height * 4;
vkb::core::BufferC dst_buffer{render_context.get_device(),
dst_size,
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VMA_MEMORY_USAGE_GPU_TO_CPU,
VMA_ALLOCATION_CREATE_MAPPED_BIT};
const auto &queue = render_context.get_device().get_queue_by_flags(VK_QUEUE_GRAPHICS_BIT, 0);
auto cmd_buf = render_context.get_device().get_command_pool().request_command_buffer();
cmd_buf->begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT);
// Enable destination buffer to be written to
{
BufferMemoryBarrier memory_barrier{};
memory_barrier.src_access_mask = 0;
memory_barrier.dst_access_mask = VK_ACCESS_TRANSFER_WRITE_BIT;
memory_barrier.src_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
memory_barrier.dst_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
cmd_buf->buffer_memory_barrier(dst_buffer, 0, dst_size, memory_barrier);
}
// Enable framebuffer image view to be read from
{
ImageMemoryBarrier memory_barrier{};
memory_barrier.old_layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
memory_barrier.new_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
memory_barrier.src_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
memory_barrier.dst_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
cmd_buf->image_memory_barrier(src_image_view, memory_barrier);
}
// Check if framebuffer images are in a BGR format
auto bgr_formats = {VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SNORM};
bool swizzle = std::ranges::find(bgr_formats, src_image_view.get_format()) != bgr_formats.end();
// Copy framebuffer image memory
VkBufferImageCopy image_copy_region{};
image_copy_region.bufferRowLength = width;
image_copy_region.bufferImageHeight = height;
image_copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_copy_region.imageSubresource.layerCount = 1;
image_copy_region.imageExtent.width = width;
image_copy_region.imageExtent.height = height;
image_copy_region.imageExtent.depth = 1;
cmd_buf->copy_image_to_buffer(src_image_view.get_image(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dst_buffer, {image_copy_region});
// Enable destination buffer to map memory
{
BufferMemoryBarrier memory_barrier{};
memory_barrier.src_access_mask = VK_ACCESS_TRANSFER_WRITE_BIT;
memory_barrier.dst_access_mask = VK_ACCESS_HOST_READ_BIT;
memory_barrier.src_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
memory_barrier.dst_stage_mask = VK_PIPELINE_STAGE_HOST_BIT;
cmd_buf->buffer_memory_barrier(dst_buffer, 0, dst_size, memory_barrier);
}
// Revert back the framebuffer image view from transfer to present
{
ImageMemoryBarrier memory_barrier{};
memory_barrier.old_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
memory_barrier.new_layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
memory_barrier.src_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
memory_barrier.dst_stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
cmd_buf->image_memory_barrier(src_image_view, memory_barrier);
}
cmd_buf->end();
queue.submit(*cmd_buf, frame.get_fence_pool().request_fence());
queue.wait_idle();
auto raw_data = dst_buffer.map();
// Creates a pointer to the address of the first byte of the image data
// Replace the A component with 255 (remove transparency)
// If swapchain format is BGR, swapping the R and B components
uint8_t *data = raw_data;
if (swizzle)
{
for (size_t i = 0; i < height; ++i)
{
// Iterate over each pixel, swapping R and B components and writing the max value for alpha
for (size_t j = 0; j < width; ++j)
{
auto temp = *(data + 2);
*(data + 2) = *(data);
*(data) = temp;
*(data + 3) = 255;
// Get next pixel
data += 4;
}
}
}
else
{
for (size_t i = 0; i < height; ++i)
{
// Iterate over each pixel, writing the max value for alpha
for (size_t j = 0; j < width; ++j)
{
*(data + 3) = 255;
// Get next pixel
data += 4;
}
}
}
vkb::fs::write_image(raw_data,
filename,
width,
height,
4,
width * 4);
dst_buffer.unmap();
} // namespace vkb
std::string to_snake_case(const std::string &text)
{
std::stringstream result;
for (const auto ch : text)
{
if (std::isalpha(ch))
{
if (std::isspace(ch))
{
result << "_";
}
else
{
if (std::isupper(ch))
{
result << "_";
}
result << static_cast<char>(std::tolower(ch));
}
}
else
{
result << ch;
}
}
return result.str();
}
sg::Light &add_light(sg::Scene &scene, sg::LightType type, const glm::vec3 &position, const glm::quat &rotation, const sg::LightProperties &props, sg::Node *parent_node)
{
auto light_ptr = std::make_unique<sg::Light>("light");
auto node = std::make_unique<sg::Node>(-1, "light node");
if (parent_node)
{
node->set_parent(*parent_node);
}
light_ptr->set_node(*node);
light_ptr->set_light_type(type);
light_ptr->set_properties(props);
auto &t = node->get_transform();
t.set_translation(position);
t.set_rotation(rotation);
// Storing the light component because the unique_ptr will be moved to the scene
auto &light = *light_ptr;
node->set_component(light);
scene.add_child(*node);
scene.add_component(std::move(light_ptr));
scene.add_node(std::move(node));
return light;
}
sg::Light &add_point_light(sg::Scene &scene, const glm::vec3 &position, const sg::LightProperties &props, sg::Node *parent_node)
{
return add_light(scene, sg::LightType::Point, position, {}, props, parent_node);
}
sg::Light &add_directional_light(sg::Scene &scene, const glm::quat &rotation, const sg::LightProperties &props, sg::Node *parent_node)
{
return add_light(scene, sg::LightType::Directional, {}, rotation, props, parent_node);
}
sg::Light &add_spot_light(sg::Scene &scene, const glm::vec3 &position, const glm::quat &rotation, const sg::LightProperties &props, sg::Node *parent_node)
{
return add_light(scene, sg::LightType::Spot, position, rotation, props, parent_node);
}
sg::Node &add_free_camera(sg::Scene &scene, const std::string &node_name, VkExtent2D extent)
{
auto camera_node = scene.find_node(node_name);
if (!camera_node)
{
LOGW("Camera node `{}` not found. Looking for `default_camera` node.", node_name.c_str());
camera_node = scene.find_node("default_camera");
}
if (!camera_node)
{
throw std::runtime_error("Camera node with name `" + node_name + "` not found.");
}
if (!camera_node->has_component<sg::Camera>())
{
throw std::runtime_error("No camera component found for `" + node_name + "` node.");
}
auto free_camera_script = std::make_unique<sg::FreeCamera>(*camera_node);
free_camera_script->resize(extent.width, extent.height);
scene.add_component(std::move(free_camera_script), *camera_node);
return *camera_node;
}
size_t calculate_hash(const std::vector<uint8_t> &data)
{
static_assert(sizeof(data[0]) == 1);
constexpr size_t chunk_size = sizeof(size_t) / sizeof(data[0]);
size_t data_hash = 0;
size_t offset = 0;
for (; offset + chunk_size < data.size(); offset += chunk_size)
{
glm::detail::hash_combine(data_hash, *reinterpret_cast<size_t const *>(&data[offset]));
}
if (offset < data.size())
{
size_t it = 0;
std::memcpy(&it, &data[offset], data.size() - offset);
glm::detail::hash_combine(data_hash, it);
}
return data_hash;
}
} // namespace vkb
+112
View File
@@ -0,0 +1,112 @@
/* Copyright (c) 2018-2025, Arm Limited and Contributors
*
* 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 "common/error.h"
#include "common/glm_common.h"
#include "glm/gtx/quaternion.hpp"
#include "filesystem/legacy.h"
#include "rendering/pipeline_state.h"
#include "rendering/render_context.h"
#include "scene_graph/components/sub_mesh.h"
#include "scene_graph/scene.h"
namespace vkb
{
/**
* @brief Extracts the extension from an uri
* @param uri An uniform Resource Identifier
* @return The extension
*/
std::string get_extension(const std::string &uri);
/**
* @brief Generates a hash from an array
* @param data
* @return data_hash hash of the data
*/
size_t calculate_hash(const std::vector<uint8_t> &data);
/**
* @param name String to convert to snake case
* @return a snake case version of the string
*/
std::string to_snake_case(const std::string &name);
/**
* @brief Takes a screenshot of the app by writing the swapchain image to file (slow function)
* @param render_context The RenderContext to use
* @param filename The name of the file to save the output to
*/
void screenshot(RenderContext &render_context, const std::string &filename);
/**
* @brief Adds a light to the scene with the specified parameters
* @param scene The scene to add the light to
* @param type The light type
* @param position The position of the light
* @param rotation The rotation of the light
* @param props The light properties, such as color and intensity
* @param parent_node The parent node for the line, defaults to root
* @return The newly created light component
*/
sg::Light &add_light(sg::Scene &scene, sg::LightType type, const glm::vec3 &position, const glm::quat &rotation = {}, const sg::LightProperties &props = {}, sg::Node *parent_node = nullptr);
/**
* @brief Adds a point light to the scene with the specified parameters
* @param scene The scene to add the light to
* @param position The position of the light
* @param props The light properties, such as color and intensity
* @param parent_node The parent node for the line, defaults to root
* @return The newly created light component
*/
sg::Light &add_point_light(sg::Scene &scene, const glm::vec3 &position, const sg::LightProperties &props = {}, sg::Node *parent_node = nullptr);
/**
* @brief Adds a directional light to the scene with the specified parameters
* @param scene The scene to add the light to
* @param rotation The rotation of the light
* @param props The light properties, such as color and intensity
* @param parent_node The parent node for the line, defaults to root
* @return The newly created light component
*/
sg::Light &add_directional_light(sg::Scene &scene, const glm::quat &rotation, const sg::LightProperties &props = {}, sg::Node *parent_node = nullptr);
/**
* @brief Adds a spot light to the scene with the specified parameters
* @param scene The scene to add the light to
* @param position The position of the light
* @param rotation The rotation of the light
* @param props The light properties, such as color and intensity
* @param parent_node The parent node for the line, defaults to root
* @return The newly created light component
*/
sg::Light &add_spot_light(sg::Scene &scene, const glm::vec3 &position, const glm::quat &rotation, const sg::LightProperties &props = {}, sg::Node *parent_node = nullptr);
/**
* @brief Add free camera script to a node with a camera object.
* Fallback to the default_camera if node not found.
* @param scene The scene to add the camera to
* @param node_name The scene node name
* @param extent The initial resolution of the camera
* @return Node where the script was attached as component
*/
sg::Node &add_free_camera(sg::Scene &scene, const std::string &node_name, VkExtent2D extent);
} // namespace vkb
+719
View File
@@ -0,0 +1,719 @@
/* Copyright (c) 2018-2025, Arm Limited and Contributors
* Copyright (c) 2019-2025, Sascha Willems
*
* 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.
*/
#include "vk_common.h"
#include <fmt/format.h>
#include "filesystem/legacy.h"
std::ostream &operator<<(std::ostream &os, const VkResult result)
{
#define WRITE_VK_ENUM(r) \
case VK_##r: \
os << #r; \
break;
switch (result)
{
WRITE_VK_ENUM(NOT_READY);
WRITE_VK_ENUM(TIMEOUT);
WRITE_VK_ENUM(EVENT_SET);
WRITE_VK_ENUM(EVENT_RESET);
WRITE_VK_ENUM(INCOMPLETE);
WRITE_VK_ENUM(ERROR_OUT_OF_HOST_MEMORY);
WRITE_VK_ENUM(ERROR_OUT_OF_DEVICE_MEMORY);
WRITE_VK_ENUM(ERROR_INITIALIZATION_FAILED);
WRITE_VK_ENUM(ERROR_DEVICE_LOST);
WRITE_VK_ENUM(ERROR_MEMORY_MAP_FAILED);
WRITE_VK_ENUM(ERROR_LAYER_NOT_PRESENT);
WRITE_VK_ENUM(ERROR_EXTENSION_NOT_PRESENT);
WRITE_VK_ENUM(ERROR_FEATURE_NOT_PRESENT);
WRITE_VK_ENUM(ERROR_INCOMPATIBLE_DRIVER);
WRITE_VK_ENUM(ERROR_TOO_MANY_OBJECTS);
WRITE_VK_ENUM(ERROR_FORMAT_NOT_SUPPORTED);
WRITE_VK_ENUM(ERROR_SURFACE_LOST_KHR);
WRITE_VK_ENUM(ERROR_NATIVE_WINDOW_IN_USE_KHR);
WRITE_VK_ENUM(SUBOPTIMAL_KHR);
WRITE_VK_ENUM(ERROR_OUT_OF_DATE_KHR);
WRITE_VK_ENUM(ERROR_INCOMPATIBLE_DISPLAY_KHR);
WRITE_VK_ENUM(ERROR_VALIDATION_FAILED_EXT);
WRITE_VK_ENUM(ERROR_INVALID_SHADER_NV);
default:
os << "UNKNOWN_ERROR";
}
#undef WRITE_VK_ENUM
return os;
}
namespace vkb
{
bool is_depth_only_format(VkFormat format)
{
return format == VK_FORMAT_D16_UNORM ||
format == VK_FORMAT_D32_SFLOAT;
}
bool is_depth_stencil_format(VkFormat format)
{
return format == VK_FORMAT_D16_UNORM_S8_UINT ||
format == VK_FORMAT_D24_UNORM_S8_UINT ||
format == VK_FORMAT_D32_SFLOAT_S8_UINT;
}
bool is_depth_format(VkFormat format)
{
return is_depth_only_format(format) || is_depth_stencil_format(format);
}
VkFormat get_suitable_depth_format(VkPhysicalDevice physical_device, bool depth_only, const std::vector<VkFormat> &depth_format_priority_list)
{
VkFormat depth_format{VK_FORMAT_UNDEFINED};
for (auto &format : depth_format_priority_list)
{
if (depth_only && !is_depth_only_format(format))
{
continue;
}
VkFormatProperties properties;
vkGetPhysicalDeviceFormatProperties(physical_device, format, &properties);
// Format must support depth stencil attachment for optimal tiling
if (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)
{
depth_format = format;
break;
}
}
if (depth_format != VK_FORMAT_UNDEFINED)
{
LOGI("Depth format selected: {}", to_string(depth_format));
return depth_format;
}
throw std::runtime_error("No suitable depth format could be determined");
}
VkFormat choose_blendable_format(VkPhysicalDevice physical_device, const std::vector<VkFormat> &format_priority_list)
{
for (const auto &format : format_priority_list)
{
VkFormatProperties properties;
vkGetPhysicalDeviceFormatProperties(physical_device, format, &properties);
if (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT)
{
return format;
}
}
throw std::runtime_error("No suitable blendable format could be determined");
}
void make_filters_valid(VkPhysicalDevice physical_device, VkFormat format, VkFilter *filter, VkSamplerMipmapMode *mipmapMode)
{
// Not all formats support linear filtering, so we need to adjust them if they don't
if (*filter == VK_FILTER_NEAREST && (mipmapMode == nullptr || *mipmapMode == VK_SAMPLER_MIPMAP_MODE_NEAREST))
{
return; // These must already be valid
}
VkFormatProperties properties;
vkGetPhysicalDeviceFormatProperties(physical_device, format, &properties);
if (!(properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT))
{
*filter = VK_FILTER_NEAREST;
if (mipmapMode)
{
*mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
}
}
}
bool is_dynamic_buffer_descriptor_type(VkDescriptorType descriptor_type)
{
return descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC ||
descriptor_type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
}
bool is_buffer_descriptor_type(VkDescriptorType descriptor_type)
{
return descriptor_type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ||
descriptor_type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER ||
is_dynamic_buffer_descriptor_type(descriptor_type);
}
int32_t get_bits_per_pixel(VkFormat format)
{
switch (format)
{
case VK_FORMAT_R4G4_UNORM_PACK8:
return 8;
case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
case VK_FORMAT_R5G6B5_UNORM_PACK16:
case VK_FORMAT_B5G6R5_UNORM_PACK16:
case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
case VK_FORMAT_B5G5R5A1_UNORM_PACK16:
case VK_FORMAT_A1R5G5B5_UNORM_PACK16:
return 16;
case VK_FORMAT_R8_UNORM:
case VK_FORMAT_R8_SNORM:
case VK_FORMAT_R8_USCALED:
case VK_FORMAT_R8_SSCALED:
case VK_FORMAT_R8_UINT:
case VK_FORMAT_R8_SINT:
case VK_FORMAT_R8_SRGB:
return 8;
case VK_FORMAT_R8G8_UNORM:
case VK_FORMAT_R8G8_SNORM:
case VK_FORMAT_R8G8_USCALED:
case VK_FORMAT_R8G8_SSCALED:
case VK_FORMAT_R8G8_UINT:
case VK_FORMAT_R8G8_SINT:
case VK_FORMAT_R8G8_SRGB:
return 16;
case VK_FORMAT_R8G8B8_UNORM:
case VK_FORMAT_R8G8B8_SNORM:
case VK_FORMAT_R8G8B8_USCALED:
case VK_FORMAT_R8G8B8_SSCALED:
case VK_FORMAT_R8G8B8_UINT:
case VK_FORMAT_R8G8B8_SINT:
case VK_FORMAT_R8G8B8_SRGB:
case VK_FORMAT_B8G8R8_UNORM:
case VK_FORMAT_B8G8R8_SNORM:
case VK_FORMAT_B8G8R8_USCALED:
case VK_FORMAT_B8G8R8_SSCALED:
case VK_FORMAT_B8G8R8_UINT:
case VK_FORMAT_B8G8R8_SINT:
case VK_FORMAT_B8G8R8_SRGB:
return 24;
case VK_FORMAT_R8G8B8A8_UNORM:
case VK_FORMAT_R8G8B8A8_SNORM:
case VK_FORMAT_R8G8B8A8_USCALED:
case VK_FORMAT_R8G8B8A8_SSCALED:
case VK_FORMAT_R8G8B8A8_UINT:
case VK_FORMAT_R8G8B8A8_SINT:
case VK_FORMAT_R8G8B8A8_SRGB:
case VK_FORMAT_B8G8R8A8_UNORM:
case VK_FORMAT_B8G8R8A8_SNORM:
case VK_FORMAT_B8G8R8A8_USCALED:
case VK_FORMAT_B8G8R8A8_SSCALED:
case VK_FORMAT_B8G8R8A8_UINT:
case VK_FORMAT_B8G8R8A8_SINT:
case VK_FORMAT_B8G8R8A8_SRGB:
case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
case VK_FORMAT_A8B8G8R8_SNORM_PACK32:
case VK_FORMAT_A8B8G8R8_USCALED_PACK32:
case VK_FORMAT_A8B8G8R8_SSCALED_PACK32:
case VK_FORMAT_A8B8G8R8_UINT_PACK32:
case VK_FORMAT_A8B8G8R8_SINT_PACK32:
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
return 32;
case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
case VK_FORMAT_A2R10G10B10_SNORM_PACK32:
case VK_FORMAT_A2R10G10B10_USCALED_PACK32:
case VK_FORMAT_A2R10G10B10_SSCALED_PACK32:
case VK_FORMAT_A2R10G10B10_UINT_PACK32:
case VK_FORMAT_A2R10G10B10_SINT_PACK32:
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
case VK_FORMAT_A2B10G10R10_SNORM_PACK32:
case VK_FORMAT_A2B10G10R10_USCALED_PACK32:
case VK_FORMAT_A2B10G10R10_SSCALED_PACK32:
case VK_FORMAT_A2B10G10R10_UINT_PACK32:
case VK_FORMAT_A2B10G10R10_SINT_PACK32:
return 32;
case VK_FORMAT_R16_UNORM:
case VK_FORMAT_R16_SNORM:
case VK_FORMAT_R16_USCALED:
case VK_FORMAT_R16_SSCALED:
case VK_FORMAT_R16_UINT:
case VK_FORMAT_R16_SINT:
case VK_FORMAT_R16_SFLOAT:
return 16;
case VK_FORMAT_R16G16_UNORM:
case VK_FORMAT_R16G16_SNORM:
case VK_FORMAT_R16G16_USCALED:
case VK_FORMAT_R16G16_SSCALED:
case VK_FORMAT_R16G16_UINT:
case VK_FORMAT_R16G16_SINT:
case VK_FORMAT_R16G16_SFLOAT:
return 32;
case VK_FORMAT_R16G16B16_UNORM:
case VK_FORMAT_R16G16B16_SNORM:
case VK_FORMAT_R16G16B16_USCALED:
case VK_FORMAT_R16G16B16_SSCALED:
case VK_FORMAT_R16G16B16_UINT:
case VK_FORMAT_R16G16B16_SINT:
case VK_FORMAT_R16G16B16_SFLOAT:
return 48;
case VK_FORMAT_R16G16B16A16_UNORM:
case VK_FORMAT_R16G16B16A16_SNORM:
case VK_FORMAT_R16G16B16A16_USCALED:
case VK_FORMAT_R16G16B16A16_SSCALED:
case VK_FORMAT_R16G16B16A16_UINT:
case VK_FORMAT_R16G16B16A16_SINT:
case VK_FORMAT_R16G16B16A16_SFLOAT:
return 64;
case VK_FORMAT_R32_UINT:
case VK_FORMAT_R32_SINT:
case VK_FORMAT_R32_SFLOAT:
return 32;
case VK_FORMAT_R32G32_UINT:
case VK_FORMAT_R32G32_SINT:
case VK_FORMAT_R32G32_SFLOAT:
return 64;
case VK_FORMAT_R32G32B32_UINT:
case VK_FORMAT_R32G32B32_SINT:
case VK_FORMAT_R32G32B32_SFLOAT:
return 96;
case VK_FORMAT_R32G32B32A32_UINT:
case VK_FORMAT_R32G32B32A32_SINT:
case VK_FORMAT_R32G32B32A32_SFLOAT:
return 128;
case VK_FORMAT_R64_UINT:
case VK_FORMAT_R64_SINT:
case VK_FORMAT_R64_SFLOAT:
return 64;
case VK_FORMAT_R64G64_UINT:
case VK_FORMAT_R64G64_SINT:
case VK_FORMAT_R64G64_SFLOAT:
return 128;
case VK_FORMAT_R64G64B64_UINT:
case VK_FORMAT_R64G64B64_SINT:
case VK_FORMAT_R64G64B64_SFLOAT:
return 192;
case VK_FORMAT_R64G64B64A64_UINT:
case VK_FORMAT_R64G64B64A64_SINT:
case VK_FORMAT_R64G64B64A64_SFLOAT:
return 256;
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
return 32;
case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32:
return 32;
case VK_FORMAT_D16_UNORM:
return 16;
case VK_FORMAT_X8_D24_UNORM_PACK32:
return 32;
case VK_FORMAT_D32_SFLOAT:
return 32;
case VK_FORMAT_S8_UINT:
return 8;
case VK_FORMAT_D16_UNORM_S8_UINT:
return 24;
case VK_FORMAT_D24_UNORM_S8_UINT:
return 32;
case VK_FORMAT_D32_SFLOAT_S8_UINT:
return 40;
case VK_FORMAT_UNDEFINED:
default:
return -1;
}
}
VkShaderModule load_shader(const std::string &filename, VkDevice device, VkShaderStageFlagBits stage)
{
auto spirv = vkb::fs::read_shader_binary_u32(filename);
VkShaderModule shader_module;
VkShaderModuleCreateInfo module_create_info{};
module_create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
module_create_info.codeSize = spirv.size() * sizeof(uint32_t);
module_create_info.pCode = spirv.data();
VK_CHECK(vkCreateShaderModule(device, &module_create_info, NULL, &shader_module));
return shader_module;
}
VkAccessFlags getAccessFlags(VkImageLayout layout)
{
switch (layout)
{
case VK_IMAGE_LAYOUT_UNDEFINED:
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
return 0;
case VK_IMAGE_LAYOUT_PREINITIALIZED:
return VK_ACCESS_HOST_WRITE_BIT;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
return VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL:
return VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
case VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR:
return VK_ACCESS_FRAGMENT_SHADING_RATE_ATTACHMENT_READ_BIT_KHR;
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
return VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
return VK_ACCESS_TRANSFER_READ_BIT;
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
return VK_ACCESS_TRANSFER_WRITE_BIT;
case VK_IMAGE_LAYOUT_GENERAL:
assert(false && "Don't know how to get a meaningful VkAccessFlags for VK_IMAGE_LAYOUT_GENERAL! Don't use it!");
return 0;
default:
assert(false);
return 0;
}
}
VkPipelineStageFlags getPipelineStageFlags(VkImageLayout layout)
{
switch (layout)
{
case VK_IMAGE_LAYOUT_UNDEFINED:
return VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
case VK_IMAGE_LAYOUT_PREINITIALIZED:
return VK_PIPELINE_STAGE_HOST_BIT;
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
return VK_PIPELINE_STAGE_TRANSFER_BIT;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
return VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL:
return VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
case VK_IMAGE_LAYOUT_FRAGMENT_SHADING_RATE_ATTACHMENT_OPTIMAL_KHR:
return VK_PIPELINE_STAGE_FRAGMENT_SHADING_RATE_ATTACHMENT_BIT_KHR;
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
return VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
return VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
case VK_IMAGE_LAYOUT_GENERAL:
assert(false && "Don't know how to get a meaningful VkPipelineStageFlags for VK_IMAGE_LAYOUT_GENERAL! Don't use it!");
return 0;
default:
assert(false);
return 0;
}
}
// Create an image memory barrier for changing the layout of
// an image and put it into an active command buffer
// See chapter 12.4 "Image Layout" for details
void image_layout_transition(VkCommandBuffer command_buffer,
VkImage image,
VkPipelineStageFlags src_stage_mask,
VkPipelineStageFlags dst_stage_mask,
VkAccessFlags src_access_mask,
VkAccessFlags dst_access_mask,
VkImageLayout old_layout,
VkImageLayout new_layout,
VkImageSubresourceRange const &subresource_range)
{
// Create an image barrier object
VkImageMemoryBarrier image_memory_barrier{};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.srcAccessMask = src_access_mask;
image_memory_barrier.dstAccessMask = dst_access_mask;
image_memory_barrier.oldLayout = old_layout;
image_memory_barrier.newLayout = new_layout;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.image = image;
image_memory_barrier.subresourceRange = subresource_range;
// Put barrier inside setup command buffer
vkCmdPipelineBarrier(command_buffer, src_stage_mask, dst_stage_mask, 0, 0, nullptr, 0, nullptr, 1, &image_memory_barrier);
}
void image_layout_transition(VkCommandBuffer command_buffer,
VkImage image,
VkImageLayout old_layout,
VkImageLayout new_layout,
VkImageSubresourceRange const &subresource_range)
{
VkPipelineStageFlags src_stage_mask = getPipelineStageFlags(old_layout);
VkPipelineStageFlags dst_stage_mask = getPipelineStageFlags(new_layout);
VkAccessFlags src_access_mask = getAccessFlags(old_layout);
VkAccessFlags dst_access_mask = getAccessFlags(new_layout);
image_layout_transition(command_buffer, image, src_stage_mask, dst_stage_mask, src_access_mask, dst_access_mask, old_layout, new_layout, subresource_range);
}
// Fixed sub resource on first mip level and layer
void image_layout_transition(VkCommandBuffer command_buffer,
VkImage image,
VkImageLayout old_layout,
VkImageLayout new_layout)
{
VkImageSubresourceRange subresource_range = {};
subresource_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
subresource_range.baseMipLevel = 0;
subresource_range.levelCount = 1;
subresource_range.baseArrayLayer = 0;
subresource_range.layerCount = 1;
image_layout_transition(command_buffer, image, old_layout, new_layout, subresource_range);
}
void image_layout_transition(VkCommandBuffer command_buffer,
std::vector<std::pair<VkImage, VkImageSubresourceRange>> const &imagesAndRanges,
VkImageLayout old_layout,
VkImageLayout new_layout)
{
VkPipelineStageFlags src_stage_mask = getPipelineStageFlags(old_layout);
VkPipelineStageFlags dst_stage_mask = getPipelineStageFlags(new_layout);
VkAccessFlags src_access_mask = getAccessFlags(old_layout);
VkAccessFlags dst_access_mask = getAccessFlags(new_layout);
// Create image barrier objects
std::vector<VkImageMemoryBarrier> image_memory_barriers;
image_memory_barriers.reserve(imagesAndRanges.size());
for (size_t i = 0; i < imagesAndRanges.size(); i++)
{
image_memory_barriers.emplace_back(VkImageMemoryBarrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
nullptr,
src_access_mask,
dst_access_mask,
old_layout,
new_layout,
VK_QUEUE_FAMILY_IGNORED,
VK_QUEUE_FAMILY_IGNORED,
imagesAndRanges[i].first,
imagesAndRanges[i].second});
}
// Put barriers inside setup command buffer
vkCmdPipelineBarrier(command_buffer,
src_stage_mask,
dst_stage_mask,
0,
0,
nullptr,
0,
nullptr,
static_cast<uint32_t>(image_memory_barriers.size()),
image_memory_barriers.data());
}
std::vector<VkImageCompressionFixedRateFlagBitsEXT> fixed_rate_compression_flags_to_vector(VkImageCompressionFixedRateFlagsEXT flags)
{
const std::vector<VkImageCompressionFixedRateFlagBitsEXT> all_flags = {VK_IMAGE_COMPRESSION_FIXED_RATE_1BPC_BIT_EXT, VK_IMAGE_COMPRESSION_FIXED_RATE_2BPC_BIT_EXT,
VK_IMAGE_COMPRESSION_FIXED_RATE_3BPC_BIT_EXT, VK_IMAGE_COMPRESSION_FIXED_RATE_4BPC_BIT_EXT,
VK_IMAGE_COMPRESSION_FIXED_RATE_5BPC_BIT_EXT, VK_IMAGE_COMPRESSION_FIXED_RATE_6BPC_BIT_EXT,
VK_IMAGE_COMPRESSION_FIXED_RATE_7BPC_BIT_EXT, VK_IMAGE_COMPRESSION_FIXED_RATE_8BPC_BIT_EXT,
VK_IMAGE_COMPRESSION_FIXED_RATE_9BPC_BIT_EXT, VK_IMAGE_COMPRESSION_FIXED_RATE_10BPC_BIT_EXT,
VK_IMAGE_COMPRESSION_FIXED_RATE_11BPC_BIT_EXT, VK_IMAGE_COMPRESSION_FIXED_RATE_12BPC_BIT_EXT,
VK_IMAGE_COMPRESSION_FIXED_RATE_13BPC_BIT_EXT, VK_IMAGE_COMPRESSION_FIXED_RATE_14BPC_BIT_EXT,
VK_IMAGE_COMPRESSION_FIXED_RATE_15BPC_BIT_EXT, VK_IMAGE_COMPRESSION_FIXED_RATE_16BPC_BIT_EXT,
VK_IMAGE_COMPRESSION_FIXED_RATE_17BPC_BIT_EXT, VK_IMAGE_COMPRESSION_FIXED_RATE_18BPC_BIT_EXT,
VK_IMAGE_COMPRESSION_FIXED_RATE_19BPC_BIT_EXT, VK_IMAGE_COMPRESSION_FIXED_RATE_20BPC_BIT_EXT,
VK_IMAGE_COMPRESSION_FIXED_RATE_21BPC_BIT_EXT, VK_IMAGE_COMPRESSION_FIXED_RATE_22BPC_BIT_EXT,
VK_IMAGE_COMPRESSION_FIXED_RATE_23BPC_BIT_EXT, VK_IMAGE_COMPRESSION_FIXED_RATE_24BPC_BIT_EXT};
std::vector<VkImageCompressionFixedRateFlagBitsEXT> flags_vector;
for (size_t i = 0; i < all_flags.size(); i++)
{
if (all_flags[i] & flags)
{
flags_vector.push_back(all_flags[i]);
}
}
return flags_vector;
}
VkImageCompressionPropertiesEXT query_supported_fixed_rate_compression(VkPhysicalDevice gpu, const VkImageCreateInfo &create_info)
{
VkImageCompressionPropertiesEXT supported_compression_properties{VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_PROPERTIES_EXT};
VkImageCompressionControlEXT compression_control{VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT};
compression_control.flags = VK_IMAGE_COMPRESSION_FIXED_RATE_DEFAULT_EXT;
VkPhysicalDeviceImageFormatInfo2 image_format_info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2};
image_format_info.format = create_info.format;
image_format_info.type = create_info.imageType;
image_format_info.tiling = create_info.tiling;
image_format_info.usage = create_info.usage;
image_format_info.pNext = &compression_control;
VkImageFormatProperties2 image_format_properties{VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2};
image_format_properties.pNext = &supported_compression_properties;
vkGetPhysicalDeviceImageFormatProperties2KHR(gpu, &image_format_info, &image_format_properties);
return supported_compression_properties;
}
VkImageCompressionPropertiesEXT query_applied_compression(VkDevice device, VkImage image)
{
VkImageSubresource2EXT image_subresource{VK_STRUCTURE_TYPE_IMAGE_SUBRESOURCE_2_KHR};
image_subresource.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
image_subresource.imageSubresource.mipLevel = 0;
image_subresource.imageSubresource.arrayLayer = 0;
VkImageCompressionPropertiesEXT compression_properties{VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_PROPERTIES_EXT};
VkSubresourceLayout2EXT subresource_layout{VK_STRUCTURE_TYPE_SUBRESOURCE_LAYOUT_2_KHR};
subresource_layout.pNext = &compression_properties;
vkGetImageSubresourceLayout2EXT(device, image, &image_subresource, &subresource_layout);
return compression_properties;
}
VkSurfaceFormatKHR select_surface_format(VkPhysicalDevice gpu, VkSurfaceKHR surface, std::vector<VkFormat> const &preferred_formats)
{
uint32_t surface_format_count;
vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, surface, &surface_format_count, nullptr);
assert(0 < surface_format_count);
std::vector<VkSurfaceFormatKHR> supported_surface_formats(surface_format_count);
vkGetPhysicalDeviceSurfaceFormatsKHR(gpu, surface, &surface_format_count, supported_surface_formats.data());
auto it = std::ranges::find_if(supported_surface_formats,
[&preferred_formats](VkSurfaceFormatKHR surface_format) {
return std::ranges::any_of(preferred_formats,
[&surface_format](VkFormat format) { return format == surface_format.format; });
});
// We use the first supported format as a fallback in case none of the preferred formats is available
return it != supported_surface_formats.end() ? *it : supported_surface_formats[0];
}
namespace gbuffer
{
std::vector<LoadStoreInfo> get_load_all_store_swapchain()
{
// Load every attachment and store only swapchain
std::vector<LoadStoreInfo> load_store{4};
// Swapchain
load_store[0].load_op = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
load_store[0].store_op = VK_ATTACHMENT_STORE_OP_STORE;
// Depth
load_store[1].load_op = VK_ATTACHMENT_LOAD_OP_LOAD;
load_store[1].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
// Albedo
load_store[2].load_op = VK_ATTACHMENT_LOAD_OP_LOAD;
load_store[2].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
// Normal
load_store[3].load_op = VK_ATTACHMENT_LOAD_OP_LOAD;
load_store[3].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
return load_store;
}
std::vector<LoadStoreInfo> get_clear_all_store_swapchain()
{
// Clear every attachment and store only swapchain
std::vector<LoadStoreInfo> load_store{4};
// Swapchain
load_store[0].load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
load_store[0].store_op = VK_ATTACHMENT_STORE_OP_STORE;
// Depth
load_store[1].load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
load_store[1].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
// Albedo
load_store[2].load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
load_store[2].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
// Normal
load_store[3].load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
load_store[3].store_op = VK_ATTACHMENT_STORE_OP_DONT_CARE;
return load_store;
}
std::vector<LoadStoreInfo> get_clear_store_all()
{
// Clear and store every attachment
std::vector<LoadStoreInfo> load_store{4};
// Swapchain
load_store[0].load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
load_store[0].store_op = VK_ATTACHMENT_STORE_OP_STORE;
// Depth
load_store[1].load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
load_store[1].store_op = VK_ATTACHMENT_STORE_OP_STORE;
// Albedo
load_store[2].load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
load_store[2].store_op = VK_ATTACHMENT_STORE_OP_STORE;
// Normal
load_store[3].load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
load_store[3].store_op = VK_ATTACHMENT_STORE_OP_STORE;
return load_store;
}
std::vector<VkClearValue> get_clear_value()
{
// Clear values
std::vector<VkClearValue> clear_value{4};
clear_value[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}};
clear_value[1].depthStencil = {0.0f, ~0U};
clear_value[2].color = {{0.0f, 0.0f, 0.0f, 1.0f}};
clear_value[3].color = {{0.0f, 0.0f, 0.0f, 1.0f}};
return clear_value;
}
} // namespace gbuffer
uint32_t get_queue_family_index(std::vector<VkQueueFamilyProperties> const &queue_family_properties, VkQueueFlagBits queue_flag)
{
// Dedicated queue for compute
// Try to find a queue family index that supports compute but not graphics
if (queue_flag & VK_QUEUE_COMPUTE_BIT)
{
auto propertyIt = std::ranges::find_if(queue_family_properties,
[queue_flag](const VkQueueFamilyProperties &property) { return (property.queueFlags & queue_flag) && !(property.queueFlags & VK_QUEUE_GRAPHICS_BIT); });
if (propertyIt != queue_family_properties.end())
{
return static_cast<uint32_t>(std::distance(queue_family_properties.begin(), propertyIt));
}
}
// Dedicated queue for transfer
// Try to find a queue family index that supports transfer but not graphics and compute
if (queue_flag & VK_QUEUE_TRANSFER_BIT)
{
auto propertyIt = std::ranges::find_if(queue_family_properties,
[queue_flag](const VkQueueFamilyProperties &property) {
return (property.queueFlags & queue_flag) && !(property.queueFlags & VK_QUEUE_GRAPHICS_BIT) &&
!(property.queueFlags & VK_QUEUE_COMPUTE_BIT);
});
if (propertyIt != queue_family_properties.end())
{
return static_cast<uint32_t>(std::distance(queue_family_properties.begin(), propertyIt));
}
}
// For other queue types or if no separate compute queue is present, return the first one to support the requested flags
auto propertyIt = std::ranges::find_if(
queue_family_properties, [queue_flag](const VkQueueFamilyProperties &property) { return (property.queueFlags & queue_flag) == queue_flag; });
if (propertyIt != queue_family_properties.end())
{
return static_cast<uint32_t>(std::distance(queue_family_properties.begin(), propertyIt));
}
throw std::runtime_error("Could not find a matching queue family index");
}
} // namespace vkb
+311
View File
@@ -0,0 +1,311 @@
/* Copyright (c) 2018-2025, Arm Limited and Contributors
* Copyright (c) 2019-2025, Sascha Willems
* Copyright (c) 2024-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 <cstdio>
#include <map>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
#include "common/error.h"
#include <vk_mem_alloc.h>
#include <volk.h>
#define VK_FLAGS_NONE 0 // Custom define for better code readability
#define DEFAULT_FENCE_TIMEOUT 100000000000 // Default fence timeout in nanoseconds
template <class T>
using ShaderStageMap = std::map<VkShaderStageFlagBits, T>;
template <class T>
using BindingMap = std::map<uint32_t, std::map<uint32_t, T>>;
namespace vkb
{
enum class BindingType
{
C,
Cpp
};
enum class CommandBufferResetMode
{
ResetPool,
ResetIndividually,
AlwaysAllocate,
};
/**
* @brief Helper function to determine if a Vulkan format is depth only.
* @param format Vulkan format to check.
* @return True if format is a depth only, false otherwise.
*/
bool is_depth_only_format(VkFormat format);
/**
* @brief Helper function to determine if a Vulkan format is depth with stencil.
* @param format Vulkan format to check.
* @return True if format is a depth with stencil, false otherwise.
*/
bool is_depth_stencil_format(VkFormat format);
/**
* @brief Helper function to determine if a Vulkan format is depth.
* @param format Vulkan format to check.
* @return True if format is a depth, false otherwise.
*/
bool is_depth_format(VkFormat format);
/**
* @brief Helper function to determine a suitable supported depth format based on a priority list
* @param physical_device The physical device to check the depth formats against
* @param depth_only (Optional) Wether to include the stencil component in the format or not
* @param depth_format_priority_list (Optional) The list of depth formats to prefer over one another
* By default we start with the highest precision packed format
* @return The valid suited depth format
*/
VkFormat get_suitable_depth_format(VkPhysicalDevice physical_device,
bool depth_only = false,
const std::vector<VkFormat> &depth_format_priority_list = {
VK_FORMAT_D32_SFLOAT,
VK_FORMAT_D24_UNORM_S8_UINT,
VK_FORMAT_D16_UNORM});
/**
* @brief Helper function to pick a blendable format from a priority ordered list
* @param physical_device The physical device to check the formats against
* @param format_priority_list List of formats in order of priority
* @return The selected format
*/
VkFormat choose_blendable_format(VkPhysicalDevice physical_device, const std::vector<VkFormat> &format_priority_list);
/**
* @brief Helper function to check support for linear filtering and adjust its parameters if required
* @param physical_device The physical device to check the depth formats against
* @param format The format to check against
* @param filter The preferred filter to adjust
* @param mipmapMode (Optional) The preferred mipmap mode to adjust
*/
void make_filters_valid(VkPhysicalDevice physical_device, VkFormat format, VkFilter *filter, VkSamplerMipmapMode *mipmapMode = nullptr);
/**
* @brief Helper function to determine if a Vulkan descriptor type is a dynamic storage buffer or dynamic uniform buffer.
* @param descriptor_type Vulkan descriptor type to check.
* @return True if type is dynamic buffer, false otherwise.
*/
bool is_dynamic_buffer_descriptor_type(VkDescriptorType descriptor_type);
/**
* @brief Helper function to determine if a Vulkan descriptor type is a buffer (either uniform or storage buffer, dynamic or not).
* @param descriptor_type Vulkan descriptor type to check.
* @return True if type is buffer, false otherwise.
*/
bool is_buffer_descriptor_type(VkDescriptorType descriptor_type);
/**
* @brief Helper function to get the bits per pixel of a Vulkan format.
* @param format Vulkan format to check.
* @return The bits per pixel of the given format, -1 for invalid formats.
*/
int32_t get_bits_per_pixel(VkFormat format);
enum class ShadingLanguage
{
GLSL,
HLSL,
SLANG,
};
/**
* @brief Helper function to create a VkShaderModule from a SPIR-V shader file
* @param filename The shader location
* @param device The logical device
* @param stage The shader stage
* @return The shader module containing the loaded shader
*/
VkShaderModule load_shader(const std::string &filename, VkDevice device, VkShaderStageFlagBits stage);
/**
* @brief Helper function to select a VkSurfaceFormatKHR
* @param gpu The VkPhysicalDevice to select a format for.
* @param surface The VkSurfaceKHR to select a format for.
* @param preferred_formats List of preferred VkFormats to use.
* @return The preferred VkSurfaceFormatKHR.
*/
VkSurfaceFormatKHR select_surface_format(VkPhysicalDevice gpu,
VkSurfaceKHR surface,
std::vector<VkFormat> const &preferred_formats = {
VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_A8B8G8R8_SRGB_PACK32});
/**
* @brief Image memory barrier structure used to define
* memory access for an image view during command recording.
*/
struct ImageMemoryBarrier
{
VkPipelineStageFlags src_stage_mask{VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT};
VkPipelineStageFlags dst_stage_mask{VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT};
VkAccessFlags src_access_mask{0};
VkAccessFlags dst_access_mask{0};
VkImageLayout old_layout{VK_IMAGE_LAYOUT_UNDEFINED};
VkImageLayout new_layout{VK_IMAGE_LAYOUT_UNDEFINED};
uint32_t src_queue_family{VK_QUEUE_FAMILY_IGNORED};
uint32_t dst_queue_family{VK_QUEUE_FAMILY_IGNORED};
};
/**
* @brief Buffer memory barrier structure used to define
* memory access for a buffer during command recording.
*/
struct BufferMemoryBarrier
{
VkPipelineStageFlags src_stage_mask{VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT};
VkPipelineStageFlags dst_stage_mask{VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT};
VkAccessFlags src_access_mask{0};
VkAccessFlags dst_access_mask{0};
};
/**
* @brief Put an image memory barrier for a layout transition of an image, using explicitly give transition parameters.
* @param command_buffer The VkCommandBuffer to record the barrier.
* @param image The VkImage to transition.
* @param src_stage_mask The VkPipelineStageFlags to use as source.
* @param dst_stage_mask The VkPipelineStageFlags to use as destination.
* @param src_access_mask The VkAccessFlags to use as source.
* @param dst_access_mask The VkAccessFlags to use as destination.
* @param old_layout The VkImageLayout to transition from.
* @param new_layout The VkImageLayout to transition to.
* @param subresource_range The VkImageSubresourceRange to use with the transition.
*/
void image_layout_transition(VkCommandBuffer command_buffer,
VkImage image,
VkPipelineStageFlags src_stage_mask,
VkPipelineStageFlags dst_stage_mask,
VkAccessFlags src_access_mask,
VkAccessFlags dst_access_mask,
VkImageLayout old_layout,
VkImageLayout new_layout,
VkImageSubresourceRange const &subresource_range);
/**
* @brief Put an image memory barrier for a layout transition of an image, on a given subresource range.
*
* The src_stage_mask, dst_stage_mask, src_access_mask, and dst_access_mask used are determined from old_layout and new_layout.
*
* @param command_buffer The VkCommandBuffer to record the barrier.
* @param image The VkImage to transition.
* @param old_layout The VkImageLayout to transition from.
* @param new_layout The VkImageLayout to transition to.
* @param subresource_range The VkImageSubresourceRange to use with the transition.
*/
void image_layout_transition(VkCommandBuffer command_buffer,
VkImage image,
VkImageLayout old_layout,
VkImageLayout new_layout,
VkImageSubresourceRange const &subresource_range);
/**
* @brief Put an image memory barrier for a layout transition of an image, on a fixed subresource with first mip level and layer.
*
* The src_stage_mask, dst_stage_mask, src_access_mask, and dst_access_mask used are determined from old_layout and new_layout.
*
* @param command_buffer The VkCommandBuffer to record the barrier.
* @param image The VkImage to transition.
* @param old_layout The VkImageLayout to transition from.
* @param new_layout The VkImageLayout to transition to.
*/
void image_layout_transition(VkCommandBuffer command_buffer,
VkImage image,
VkImageLayout old_layout,
VkImageLayout new_layout);
/**
* @brief Put an image memory barrier for a layout transition of a vector of images, with a given subresource range per image.
*
* The src_stage_mask, dst_stage_mask, src_access_mask, and dst_access_mask used are determined from old_layout and new_layout.
*
* @param command_buffer The VkCommandBuffer to record the barrier.
* @param imagesAndRanges The images to transition, with accompanying subresource ranges.
* @param old_layout The VkImageLayout to transition from.
* @param new_layout The VkImageLayout to transition to.
*/
void image_layout_transition(VkCommandBuffer command_buffer,
std::vector<std::pair<VkImage, VkImageSubresourceRange>> const &imagesAndRanges,
VkImageLayout old_layout,
VkImageLayout new_layout);
/**
* @brief Helper functions for compression controls
*/
std::vector<VkImageCompressionFixedRateFlagBitsEXT> fixed_rate_compression_flags_to_vector(VkImageCompressionFixedRateFlagsEXT flags);
VkImageCompressionPropertiesEXT query_supported_fixed_rate_compression(VkPhysicalDevice gpu, const VkImageCreateInfo &create_info);
VkImageCompressionPropertiesEXT query_applied_compression(VkDevice device, VkImage image);
/**
* @brief Load and store info for a render pass attachment.
*/
struct LoadStoreInfo
{
VkAttachmentLoadOp load_op = VK_ATTACHMENT_LOAD_OP_CLEAR;
VkAttachmentStoreOp store_op = VK_ATTACHMENT_STORE_OP_STORE;
};
namespace gbuffer
{
/**
* @return Load store info to load all and store only the swapchain
*/
std::vector<LoadStoreInfo> get_load_all_store_swapchain();
/**
* @return Load store info to clear all and store only the swapchain
*/
std::vector<LoadStoreInfo> get_clear_all_store_swapchain();
/**
* @return Load store info to clear and store all images
*/
std::vector<LoadStoreInfo> get_clear_store_all();
/**
* @return Default clear values for the G-buffer
*/
std::vector<VkClearValue> get_clear_value();
} // namespace gbuffer
uint32_t get_queue_family_index(std::vector<VkQueueFamilyProperties> const &queue_family_properties, VkQueueFlagBits queue_flag);
} // namespace vkb
+667
View File
@@ -0,0 +1,667 @@
/* Copyright (c) 2019-2024, Sascha Willems
*
* 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 "volk.h"
#include <vector>
namespace vkb
{
namespace initializers
{
inline VkMemoryAllocateInfo memory_allocate_info()
{
VkMemoryAllocateInfo memory_allocation{};
memory_allocation.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
return memory_allocation;
}
inline VkMappedMemoryRange mapped_memory_range()
{
VkMappedMemoryRange mapped_memory_range{};
mapped_memory_range.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
return mapped_memory_range;
}
inline VkCommandBufferAllocateInfo command_buffer_allocate_info(
VkCommandPool command_pool,
VkCommandBufferLevel level,
uint32_t buffer_count)
{
VkCommandBufferAllocateInfo command_buffer_allocate_info{};
command_buffer_allocate_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
command_buffer_allocate_info.commandPool = command_pool;
command_buffer_allocate_info.level = level;
command_buffer_allocate_info.commandBufferCount = buffer_count;
return command_buffer_allocate_info;
}
inline VkCommandPoolCreateInfo command_pool_create_info()
{
VkCommandPoolCreateInfo command_pool_create_info{};
command_pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
return command_pool_create_info;
}
inline VkCommandBufferBeginInfo command_buffer_begin_info()
{
VkCommandBufferBeginInfo cmdBufferBeginInfo{};
cmdBufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
return cmdBufferBeginInfo;
}
inline VkCommandBufferInheritanceInfo command_buffer_inheritance_info()
{
VkCommandBufferInheritanceInfo command_buffer_inheritance_info{};
command_buffer_inheritance_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
return command_buffer_inheritance_info;
}
inline VkRenderPassBeginInfo render_pass_begin_info()
{
VkRenderPassBeginInfo render_pass_begin_info{};
render_pass_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
return render_pass_begin_info;
}
inline VkRenderPassCreateInfo render_pass_create_info()
{
VkRenderPassCreateInfo render_pass_create_info{};
render_pass_create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
return render_pass_create_info;
}
/** @brief Initialize rendering_attachment_info */
inline VkRenderingAttachmentInfoKHR rendering_attachment_info()
{
VkRenderingAttachmentInfoKHR attachment_info{};
attachment_info.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;
attachment_info.pNext = VK_NULL_HANDLE;
return attachment_info;
}
/** @brief Initialize VkRenderingInfoKHR, e.g. for use with dynamic rendering extension */
inline VkRenderingInfoKHR rendering_info(VkRect2D render_area = {},
uint32_t color_attachment_count = 0,
const VkRenderingAttachmentInfoKHR *pColorAttachments = VK_NULL_HANDLE,
VkRenderingFlagsKHR flags = 0)
{
VkRenderingInfoKHR rendering_info = {};
rendering_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO_KHR;
rendering_info.pNext = VK_NULL_HANDLE;
rendering_info.flags = flags;
rendering_info.renderArea = render_area;
rendering_info.layerCount = 0;
rendering_info.viewMask = 0;
rendering_info.colorAttachmentCount = color_attachment_count;
rendering_info.pColorAttachments = pColorAttachments;
rendering_info.pDepthAttachment = VK_NULL_HANDLE;
rendering_info.pStencilAttachment = VK_NULL_HANDLE;
return rendering_info;
}
/** @brief Initialize an image memory barrier with no image transfer ownership */
inline VkImageMemoryBarrier image_memory_barrier()
{
VkImageMemoryBarrier image_memory_barrier{};
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
return image_memory_barrier;
}
/** @brief Initialize a buffer memory barrier with no image transfer ownership */
inline VkBufferMemoryBarrier buffer_memory_barrier()
{
VkBufferMemoryBarrier buffer_memory_barrier{};
buffer_memory_barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
buffer_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
buffer_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
return buffer_memory_barrier;
}
inline VkMemoryBarrier memory_barrier()
{
VkMemoryBarrier memory_barrier{};
memory_barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
return memory_barrier;
}
inline VkImageCreateInfo image_create_info()
{
VkImageCreateInfo image_create_info{};
image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
return image_create_info;
}
inline VkSamplerCreateInfo sampler_create_info()
{
VkSamplerCreateInfo sampler_create_info{};
sampler_create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
sampler_create_info.maxAnisotropy = 1.0f;
return sampler_create_info;
}
inline VkImageViewCreateInfo image_view_create_info()
{
VkImageViewCreateInfo image_view_create_info{};
image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
return image_view_create_info;
}
inline VkFramebufferCreateInfo framebuffer_create_info()
{
VkFramebufferCreateInfo framebuffer_create_info{};
framebuffer_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
return framebuffer_create_info;
}
inline VkSemaphoreCreateInfo semaphore_create_info()
{
VkSemaphoreCreateInfo semaphore_create_info{};
semaphore_create_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
return semaphore_create_info;
}
inline VkFenceCreateInfo fence_create_info(VkFenceCreateFlags flags = 0)
{
VkFenceCreateInfo fence_create_info{};
fence_create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_create_info.flags = flags;
return fence_create_info;
}
inline VkEventCreateInfo event_create_info()
{
VkEventCreateInfo event_create_info{};
event_create_info.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO;
return event_create_info;
}
inline VkSubmitInfo submit_info()
{
VkSubmitInfo submit_info{};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
return submit_info;
}
inline VkViewport viewport(
float width,
float height,
float min_depth,
float max_depth)
{
VkViewport viewport{};
viewport.width = width;
viewport.height = height;
viewport.minDepth = min_depth;
viewport.maxDepth = max_depth;
return viewport;
}
inline VkRect2D rect2D(
int32_t width,
int32_t height,
int32_t offset_x,
int32_t offset_y)
{
VkRect2D rect2D{};
rect2D.extent.width = width;
rect2D.extent.height = height;
rect2D.offset.x = offset_x;
rect2D.offset.y = offset_y;
return rect2D;
}
inline VkBufferCreateInfo buffer_create_info()
{
VkBufferCreateInfo buffer_create_info{};
buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
return buffer_create_info;
}
inline VkBufferCreateInfo buffer_create_info(
VkBufferUsageFlags usage,
VkDeviceSize size)
{
VkBufferCreateInfo buffer_create_info{};
buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buffer_create_info.usage = usage;
buffer_create_info.size = size;
return buffer_create_info;
}
inline VkDescriptorPoolCreateInfo descriptor_pool_create_info(
uint32_t count,
VkDescriptorPoolSize *pool_sizes,
uint32_t max_sets)
{
VkDescriptorPoolCreateInfo descriptor_pool_info{};
descriptor_pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
descriptor_pool_info.poolSizeCount = count;
descriptor_pool_info.pPoolSizes = pool_sizes;
descriptor_pool_info.maxSets = max_sets;
return descriptor_pool_info;
}
inline VkDescriptorPoolCreateInfo descriptor_pool_create_info(
const std::vector<VkDescriptorPoolSize> &pool_sizes,
uint32_t max_sets)
{
VkDescriptorPoolCreateInfo descriptor_pool_info{};
descriptor_pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
descriptor_pool_info.poolSizeCount = static_cast<uint32_t>(pool_sizes.size());
descriptor_pool_info.pPoolSizes = pool_sizes.data();
descriptor_pool_info.maxSets = max_sets;
return descriptor_pool_info;
}
inline VkDescriptorPoolSize descriptor_pool_size(
VkDescriptorType type,
uint32_t count)
{
VkDescriptorPoolSize descriptor_pool_size{};
descriptor_pool_size.type = type;
descriptor_pool_size.descriptorCount = count;
return descriptor_pool_size;
}
inline VkDescriptorSetLayoutBinding descriptor_set_layout_binding(
VkDescriptorType type,
VkShaderStageFlags flags,
uint32_t binding,
uint32_t count = 1)
{
VkDescriptorSetLayoutBinding set_layout_binding{};
set_layout_binding.descriptorType = type;
set_layout_binding.stageFlags = flags;
set_layout_binding.binding = binding;
set_layout_binding.descriptorCount = count;
return set_layout_binding;
}
inline VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info(
const VkDescriptorSetLayoutBinding *bindings,
uint32_t binding_count)
{
VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info{};
descriptor_set_layout_create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptor_set_layout_create_info.pBindings = bindings;
descriptor_set_layout_create_info.bindingCount = binding_count;
return descriptor_set_layout_create_info;
}
inline VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info(
const std::vector<VkDescriptorSetLayoutBinding> &bindings)
{
VkDescriptorSetLayoutCreateInfo descriptor_set_layout_create_info{};
descriptor_set_layout_create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
descriptor_set_layout_create_info.pBindings = bindings.data();
descriptor_set_layout_create_info.bindingCount = static_cast<uint32_t>(bindings.size());
return descriptor_set_layout_create_info;
}
inline VkPipelineLayoutCreateInfo pipeline_layout_create_info(
const VkDescriptorSetLayout *set_layouts,
uint32_t set_layout_count = 1)
{
VkPipelineLayoutCreateInfo pipeline_layout_create_info{};
pipeline_layout_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipeline_layout_create_info.setLayoutCount = set_layout_count;
pipeline_layout_create_info.pSetLayouts = set_layouts;
return pipeline_layout_create_info;
}
inline VkPipelineLayoutCreateInfo pipeline_layout_create_info(
uint32_t set_layout_count = 1)
{
VkPipelineLayoutCreateInfo pipeline_layout_create_info{};
pipeline_layout_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipeline_layout_create_info.setLayoutCount = set_layout_count;
return pipeline_layout_create_info;
}
inline VkDescriptorSetAllocateInfo descriptor_set_allocate_info(
VkDescriptorPool descriptor_pool,
const VkDescriptorSetLayout *set_layouts,
uint32_t descriptor_set_count)
{
VkDescriptorSetAllocateInfo descriptor_set_allocate_info{};
descriptor_set_allocate_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
descriptor_set_allocate_info.descriptorPool = descriptor_pool;
descriptor_set_allocate_info.pSetLayouts = set_layouts;
descriptor_set_allocate_info.descriptorSetCount = descriptor_set_count;
return descriptor_set_allocate_info;
}
inline VkDescriptorImageInfo descriptor_image_info(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout)
{
VkDescriptorImageInfo descriptor_image_info{};
descriptor_image_info.sampler = sampler;
descriptor_image_info.imageView = image_view;
descriptor_image_info.imageLayout = image_layout;
return descriptor_image_info;
}
inline VkWriteDescriptorSet write_descriptor_set(
VkDescriptorSet dst_set,
VkDescriptorType type,
uint32_t binding,
VkDescriptorBufferInfo *buffer_info,
uint32_t descriptor_count = 1)
{
VkWriteDescriptorSet write_descriptor_set{};
write_descriptor_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write_descriptor_set.dstSet = dst_set;
write_descriptor_set.descriptorType = type;
write_descriptor_set.dstBinding = binding;
write_descriptor_set.pBufferInfo = buffer_info;
write_descriptor_set.descriptorCount = descriptor_count;
return write_descriptor_set;
}
inline VkWriteDescriptorSet write_descriptor_set(
VkDescriptorSet dst_set,
VkDescriptorType type,
uint32_t binding,
VkDescriptorImageInfo *image_info,
uint32_t descriptor_count = 1)
{
VkWriteDescriptorSet write_descriptor_set{};
write_descriptor_set.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write_descriptor_set.dstSet = dst_set;
write_descriptor_set.descriptorType = type;
write_descriptor_set.dstBinding = binding;
write_descriptor_set.pImageInfo = image_info;
write_descriptor_set.descriptorCount = descriptor_count;
return write_descriptor_set;
}
inline VkVertexInputBindingDescription vertex_input_binding_description(
uint32_t binding,
uint32_t stride,
VkVertexInputRate input_rate)
{
VkVertexInputBindingDescription vertex_input_binding_description{};
vertex_input_binding_description.binding = binding;
vertex_input_binding_description.stride = stride;
vertex_input_binding_description.inputRate = input_rate;
return vertex_input_binding_description;
}
inline VkVertexInputBindingDescription2EXT vertex_input_binding_description2ext(
uint32_t binding,
uint32_t stride,
VkVertexInputRate input_rate,
uint32_t divisor)
{
VkVertexInputBindingDescription2EXT vertex_input_binding_description2ext{};
vertex_input_binding_description2ext.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_BINDING_DESCRIPTION_2_EXT;
vertex_input_binding_description2ext.binding = binding;
vertex_input_binding_description2ext.stride = stride;
vertex_input_binding_description2ext.inputRate = input_rate;
vertex_input_binding_description2ext.divisor = divisor;
return vertex_input_binding_description2ext;
}
inline VkVertexInputAttributeDescription vertex_input_attribute_description(
uint32_t binding,
uint32_t location,
VkFormat format,
uint32_t offset)
{
VkVertexInputAttributeDescription vertex_input_attribute_description{};
vertex_input_attribute_description.location = location;
vertex_input_attribute_description.binding = binding;
vertex_input_attribute_description.format = format;
vertex_input_attribute_description.offset = offset;
return vertex_input_attribute_description;
}
inline VkVertexInputAttributeDescription2EXT vertex_input_attribute_description2ext(
uint32_t binding,
uint32_t location,
VkFormat format,
uint32_t offset)
{
VkVertexInputAttributeDescription2EXT vertex_input_attribute_description2ext{};
vertex_input_attribute_description2ext.sType = VK_STRUCTURE_TYPE_VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_2_EXT;
vertex_input_attribute_description2ext.location = location;
vertex_input_attribute_description2ext.binding = binding;
vertex_input_attribute_description2ext.format = format;
vertex_input_attribute_description2ext.offset = offset;
return vertex_input_attribute_description2ext;
}
inline VkPipelineVertexInputStateCreateInfo pipeline_vertex_input_state_create_info()
{
VkPipelineVertexInputStateCreateInfo pipeline_vertex_input_state_create_info{};
pipeline_vertex_input_state_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
return pipeline_vertex_input_state_create_info;
}
inline VkPipelineInputAssemblyStateCreateInfo pipeline_input_assembly_state_create_info(
VkPrimitiveTopology topology,
VkPipelineInputAssemblyStateCreateFlags flags,
VkBool32 primitive_restart_enable)
{
VkPipelineInputAssemblyStateCreateInfo pipeline_input_assembly_state_create_info{};
pipeline_input_assembly_state_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
pipeline_input_assembly_state_create_info.topology = topology;
pipeline_input_assembly_state_create_info.flags = flags;
pipeline_input_assembly_state_create_info.primitiveRestartEnable = primitive_restart_enable;
return pipeline_input_assembly_state_create_info;
}
inline VkPipelineRasterizationStateCreateInfo pipeline_rasterization_state_create_info(
VkPolygonMode polygon_mode,
VkCullModeFlags cull_mode,
VkFrontFace front_face,
VkPipelineRasterizationStateCreateFlags flags = 0)
{
VkPipelineRasterizationStateCreateInfo pipeline_rasterization_state_create_info{};
pipeline_rasterization_state_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
pipeline_rasterization_state_create_info.polygonMode = polygon_mode;
pipeline_rasterization_state_create_info.cullMode = cull_mode;
pipeline_rasterization_state_create_info.frontFace = front_face;
pipeline_rasterization_state_create_info.flags = flags;
pipeline_rasterization_state_create_info.depthClampEnable = VK_FALSE;
pipeline_rasterization_state_create_info.lineWidth = 1.0f;
return pipeline_rasterization_state_create_info;
}
inline VkPipelineColorBlendAttachmentState pipeline_color_blend_attachment_state(
VkColorComponentFlags color_write_mask,
VkBool32 blend_enable)
{
VkPipelineColorBlendAttachmentState pipeline_color_blend_attachment_state{};
pipeline_color_blend_attachment_state.colorWriteMask = color_write_mask;
pipeline_color_blend_attachment_state.blendEnable = blend_enable;
return pipeline_color_blend_attachment_state;
}
inline VkPipelineColorBlendStateCreateInfo pipeline_color_blend_state_create_info(
uint32_t attachment_count,
const VkPipelineColorBlendAttachmentState *attachments)
{
VkPipelineColorBlendStateCreateInfo pipeline_color_blend_state_create_info{};
pipeline_color_blend_state_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
pipeline_color_blend_state_create_info.attachmentCount = attachment_count;
pipeline_color_blend_state_create_info.pAttachments = attachments;
return pipeline_color_blend_state_create_info;
}
inline VkPipelineDepthStencilStateCreateInfo pipeline_depth_stencil_state_create_info(
VkBool32 depth_test_enable,
VkBool32 depth_write_enable,
VkCompareOp depth_compare_op)
{
VkPipelineDepthStencilStateCreateInfo pipeline_depth_stencil_state_create_info{};
pipeline_depth_stencil_state_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
pipeline_depth_stencil_state_create_info.depthTestEnable = depth_test_enable;
pipeline_depth_stencil_state_create_info.depthWriteEnable = depth_write_enable;
pipeline_depth_stencil_state_create_info.depthCompareOp = depth_compare_op;
pipeline_depth_stencil_state_create_info.front = pipeline_depth_stencil_state_create_info.back;
pipeline_depth_stencil_state_create_info.back.compareOp = VK_COMPARE_OP_ALWAYS;
return pipeline_depth_stencil_state_create_info;
}
inline VkPipelineViewportStateCreateInfo pipeline_viewport_state_create_info(
uint32_t viewport_count,
uint32_t scissor_count,
VkPipelineViewportStateCreateFlags flags = 0)
{
VkPipelineViewportStateCreateInfo pipeline_viewport_state_create_info{};
pipeline_viewport_state_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
pipeline_viewport_state_create_info.viewportCount = viewport_count;
pipeline_viewport_state_create_info.scissorCount = scissor_count;
pipeline_viewport_state_create_info.flags = flags;
return pipeline_viewport_state_create_info;
}
inline VkPipelineMultisampleStateCreateInfo pipeline_multisample_state_create_info(
VkSampleCountFlagBits rasterization_samples,
VkPipelineMultisampleStateCreateFlags flags = 0)
{
VkPipelineMultisampleStateCreateInfo pipeline_multisample_state_create_info{};
pipeline_multisample_state_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
pipeline_multisample_state_create_info.rasterizationSamples = rasterization_samples;
pipeline_multisample_state_create_info.flags = flags;
return pipeline_multisample_state_create_info;
}
inline VkPipelineDynamicStateCreateInfo pipeline_dynamic_state_create_info(
const VkDynamicState *dynamic_states,
uint32_t dynamicStateCount,
VkPipelineDynamicStateCreateFlags flags = 0)
{
VkPipelineDynamicStateCreateInfo pipeline_dynamic_state_create_info{};
pipeline_dynamic_state_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
pipeline_dynamic_state_create_info.pDynamicStates = dynamic_states;
pipeline_dynamic_state_create_info.dynamicStateCount = dynamicStateCount;
pipeline_dynamic_state_create_info.flags = flags;
return pipeline_dynamic_state_create_info;
}
inline VkPipelineDynamicStateCreateInfo pipeline_dynamic_state_create_info(
const std::vector<VkDynamicState> &dynamic_states,
VkPipelineDynamicStateCreateFlags flags = 0)
{
VkPipelineDynamicStateCreateInfo pipeline_dynamic_state_create_info{};
pipeline_dynamic_state_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
pipeline_dynamic_state_create_info.pDynamicStates = dynamic_states.data();
pipeline_dynamic_state_create_info.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
pipeline_dynamic_state_create_info.flags = flags;
return pipeline_dynamic_state_create_info;
}
inline VkPipelineTessellationStateCreateInfo pipeline_tessellation_state_create_info(uint32_t patch_control_points)
{
VkPipelineTessellationStateCreateInfo pipeline_tessellation_state_create_info{};
pipeline_tessellation_state_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
pipeline_tessellation_state_create_info.patchControlPoints = patch_control_points;
return pipeline_tessellation_state_create_info;
}
inline VkGraphicsPipelineCreateInfo pipeline_create_info(
VkPipelineLayout layout,
VkRenderPass render_pass,
VkPipelineCreateFlags flags = 0)
{
VkGraphicsPipelineCreateInfo pipeline_create_info{};
pipeline_create_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipeline_create_info.layout = layout;
pipeline_create_info.renderPass = render_pass;
pipeline_create_info.flags = flags;
pipeline_create_info.basePipelineIndex = -1;
pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE;
return pipeline_create_info;
}
inline VkGraphicsPipelineCreateInfo pipeline_create_info()
{
VkGraphicsPipelineCreateInfo pipeline_create_info{};
pipeline_create_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipeline_create_info.basePipelineIndex = -1;
pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE;
return pipeline_create_info;
}
inline VkComputePipelineCreateInfo compute_pipeline_create_info(
VkPipelineLayout layout,
VkPipelineCreateFlags flags = 0)
{
VkComputePipelineCreateInfo compute_pipeline_create_info{};
compute_pipeline_create_info.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
compute_pipeline_create_info.layout = layout;
compute_pipeline_create_info.flags = flags;
return compute_pipeline_create_info;
}
inline VkPushConstantRange push_constant_range(
VkShaderStageFlags stage_flags,
uint32_t size,
uint32_t offset)
{
VkPushConstantRange push_constant_range{};
push_constant_range.stageFlags = stage_flags;
push_constant_range.offset = offset;
push_constant_range.size = size;
return push_constant_range;
}
inline VkBindSparseInfo bind_sparse_info()
{
VkBindSparseInfo bind_sparse_info{};
bind_sparse_info.sType = VK_STRUCTURE_TYPE_BIND_SPARSE_INFO;
return bind_sparse_info;
}
/** @brief Initialize a map entry for a shader specialization constant */
inline VkSpecializationMapEntry specialization_map_entry(uint32_t constant_id, uint32_t offset, size_t size)
{
VkSpecializationMapEntry specialization_map_entry{};
specialization_map_entry.constantID = constant_id;
specialization_map_entry.offset = offset;
specialization_map_entry.size = size;
return specialization_map_entry;
}
/** @brief Initialize a specialization constant info structure to pass to a shader stage */
inline VkSpecializationInfo specialization_info(uint32_t map_entry_count, const VkSpecializationMapEntry *map_entries, size_t data_size, const void *data)
{
VkSpecializationInfo specialization_info{};
specialization_info.mapEntryCount = map_entry_count;
specialization_info.pMapEntries = map_entries;
specialization_info.dataSize = data_size;
specialization_info.pData = data;
return specialization_info;
}
inline VkTimelineSemaphoreSubmitInfo timeline_semaphore_submit_info(uint32_t wait_value_count, uint64_t *wait_values, uint32_t signal_value_count, uint64_t *signal_values)
{
return VkTimelineSemaphoreSubmitInfo{
VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
NULL,
wait_value_count,
wait_values,
signal_value_count,
signal_values};
}
} // namespace initializers
} // namespace vkb
+246
View File
@@ -0,0 +1,246 @@
/* Copyright (c) 2021-2025, Sascha Willems
*
* 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.
*/
#include "acceleration_structure.h"
#include "device.h"
namespace vkb
{
namespace core
{
AccelerationStructure::AccelerationStructure(vkb::core::DeviceC &device,
VkAccelerationStructureTypeKHR type) :
device{device},
type{type}
{
}
AccelerationStructure::~AccelerationStructure()
{
if (handle != VK_NULL_HANDLE)
{
vkDestroyAccelerationStructureKHR(device.get_handle(), handle, nullptr);
}
}
uint64_t AccelerationStructure::add_triangle_geometry(vkb::core::BufferC &vertex_buffer,
vkb::core::BufferC &index_buffer,
vkb::core::BufferC &transform_buffer,
uint32_t triangle_count,
uint32_t max_vertex,
VkDeviceSize vertex_stride,
uint32_t transform_offset,
VkFormat vertex_format,
VkIndexType index_type,
VkGeometryFlagsKHR flags,
uint64_t vertex_buffer_data_address,
uint64_t index_buffer_data_address,
uint64_t transform_buffer_data_address)
{
VkAccelerationStructureGeometryKHR geometry{};
geometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
geometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR;
geometry.flags = flags;
geometry.geometry.triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR;
geometry.geometry.triangles.vertexFormat = vertex_format;
geometry.geometry.triangles.maxVertex = max_vertex;
geometry.geometry.triangles.vertexStride = vertex_stride;
geometry.geometry.triangles.indexType = index_type;
geometry.geometry.triangles.vertexData.deviceAddress = vertex_buffer_data_address == 0 ? vertex_buffer.get_device_address() : vertex_buffer_data_address;
geometry.geometry.triangles.indexData.deviceAddress = index_buffer_data_address == 0 ? index_buffer.get_device_address() : index_buffer_data_address;
geometry.geometry.triangles.transformData.deviceAddress = transform_buffer_data_address == 0 ? transform_buffer.get_device_address() : transform_buffer_data_address;
uint64_t index = geometries.size();
geometries.insert({index, {geometry, triangle_count, transform_offset}});
return index;
}
void AccelerationStructure::update_triangle_geometry(uint64_t triangleUUID,
std::unique_ptr<vkb::core::BufferC> &vertex_buffer,
std::unique_ptr<vkb::core::BufferC> &index_buffer,
std::unique_ptr<vkb::core::BufferC> &transform_buffer,
uint32_t triangle_count, uint32_t max_vertex,
VkDeviceSize vertex_stride, uint32_t transform_offset,
VkFormat vertex_format, VkGeometryFlagsKHR flags,
uint64_t vertex_buffer_data_address,
uint64_t index_buffer_data_address,
uint64_t transform_buffer_data_address)
{
VkAccelerationStructureGeometryKHR *geometry = &geometries[triangleUUID].geometry;
geometry->sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
geometry->geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR;
geometry->flags = flags;
geometry->geometry.triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR;
geometry->geometry.triangles.vertexFormat = vertex_format;
geometry->geometry.triangles.maxVertex = max_vertex;
geometry->geometry.triangles.vertexStride = vertex_stride;
geometry->geometry.triangles.indexType = VK_INDEX_TYPE_UINT32;
geometry->geometry.triangles.vertexData.deviceAddress = vertex_buffer_data_address == 0 ? vertex_buffer->get_device_address() : vertex_buffer_data_address;
geometry->geometry.triangles.indexData.deviceAddress = index_buffer_data_address == 0 ? index_buffer->get_device_address() : index_buffer_data_address;
geometry->geometry.triangles.transformData.deviceAddress = transform_buffer_data_address == 0 ? transform_buffer->get_device_address() : transform_buffer_data_address;
geometries[triangleUUID].primitive_count = triangle_count;
geometries[triangleUUID].transform_offset = transform_offset;
geometries[triangleUUID].updated = true;
}
uint64_t AccelerationStructure::add_instance_geometry(std::unique_ptr<vkb::core::BufferC> &instance_buffer, uint32_t instance_count, uint32_t transform_offset, VkGeometryFlagsKHR flags)
{
VkAccelerationStructureGeometryKHR geometry{};
geometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
geometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR;
geometry.flags = flags;
geometry.geometry.instances.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR;
geometry.geometry.instances.arrayOfPointers = VK_FALSE;
geometry.geometry.instances.data.deviceAddress = instance_buffer->get_device_address();
uint64_t index = geometries.size();
geometries.insert({index, {geometry, instance_count, transform_offset}});
return index;
}
void AccelerationStructure::update_instance_geometry(uint64_t instance_UID,
std::unique_ptr<vkb::core::BufferC> &instance_buffer,
uint32_t instance_count, uint32_t transform_offset,
VkGeometryFlagsKHR flags)
{
VkAccelerationStructureGeometryKHR *geometry = &geometries[instance_UID].geometry;
geometry->sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
geometry->geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR;
geometry->flags = flags;
geometry->geometry.instances.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR;
geometry->geometry.instances.arrayOfPointers = VK_FALSE;
geometry->geometry.instances.data.deviceAddress = instance_buffer->get_device_address();
geometries[instance_UID].primitive_count = instance_count;
geometries[instance_UID].transform_offset = transform_offset;
geometries[instance_UID].updated = true;
}
void AccelerationStructure::build(VkQueue queue, VkBuildAccelerationStructureFlagsKHR flags, VkBuildAccelerationStructureModeKHR mode)
{
assert(!geometries.empty());
std::vector<VkAccelerationStructureGeometryKHR> acceleration_structure_geometries;
std::vector<VkAccelerationStructureBuildRangeInfoKHR> acceleration_structure_build_range_infos;
std::vector<uint32_t> primitive_counts;
for (auto &geometry : geometries)
{
if (mode == VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR && !geometry.second.updated)
{
continue;
}
acceleration_structure_geometries.push_back(geometry.second.geometry);
// Infer build range info from geometry
VkAccelerationStructureBuildRangeInfoKHR build_range_info;
build_range_info.primitiveCount = geometry.second.primitive_count;
build_range_info.primitiveOffset = 0;
build_range_info.firstVertex = 0;
build_range_info.transformOffset = geometry.second.transform_offset;
acceleration_structure_build_range_infos.push_back(build_range_info);
primitive_counts.push_back(geometry.second.primitive_count);
geometry.second.updated = false;
}
VkAccelerationStructureBuildGeometryInfoKHR build_geometry_info{};
build_geometry_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
build_geometry_info.type = type;
build_geometry_info.flags = flags;
build_geometry_info.mode = mode;
if (mode == VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR && handle != VK_NULL_HANDLE)
{
build_geometry_info.srcAccelerationStructure = handle;
build_geometry_info.dstAccelerationStructure = handle;
}
build_geometry_info.geometryCount = static_cast<uint32_t>(acceleration_structure_geometries.size());
build_geometry_info.pGeometries = acceleration_structure_geometries.data();
// Get required build sizes
build_sizes_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR;
vkGetAccelerationStructureBuildSizesKHR(
device.get_handle(),
VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
&build_geometry_info,
primitive_counts.data(),
&build_sizes_info);
// Create a buffer for the acceleration structure
if (!buffer || buffer->get_size() != build_sizes_info.accelerationStructureSize)
{
buffer = std::make_unique<vkb::core::BufferC>(
device,
build_sizes_info.accelerationStructureSize,
VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
VMA_MEMORY_USAGE_GPU_ONLY);
VkAccelerationStructureCreateInfoKHR acceleration_structure_create_info{};
acceleration_structure_create_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR;
acceleration_structure_create_info.buffer = buffer->get_handle();
acceleration_structure_create_info.size = build_sizes_info.accelerationStructureSize;
acceleration_structure_create_info.type = type;
VkResult result = vkCreateAccelerationStructureKHR(device.get_handle(), &acceleration_structure_create_info, nullptr, &handle);
if (result != VK_SUCCESS)
{
throw VulkanException{result, "Could not create acceleration structure"};
}
}
// Get the acceleration structure's handle
VkAccelerationStructureDeviceAddressInfoKHR acceleration_device_address_info{};
acceleration_device_address_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR;
acceleration_device_address_info.accelerationStructure = handle;
device_address = vkGetAccelerationStructureDeviceAddressKHR(device.get_handle(), &acceleration_device_address_info);
// Create a scratch buffer as a temporary storage for the acceleration structure build
scratch_buffer = std::make_unique<vkb::core::BufferC>(
device,
build_sizes_info.buildScratchSize,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
VMA_MEMORY_USAGE_GPU_ONLY);
build_geometry_info.scratchData.deviceAddress = scratch_buffer->get_device_address();
build_geometry_info.dstAccelerationStructure = handle;
// Build the acceleration structure on the device via a one-time command buffer submission
VkCommandBuffer command_buffer = device.create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
auto as_build_range_infos = &*acceleration_structure_build_range_infos.data();
vkCmdBuildAccelerationStructuresKHR(
command_buffer,
1,
&build_geometry_info,
&as_build_range_infos);
device.flush_command_buffer(command_buffer, queue);
scratch_buffer.reset();
}
VkAccelerationStructureKHR AccelerationStructure::get_handle() const
{
return handle;
}
const VkAccelerationStructureKHR *AccelerationStructure::get() const
{
return &handle;
}
uint64_t AccelerationStructure::get_device_address() const
{
return device_address;
}
} // namespace core
} // namespace vkb
+162
View File
@@ -0,0 +1,162 @@
/* Copyright (c) 2021-2025, Sascha Willems
*
* 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 "common/helpers.h"
#include "common/vk_common.h"
#include "core/buffer.h"
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceC = Device<vkb::BindingType::C>;
/**
* @brief Wraps setup and access for a ray tracing top- or bottom-level acceleration structure
*/
class AccelerationStructure
{
public:
/**
* @brief Creates a acceleration structure and the required buffer to store it's geometries
* @param device A valid Vulkan device
* @param type The type of the acceleration structure (top- or bottom-level)
*/
AccelerationStructure(vkb::core::DeviceC &device,
VkAccelerationStructureTypeKHR type);
~AccelerationStructure();
/**
* @brief Adds triangle geometry to the acceleration structure (only valid for bottom level)
* @returns UUID for the geometry instance for the case of multiple geometries to look up in the map
* @param vertex_buffer Buffer containing vertices
* @param index_buffer Buffer containing indices
* @param transform_buffer Buffer containing transform data
* @param triangle_count Number of triangles for this geometry
* @param max_vertex Index of the last vertex in the geometry
* @param vertex_stride Stride of the vertex structure
* @param transform_offset Offset of this geometry in the transform data buffer
* @param vertex_format Format of the vertex structure
* @param index_type Type of the indices
* @param flags Ray tracing geometry flags
* @param vertex_buffer_data_address set this if don't want the vertex_buffer data_address
* @param index_buffer_data_address set this if don't want the index_buffer data_address
* @param transform_buffer_data_address set this if don't want the transform_buffer data_address
*/
uint64_t add_triangle_geometry(vkb::core::BufferC &vertex_buffer,
vkb::core::BufferC &index_buffer,
vkb::core::BufferC &transform_buffer,
uint32_t triangle_count,
uint32_t max_vertex,
VkDeviceSize vertex_stride,
uint32_t transform_offset = 0,
VkFormat vertex_format = VK_FORMAT_R32G32B32_SFLOAT,
VkIndexType index_type = VK_INDEX_TYPE_UINT32,
VkGeometryFlagsKHR flags = VK_GEOMETRY_OPAQUE_BIT_KHR,
uint64_t vertex_buffer_data_address = 0,
uint64_t index_buffer_data_address = 0,
uint64_t transform_buffer_data_address = 0);
void update_triangle_geometry(uint64_t triangleUUID, std::unique_ptr<vkb::core::BufferC> &vertex_buffer,
std::unique_ptr<vkb::core::BufferC> &index_buffer,
std::unique_ptr<vkb::core::BufferC> &transform_buffer,
uint32_t triangle_count,
uint32_t max_vertex,
VkDeviceSize vertex_stride,
uint32_t transform_offset = 0,
VkFormat vertex_format = VK_FORMAT_R32G32B32_SFLOAT,
VkGeometryFlagsKHR flags = VK_GEOMETRY_OPAQUE_BIT_KHR,
uint64_t vertex_buffer_data_address = 0,
uint64_t index_buffer_data_address = 0,
uint64_t transform_buffer_data_address = 0);
/**
* @brief Adds instance geometry to the acceleration structure (only valid for top level)
* @returns index of the instance geometry into the structure.
* @param instance_buffer Buffer containing instances
* @param instance_count Number of instances for this geometry
* @param transform_offset Offset of this geometry in the transform data buffer
* @param flags Ray tracing geometry flags
*/
uint64_t add_instance_geometry(std::unique_ptr<vkb::core::BufferC> &instance_buffer,
uint32_t instance_count,
uint32_t transform_offset = 0,
VkGeometryFlagsKHR flags = VK_GEOMETRY_OPAQUE_BIT_KHR);
void update_instance_geometry(uint64_t instance_UID, std::unique_ptr<vkb::core::BufferC> &instance_buffer,
uint32_t instance_count,
uint32_t transform_offset = 0,
VkGeometryFlagsKHR flags = VK_GEOMETRY_OPAQUE_BIT_KHR);
/**
* @brief Builds the acceleration structure on the device (requires at least one geometry to be added)
* @param queue Queue to use for the build process
* @param flags Build flags
* @param mode Build mode (build or update)
*/
void build(VkQueue queue,
VkBuildAccelerationStructureFlagsKHR flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR,
VkBuildAccelerationStructureModeKHR mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR);
VkAccelerationStructureKHR get_handle() const;
const VkAccelerationStructureKHR *get() const;
uint64_t get_device_address() const;
vkb::core::BufferC *get_buffer() const
{
return buffer.get();
}
void resetGeometries()
{
geometries.clear();
}
private:
vkb::core::DeviceC &device;
VkAccelerationStructureKHR handle{VK_NULL_HANDLE};
uint64_t device_address{0};
VkAccelerationStructureTypeKHR type{};
VkAccelerationStructureBuildSizesInfoKHR build_sizes_info{};
struct Geometry
{
VkAccelerationStructureGeometryKHR geometry{};
uint32_t primitive_count{};
uint32_t transform_offset{};
bool updated = false;
};
std::unique_ptr<vkb::core::BufferC> scratch_buffer;
std::map<uint64_t, Geometry> geometries{};
std::unique_ptr<vkb::core::BufferC> buffer{nullptr};
};
} // namespace core
} // namespace vkb
+61
View File
@@ -0,0 +1,61 @@
/* Copyright (c) 2021-2024, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2024, Bradley Austin Davis. All rights reserved.
*
* 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.
*/
#include "allocated.h"
#include "common/error.h"
namespace vkb
{
namespace allocated
{
VmaAllocator &get_memory_allocator()
{
static VmaAllocator memory_allocator = VK_NULL_HANDLE;
return memory_allocator;
}
void init(const VmaAllocatorCreateInfo &create_info)
{
auto &allocator = get_memory_allocator();
if (allocator == VK_NULL_HANDLE)
{
VkResult result = vmaCreateAllocator(&create_info, &allocator);
if (result != VK_SUCCESS)
{
throw VulkanException{result, "Cannot create allocator"};
}
}
}
void shutdown()
{
auto &allocator = get_memory_allocator();
if (allocator != VK_NULL_HANDLE)
{
VmaTotalStatistics stats;
vmaCalculateStatistics(allocator, &stats);
LOGI("Total device memory leaked: {} bytes.", stats.total.statistics.allocationBytes);
vmaDestroyAllocator(allocator);
allocator = VK_NULL_HANDLE;
}
}
} // namespace allocated
} // namespace vkb
+633
View File
@@ -0,0 +1,633 @@
/* Copyright (c) 2021-2025, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2024-2025, Bradley Austin Davis. All rights reserved.
*
* 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 "common/error.h"
#include "core/physical_device.h"
#include "core/vulkan_resource.h"
namespace vkb
{
namespace allocated
{
/**
* @brief Retrieves a reference to the VMA allocator singleton. It will hold an opaque handle to the VMA
* allocator between calls to `init` and `shutdown`. Otherwise it contains a null pointer.
* @return A reference to the VMA allocator singleton handle.
*/
VmaAllocator &get_memory_allocator();
/**
* @brief The non-templatized VMA initializer function, referenced by the template version to smooth
* over the differences between the `vkb::Device` and `vkb::core::HPPDevice` classes.
* Idempotent, but should be paired with `shutdown`.
* @param create_info The VMA allocator create info.
*/
void init(const VmaAllocatorCreateInfo &create_info);
/**
* @brief Initializes the VMA allocator with the specified device, expressed
* as the `vkb` wrapper class, which might be `vkb::Device` or `vkb::core::HPPDevice`.
* @tparam DeviceType The type of the device.
* @param device The Vulkan device.
*/
template <typename DeviceType = vkb::core::DeviceC>
void init(const DeviceType &device)
{
VmaVulkanFunctions vma_vulkan_func{};
vma_vulkan_func.vkGetInstanceProcAddr = vkGetInstanceProcAddr;
vma_vulkan_func.vkGetDeviceProcAddr = vkGetDeviceProcAddr;
VmaAllocatorCreateInfo allocator_info{};
allocator_info.pVulkanFunctions = &vma_vulkan_func;
allocator_info.physicalDevice = static_cast<VkPhysicalDevice>(device.get_gpu().get_handle());
allocator_info.device = static_cast<VkDevice>(device.get_handle());
allocator_info.instance = static_cast<VkInstance>(device.get_gpu().get_instance().get_handle());
bool can_get_memory_requirements = device.get_gpu().is_extension_supported(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME);
bool has_dedicated_allocation = device.get_gpu().is_extension_supported(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME);
if (can_get_memory_requirements && has_dedicated_allocation && device.is_extension_enabled(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME))
{
allocator_info.flags |= VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT;
}
if (device.get_gpu().is_extension_supported(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) &&
device.is_extension_enabled(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME))
{
allocator_info.flags |= VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT;
}
if (device.get_gpu().is_extension_supported(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME) && device.is_extension_enabled(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME))
{
allocator_info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
}
if (device.get_gpu().is_extension_supported(VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME) &&
device.is_extension_enabled(VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME))
{
allocator_info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT;
}
if (device.get_gpu().is_extension_supported(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME) && device.is_extension_enabled(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME))
{
allocator_info.flags |= VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT;
}
if (device.get_gpu().is_extension_supported(VK_AMD_DEVICE_COHERENT_MEMORY_EXTENSION_NAME) &&
device.is_extension_enabled(VK_AMD_DEVICE_COHERENT_MEMORY_EXTENSION_NAME))
{
allocator_info.flags |= VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT;
}
init(allocator_info);
}
/**
* @brief Shuts down the VMA allocator and releases all resources. Should be preceeded with a call to `init`.
*/
void shutdown();
/**
* @brief The `Allocated` class serves as a base class for wrappers around Vulkan that require memory allocation
* (`VkImage` and `VkBuffer`). This class mostly ensures proper behavior for a RAII pattern, preventing double-release by
* preventing copy assignment and copy construction in favor of move semantics, as well as preventing default construction
* in favor of explicit construction with a pre-existing handle or a populated create info struct.
*
* This project uses the [VMA](https://gpuopen.com/vulkan-memory-allocator/) to handle the low
* level details of memory allocation and management, as it hides away many of the messyy details of
* memory allocation when a user is first learning Vulkan, but still allows for fine grained control
* when a user becomes more experienced and the situation calls for it.
*
* @note Constants used in this documentation in the form of `HOST_COHERENT` are shorthand for
* `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` used for the sake of brevity.
*
* @tparam bindingType A flag indicating whether this is being used with the C or C++ API
*/
template <vkb::BindingType bindingType, typename HandleType>
class Allocated : public vkb::core::VulkanResource<bindingType, HandleType>
{
public:
using ParentType = vkb::core::VulkanResource<bindingType, HandleType>;
using BufferType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Buffer, VkBuffer>::type;
using BufferCreateInfoType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::BufferCreateInfo, VkBufferCreateInfo>::type;
using DeviceMemoryType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DeviceMemory, VkDeviceMemory>::type;
using DeviceSizeType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DeviceSize, VkDeviceSize>::type;
using ImageCreateInfoType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::ImageCreateInfo, VkImageCreateInfo>::type;
using ImageType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Image, VkImage>::type;
public:
Allocated() = delete;
Allocated(const Allocated &) = delete;
Allocated(Allocated &&other) noexcept;
Allocated &operator=(Allocated const &other) = delete;
Allocated &operator=(Allocated &&other) = default;
protected:
/**
* @brief The VMA-specific constructor for new objects. This should only be visible to derived classes.
* @param allocation_create_info All of the non-resource-specific information needed by the VMA to allocate the memory.
* @param args Additional constructor arguments needed for the derived class. Typically a `VkImageCreateInfo` or `VkBufferCreateInfo` struct.
*/
template <typename... Args>
Allocated(const VmaAllocationCreateInfo &allocation_create_info, Args &&...args);
/**
* @brief This constructor is used when the handle is already created, and the user wants to wrap it in an `Allocated` object.
* @note This constructor is used when the API provides us a pre-existing handle to something we didn't actually allocate, for instance
* when we allocate a swapchain and access the images in it. In these cases the `allocation` member variable will remain null for the
* lifetime of the wrapper object (which is NOT necessarily the lifetime of the handle) and the wrapper will make no attempt to apply
* RAII semantics.
*/
Allocated(HandleType handle, vkb::core::Device<bindingType> *device_ = nullptr);
public:
const HandleType *get() const;
/**
* @brief Flushes memory if it is NOT `HOST_COHERENT` (which also implies `HOST_VISIBLE`).
* This is a no-op for `HOST_COHERENT` memory.
*
* @param offset The offset into the memory to flush. Defaults to 0.
* @param size The size of the memory to flush. Defaults to the entire block of memory.
*/
void flush(DeviceSizeType offset = 0, DeviceSizeType size = VK_WHOLE_SIZE);
/**
* @brief Retrieves a pointer to the host visible memory as an unsigned byte array.
* @return The pointer to the host visible memory.
* @note This performs no checking that the memory is actually mapped, so it's possible to get a nullptr
*/
const uint8_t *get_data() const;
/**
* @brief Retrieves the raw Vulkan memory object.
* @return The Vulkan memory object.
*/
DeviceMemoryType get_memory() const;
/**
* @brief Maps Vulkan memory if it isn't already mapped to a host visible address. Does nothing if the
* allocation is already mapped (including persistently mapped allocations).
* @return Pointer to host visible memory.
*/
uint8_t *map();
/**
* @brief Returns true if the memory is mapped (i.e. the object contains a pointer for the mapping).
* This is true for both objects where `map` has been called as well as objects created with persistent
* mapping, where no call to `map` is necessary.
* @return mapping status.
*/
bool mapped() const;
/**
* @brief Unmaps Vulkan memory from the host visible address. Does nothing if the memory is not mapped or
* if the allocation is persistently mapped.
*/
void unmap();
/**
* @brief Copies the specified unsigned byte data into the mapped memory region.
* @note For non-persistently mapped memory, this function will call the `map` and `unmap` methods and SHOULD NOT
* be used if the user intends to make multiple updates to the memory region. In that case, the user should call
* `map` once, make all the updates against the pointer returned by `get_data`, and then call `unmap`. This may
* be a poor design choice as it creates a side effect of using the method (that mapped memory will
* unexpectedly be unmapped), but it is the current design of the method and changing it would be burdensome.
* Refactoring could be eased by creating a new method with a more explicit name, and then removing this method
* entirely.
*
* @param data The data to copy from.
* @param size The amount of bytes to copy.
* @param offset The offset to start the copying into the mapped data. Defaults to 0.
*/
size_t update(const uint8_t *data, size_t size, size_t offset = 0);
/**
* @brief Converts any non-byte data into bytes and then updates the buffer. This allows the user to pass
* arbitrary structure pointers to the update method, which will then be copied into the buffer as bytes.
* @param data The data to copy from.
* @param size The amount of bytes to copy.
* @param offset The offset to start the copying into the mapped data. Defaults to 0.
*/
size_t update(void const *data, size_t size, size_t offset = 0);
/**
* @brief Copies a vector of items into the buffer. This is a convenience method that allows the user to
* pass a vector of items to the update method, which will then be copied into the buffer as bytes.
*
* This function DOES NOT automatically manage adhering to the alignment requirements of the items being copied,
* for instance the `minUniformBufferOffsetAlignment` property of the [device](https://vulkan.gpuinfo.org/displaydevicelimit.php?name=minUniformBufferOffsetAlignment&platform=all).
* If the data needs to be aligned on something other than `sizeof(T)`, the user must manage that themselves.
* @param data The data vector to upload
* @param offset The offset to start the copying into the mapped data
* @deprecated Use the `updateTyped` method that uses the `vk::ArrayProxy` class instead.
*/
template <typename T>
size_t update(std::vector<T> const &data, size_t offset = 0)
{
return update(data.data(), data.size() * sizeof(T), offset);
}
/**
* @brief Another convenience method, similar to the vector update method, but for std::array. The same caveats apply.
* @param data The data vector to upload
* @param offset The offset to start the copying into the mapped data
* @see update(std::vector<T> const &data, size_t offset = 0)
* @deprecated Use the `updateTyped` method that uses the `vk::ArrayProxy` class instead.
*/
template <typename T, size_t N>
size_t update(std::array<T, N> const &data, size_t offset = 0)
{
return update(data.data(), data.size() * sizeof(T), offset);
}
/**
* @brief Copies an object as byte data into the buffer. This is a convenience method that allows the user to
* pass an object to the update method, which will then be copied into the buffer as bytes. The name difference
* is to avoid amibuity with the `update` method signatures (including the non-templated version)
* @param object The object to convert into byte data
* @param offset The offset to start the copying into the mapped data
* @deprecated Use the `updateTyped` method that uses the `vk::ArrayProxy` class instead.
*/
template <class T>
size_t convert_and_update(const T &object, size_t offset = 0)
{
return update(reinterpret_cast<const uint8_t *>(&object), sizeof(T), offset);
}
/**
* @brief Copies an object as byte data into the buffer. This is a convenience method that allows the user to
* pass an object to the update method, which will then be copied into the buffer as bytes. The use of the `vk::ArrayProxy`
* type here to wrap the passed data means you can use any type related to T that can be used as a constructor to `vk::ArrayProxy`.
* This includes `T`, `std::vector<T>`, `std::array<T, N>`, and `vk::ArrayProxy<T>`.
*
* @remark This was previously not feasible as it would have been undesirable to create a strong coupling with the
* C++ Vulkan bindings where the `vk::ArrayProxy` type is defined. However, structural changes have ensured that this
* coupling is always present, so the `vk::ArrayProxy` may as well be used to our advantage here.
*
* @note This function DOES NOT automatically manage adhering to the alignment requirements of the items being copied,
* for instance the `minUniformBufferOffsetAlignment` property of the [device](https://vulkan.gpuinfo.org/displaydevicelimit.php?name=minUniformBufferOffsetAlignment&platform=all).
* If the data needs to be aligned on something other than `sizeof(T)`, the user must manage that themselves.
*
* @todo create `updateTypedAligned` which has an additional argument specifying the required GPU alignment of the elements of the array.
*/
template <class T>
size_t updateTyped(const vk::ArrayProxy<T> &object, size_t offset = 0)
{
return update(reinterpret_cast<const uint8_t *>(object.data()), object.size() * sizeof(T), offset);
}
protected:
/**
* @brief Internal method to actually create the buffer, allocate the memory and bind them.
* Should only be called from the `Buffer` derived class.
*
* Present in this common base class in order to allow the internal state members to remain `private`
* instead of `protected`, and because it (mostly) isolates interaction with the VMA to a single class
*/
[[nodiscard]] BufferType create_buffer(BufferCreateInfoType const &create_info);
/**
* @brief Internal method to actually create the image, allocate the memory and bind them.
* Should only be called from the `Image` derived class.
*
* Present in this common base class in order to allow the internal state members to remain `private`
* instead of `protected`, and because it (mostly) isolates interaction with the VMA to a single class
*/
[[nodiscard]] ImageType create_image(ImageCreateInfoType const &create_info);
/**
* @brief The post_create method is called after the creation of a buffer or image to store the allocation info internally. Derived classes
* could in theory override this to ensure any post-allocation operations are performed, but the base class should always be called to ensure
* the allocation info is stored.
* Should only be called in the corresponding `create_xxx` methods.
*/
virtual void post_create(VmaAllocationInfo const &allocation_info);
/**
* @brief Internal method to actually destroy the buffer and release the allocated memory. Should
* only be called from the `Buffer` derived class.
* Present in this common base class in order to allow the internal state members to remain `private`
* instead of `protected`, and because it (mostly) isolates interaction with the VMA to a single class
*/
void destroy_buffer(BufferType buffer);
/**
* @brief Internal method to actually destroy the image and release the allocated memory. Should
* only be called from the `Image` derived class.
* Present in this common base class in order to allow the internal state members to remain `private`
* instead of `protected`, and because it (mostly) isolates interaction with the VMA to a single class
*/
void destroy_image(ImageType image);
/**
* @brief Clears the internal state. Can be overridden by derived classes to perform additional cleanup of members.
* Should only be called in the corresping `destroy_xxx` methods.
*/
void clear();
private:
vk::Buffer create_buffer_impl(vk::BufferCreateInfo const &create_info);
vk::Image create_image_impl(vk::ImageCreateInfo const &create_info);
VmaAllocationCreateInfo allocation_create_info = {};
VmaAllocation allocation = VK_NULL_HANDLE;
/**
* @brief A pointer to the allocation memory, if the memory is HOST_VISIBLE and is currently (or persistently) mapped.
* Contains null otherwise.
*/
uint8_t *mapped_data = nullptr;
/**
* @brief This flag is set to true if the memory is coherent and doesn't need to be flushed after writes.
*
* @note This is initialized at allocation time to avoid subsequent need to call a function to fetch the
* allocation information from the VMA, since this property won't change for the lifetime of the allocation.
*/
bool coherent = false;
/**
* @brief This flag is set to true if the memory is persistently mapped (i.e. not just HOST_VISIBLE, but available
* as a pointer to the application for the lifetime of the allocation).
*
* @note This is initialized at allocation time to avoid subsequent need to call a function to fetch the
* allocation information from the VMA, since this property won't change for the lifetime of the allocation.
*/
bool persistent = false;
};
template <vkb::BindingType bindingType, typename HandleType>
inline Allocated<bindingType, HandleType>::Allocated(Allocated &&other) noexcept :
ParentType{static_cast<ParentType &&>(other)},
allocation_create_info(std::exchange(other.allocation_create_info, {})),
allocation(std::exchange(other.allocation, {})),
mapped_data(std::exchange(other.mapped_data, {})),
coherent(std::exchange(other.coherent, {})),
persistent(std::exchange(other.persistent, {}))
{
}
template <vkb::BindingType bindingType, typename HandleType>
template <typename... Args>
inline Allocated<bindingType, HandleType>::Allocated(const VmaAllocationCreateInfo &allocation_create_info, Args &&...args) :
ParentType{std::forward<Args>(args)...},
allocation_create_info(allocation_create_info)
{}
template <vkb::BindingType bindingType, typename HandleType>
inline Allocated<bindingType, HandleType>::Allocated(HandleType handle, vkb::core::Device<bindingType> *device_) :
ParentType(handle, device_)
{}
template <vkb::BindingType bindingType, typename HandleType>
inline const HandleType *Allocated<bindingType, HandleType>::get() const
{
return &ParentType::get_handle();
}
template <vkb::BindingType bindingType, typename HandleType>
inline void Allocated<bindingType, HandleType>::clear()
{
mapped_data = nullptr;
persistent = false;
allocation_create_info = {};
}
template <vkb::BindingType bindingType, typename HandleType>
inline typename Allocated<bindingType, HandleType>::BufferType Allocated<bindingType, HandleType>::create_buffer(BufferCreateInfoType const &create_info)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return create_buffer_impl(create_info);
}
else
{
return static_cast<VkBuffer>(create_buffer_impl(reinterpret_cast<vk::BufferCreateInfo const &>(create_info)));
}
}
template <vkb::BindingType bindingType, typename HandleType>
inline vk::Buffer Allocated<bindingType, HandleType>::create_buffer_impl(vk::BufferCreateInfo const &create_info)
{
vk::Buffer buffer = VK_NULL_HANDLE;
VmaAllocationInfo allocation_info{};
auto result = vmaCreateBuffer(
get_memory_allocator(),
reinterpret_cast<VkBufferCreateInfo const *>(&create_info),
&allocation_create_info,
reinterpret_cast<VkBuffer *>(&buffer),
&allocation,
&allocation_info);
if (result != VK_SUCCESS)
{
throw VulkanException{result, "Cannot create Buffer"};
}
post_create(allocation_info);
return buffer;
}
template <vkb::BindingType bindingType, typename HandleType>
inline typename Allocated<bindingType, HandleType>::ImageType Allocated<bindingType, HandleType>::create_image(ImageCreateInfoType const &create_info)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return create_image_impl(create_info);
}
else
{
return static_cast<VkImage>(create_image_impl(reinterpret_cast<vk::ImageCreateInfo const &>(create_info)));
}
}
template <vkb::BindingType bindingType, typename HandleType>
inline vk::Image Allocated<bindingType, HandleType>::create_image_impl(vk::ImageCreateInfo const &create_info)
{
assert(0 < create_info.mipLevels && "Images should have at least one level");
assert(0 < create_info.arrayLayers && "Images should have at least one layer");
assert(create_info.usage && "Images should have at least one usage type");
vk::Image image = VK_NULL_HANDLE;
VmaAllocationInfo allocation_info{};
#if 0
// If the image is an attachment, prefer dedicated memory
constexpr vk::ImageUsageFlags attachment_only_flags = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eTransientAttachment;
if (create_info.usage & attachment_only_flags)
{
allocation_create_info.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
}
if (create_info.usage & vk::ImageUsageFlagBits::eTransientAttachment)
{
allocation_create_info.preferredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT;
}
#endif
VkResult result = vmaCreateImage(get_memory_allocator(),
reinterpret_cast<VkImageCreateInfo const *>(&create_info),
&allocation_create_info,
reinterpret_cast<VkImage *>(&image),
&allocation,
&allocation_info);
if (result != VK_SUCCESS)
{
throw VulkanException{result, "Cannot create Image"};
}
post_create(allocation_info);
return image;
}
template <vkb::BindingType bindingType, typename HandleType>
inline void Allocated<bindingType, HandleType>::destroy_buffer(BufferType handle)
{
if (handle != VK_NULL_HANDLE && allocation != VK_NULL_HANDLE)
{
unmap();
if constexpr (bindingType == vkb::BindingType::Cpp)
{
vmaDestroyBuffer(get_memory_allocator(), static_cast<VkBuffer>(handle), allocation);
}
else
{
vmaDestroyBuffer(get_memory_allocator(), handle, allocation);
}
clear();
}
}
template <vkb::BindingType bindingType, typename HandleType>
inline void Allocated<bindingType, HandleType>::destroy_image(ImageType image)
{
if (image != VK_NULL_HANDLE && allocation != VK_NULL_HANDLE)
{
unmap();
if constexpr (bindingType == vkb::BindingType::Cpp)
{
vmaDestroyImage(get_memory_allocator(), static_cast<VkImage>(image), allocation);
}
else
{
vmaDestroyImage(get_memory_allocator(), image, allocation);
}
clear();
}
}
template <vkb::BindingType bindingType, typename HandleType>
inline void Allocated<bindingType, HandleType>::flush(DeviceSizeType offset, DeviceSizeType size)
{
if (!coherent)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
vmaFlushAllocation(get_memory_allocator(), allocation, static_cast<VkDeviceSize>(offset), static_cast<VkDeviceSize>(size));
}
else
{
vmaFlushAllocation(get_memory_allocator(), allocation, offset, size);
}
}
}
template <vkb::BindingType bindingType, typename HandleType>
inline const uint8_t *Allocated<bindingType, HandleType>::get_data() const
{
return mapped_data;
}
template <vkb::BindingType bindingType, typename HandleType>
inline typename Allocated<bindingType, HandleType>::DeviceMemoryType Allocated<bindingType, HandleType>::get_memory() const
{
VmaAllocationInfo alloc_info;
vmaGetAllocationInfo(get_memory_allocator(), allocation, &alloc_info);
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return static_cast<vk::DeviceMemory>(alloc_info.deviceMemory);
}
else
{
return alloc_info.deviceMemory;
}
}
template <vkb::BindingType bindingType, typename HandleType>
inline uint8_t *Allocated<bindingType, HandleType>::map()
{
if (!persistent && !mapped())
{
VK_CHECK(vmaMapMemory(get_memory_allocator(), allocation, reinterpret_cast<void **>(&mapped_data)));
assert(mapped_data);
}
return mapped_data;
}
template <vkb::BindingType bindingType, typename HandleType>
inline bool Allocated<bindingType, HandleType>::mapped() const
{
return mapped_data != nullptr;
}
template <vkb::BindingType bindingType, typename HandleType>
inline void Allocated<bindingType, HandleType>::post_create(VmaAllocationInfo const &allocation_info)
{
VkMemoryPropertyFlags memory_properties;
vmaGetAllocationMemoryProperties(get_memory_allocator(), allocation, &memory_properties);
coherent = (memory_properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
mapped_data = static_cast<uint8_t *>(allocation_info.pMappedData);
persistent = mapped();
}
template <vkb::BindingType bindingType, typename HandleType>
inline void Allocated<bindingType, HandleType>::unmap()
{
if (!persistent && mapped())
{
vmaUnmapMemory(get_memory_allocator(), allocation);
mapped_data = nullptr;
}
}
template <vkb::BindingType bindingType, typename HandleType>
inline size_t Allocated<bindingType, HandleType>::update(const uint8_t *data, size_t size, size_t offset)
{
if (persistent)
{
std::copy(data, data + size, mapped_data + offset);
flush();
}
else
{
map();
std::copy(data, data + size, mapped_data + offset);
flush();
unmap();
}
return size;
}
template <vkb::BindingType bindingType, typename HandleType>
inline size_t Allocated<bindingType, HandleType>::update(void const *data, size_t size, size_t offset)
{
return update(reinterpret_cast<const uint8_t *>(data), size, offset);
}
template <typename HandleType>
using AllocatedC = Allocated<vkb::BindingType::C, HandleType>;
template <typename HandleType>
using AllocatedCpp = Allocated<vkb::BindingType::Cpp, HandleType>;
} // namespace allocated
} // namespace vkb
+273
View File
@@ -0,0 +1,273 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
* Copyright (c) 2021-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "builder_base.h"
#include "common/vk_common.h"
#include "core/allocated.h"
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class Buffer;
template <vkb::BindingType bindingType>
using BufferPtr = std::unique_ptr<Buffer<bindingType>>;
template <vkb::BindingType bindingType>
class Device;
template <vkb::BindingType bindingType>
struct BufferBuilder
: public vkb::allocated::BuilderBase<bindingType,
BufferBuilder<bindingType>,
typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::BufferCreateInfo, VkBufferCreateInfo>::type>
{
public:
using BufferCreateFlagsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::BufferCreateFlags, VkBufferCreateFlags>::type;
using BufferCreateInfoType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::BufferCreateInfo, VkBufferCreateInfo>::type;
using BufferUsageFlagsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::BufferUsageFlags, VkBufferUsageFlags>::type;
using DeviceSizeType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DeviceSize, VkDeviceSize>::type;
using SharingModeType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::SharingMode, VkSharingMode>::type;
private:
using ParentType = vkb::allocated::BuilderBase<bindingType, BufferBuilder<bindingType>, BufferCreateInfoType>;
public:
BufferBuilder(DeviceSizeType size);
Buffer<bindingType> build(vkb::core::Device<bindingType> &device) const;
BufferPtr<bindingType> build_unique(vkb::core::Device<bindingType> &device) const;
BufferBuilder &with_flags(BufferCreateFlagsType flags);
BufferBuilder &with_usage(BufferUsageFlagsType usage);
};
using BufferBuilderC = BufferBuilder<vkb::BindingType::C>;
using BufferBuilderCpp = BufferBuilder<vkb::BindingType::Cpp>;
template <>
inline BufferBuilder<vkb::BindingType::Cpp>::BufferBuilder(vk::DeviceSize size) :
ParentType(BufferCreateInfoType{.size = size})
{
}
template <>
inline BufferBuilder<vkb::BindingType::C>::BufferBuilder(VkDeviceSize size) :
ParentType(VkBufferCreateInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, nullptr, 0, size})
{}
template <vkb::BindingType bindingType>
inline Buffer<bindingType> BufferBuilder<bindingType>::build(vkb::core::Device<bindingType> &device) const
{
return Buffer<bindingType>{device, *this};
}
template <vkb::BindingType bindingType>
inline BufferPtr<bindingType> BufferBuilder<bindingType>::build_unique(vkb::core::Device<bindingType> &device) const
{
return std::make_unique<Buffer<bindingType>>(device, *this);
}
template <vkb::BindingType bindingType>
inline BufferBuilder<bindingType> &BufferBuilder<bindingType>::with_flags(BufferCreateFlagsType flags)
{
this->create_info.flags = flags;
return *this;
}
template <vkb::BindingType bindingType>
inline BufferBuilder<bindingType> &BufferBuilder<bindingType>::with_usage(BufferUsageFlagsType usage)
{
this->get_create_info().usage = usage;
return *this;
}
/*=========================================================*/
template <vkb::BindingType bindingType>
class Buffer
: public vkb::allocated::Allocated<bindingType, typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Buffer, VkBuffer>::type>
{
public:
using BufferType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Buffer, VkBuffer>::type;
using BufferUsageFlagsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::BufferUsageFlags, VkBufferUsageFlags>::type;
using DeviceSizeType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DeviceSize, VkDeviceSize>::type;
private:
using ParentType = vkb::allocated::Allocated<bindingType, BufferType>;
public:
static Buffer<bindingType> create_staging_buffer(vkb::core::Device<bindingType> &device, DeviceSizeType size, const void *data);
template <typename T>
static Buffer create_staging_buffer(vkb::core::Device<bindingType> &device, std::vector<T> const &data);
template <typename T>
static Buffer create_staging_buffer(vkb::core::Device<bindingType> &device, const T &data);
Buffer() = delete;
Buffer(const Buffer &) = delete;
Buffer(Buffer &&other) = default;
Buffer &operator=(const Buffer &) = delete;
Buffer &operator=(Buffer &&) = default;
/**
* @brief Creates a buffer using VMA
* @param device A valid Vulkan device
* @param size The size in bytes of the buffer
* @param buffer_usage The usage flags for the VkBuffer
* @param memory_usage The memory usage of the buffer
* @param flags The allocation create flags
* @param queue_family_indices optional queue family indices
*/
// [[deprecated("Use the BufferBuilder ctor instead")]]
Buffer(vkb::core::Device<bindingType> &device,
DeviceSizeType size,
BufferUsageFlagsType buffer_usage,
VmaMemoryUsage memory_usage,
VmaAllocationCreateFlags flags = VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT,
const std::vector<uint32_t> &queue_family_indices = {});
Buffer(vkb::core::Device<bindingType> &device, BufferBuilder<bindingType> const &builder);
~Buffer();
/**
* @return Return the buffer's device address (note: requires that the buffer has been created with the VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT usage fla)
*/
uint64_t get_device_address() const;
/**
* @return The size of the buffer
*/
DeviceSizeType get_size() const;
private:
static Buffer<vkb::BindingType::Cpp> create_staging_buffer_impl(vkb::core::DeviceCpp &device, vk::DeviceSize size, const void *data);
private:
vk::DeviceSize size = 0;
};
using BufferC = Buffer<vkb::BindingType::C>;
using BufferCpp = Buffer<vkb::BindingType::Cpp>;
template <vkb::BindingType bindingType>
template <typename T>
inline Buffer<bindingType> Buffer<bindingType>::create_staging_buffer(vkb::core::Device<bindingType> &device, const T &data)
{
return create_staging_buffer(device, sizeof(T), &data);
}
template <vkb::BindingType bindingType>
inline Buffer<bindingType> Buffer<bindingType>::create_staging_buffer(vkb::core::Device<bindingType> &device, DeviceSizeType size, const void *data)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return create_staging_buffer_impl(device, size, data);
}
else
{
BufferCpp buffer = create_staging_buffer_impl(reinterpret_cast<vkb::core::DeviceCpp &>(device), static_cast<vk::DeviceSize>(size), data);
return std::move(*reinterpret_cast<BufferC *>(&buffer));
}
}
template <vkb::BindingType bindingType>
inline BufferCpp Buffer<bindingType>::create_staging_buffer_impl(vkb::core::DeviceCpp &device, vk::DeviceSize size, const void *data)
{
BufferBuilderCpp builder(size);
builder.with_vma_flags(VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT)
.with_usage(vk::BufferUsageFlagBits::eTransferSrc);
BufferCpp result(device, builder);
if (data != nullptr)
{
result.update(data, size);
}
return result;
}
template <vkb::BindingType bindingType>
template <typename T>
inline Buffer<bindingType> Buffer<bindingType>::create_staging_buffer(vkb::core::Device<bindingType> &device, std::vector<T> const &data)
{
return create_staging_buffer(device, data.size() * sizeof(T), data.data());
}
template <vkb::BindingType bindingType>
inline Buffer<bindingType>::Buffer(vkb::core::Device<bindingType> &device,
DeviceSizeType size,
BufferUsageFlagsType buffer_usage,
VmaMemoryUsage memory_usage,
VmaAllocationCreateFlags flags,
const std::vector<uint32_t> &queue_family_indices) :
Buffer(device,
BufferBuilder<bindingType>(size)
.with_usage(buffer_usage)
.with_vma_usage(memory_usage)
.with_vma_flags(flags)
.with_queue_families(queue_family_indices)
.with_implicit_sharing_mode())
{}
template <vkb::BindingType bindingType>
inline Buffer<bindingType>::Buffer(vkb::core::Device<bindingType> &device, const BufferBuilder<bindingType> &builder) :
ParentType(builder.get_allocation_create_info(), nullptr, &device), size(builder.get_create_info().size)
{
this->set_handle(this->create_buffer(builder.get_create_info()));
if (!builder.get_debug_name().empty())
{
this->set_debug_name(builder.get_debug_name());
}
}
template <vkb::BindingType bindingType>
inline Buffer<bindingType>::~Buffer()
{
this->destroy_buffer(this->get_handle());
}
template <vkb::BindingType bindingType>
inline uint64_t Buffer<bindingType>::get_device_address() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return this->get_device().get_handle().getBufferAddressKHR({.buffer = this->get_handle()});
}
else
{
return static_cast<vk::Device>(this->get_device().get_handle()).getBufferAddressKHR({.buffer = static_cast<vk::Buffer>(this->get_handle())});
}
}
template <vkb::BindingType bindingType>
inline typename Buffer<bindingType>::DeviceSizeType Buffer<bindingType>::get_size() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return size;
}
else
{
return static_cast<VkDeviceSize>(size);
}
}
} // namespace core
} // namespace vkb
File diff suppressed because it is too large Load Diff
+183
View File
@@ -0,0 +1,183 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
* Copyright (c) 2024-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "core/command_pool_base.h"
#include "core/device.h"
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace rendering
{
template <vkb::BindingType bindingType>
class RenderFrame;
using RenderFrameC = RenderFrame<vkb::BindingType::C>;
using RenderFrameCpp = RenderFrame<vkb::BindingType::Cpp>;
} // namespace rendering
namespace core
{
template <vkb::BindingType bindingType>
class CommandBuffer;
using CommandBufferC = CommandBuffer<vkb::BindingType::C>;
using CommandBufferCpp = CommandBuffer<vkb::BindingType::Cpp>;
namespace
{
// type trait to get the default value for request_command_buffer
template <typename T>
struct DefaultCommandBufferLevelValue;
template <>
struct DefaultCommandBufferLevelValue<vk::CommandBufferLevel>
{
static constexpr vk::CommandBufferLevel value = vk::CommandBufferLevel::ePrimary;
};
template <>
struct DefaultCommandBufferLevelValue<VkCommandBufferLevel>
{
static constexpr VkCommandBufferLevel value = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
};
} // namespace
template <vkb::BindingType bindingType>
class CommandPool : private vkb::core::CommandPoolBase
{
public:
using CommandBufferLevelType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::CommandBufferLevel, VkCommandBufferLevel>::type;
using CommandPoolType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::CommandPool, VkCommandPool>::type;
public:
CommandPool(vkb::core::Device<bindingType> &device,
uint32_t queue_family_index,
vkb::rendering::RenderFrame<bindingType> *render_frame = nullptr,
size_t thread_index = 0,
vkb::CommandBufferResetMode reset_mode = vkb::CommandBufferResetMode::ResetPool);
CommandPool(CommandPool<bindingType> const &) = delete;
CommandPool(CommandPool<bindingType> &&other) = default;
CommandPool &operator=(CommandPool<bindingType> const &) = delete;
CommandPool &operator=(CommandPool<bindingType> &&other) = default;
~CommandPool() = default;
vkb::core::Device<bindingType> &get_device();
CommandPoolType get_handle() const;
uint32_t get_queue_family_index() const;
vkb::rendering::RenderFrame<bindingType> *get_render_frame();
vkb::CommandBufferResetMode get_reset_mode() const;
size_t get_thread_index() const;
std::shared_ptr<vkb::core::CommandBuffer<bindingType>> request_command_buffer(CommandBufferLevelType level = DefaultCommandBufferLevelValue<CommandBufferLevelType>::value);
void reset_pool();
};
using CommandPoolC = CommandPool<vkb::BindingType::C>;
using CommandPoolCpp = CommandPool<vkb::BindingType::Cpp>;
template <vkb::BindingType bindingType>
inline vkb::core::CommandPool<bindingType>::CommandPool(vkb::core::Device<bindingType> &device,
uint32_t queue_family_index,
vkb::rendering::RenderFrame<bindingType> *render_frame,
size_t thread_index,
vkb::CommandBufferResetMode reset_mode) :
CommandPoolBase(reinterpret_cast<vkb::core::DeviceCpp &>(device),
queue_family_index,
reinterpret_cast<vkb::rendering::RenderFrameCpp *>(render_frame),
thread_index,
reset_mode)
{}
template <vkb::BindingType bindingType>
inline typename vkb::core::Device<bindingType> &CommandPool<bindingType>::get_device()
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return CommandPoolBase::get_device();
}
else
{
return reinterpret_cast<vkb::core::DeviceC &>(CommandPoolBase::get_device());
}
}
template <vkb::BindingType bindingType>
inline typename vkb::core::CommandPool<bindingType>::CommandPoolType CommandPool<bindingType>::get_handle() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return CommandPoolBase::get_handle();
}
else
{
return static_cast<VkCommandPool>(CommandPoolBase::get_handle());
}
}
template <vkb::BindingType bindingType>
inline uint32_t CommandPool<bindingType>::get_queue_family_index() const
{
return CommandPoolBase::get_queue_family_index();
}
template <vkb::BindingType bindingType>
inline vkb::rendering::RenderFrame<bindingType> *CommandPool<bindingType>::get_render_frame()
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return CommandPoolBase::get_render_frame();
}
else
{
return reinterpret_cast<vkb::rendering::RenderFrameC *>(CommandPoolBase::get_render_frame());
}
}
template <vkb::BindingType bindingType>
inline vkb::CommandBufferResetMode CommandPool<bindingType>::get_reset_mode() const
{
return CommandPoolBase::get_reset_mode();
}
template <vkb::BindingType bindingType>
inline size_t CommandPool<bindingType>::get_thread_index() const
{
return CommandPoolBase::get_thread_index();
}
template <vkb::BindingType bindingType>
std::shared_ptr<vkb::core::CommandBuffer<bindingType>> CommandPool<bindingType>::request_command_buffer(CommandBufferLevelType level)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return CommandPoolBase::request_command_buffer(*this, level);
}
else
{
std::shared_ptr<vkb::core::CommandBufferCpp> command_buffer =
CommandPoolBase::request_command_buffer(reinterpret_cast<vkb::core::CommandPoolCpp &>(*this), static_cast<vk::CommandBufferLevel>(level));
return *reinterpret_cast<std::shared_ptr<CommandBufferC> *>(&command_buffer);
}
}
template <vkb::BindingType bindingType>
inline void CommandPool<bindingType>::reset_pool()
{
CommandPoolBase::reset_pool();
}
} // namespace core
} // namespace vkb
+175
View File
@@ -0,0 +1,175 @@
/* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
*
* 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.
*/
#include "core/command_pool_base.h"
#include "core/command_buffer.h"
namespace vkb
{
namespace core
{
vkb::core::CommandPoolBase::CommandPoolBase(vkb::core::DeviceCpp &device_,
uint32_t queue_family_index,
vkb::rendering::RenderFrameCpp *render_frame_,
size_t thread_index,
vkb::CommandBufferResetMode reset_mode) :
device{device_}, render_frame{render_frame_}, thread_index{thread_index}, reset_mode{reset_mode}
{
vk::CommandPoolCreateFlags flags;
switch (reset_mode)
{
case vkb::CommandBufferResetMode::ResetIndividually:
case vkb::CommandBufferResetMode::AlwaysAllocate:
flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
break;
case vkb::CommandBufferResetMode::ResetPool:
default:
flags = vk::CommandPoolCreateFlagBits::eTransient;
break;
}
vk::CommandPoolCreateInfo command_pool_create_info{.flags = flags, .queueFamilyIndex = queue_family_index};
handle = device.get_handle().createCommandPool(command_pool_create_info);
}
CommandPoolBase::CommandPoolBase(CommandPoolBase &&other) :
device{other.device},
handle{other.handle},
render_frame{other.render_frame},
thread_index{other.thread_index},
queue_family_index{other.queue_family_index},
primary_command_buffers{std::move(other.primary_command_buffers)},
active_primary_command_buffer_count{other.active_primary_command_buffer_count},
secondary_command_buffers{std::move(other.secondary_command_buffers)},
active_secondary_command_buffer_count{other.active_secondary_command_buffer_count},
reset_mode{other.reset_mode}
{
other.handle = nullptr;
}
CommandPoolBase::~CommandPoolBase()
{
// clear command buffers before destroying the command pool
primary_command_buffers.clear();
secondary_command_buffers.clear();
// Destroy command pool
if (handle)
{
device.get_handle().destroyCommandPool(handle);
}
}
vkb::core::DeviceCpp &CommandPoolBase::get_device()
{
return device;
}
vk::CommandPool CommandPoolBase::get_handle() const
{
return handle;
}
uint32_t CommandPoolBase::get_queue_family_index() const
{
return queue_family_index;
}
vkb::rendering::RenderFrameCpp *CommandPoolBase::get_render_frame()
{
return render_frame;
}
vkb::CommandBufferResetMode CommandPoolBase::get_reset_mode() const
{
return reset_mode;
}
size_t CommandPoolBase::get_thread_index() const
{
return thread_index;
}
std::shared_ptr<vkb::core::CommandBufferCpp> CommandPoolBase::request_command_buffer(vkb::core::CommandPoolCpp &commandPool, vk::CommandBufferLevel level)
{
if (static_cast<vk::CommandBufferLevel>(level) == vk::CommandBufferLevel::ePrimary)
{
if (active_primary_command_buffer_count < primary_command_buffers.size())
{
return primary_command_buffers[active_primary_command_buffer_count++];
}
primary_command_buffers.emplace_back(std::make_shared<vkb::core::CommandBufferCpp>(commandPool, level));
active_primary_command_buffer_count++;
return primary_command_buffers.back();
}
else
{
if (active_secondary_command_buffer_count < secondary_command_buffers.size())
{
return secondary_command_buffers[active_secondary_command_buffer_count++];
}
secondary_command_buffers.emplace_back(std::make_shared<vkb::core::CommandBufferCpp>(commandPool, level));
active_secondary_command_buffer_count++;
return secondary_command_buffers.back();
}
}
void CommandPoolBase::reset_pool()
{
switch (reset_mode)
{
case vkb::CommandBufferResetMode::ResetIndividually:
for (auto &cmd_buf : primary_command_buffers)
{
cmd_buf->reset(reset_mode);
}
active_primary_command_buffer_count = 0;
for (auto &cmd_buf : secondary_command_buffers)
{
cmd_buf->reset(reset_mode);
}
active_secondary_command_buffer_count = 0;
break;
case vkb::CommandBufferResetMode::ResetPool:
device.get_handle().resetCommandPool(handle);
active_primary_command_buffer_count = 0;
active_secondary_command_buffer_count = 0;
break;
case vkb::CommandBufferResetMode::AlwaysAllocate:
primary_command_buffers.clear();
active_primary_command_buffer_count = 0;
secondary_command_buffers.clear();
active_secondary_command_buffer_count = 0;
break;
default:
throw std::runtime_error("Unknown reset mode for command pools");
}
}
} // namespace core
} // namespace vkb
+84
View File
@@ -0,0 +1,84 @@
/* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "common/vk_common.h"
#include <memory>
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace rendering
{
template <vkb::BindingType bindingType>
class RenderFrame;
using RenderFrameCpp = RenderFrame<vkb::BindingType::Cpp>;
} // namespace rendering
namespace core
{
template <vkb::BindingType bindingType>
class CommandBuffer;
using CommandBufferCpp = CommandBuffer<vkb::BindingType::Cpp>;
template <vkb::BindingType bindingType>
class CommandPool;
using CommandPoolCpp = CommandPool<vkb::BindingType::Cpp>;
template <vkb::BindingType bindingType>
class Device;
using DeviceCpp = Device<vkb::BindingType::Cpp>;
class CommandPoolBase
{
public:
CommandPoolBase(vkb::core::DeviceCpp &device,
uint32_t queue_family_index,
vkb::rendering::RenderFrameCpp *render_frame = nullptr,
size_t thread_index = 0,
vkb::CommandBufferResetMode reset_mode = vkb::CommandBufferResetMode::ResetPool);
CommandPoolBase(CommandPoolBase const &) = delete;
CommandPoolBase(CommandPoolBase &&other);
CommandPoolBase &operator=(CommandPoolBase const &) = delete;
CommandPoolBase &operator=(CommandPoolBase &&other) = delete;
~CommandPoolBase();
protected:
vkb::core::DeviceCpp &get_device();
vk::CommandPool get_handle() const;
uint32_t get_queue_family_index() const;
vkb::rendering::RenderFrameCpp *get_render_frame();
vkb::CommandBufferResetMode get_reset_mode() const;
size_t get_thread_index() const;
std::shared_ptr<vkb::core::CommandBufferCpp> request_command_buffer(vkb::core::CommandPoolCpp &commandPool, vk::CommandBufferLevel level);
void reset_pool();
private:
vkb::core::DeviceCpp &device;
vk::CommandPool handle = nullptr;
vkb::rendering::RenderFrameCpp *render_frame = nullptr;
size_t thread_index = 0;
uint32_t queue_family_index = 0;
std::vector<std::shared_ptr<vkb::core::CommandBufferCpp>> primary_command_buffers;
uint32_t active_primary_command_buffer_count = 0;
std::vector<std::shared_ptr<vkb::core::CommandBufferCpp>> secondary_command_buffers;
uint32_t active_secondary_command_buffer_count = 0;
vkb::CommandBufferResetMode reset_mode = vkb::CommandBufferResetMode::ResetPool;
};
} // namespace core
} // namespace vkb
+207
View File
@@ -0,0 +1,207 @@
/* Copyright (c) 2021-2025, Arm Limited and Contributors
*
* 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.
*/
#include "debug.h"
#include "core/command_buffer.h"
#include "core/device.h"
#include <glm/gtc/type_ptr.hpp>
#include <unordered_map>
namespace vkb
{
void DebugUtilsExtDebugUtils::set_debug_name(VkDevice device, VkObjectType object_type, uint64_t object_handle,
const char *name) const
{
VkDebugUtilsObjectNameInfoEXT name_info{};
name_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT;
name_info.objectType = object_type;
name_info.objectHandle = object_handle;
name_info.pObjectName = name;
assert(vkSetDebugUtilsObjectNameEXT);
vkSetDebugUtilsObjectNameEXT(device, &name_info);
}
void DebugUtilsExtDebugUtils::set_debug_tag(VkDevice device, VkObjectType object_type, uint64_t object_handle,
uint64_t tag_name, const void *tag_data, size_t tag_data_size) const
{
VkDebugUtilsObjectTagInfoEXT tag_info{};
tag_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_TAG_INFO_EXT;
tag_info.objectType = object_type;
tag_info.objectHandle = object_handle;
tag_info.tagName = tag_name;
tag_info.tagSize = tag_data_size;
tag_info.pTag = tag_data;
assert(vkSetDebugUtilsObjectTagEXT);
vkSetDebugUtilsObjectTagEXT(device, &tag_info);
}
void DebugUtilsExtDebugUtils::cmd_begin_label(VkCommandBuffer command_buffer,
const char *name, glm::vec4 color) const
{
VkDebugUtilsLabelEXT label_info{};
label_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
label_info.pLabelName = name;
memcpy(label_info.color, glm::value_ptr(color), sizeof(glm::vec4));
assert(vkCmdBeginDebugUtilsLabelEXT);
vkCmdBeginDebugUtilsLabelEXT(command_buffer, &label_info);
}
void DebugUtilsExtDebugUtils::cmd_end_label(VkCommandBuffer command_buffer) const
{
assert(vkCmdEndDebugUtilsLabelEXT);
vkCmdEndDebugUtilsLabelEXT(command_buffer);
}
void DebugUtilsExtDebugUtils::cmd_insert_label(VkCommandBuffer command_buffer,
const char *name, glm::vec4 color) const
{
VkDebugUtilsLabelEXT label_info{};
label_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
label_info.pLabelName = name;
memcpy(label_info.color, glm::value_ptr(color), sizeof(glm::vec4));
assert(vkCmdInsertDebugUtilsLabelEXT);
vkCmdInsertDebugUtilsLabelEXT(command_buffer, &label_info);
}
// See https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkDebugReportObjectTypeEXT.html
static const std::unordered_map<VkObjectType, VkDebugReportObjectTypeEXT> VK_OBJECT_TYPE_TO_DEBUG_REPORT_TYPE{
{VK_OBJECT_TYPE_UNKNOWN, VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT},
{VK_OBJECT_TYPE_INSTANCE, VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT},
{VK_OBJECT_TYPE_PHYSICAL_DEVICE, VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT},
{VK_OBJECT_TYPE_DEVICE, VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT},
{VK_OBJECT_TYPE_QUEUE, VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT},
{VK_OBJECT_TYPE_SEMAPHORE, VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT},
{VK_OBJECT_TYPE_COMMAND_BUFFER, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT},
{VK_OBJECT_TYPE_FENCE, VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT},
{VK_OBJECT_TYPE_DEVICE_MEMORY, VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT},
{VK_OBJECT_TYPE_BUFFER, VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT},
{VK_OBJECT_TYPE_IMAGE, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT},
{VK_OBJECT_TYPE_EVENT, VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT},
{VK_OBJECT_TYPE_QUERY_POOL, VK_DEBUG_REPORT_OBJECT_TYPE_QUERY_POOL_EXT},
{VK_OBJECT_TYPE_BUFFER_VIEW, VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT},
{VK_OBJECT_TYPE_IMAGE_VIEW, VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_VIEW_EXT},
{VK_OBJECT_TYPE_SHADER_MODULE, VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT},
{VK_OBJECT_TYPE_PIPELINE_CACHE, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_CACHE_EXT},
{VK_OBJECT_TYPE_PIPELINE_LAYOUT, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT},
{VK_OBJECT_TYPE_RENDER_PASS, VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT},
{VK_OBJECT_TYPE_PIPELINE, VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT},
{VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT},
{VK_OBJECT_TYPE_SAMPLER, VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT},
{VK_OBJECT_TYPE_DESCRIPTOR_POOL, VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT},
{VK_OBJECT_TYPE_DESCRIPTOR_SET, VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT},
{VK_OBJECT_TYPE_FRAMEBUFFER, VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT},
{VK_OBJECT_TYPE_COMMAND_POOL, VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT},
{VK_OBJECT_TYPE_SURFACE_KHR, VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT},
{VK_OBJECT_TYPE_SWAPCHAIN_KHR, VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT},
{VK_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT, VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT_EXT},
{VK_OBJECT_TYPE_DISPLAY_KHR, VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_KHR_EXT},
{VK_OBJECT_TYPE_DISPLAY_MODE_KHR, VK_DEBUG_REPORT_OBJECT_TYPE_DISPLAY_MODE_KHR_EXT},
{VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_KHR, VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_EXT},
};
void DebugMarkerExtDebugUtils::set_debug_name(VkDevice device, VkObjectType object_type, uint64_t object_handle,
const char *name) const
{
VkDebugMarkerObjectNameInfoEXT name_info{};
name_info.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_NAME_INFO_EXT;
name_info.objectType = VK_OBJECT_TYPE_TO_DEBUG_REPORT_TYPE.at(object_type);
name_info.object = object_handle;
name_info.pObjectName = name;
assert(vkDebugMarkerSetObjectNameEXT);
vkDebugMarkerSetObjectNameEXT(device, &name_info);
}
void DebugMarkerExtDebugUtils::set_debug_tag(VkDevice device, VkObjectType object_type, uint64_t object_handle,
uint64_t tag_name, const void *tag_data, size_t tag_data_size) const
{
VkDebugMarkerObjectTagInfoEXT tag_info{};
tag_info.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_OBJECT_TAG_INFO_EXT;
tag_info.objectType = VK_OBJECT_TYPE_TO_DEBUG_REPORT_TYPE.at(object_type);
tag_info.object = object_handle;
tag_info.tagName = tag_name;
tag_info.tagSize = tag_data_size;
tag_info.pTag = tag_data;
assert(vkDebugMarkerSetObjectTagEXT);
vkDebugMarkerSetObjectTagEXT(device, &tag_info);
}
void DebugMarkerExtDebugUtils::cmd_begin_label(VkCommandBuffer command_buffer,
const char *name, glm::vec4 color) const
{
VkDebugMarkerMarkerInfoEXT marker_info{};
marker_info.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
marker_info.pMarkerName = name;
memcpy(marker_info.color, glm::value_ptr(color), sizeof(glm::vec4));
assert(vkCmdDebugMarkerBeginEXT);
vkCmdDebugMarkerBeginEXT(command_buffer, &marker_info);
}
void DebugMarkerExtDebugUtils::cmd_end_label(VkCommandBuffer command_buffer) const
{
assert(vkCmdDebugMarkerEndEXT);
vkCmdDebugMarkerEndEXT(command_buffer);
}
void DebugMarkerExtDebugUtils::cmd_insert_label(VkCommandBuffer command_buffer,
const char *name, glm::vec4 color) const
{
VkDebugMarkerMarkerInfoEXT marker_info{};
marker_info.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
marker_info.pMarkerName = name;
memcpy(marker_info.color, glm::value_ptr(color), sizeof(glm::vec4));
assert(vkCmdDebugMarkerInsertEXT);
vkCmdDebugMarkerInsertEXT(command_buffer, &marker_info);
}
ScopedDebugLabel::ScopedDebugLabel(const DebugUtils &debug_utils, VkCommandBuffer command_buffer,
const char *name, glm::vec4 color) :
debug_utils{&debug_utils},
command_buffer{VK_NULL_HANDLE}
{
if (name && *name != '\0')
{
assert(command_buffer != VK_NULL_HANDLE);
this->command_buffer = command_buffer;
debug_utils.cmd_begin_label(command_buffer, name, color);
}
}
ScopedDebugLabel::ScopedDebugLabel(const vkb::core::CommandBufferC &command_buffer, const char *name, glm::vec4 color) :
ScopedDebugLabel{command_buffer.get_device().get_debug_utils(), command_buffer.get_handle(), name, color}
{
}
ScopedDebugLabel::~ScopedDebugLabel()
{
if (command_buffer != VK_NULL_HANDLE)
{
debug_utils->cmd_end_label(command_buffer);
}
}
} // namespace vkb
+165
View File
@@ -0,0 +1,165 @@
/* Copyright (c) 2021-2025, Arm Limited and Contributors
*
* 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 "common/glm_common.h"
#include "common/vk_common.h"
#include <cassert>
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class CommandBuffer;
using CommandBufferC = CommandBuffer<vkb::BindingType::C>;
} // namespace core
/**
* @brief An interface over platform-specific debug extensions.
*/
class DebugUtils
{
public:
virtual ~DebugUtils() = default;
/**
* @brief Sets the debug name for a Vulkan object.
*/
virtual void set_debug_name(VkDevice device, VkObjectType object_type, uint64_t object_handle,
const char *name) const = 0;
/**
* @brief Tags the given Vulkan object with some data.
*/
virtual void set_debug_tag(VkDevice device, VkObjectType object_type, uint64_t object_handle,
uint64_t tag_name, const void *tag_data, size_t tag_data_size) const = 0;
/**
* @brief Inserts a command to begin a new debug label/marker scope.
*/
virtual void cmd_begin_label(VkCommandBuffer command_buffer,
const char *name, glm::vec4 color = {}) const = 0;
/**
* @brief Inserts a command to end the current debug label/marker scope.
*/
virtual void cmd_end_label(VkCommandBuffer command_buffer) const = 0;
/**
* @brief Inserts a (non-scoped) debug label/marker in the command buffer.
*/
virtual void cmd_insert_label(VkCommandBuffer command_buffer,
const char *name, glm::vec4 color = {}) const = 0;
};
/**
* @brief DebugUtils implemented on top of VK_EXT_debug_utils.
*/
class DebugUtilsExtDebugUtils final : public DebugUtils
{
public:
~DebugUtilsExtDebugUtils() override = default;
void set_debug_name(VkDevice device, VkObjectType object_type, uint64_t object_handle,
const char *name) const override;
void set_debug_tag(VkDevice device, VkObjectType object_type, uint64_t object_handle,
uint64_t tag_name, const void *tag_data, size_t tag_data_size) const override;
void cmd_begin_label(VkCommandBuffer command_buffer,
const char *name, glm::vec4 color) const override;
void cmd_end_label(VkCommandBuffer command_buffer) const override;
void cmd_insert_label(VkCommandBuffer command_buffer,
const char *name, glm::vec4 color) const override;
};
/**
* @brief DebugUtils implemented on top of VK_EXT_debug_marker.
*/
class DebugMarkerExtDebugUtils final : public DebugUtils
{
public:
~DebugMarkerExtDebugUtils() override = default;
void set_debug_name(VkDevice device, VkObjectType object_type, uint64_t object_handle,
const char *name) const override;
void set_debug_tag(VkDevice device, VkObjectType object_type, uint64_t object_handle,
uint64_t tag_name, const void *tag_data, size_t tag_data_size) const override;
void cmd_begin_label(VkCommandBuffer command_buffer,
const char *name, glm::vec4 color) const override;
void cmd_end_label(VkCommandBuffer command_buffer) const override;
void cmd_insert_label(VkCommandBuffer command_buffer,
const char *name, glm::vec4 color) const override;
};
/**
* @brief No-op DebugUtils.
*/
class DummyDebugUtils final : public DebugUtils
{
public:
~DummyDebugUtils() override = default;
inline void set_debug_name(VkDevice, VkObjectType, uint64_t, const char *) const override
{}
inline void set_debug_tag(VkDevice, VkObjectType, uint64_t,
uint64_t, const void *, size_t) const override
{}
inline void cmd_begin_label(VkCommandBuffer,
const char *, glm::vec4) const override
{}
inline void cmd_end_label(VkCommandBuffer) const override
{}
inline void cmd_insert_label(VkCommandBuffer,
const char *, glm::vec4) const override
{}
};
/**
* @brief A RAII debug label.
* If any of EXT_debug_utils or EXT_debug_marker is available, this:
* - Begins a debug label / marker on construction
* - Ends it on destruction
*/
class ScopedDebugLabel final
{
public:
ScopedDebugLabel(const DebugUtils &debug_utils, VkCommandBuffer command_buffer,
const char *name, glm::vec4 color = {});
ScopedDebugLabel(const vkb::core::CommandBufferC &command_buffer, const char *name, glm::vec4 color = {});
~ScopedDebugLabel();
private:
const DebugUtils *debug_utils;
VkCommandBuffer command_buffer;
};
} // namespace vkb
+205
View File
@@ -0,0 +1,205 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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.
*/
#include "descriptor_pool.h"
#include "descriptor_set_layout.h"
#include "device.h"
namespace vkb
{
DescriptorPool::DescriptorPool(vkb::core::DeviceC &device,
const DescriptorSetLayout &descriptor_set_layout,
uint32_t pool_size) :
device{device},
descriptor_set_layout{&descriptor_set_layout}
{
const auto &bindings = descriptor_set_layout.get_bindings();
std::map<VkDescriptorType, std::uint32_t> descriptor_type_counts;
// Count each type of descriptor set
for (auto &binding : bindings)
{
descriptor_type_counts[binding.descriptorType] += binding.descriptorCount;
}
// Allocate pool sizes array
pool_sizes.resize(descriptor_type_counts.size());
auto pool_size_it = pool_sizes.begin();
// Fill pool size for each descriptor type count multiplied by the pool size
for (auto &it : descriptor_type_counts)
{
pool_size_it->type = it.first;
pool_size_it->descriptorCount = it.second * pool_size;
++pool_size_it;
}
pool_max_sets = pool_size;
}
DescriptorPool::~DescriptorPool()
{
// Destroy all descriptor pools
for (auto pool : pools)
{
vkDestroyDescriptorPool(device.get_handle(), pool, nullptr);
}
}
void DescriptorPool::reset()
{
// Reset all descriptor pools
for (auto pool : pools)
{
vkResetDescriptorPool(device.get_handle(), pool, 0);
}
// Clear internal tracking of descriptor set allocations
std::fill(pool_sets_count.begin(), pool_sets_count.end(), 0);
set_pool_mapping.clear();
// Reset the pool index from which descriptor sets are allocated
pool_index = 0;
}
const DescriptorSetLayout &DescriptorPool::get_descriptor_set_layout() const
{
assert(descriptor_set_layout && "Descriptor set layout is invalid");
return *descriptor_set_layout;
}
void DescriptorPool::set_descriptor_set_layout(const DescriptorSetLayout &set_layout)
{
descriptor_set_layout = &set_layout;
}
VkDescriptorSet DescriptorPool::allocate()
{
pool_index = find_available_pool(pool_index);
// Increment allocated set count for the current pool
++pool_sets_count[pool_index];
VkDescriptorSetLayout set_layout = get_descriptor_set_layout().get_handle();
VkDescriptorSetAllocateInfo alloc_info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
alloc_info.descriptorPool = pools[pool_index];
alloc_info.descriptorSetCount = 1;
alloc_info.pSetLayouts = &set_layout;
VkDescriptorSet handle = VK_NULL_HANDLE;
// Allocate a new descriptor set from the current pool
auto result = vkAllocateDescriptorSets(device.get_handle(), &alloc_info, &handle);
if (result != VK_SUCCESS)
{
// Decrement allocated set count for the current pool
--pool_sets_count[pool_index];
return VK_NULL_HANDLE;
}
// Store mapping between the descriptor set and the pool
set_pool_mapping.emplace(handle, pool_index);
return handle;
}
VkResult DescriptorPool::free(VkDescriptorSet descriptor_set)
{
// Get the pool index of the descriptor set
auto it = set_pool_mapping.find(descriptor_set);
if (it == set_pool_mapping.end())
{
return VK_INCOMPLETE;
}
auto desc_pool_index = it->second;
// Free descriptor set from the pool
vkFreeDescriptorSets(device.get_handle(), pools[desc_pool_index], 1, &descriptor_set);
// Remove descriptor set mapping to the pool
set_pool_mapping.erase(it);
// Decrement allocated set count for the pool
--pool_sets_count[desc_pool_index];
// Change the current pool index to use the available pool
pool_index = desc_pool_index;
return VK_SUCCESS;
}
std::uint32_t DescriptorPool::find_available_pool(std::uint32_t search_index)
{
// Create a new pool
if (pools.size() <= search_index)
{
VkDescriptorPoolCreateInfo create_info{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
create_info.poolSizeCount = to_u32(pool_sizes.size());
create_info.pPoolSizes = pool_sizes.data();
create_info.maxSets = pool_max_sets;
// We do not set FREE_DESCRIPTOR_SET_BIT as we do not need to free individual descriptor sets
create_info.flags = 0;
// Check descriptor set layout and enable the required flags
auto &binding_flags = descriptor_set_layout->get_binding_flags();
for (auto binding_flag : binding_flags)
{
if (binding_flag & VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT)
{
create_info.flags |= VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT_EXT;
}
}
VkDescriptorPool handle = VK_NULL_HANDLE;
// Create the Vulkan descriptor pool
auto result = vkCreateDescriptorPool(device.get_handle(), &create_info, nullptr, &handle);
if (result != VK_SUCCESS)
{
return 0;
}
// Store internally the Vulkan handle
pools.push_back(handle);
// Add set count for the descriptor pool
pool_sets_count.push_back(0);
return search_index;
}
else if (pool_sets_count[search_index] < pool_max_sets)
{
return search_index;
}
// Increment pool index
return find_available_pool(++search_index);
}
} // namespace vkb
+94
View File
@@ -0,0 +1,94 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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 <unordered_map>
#include "common/helpers.h"
#include "common/vk_common.h"
namespace vkb
{
class DescriptorSetLayout;
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceC = Device<vkb::BindingType::C>;
} // namespace core
/**
* @brief Manages an array of fixed size VkDescriptorPool and is able to allocate descriptor sets
*/
class DescriptorPool
{
public:
static const uint32_t MAX_SETS_PER_POOL = 16;
DescriptorPool(vkb::core::DeviceC &device,
const DescriptorSetLayout &descriptor_set_layout,
uint32_t pool_size = MAX_SETS_PER_POOL);
DescriptorPool(const DescriptorPool &) = delete;
DescriptorPool(DescriptorPool &&) = default;
~DescriptorPool();
DescriptorPool &operator=(const DescriptorPool &) = delete;
DescriptorPool &operator=(DescriptorPool &&) = delete;
void reset();
const DescriptorSetLayout &get_descriptor_set_layout() const;
void set_descriptor_set_layout(const DescriptorSetLayout &set_layout);
VkDescriptorSet allocate();
VkResult free(VkDescriptorSet descriptor_set);
private:
vkb::core::DeviceC &device;
const DescriptorSetLayout *descriptor_set_layout{nullptr};
// Descriptor pool size
std::vector<VkDescriptorPoolSize> pool_sizes;
// Number of sets to allocate for each pool
uint32_t pool_max_sets{0};
// Total descriptor pools created
std::vector<VkDescriptorPool> pools;
// Count sets for each pool
std::vector<uint32_t> pool_sets_count;
// Current pool index to allocate descriptor set
uint32_t pool_index{0};
// Map between descriptor set and pool index
std::unordered_map<VkDescriptorSet, uint32_t> set_pool_mapping;
// Find next pool index or create new pool
uint32_t find_available_pool(uint32_t pool_index);
};
} // namespace vkb
+256
View File
@@ -0,0 +1,256 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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.
*/
#include "descriptor_set.h"
#include "common/resource_caching.h"
#include "core/device.h"
#include "core/physical_device.h"
namespace vkb
{
DescriptorSet::DescriptorSet(vkb::core::DeviceC &device,
const DescriptorSetLayout &descriptor_set_layout,
DescriptorPool &descriptor_pool,
const BindingMap<VkDescriptorBufferInfo> &buffer_infos,
const BindingMap<VkDescriptorImageInfo> &image_infos) :
device{device},
descriptor_set_layout{descriptor_set_layout},
descriptor_pool{descriptor_pool},
buffer_infos{buffer_infos},
image_infos{image_infos},
handle{descriptor_pool.allocate()}
{
prepare();
}
void DescriptorSet::reset(const BindingMap<VkDescriptorBufferInfo> &new_buffer_infos, const BindingMap<VkDescriptorImageInfo> &new_image_infos)
{
if (!new_buffer_infos.empty() || !new_image_infos.empty())
{
buffer_infos = new_buffer_infos;
image_infos = new_image_infos;
}
else
{
LOGW("Calling reset on Descriptor Set with no new buffer infos and no new image infos.");
}
this->write_descriptor_sets.clear();
this->updated_bindings.clear();
prepare();
}
void DescriptorSet::prepare()
{
// We don't want to prepare twice during the life cycle of a Descriptor Set
if (!write_descriptor_sets.empty())
{
LOGW("Trying to prepare a descriptor set that has already been prepared, skipping.");
return;
}
// Iterate over all buffer bindings
for (auto &binding_it : buffer_infos)
{
auto binding_index = binding_it.first;
auto &buffer_bindings = binding_it.second;
if (auto binding_info = descriptor_set_layout.get_layout_binding(binding_index))
{
// Iterate over all binding buffers in array
for (auto &element_it : buffer_bindings)
{
auto &buffer_info = element_it.second;
size_t uniform_buffer_range_limit = device.get_gpu().get_properties().limits.maxUniformBufferRange;
size_t storage_buffer_range_limit = device.get_gpu().get_properties().limits.maxStorageBufferRange;
size_t buffer_range_limit = static_cast<size_t>(buffer_info.range);
if ((binding_info->descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER || binding_info->descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC) && buffer_range_limit > uniform_buffer_range_limit)
{
LOGE("Set {} binding {} cannot be updated: buffer size {} exceeds the uniform buffer range limit {}", descriptor_set_layout.get_index(), binding_index, buffer_info.range, uniform_buffer_range_limit);
buffer_range_limit = uniform_buffer_range_limit;
}
else if ((binding_info->descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER || binding_info->descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC) && buffer_range_limit > storage_buffer_range_limit)
{
LOGE("Set {} binding {} cannot be updated: buffer size {} exceeds the storage buffer range limit {}", descriptor_set_layout.get_index(), binding_index, buffer_info.range, storage_buffer_range_limit);
buffer_range_limit = storage_buffer_range_limit;
}
// Clip the buffers range to the limit if one exists as otherwise we will receive a Vulkan validation error
buffer_info.range = buffer_range_limit;
VkWriteDescriptorSet write_descriptor_set{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
write_descriptor_set.dstBinding = binding_index;
write_descriptor_set.descriptorType = binding_info->descriptorType;
write_descriptor_set.pBufferInfo = &buffer_info;
write_descriptor_set.dstSet = handle;
write_descriptor_set.dstArrayElement = element_it.first;
write_descriptor_set.descriptorCount = 1;
write_descriptor_sets.push_back(write_descriptor_set);
}
}
else
{
LOGE("Shader layout set does not use buffer binding at #{}", binding_index);
}
}
// Iterate over all image bindings
for (auto &binding_it : image_infos)
{
auto binding_index = binding_it.first;
auto &binding_resources = binding_it.second;
if (auto binding_info = descriptor_set_layout.get_layout_binding(binding_index))
{
// Iterate over all binding images in array
for (auto &element_it : binding_resources)
{
auto &image_info = element_it.second;
VkWriteDescriptorSet write_descriptor_set{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
write_descriptor_set.dstBinding = binding_index;
write_descriptor_set.descriptorType = binding_info->descriptorType;
write_descriptor_set.pImageInfo = &image_info;
write_descriptor_set.dstSet = handle;
write_descriptor_set.dstArrayElement = element_it.first;
write_descriptor_set.descriptorCount = 1;
write_descriptor_sets.push_back(write_descriptor_set);
}
}
else
{
LOGE("Shader layout set does not use image binding at #{}", binding_index);
}
}
}
void DescriptorSet::update(const std::vector<uint32_t> &bindings_to_update)
{
std::vector<VkWriteDescriptorSet> write_operations;
std::vector<size_t> write_operation_hashes;
// If the 'bindings_to_update' vector is empty, we want to write to all the bindings
// (but skipping all to-update bindings that haven't been written yet)
if (bindings_to_update.empty())
{
for (size_t i = 0; i < write_descriptor_sets.size(); i++)
{
const auto &write_operation = write_descriptor_sets[i];
size_t write_operation_hash = 0;
hash_param(write_operation_hash, write_operation);
auto update_pair_it = updated_bindings.find(write_operation.dstBinding);
if (update_pair_it == updated_bindings.end() || update_pair_it->second != write_operation_hash)
{
write_operations.push_back(write_operation);
write_operation_hashes.push_back(write_operation_hash);
}
}
}
else
{
// Otherwise we want to update the binding indices present in the 'bindings_to_update' vector.
// (again, skipping those to update but not updated yet)
for (size_t i = 0; i < write_descriptor_sets.size(); i++)
{
const auto &write_operation = write_descriptor_sets[i];
if (std::ranges::find(bindings_to_update, write_operation.dstBinding) != bindings_to_update.end())
{
size_t write_operation_hash = 0;
hash_param(write_operation_hash, write_operation);
auto update_pair_it = updated_bindings.find(write_operation.dstBinding);
if (update_pair_it == updated_bindings.end() || update_pair_it->second != write_operation_hash)
{
write_operations.push_back(write_operation);
write_operation_hashes.push_back(write_operation_hash);
}
}
}
}
// Perform the Vulkan call to update the DescriptorSet by executing the write operations
if (!write_operations.empty())
{
vkUpdateDescriptorSets(device.get_handle(),
to_u32(write_operations.size()),
write_operations.data(),
0,
nullptr);
}
// Store the bindings from the write operations that were executed by vkUpdateDescriptorSets (and their hash)
// to prevent overwriting by future calls to "update()"
for (size_t i = 0; i < write_operations.size(); i++)
{
updated_bindings[write_operations[i].dstBinding] = write_operation_hashes[i];
}
}
void DescriptorSet::apply_writes() const
{
vkUpdateDescriptorSets(device.get_handle(),
to_u32(write_descriptor_sets.size()),
write_descriptor_sets.data(),
0,
nullptr);
}
DescriptorSet::DescriptorSet(DescriptorSet &&other) :
device{other.device},
descriptor_set_layout{other.descriptor_set_layout},
descriptor_pool{other.descriptor_pool},
buffer_infos{std::move(other.buffer_infos)},
image_infos{std::move(other.image_infos)},
handle{other.handle},
write_descriptor_sets{std::move(other.write_descriptor_sets)},
updated_bindings{std::move(other.updated_bindings)}
{
other.handle = VK_NULL_HANDLE;
}
VkDescriptorSet DescriptorSet::get_handle() const
{
return handle;
}
const DescriptorSetLayout &DescriptorSet::get_layout() const
{
return descriptor_set_layout;
}
BindingMap<VkDescriptorBufferInfo> &DescriptorSet::get_buffer_infos()
{
return buffer_infos;
}
BindingMap<VkDescriptorImageInfo> &DescriptorSet::get_image_infos()
{
return image_infos;
}
} // namespace vkb
+125
View File
@@ -0,0 +1,125 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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 "common/helpers.h"
#include "common/vk_common.h"
namespace vkb
{
class DescriptorSetLayout;
class DescriptorPool;
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceC = Device<vkb::BindingType::C>;
} // namespace core
/**
* @brief A descriptor set handle allocated from a \ref DescriptorPool.
* Destroying the handle has no effect, as the pool manages the lifecycle of its descriptor sets.
*
* Keeps track of what bindings were written to prevent a double write.
*/
class DescriptorSet
{
public:
/**
* @brief Constructs a descriptor set from buffer infos and image infos
* Implicitly calls prepare()
* @param device A valid Vulkan device
* @param descriptor_set_layout The Vulkan descriptor set layout this descriptor set has
* @param descriptor_pool The Vulkan descriptor pool the descriptor set is allocated from
* @param buffer_infos The descriptors that describe buffer data
* @param image_infos The descriptors that describe image data
*/
DescriptorSet(vkb::core::DeviceC &device,
const DescriptorSetLayout &descriptor_set_layout,
DescriptorPool &descriptor_pool,
const BindingMap<VkDescriptorBufferInfo> &buffer_infos = {},
const BindingMap<VkDescriptorImageInfo> &image_infos = {});
DescriptorSet(const DescriptorSet &) = delete;
DescriptorSet(DescriptorSet &&other);
// The descriptor set handle is managed by the pool, and will be destroyed when the pool is reset
~DescriptorSet() = default;
DescriptorSet &operator=(const DescriptorSet &) = delete;
DescriptorSet &operator=(DescriptorSet &&) = delete;
/**
* @brief Resets the DescriptorSet state
* Optionally prepares a new set of buffer infos and/or image infos
* @param new_buffer_infos A map of buffer descriptors and their respective bindings
* @param new_image_infos A map of image descriptors and their respective bindings
*/
void reset(const BindingMap<VkDescriptorBufferInfo> &new_buffer_infos = {},
const BindingMap<VkDescriptorImageInfo> &new_image_infos = {});
/**
* @brief Updates the contents of the DescriptorSet by performing the write operations
* @param bindings_to_update If empty. we update all bindings. Otherwise, only write the specified bindings if they haven't already been written
*/
void update(const std::vector<uint32_t> &bindings_to_update = {});
/**
* @brief Applies pending write operations without updating the state
*/
void apply_writes() const;
const DescriptorSetLayout &get_layout() const;
VkDescriptorSet get_handle() const;
BindingMap<VkDescriptorBufferInfo> &get_buffer_infos();
BindingMap<VkDescriptorImageInfo> &get_image_infos();
protected:
/**
* @brief Prepares the descriptor set to have its contents updated by loading a vector of write operations
* Cannot be called twice during the lifetime of a DescriptorSet
*/
void prepare();
private:
vkb::core::DeviceC &device;
const DescriptorSetLayout &descriptor_set_layout;
DescriptorPool &descriptor_pool;
BindingMap<VkDescriptorBufferInfo> buffer_infos;
BindingMap<VkDescriptorImageInfo> image_infos;
VkDescriptorSet handle{VK_NULL_HANDLE};
// The list of write operations for the descriptor set
std::vector<VkWriteDescriptorSet> write_descriptor_sets;
// The bindings of the write descriptors that have had vkUpdateDescriptorSets since the last call to update().
// Each binding number is mapped to a hash of the binding description that it will be updated to.
std::unordered_map<uint32_t, size_t> updated_bindings;
};
} // namespace vkb
+275
View File
@@ -0,0 +1,275 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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.
*/
#include "descriptor_set_layout.h"
#include "device.h"
#include "physical_device.h"
#include "shader_module.h"
namespace vkb
{
namespace
{
inline VkDescriptorType find_descriptor_type(ShaderResourceType resource_type, bool dynamic)
{
switch (resource_type)
{
case ShaderResourceType::InputAttachment:
return VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
break;
case ShaderResourceType::Image:
return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
break;
case ShaderResourceType::ImageSampler:
return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
break;
case ShaderResourceType::ImageStorage:
return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
break;
case ShaderResourceType::Sampler:
return VK_DESCRIPTOR_TYPE_SAMPLER;
break;
case ShaderResourceType::BufferUniform:
if (dynamic)
{
return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
}
else
{
return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
}
break;
case ShaderResourceType::BufferStorage:
if (dynamic)
{
return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC;
}
else
{
return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
}
break;
default:
throw std::runtime_error("No conversion possible for the shader resource type.");
break;
}
}
inline bool validate_binding(const VkDescriptorSetLayoutBinding &binding, const std::vector<VkDescriptorType> &blacklist)
{
return !(std::ranges::find_if(blacklist, [binding](const VkDescriptorType &type) { return type == binding.descriptorType; }) != blacklist.end());
}
inline bool validate_flags(const PhysicalDevice &gpu, const std::vector<VkDescriptorSetLayoutBinding> &bindings, const std::vector<VkDescriptorBindingFlagsEXT> &flags)
{
// Assume bindings are valid if there are no flags
if (flags.empty())
{
return true;
}
// Binding count has to equal flag count as its a 1:1 mapping
if (bindings.size() != flags.size())
{
LOGE("Binding count has to be equal to flag count.");
return false;
}
return true;
}
} // namespace
DescriptorSetLayout::DescriptorSetLayout(vkb::core::DeviceC &device,
const uint32_t set_index,
const std::vector<ShaderModule *> &shader_modules,
const std::vector<ShaderResource> &resource_set) :
device{device},
set_index{set_index},
shader_modules{shader_modules}
{
// NOTE: `shader_modules` is passed in mainly for hashing their handles in `request_resource`.
// This way, different pipelines (with different shaders / shader variants) will get
// different descriptor set layouts (incl. appropriate name -> binding lookups)
for (auto &resource : resource_set)
{
// Skip shader resources whitout a binding point
if (resource.type == ShaderResourceType::Input ||
resource.type == ShaderResourceType::Output ||
resource.type == ShaderResourceType::PushConstant ||
resource.type == ShaderResourceType::SpecializationConstant)
{
continue;
}
// Convert from ShaderResourceType to VkDescriptorType.
auto descriptor_type = find_descriptor_type(resource.type, resource.mode == ShaderResourceMode::Dynamic);
if (resource.mode == ShaderResourceMode::UpdateAfterBind)
{
binding_flags.push_back(VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT);
}
else
{
// When creating a descriptor set layout, if we give a structure to create_info.pNext, each binding needs to have a binding flag
// (pBindings[i] uses the flags in pBindingFlags[i])
// Adding 0 ensures the bindings that dont use any flags are mapped correctly.
binding_flags.push_back(0);
}
// Convert ShaderResource to VkDescriptorSetLayoutBinding
VkDescriptorSetLayoutBinding layout_binding{};
layout_binding.binding = resource.binding;
layout_binding.descriptorCount = resource.array_size;
layout_binding.descriptorType = descriptor_type;
layout_binding.stageFlags = static_cast<VkShaderStageFlags>(resource.stages);
bindings.push_back(layout_binding);
// Store mapping between binding and the binding point
bindings_lookup.emplace(resource.binding, layout_binding);
binding_flags_lookup.emplace(resource.binding, binding_flags.back());
resources_lookup.emplace(resource.name, resource.binding);
}
VkDescriptorSetLayoutCreateInfo create_info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
create_info.flags = 0;
create_info.bindingCount = to_u32(bindings.size());
create_info.pBindings = bindings.data();
// Handle update-after-bind extensions
VkDescriptorSetLayoutBindingFlagsCreateInfoEXT binding_flags_create_info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT};
if (std::ranges::find_if(resource_set,
[](const ShaderResource &shader_resource) { return shader_resource.mode == ShaderResourceMode::UpdateAfterBind; }) != resource_set.end())
{
// Spec states you can't have ANY dynamic resources if you have one of the bindings set to update-after-bind
if (std::ranges::find_if(resource_set,
[](const ShaderResource &shader_resource) { return shader_resource.mode == ShaderResourceMode::Dynamic; }) != resource_set.end())
{
throw std::runtime_error("Cannot create descriptor set layout, dynamic resources are not allowed if at least one resource is update-after-bind.");
}
if (!validate_flags(device.get_gpu(), bindings, binding_flags))
{
throw std::runtime_error("Invalid binding, couldn't create descriptor set layout.");
}
binding_flags_create_info.bindingCount = to_u32(binding_flags.size());
binding_flags_create_info.pBindingFlags = binding_flags.data();
create_info.pNext = &binding_flags_create_info;
create_info.flags |= std::ranges::find(binding_flags, VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT_EXT) != binding_flags.end() ? VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT_EXT : 0;
}
// Create the Vulkan descriptor set layout handle
VkResult result = vkCreateDescriptorSetLayout(device.get_handle(), &create_info, nullptr, &handle);
if (result != VK_SUCCESS)
{
throw VulkanException{result, "Cannot create DescriptorSetLayout"};
}
}
DescriptorSetLayout::DescriptorSetLayout(DescriptorSetLayout &&other) :
device{other.device},
shader_modules{other.shader_modules},
handle{other.handle},
set_index{other.set_index},
bindings{std::move(other.bindings)},
binding_flags{std::move(other.binding_flags)},
bindings_lookup{std::move(other.bindings_lookup)},
binding_flags_lookup{std::move(other.binding_flags_lookup)},
resources_lookup{std::move(other.resources_lookup)}
{
other.handle = VK_NULL_HANDLE;
}
DescriptorSetLayout::~DescriptorSetLayout()
{
// Destroy descriptor set layout
if (handle != VK_NULL_HANDLE)
{
vkDestroyDescriptorSetLayout(device.get_handle(), handle, nullptr);
}
}
VkDescriptorSetLayout DescriptorSetLayout::get_handle() const
{
return handle;
}
const uint32_t DescriptorSetLayout::get_index() const
{
return set_index;
}
const std::vector<VkDescriptorSetLayoutBinding> &DescriptorSetLayout::get_bindings() const
{
return bindings;
}
const std::vector<VkDescriptorBindingFlagsEXT> &DescriptorSetLayout::get_binding_flags() const
{
return binding_flags;
}
std::unique_ptr<VkDescriptorSetLayoutBinding> DescriptorSetLayout::get_layout_binding(uint32_t binding_index) const
{
auto it = bindings_lookup.find(binding_index);
if (it == bindings_lookup.end())
{
return nullptr;
}
return std::make_unique<VkDescriptorSetLayoutBinding>(it->second);
}
std::unique_ptr<VkDescriptorSetLayoutBinding> DescriptorSetLayout::get_layout_binding(const std::string &name) const
{
auto it = resources_lookup.find(name);
if (it == resources_lookup.end())
{
return nullptr;
}
return get_layout_binding(it->second);
}
VkDescriptorBindingFlagsEXT DescriptorSetLayout::get_layout_binding_flag(const uint32_t binding_index) const
{
auto it = binding_flags_lookup.find(binding_index);
if (it == binding_flags_lookup.end())
{
return 0;
}
return it->second;
}
const std::vector<ShaderModule *> &DescriptorSetLayout::get_shader_modules() const
{
return shader_modules;
}
} // namespace vkb
+101
View File
@@ -0,0 +1,101 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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 "common/helpers.h"
#include "common/vk_common.h"
namespace vkb
{
class DescriptorPool;
class ShaderModule;
struct ShaderResource;
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceC = Device<vkb::BindingType::C>;
} // namespace core
/**
* @brief Caches DescriptorSet objects for the shader's set index.
* Creates a DescriptorPool to allocate the DescriptorSet objects
*/
class DescriptorSetLayout
{
public:
/**
* @brief Creates a descriptor set layout from a set of resources
* @param device A valid Vulkan device
* @param set_index The descriptor set index this layout maps to
* @param shader_modules The shader modules this set layout will be used for
* @param resource_set A grouping of shader resources belonging to the same set
*/
DescriptorSetLayout(vkb::core::DeviceC &device,
const uint32_t set_index,
const std::vector<ShaderModule *> &shader_modules,
const std::vector<ShaderResource> &resource_set);
DescriptorSetLayout(const DescriptorSetLayout &) = delete;
DescriptorSetLayout(DescriptorSetLayout &&other);
~DescriptorSetLayout();
DescriptorSetLayout &operator=(const DescriptorSetLayout &) = delete;
DescriptorSetLayout &operator=(DescriptorSetLayout &&) = delete;
VkDescriptorSetLayout get_handle() const;
const uint32_t get_index() const;
const std::vector<VkDescriptorSetLayoutBinding> &get_bindings() const;
std::unique_ptr<VkDescriptorSetLayoutBinding> get_layout_binding(const uint32_t binding_index) const;
std::unique_ptr<VkDescriptorSetLayoutBinding> get_layout_binding(const std::string &name) const;
const std::vector<VkDescriptorBindingFlagsEXT> &get_binding_flags() const;
VkDescriptorBindingFlagsEXT get_layout_binding_flag(const uint32_t binding_index) const;
const std::vector<ShaderModule *> &get_shader_modules() const;
private:
vkb::core::DeviceC &device;
VkDescriptorSetLayout handle{VK_NULL_HANDLE};
const uint32_t set_index;
std::vector<VkDescriptorSetLayoutBinding> bindings;
std::vector<VkDescriptorBindingFlagsEXT> binding_flags;
std::unordered_map<uint32_t, VkDescriptorSetLayoutBinding> bindings_lookup;
std::unordered_map<uint32_t, VkDescriptorBindingFlagsEXT> binding_flags_lookup;
std::unordered_map<std::string, uint32_t> resources_lookup;
std::vector<ShaderModule *> shader_modules;
};
} // namespace vkb
+773
View File
@@ -0,0 +1,773 @@
/* Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "common/hpp_vk_common.h"
#include "common/vk_common.h"
#include "core/buffer.h"
#include "core/hpp_physical_device.h"
#include "hpp_debug.h"
#include "hpp_fence_pool.h"
#include "hpp_queue.h"
#include "hpp_resource_cache.h"
#include "queue.h"
#include <utility>
#include <vulkan/vulkan.hpp>
namespace vkb
{
class FencePool;
class DebugUtils;
class PhysicalDevice;
class ResourceCache;
namespace core
{
template <vkb::BindingType bindingType>
class CommandPool;
using CommandPoolC = CommandPool<vkb::BindingType::C>;
using CommandPoolCpp = CommandPool<vkb::BindingType::Cpp>;
template <vkb::BindingType bindingType>
class Device
: public vkb::core::VulkanResource<bindingType, typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Device, VkDevice>::type>
{
public:
using Bool32Type = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Bool32, VkBool32>::type;
using BufferCopyType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::BufferCopy, VkBufferCopy>::type;
using CommandBufferLevelType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::CommandBufferLevel, VkCommandBufferLevel>::type;
using CommandBufferType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::CommandBuffer, VkCommandBuffer>::type;
using CommandPoolCreateFlagsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::CommandPoolCreateFlags, VkCommandPoolCreateFlags>::type;
using CommandPoolType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::CommandPool, VkCommandPool>::type;
using DeviceMemoryType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DeviceMemory, VkDeviceMemory>::type;
using DeviceType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Device, VkDevice>::type;
using Extent2DType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Extent2D, VkExtent2D>::type;
using FenceType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Fence, VkFence>::type;
using FormatType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Format, VkFormat>::type;
using ImageType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Image, VkImage>::type;
using ImageUsageFlagsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::ImageUsageFlags, VkImageUsageFlags>::type;
using MemoryPropertyFlagsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::MemoryPropertyFlags, VkMemoryPropertyFlags>::type;
using QueueFamilyPropertiesType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::QueueFamilyProperties, VkQueueFamilyProperties>::type;
using QueueFlagBitsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::QueueFlagBits, VkQueueFlagBits>::type;
using QueueFlagsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::QueueFlags, VkQueueFlags>::type;
using QueueType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Queue, VkQueue>::type;
using ResultType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Result, VkResult>::type;
using SemaphoreType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Semaphore, VkSemaphore>::type;
using SurfaceType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::SurfaceKHR, VkSurfaceKHR>::type;
using DebugUtilsType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vkb::core::HPPDebugUtils, vkb::DebugUtils>::type;
using FencePoolType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vkb::HPPFencePool, vkb::FencePool>::type;
using PhysicalDeviceType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vkb::core::HPPPhysicalDevice, vkb::PhysicalDevice>::type;
using CoreQueueType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vkb::core::HPPQueue, vkb::Queue>::type;
using ResourceCacheType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vkb::HPPResourceCache, vkb::ResourceCache>::type;
public:
/**
* @brief Device constructor
* @param gpu A valid Vulkan physical device and the requested gpu features
* @param surface The surface
* @param debug_utils The debug utils to be associated to this device
* @param requested_extensions (Optional) List of required device extensions and whether support is optional or not
*/
Device(PhysicalDeviceType &gpu,
SurfaceType surface,
std::unique_ptr<DebugUtilsType> &&debug_utils,
std::unordered_map<const char *, bool> const &requested_extensions = {});
/**
* @brief Device constructor
* @param gpu A valid Vulkan physical device and the requested gpu features
* @param vulkan_device A valid Vulkan device
* @param surface The surface
*/
Device(PhysicalDeviceType &gpu, DeviceType &vulkan_device, SurfaceType surface);
Device(const Device &) = delete;
Device(Device &&) = delete;
~Device();
Device &operator=(const Device &) = delete;
Device &operator=(Device &&) = delete;
void add_queue(size_t global_index, uint32_t family_index, QueueFamilyPropertiesType const &properties, Bool32Type can_present);
void copy_buffer(
vkb::core::Buffer<bindingType> const &src, vkb::core::Buffer<bindingType> &dst, QueueType queue, BufferCopyType const *copy_region = nullptr);
CommandBufferType create_command_buffer(CommandBufferLevelType level, bool begin = false) const;
CommandPoolType create_command_pool(uint32_t queue_index, CommandPoolCreateFlagsType flags = 0);
std::pair<ImageType, DeviceMemoryType> create_image(
FormatType format, Extent2DType const &extent, uint32_t mip_levels, ImageUsageFlagsType usage, MemoryPropertyFlagsType properties) const;
void create_internal_command_pool();
void create_internal_fence_pool();
void flush_command_buffer(CommandBufferType command_buffer, QueueType queue, bool free = true, SemaphoreType signal_semaphore = VK_NULL_HANDLE) const;
vkb::core::CommandPool<bindingType> &get_command_pool() const;
DebugUtilsType const &get_debug_utils() const;
FencePoolType &get_fence_pool() const;
PhysicalDeviceType const &get_gpu() const;
CoreQueueType const &get_queue(uint32_t queue_family_index, uint32_t queue_index) const;
CoreQueueType const &get_queue_by_flags(QueueFlagsType queue_flags, uint32_t queue_index) const;
CoreQueueType const &get_queue_by_present(uint32_t queue_index) const;
ResourceCacheType &get_resource_cache();
bool is_extension_enabled(const char *extension) const;
bool is_image_format_supported(FormatType format) const;
void wait_idle() const;
private:
void copy_buffer_impl(vk::Device device, vkb::core::BufferCpp const &src, vkb::core::BufferCpp &dst, vk::Queue queue, vk::BufferCopy const *copy_region);
vk::CommandBuffer create_command_buffer_impl(vk::Device device, vk::CommandBufferLevel level, bool begin) const;
std::pair<vk::Image, vk::DeviceMemory> create_image_impl(
vk::Device device, vk::Format format, vk::Extent2D const &extent, uint32_t mip_levels, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties)
const;
void flush_command_buffer_impl(
vk::Device device, vk::CommandBuffer command_buffer, vk::Queue queue, bool free = true, vk::Semaphore signal_semaphore = nullptr) const;
vkb::core::HPPQueue const &get_queue_by_flags_impl(vk::QueueFlags queue_flags, uint32_t queue_index) const;
void init(std::unordered_map<const char *, bool> const &requested_extensions);
private:
std::unique_ptr<vkb::core::CommandPoolCpp> command_pool;
std::unique_ptr<vkb::core::HPPDebugUtils> debug_utils;
std::vector<const char *> enabled_extensions{};
std::unique_ptr<vkb::HPPFencePool> fence_pool;
vkb::core::HPPPhysicalDevice &gpu;
std::vector<std::vector<vkb::core::HPPQueue>> queues;
vkb::HPPResourceCache resource_cache;
vk::SurfaceKHR surface = nullptr;
};
using DeviceC = Device<vkb::BindingType::C>;
using DeviceCpp = Device<vkb::BindingType::Cpp>;
} // namespace core
} // namespace vkb
#include "core/command_pool.h"
namespace vkb
{
namespace core
{
template <>
inline Device<vkb::BindingType::Cpp>::Device(vkb::core::HPPPhysicalDevice &gpu,
vk::SurfaceKHR surface,
std::unique_ptr<vkb::core::HPPDebugUtils> &&debug_utils,
std::unordered_map<const char *, bool> const &requested_extensions) :
vkb::core::VulkanResourceCpp<vk::Device>{nullptr, this}, debug_utils{std::move(debug_utils)}, gpu{gpu}, resource_cache{*this}, surface(surface)
{
init(requested_extensions);
}
template <>
inline Device<vkb::BindingType::C>::Device(vkb::PhysicalDevice &gpu,
VkSurfaceKHR surface,
std::unique_ptr<vkb::DebugUtils> &&debug_utils,
std::unordered_map<const char *, bool> const &requested_extensions) :
vkb::core::VulkanResourceC<VkDevice>{VK_NULL_HANDLE, this}, debug_utils{reinterpret_cast<vkb::core::HPPDebugUtils *>(debug_utils.release())}, gpu{reinterpret_cast<vkb::core::HPPPhysicalDevice &>(gpu)}, resource_cache{*reinterpret_cast<vkb::core::DeviceCpp *>(this)}, surface(static_cast<vk::SurfaceKHR>(surface))
{
init(requested_extensions);
}
template <>
inline Device<vkb::BindingType::Cpp>::Device(vkb::core::HPPPhysicalDevice &gpu, vk::Device &vulkan_device, vk::SurfaceKHR surface) :
VulkanResource{vulkan_device}, gpu{gpu}, surface{surface}, resource_cache{*this}
{
debug_utils = std::make_unique<HPPDummyDebugUtils>();
}
template <>
inline Device<vkb::BindingType::C>::Device(vkb::PhysicalDevice &gpu, VkDevice &vulkan_device, VkSurfaceKHR surface) :
VulkanResource{vulkan_device}, gpu{reinterpret_cast<vkb::core::HPPPhysicalDevice &>(gpu)}, resource_cache{*reinterpret_cast<vkb::core::DeviceCpp *>(this)}, surface{static_cast<vk::SurfaceKHR>(surface)}
{
debug_utils = std::make_unique<HPPDummyDebugUtils>();
}
template <vkb::BindingType bindingType>
inline Device<bindingType>::~Device()
{
resource_cache.clear();
command_pool.reset();
fence_pool.reset();
vkb::allocated::shutdown();
if (this->get_handle())
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
this->get_handle().destroy();
}
else
{
static_cast<vk::Device>(this->get_handle()).destroy();
}
}
}
template <vkb::BindingType bindingType>
inline void Device<bindingType>::add_queue(size_t global_index, uint32_t family_index, QueueFamilyPropertiesType const &properties, Bool32Type can_present)
{
if (queues.size() <= global_index)
{
queues.resize(global_index + 1);
}
if constexpr (bindingType == vkb::BindingType::Cpp)
{
queues[global_index].emplace_back(*this, family_index, properties, can_present, 0);
}
else
{
queues[global_index].emplace_back(*reinterpret_cast<vkb::core::DeviceCpp *>(this),
family_index,
reinterpret_cast<vk::QueueFamilyProperties const &>(properties),
static_cast<vk::Bool32>(can_present),
0);
}
}
template <vkb::BindingType bindingType>
inline void Device<bindingType>::copy_buffer(vkb::core::Buffer<bindingType> const &src,
vkb::core::Buffer<bindingType> &dst,
QueueType queue,
BufferCopyType const *copy_region)
{
assert(dst.get_size() <= src.get_size());
assert(src.get_handle());
if constexpr (bindingType == vkb::BindingType::Cpp)
{
copy_buffer_impl(this->get_handle(), src, dst, queue, copy_region);
}
else
{
copy_buffer_impl(static_cast<vk::Device>(this->get_handle()), reinterpret_cast<vkb::core::BufferCpp const &>(src),
reinterpret_cast<vkb::core::BufferCpp &>(dst),
static_cast<vk::Queue>(queue),
reinterpret_cast<vk::BufferCopy const *>(copy_region));
}
}
template <vkb::BindingType bindingType>
inline typename Device<bindingType>::CommandBufferType Device<bindingType>::create_command_buffer(CommandBufferLevelType level, bool begin) const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return create_command_buffer_impl(this->get_handle(), level, begin);
}
else
{
return static_cast<VkCommandBuffer>(
create_command_buffer_impl(static_cast<vk::Device>(this->get_handle()), static_cast<vk::CommandBufferLevel>(level), begin));
}
}
template <vkb::BindingType bindingType>
inline typename Device<bindingType>::CommandPoolType Device<bindingType>::create_command_pool(uint32_t queue_index, CommandPoolCreateFlagsType flags)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
vk::CommandPoolCreateInfo command_pool_info{.flags = flags, .queueFamilyIndex = queue_index};
return this->get_handle().createCommandPool(command_pool_info);
}
else
{
vk::CommandPoolCreateInfo command_pool_info{.flags = static_cast<vk::CommandPoolCreateFlags>(flags), .queueFamilyIndex = queue_index};
return static_cast<vk::Device>(this->get_handle()).createCommandPool(command_pool_info);
}
}
template <vkb::BindingType bindingType>
inline std::pair<typename Device<bindingType>::ImageType, typename Device<bindingType>::DeviceMemoryType> Device<bindingType>::create_image(
FormatType format, Extent2DType const &extent, uint32_t mip_levels, ImageUsageFlagsType usage, MemoryPropertyFlagsType properties) const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return create_image_impl(this->get_handle(), format, extent, mip_levels, usage, properties);
}
else
{
return static_cast<std::pair<VkImage, VkDeviceMemory>>(create_image_impl(static_cast<vk::Device>(this->get_handle()),
static_cast<VkFormat>(format),
static_cast<VkExtent2D>(extent),
mip_levels,
static_cast<VkImageUsageFlags>(usage),
static_cast<VkMemoryPropertyFlags>(properties)));
}
}
template <vkb::BindingType bindingType>
inline void Device<bindingType>::create_internal_command_pool()
{
uint32_t familyIndex = get_queue_by_flags_impl(vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eCompute, 0).get_family_index();
if constexpr (bindingType == vkb::BindingType::Cpp)
{
command_pool = std::make_unique<vkb::core::CommandPoolCpp>(*this, familyIndex);
}
else
{
command_pool = std::make_unique<vkb::core::CommandPoolCpp>(*reinterpret_cast<vkb::core::DeviceCpp *>(this), familyIndex);
}
}
template <vkb::BindingType bindingType>
inline void Device<bindingType>::create_internal_fence_pool()
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
fence_pool = std::make_unique<vkb::HPPFencePool>(*this);
}
else
{
fence_pool = std::make_unique<vkb::HPPFencePool>(*reinterpret_cast<vkb::core::DeviceCpp *>(this));
}
}
template <vkb::BindingType bindingType>
inline void Device<bindingType>::flush_command_buffer(CommandBufferType command_buffer, QueueType queue, bool free, SemaphoreType signal_semaphore) const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
flush_command_buffer_impl(this->get_handle(), command_buffer, queue, free, signal_semaphore);
}
else
{
flush_command_buffer_impl(static_cast<vk::Device>(this->get_handle()),
static_cast<vk::CommandBuffer>(command_buffer),
static_cast<vk::Queue>(queue),
free,
static_cast<vk::Semaphore>(signal_semaphore));
}
}
template <vkb::BindingType bindingType>
inline vkb::core::CommandPool<bindingType> &Device<bindingType>::get_command_pool() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return *command_pool;
}
else
{
return reinterpret_cast<vkb::core::CommandPoolC &>(*command_pool);
}
}
template <vkb::BindingType bindingType>
inline typename Device<bindingType>::DebugUtilsType const &Device<bindingType>::get_debug_utils() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return *debug_utils;
}
else
{
return reinterpret_cast<vkb::DebugUtils const &>(*debug_utils);
}
}
template <vkb::BindingType bindingType>
inline typename Device<bindingType>::FencePoolType &Device<bindingType>::get_fence_pool() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return *fence_pool;
}
else
{
return reinterpret_cast<vkb::FencePool &>(*fence_pool);
}
}
template <vkb::BindingType bindingType>
inline typename Device<bindingType>::PhysicalDeviceType const &Device<bindingType>::get_gpu() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return gpu;
}
else
{
return reinterpret_cast<vkb::PhysicalDevice const &>(gpu);
}
}
template <vkb::BindingType bindingType>
inline typename Device<bindingType>::CoreQueueType const &Device<bindingType>::get_queue(uint32_t queue_family_index, uint32_t queue_index) const
{
assert(queue_family_index < queues.size() && "Queue family index out of bounds");
assert(queue_index < queues[queue_family_index].size() && "Queue index out of bounds");
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return queues[queue_family_index][queue_index];
}
else
{
return reinterpret_cast<vkb::Queue const &>(queues[queue_family_index][queue_index]);
}
}
template <vkb::BindingType bindingType>
inline typename Device<bindingType>::CoreQueueType const &Device<bindingType>::get_queue_by_flags(QueueFlagsType required_queue_flags,
uint32_t queue_index) const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return get_queue_by_flags_impl(required_queue_flags, queue_index);
}
else
{
return reinterpret_cast<vkb::Queue const &>(get_queue_by_flags_impl(static_cast<vk::QueueFlags>(required_queue_flags), queue_index));
}
}
template <vkb::BindingType bindingType>
inline typename Device<bindingType>::CoreQueueType const &Device<bindingType>::get_queue_by_present(uint32_t queue_index) const
{
auto queueIt =
std::ranges::find_if(queues,
[queue_index](const std::vector<vkb::core::HPPQueue> &queue_family) { return !queue_family.empty() && queue_index < queue_family[0].get_properties().queueCount && queue_family[0].support_present(); });
if (queueIt != queues.end())
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return (*queueIt)[queue_index];
}
else
{
return reinterpret_cast<vkb::Queue const &>((*queueIt)[queue_index]);
}
}
throw std::runtime_error("Queue not found");
}
template <vkb::BindingType bindingType>
inline typename Device<bindingType>::ResourceCacheType &Device<bindingType>::get_resource_cache()
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return resource_cache;
}
else
{
return reinterpret_cast<vkb::ResourceCache &>(resource_cache);
}
}
template <vkb::BindingType bindingType>
inline bool Device<bindingType>::is_extension_enabled(const char *extension) const
{
return std::ranges::find_if(enabled_extensions, [extension](const char *enabled_extension) { return strcmp(extension, enabled_extension) == 0; }) !=
enabled_extensions.end();
}
template <vkb::BindingType bindingType>
inline bool Device<bindingType>::is_image_format_supported(FormatType format) const
{
// as we want to check for an error (vk::Result::eErrorFormatNotSupported) we use the non-throwing version of getImageFormatProperties here
vk::ImageFormatProperties format_properties;
return vk::Result::eErrorFormatNotSupported !=
gpu.get_handle().getImageFormatProperties(
static_cast<vk::Format>(format), vk::ImageType::e2D, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eSampled, {}, &format_properties);
}
template <vkb::BindingType bindingType>
inline void Device<bindingType>::wait_idle() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
this->get_handle().waitIdle();
}
else
{
static_cast<vk::Device>(this->get_handle()).waitIdle();
}
}
template <vkb::BindingType bindingType>
inline void Device<bindingType>::copy_buffer_impl(
vk::Device device, vkb::core::BufferCpp const &src, vkb::core::BufferCpp &dst, vk::Queue queue, vk::BufferCopy const *copy_region)
{
vk::CommandBuffer command_buffer = create_command_buffer_impl(device, vk::CommandBufferLevel::ePrimary, true);
vk::BufferCopy buffer_copy;
if (copy_region == nullptr)
{
buffer_copy.size = src.get_size();
}
else
{
buffer_copy = *copy_region;
}
command_buffer.copyBuffer(src.get_handle(), dst.get_handle(), buffer_copy);
flush_command_buffer_impl(device, command_buffer, queue);
}
template <vkb::BindingType bindingType>
inline vk::CommandBuffer Device<bindingType>::create_command_buffer_impl(vk::Device device, vk::CommandBufferLevel level, bool begin) const
{
assert(command_pool && "No command pool exists in the device");
vk::CommandBufferAllocateInfo command_buffer_allocate_info{.commandPool = command_pool->get_handle(), .level = level, .commandBufferCount = 1};
vk::CommandBuffer command_buffer = device.allocateCommandBuffers(command_buffer_allocate_info).front();
// If requested, also start recording for the new command buffer
if (begin)
{
command_buffer.begin(vk::CommandBufferBeginInfo());
}
return command_buffer;
}
template <vkb::BindingType bindingType>
inline std::pair<vk::Image, vk::DeviceMemory> Device<bindingType>::create_image_impl(
vk::Device device, vk::Format format, vk::Extent2D const &extent, uint32_t mip_levels, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties) const
{
vk::ImageCreateInfo image_create_info{.imageType = vk::ImageType::e2D,
.format = format,
.extent = {.width = extent.width, .height = extent.height, .depth = 1},
.mipLevels = mip_levels,
.arrayLayers = 1,
.samples = vk::SampleCountFlagBits::e1,
.tiling = vk::ImageTiling::eOptimal,
.usage = usage};
vk::Image image = device.createImage(image_create_info);
vk::MemoryRequirements memory_requirements = device.getImageMemoryRequirements(image);
vk::MemoryAllocateInfo memory_allocation{.allocationSize = memory_requirements.size,
.memoryTypeIndex = gpu.get_memory_type(memory_requirements.memoryTypeBits, properties)};
vk::DeviceMemory memory = device.allocateMemory(memory_allocation);
device.bindImageMemory(image, memory, 0);
return std::make_pair(image, memory);
}
template <vkb::BindingType bindingType>
inline void Device<bindingType>::flush_command_buffer_impl(
vk::Device device, vk::CommandBuffer command_buffer, vk::Queue queue, bool free, vk::Semaphore signal_semaphore) const
{
if (command_buffer)
{
command_buffer.end();
vk::SubmitInfo submit_info{.commandBufferCount = 1, .pCommandBuffers = &command_buffer};
if (signal_semaphore)
{
submit_info.setSignalSemaphores(signal_semaphore);
}
// Create fence to ensure that the command buffer has finished executing
vk::Fence fence = device.createFence({});
// Submit to the queue
queue.submit(submit_info, fence);
// Wait for the fence to signal that command buffer has finished executing
vk::Result result = device.waitForFences(fence, true, DEFAULT_FENCE_TIMEOUT);
if (result != vk::Result::eSuccess)
{
LOGE("Detected Vulkan error: {}", vkb::to_string(result));
abort();
}
device.destroyFence(fence);
if (command_pool && free)
{
device.freeCommandBuffers(command_pool->get_handle(), command_buffer);
}
}
}
template <vkb::BindingType bindingType>
vkb::core::HPPQueue const &Device<bindingType>::get_queue_by_flags_impl(vk::QueueFlags required_queue_flags, uint32_t queue_index) const
{
auto queueIt =
std::ranges::find_if(queues,
[required_queue_flags, queue_index](const std::vector<vkb::core::HPPQueue> &queue) {
assert(!queue.empty());
vk::QueueFamilyProperties const &properties = queue[0].get_properties();
return ((properties.queueFlags & required_queue_flags) == required_queue_flags) && (queue_index < properties.queueCount);
});
if (queueIt == queues.end())
{
throw std::runtime_error("Queue not found");
}
return (*queueIt)[queue_index];
}
template <vkb::BindingType bindingType>
inline void Device<bindingType>::init(std::unordered_map<const char *, bool> const &requested_extensions)
{
LOGI("Selected GPU: {}", *gpu.get_properties().deviceName);
// Prepare the device queues
std::vector<vk::QueueFamilyProperties> queue_family_properties = gpu.get_queue_family_properties();
std::vector<vk::DeviceQueueCreateInfo> queue_create_infos;
std::vector<std::vector<float>> queue_priorities;
queue_create_infos.reserve(queue_family_properties.size());
queue_priorities.reserve(queue_family_properties.size());
for (uint32_t queue_family_index = 0U; queue_family_index < queue_family_properties.size(); ++queue_family_index)
{
auto const &queue_family_property = queue_family_properties[queue_family_index];
queue_priorities.push_back(std::vector<float>(queue_family_property.queueCount, 0.5f));
if (gpu.has_high_priority_graphics_queue() &&
(vkb::common::get_queue_family_index(queue_family_properties, vk::QueueFlagBits::eGraphics) == queue_family_index))
{
queue_priorities.back()[0] = 1.0f;
}
queue_create_infos.push_back({.queueFamilyIndex = queue_family_index,
.queueCount = queue_family_property.queueCount,
.pQueuePriorities = queue_priorities[queue_family_index].data()});
}
// Check extensions to enable Vma Dedicated Allocation
bool can_get_memory_requirements = gpu.is_extension_supported("VK_KHR_get_memory_requirements2");
bool has_dedicated_allocation = gpu.is_extension_supported("VK_KHR_dedicated_allocation");
if (can_get_memory_requirements && has_dedicated_allocation)
{
enabled_extensions.push_back("VK_KHR_get_memory_requirements2");
enabled_extensions.push_back("VK_KHR_dedicated_allocation");
LOGI("Dedicated Allocation enabled");
}
// For performance queries, we also use host query reset since queryPool resets cannot
// live in the same command buffer as beginQuery
if (gpu.is_extension_supported("VK_KHR_performance_query") &&
gpu.is_extension_supported("VK_EXT_host_query_reset"))
{
auto perf_counter_features = gpu.get_extension_features<vk::PhysicalDevicePerformanceQueryFeaturesKHR>();
auto host_query_reset_features = gpu.get_extension_features<vk::PhysicalDeviceHostQueryResetFeatures>();
if (perf_counter_features.performanceCounterQueryPools && host_query_reset_features.hostQueryReset)
{
gpu.add_extension_features<vk::PhysicalDevicePerformanceQueryFeaturesKHR>().performanceCounterQueryPools = VK_TRUE;
gpu.add_extension_features<vk::PhysicalDeviceHostQueryResetFeatures>().hostQueryReset = VK_TRUE;
enabled_extensions.push_back("VK_KHR_performance_query");
enabled_extensions.push_back("VK_EXT_host_query_reset");
LOGI("Performance query enabled");
}
}
// Check that extensions are supported before trying to create the device
std::vector<const char *> unsupported_extensions{};
for (auto &extension : requested_extensions)
{
if (gpu.is_extension_supported(extension.first))
{
enabled_extensions.emplace_back(extension.first);
}
else
{
unsupported_extensions.emplace_back(extension.first);
}
}
if (enabled_extensions.size() > 0)
{
LOGI("Device supports the following requested extensions:");
for (auto &extension : enabled_extensions)
{
LOGI(" \t{}", extension);
}
}
if (unsupported_extensions.size() > 0)
{
auto error = false;
for (auto &extension : unsupported_extensions)
{
auto extIt = requested_extensions.find(extension);
assert(extIt != requested_extensions.end());
if (extIt->second)
{
LOGW("Optional device extension {} not available, some features may be disabled", extension);
}
else
{
LOGE("Required device extension {} not available, cannot run", extension);
error = true;
}
}
if (error)
{
throw VulkanException(VK_ERROR_EXTENSION_NOT_PRESENT, "Extensions not present");
}
}
// Latest requested feature will have the pNext's all set up for device creation.
vk::DeviceCreateInfo create_info{.pNext = gpu.get_extension_feature_chain(),
.queueCreateInfoCount = static_cast<uint32_t>(queue_create_infos.size()),
.pQueueCreateInfos = queue_create_infos.data(),
.enabledExtensionCount = static_cast<uint32_t>(enabled_extensions.size()),
.ppEnabledExtensionNames = enabled_extensions.data(),
.pEnabledFeatures = &gpu.get_requested_features()};
this->set_handle(gpu.get_handle().createDevice(create_info));
queues.resize(queue_family_properties.size());
for (uint32_t queue_family_index = 0U; queue_family_index < queue_family_properties.size(); ++queue_family_index)
{
const vk::QueueFamilyProperties &queue_family_property = gpu.get_queue_family_properties()[queue_family_index];
vk::Bool32 present_supported = gpu.get_handle().getSurfaceSupportKHR(queue_family_index, surface);
for (uint32_t queue_index = 0U; queue_index < queue_family_property.queueCount; ++queue_index)
{
if constexpr (bindingType == BindingType::Cpp)
{
queues[queue_family_index].emplace_back(*this, queue_family_index, queue_family_property, present_supported, queue_index);
}
else
{
queues[queue_family_index].emplace_back(
*reinterpret_cast<vkb::core::DeviceCpp *>(this), queue_family_index, queue_family_property, present_supported, queue_index);
}
}
}
vkb::allocated::init(*this);
if constexpr (bindingType == BindingType::Cpp)
{
command_pool = std::make_unique<vkb::core::CommandPoolCpp>(
*this, get_queue_by_flags_impl(vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eCompute, 0).get_family_index());
fence_pool = std::make_unique<vkb::HPPFencePool>(*this);
}
else
{
command_pool = std::make_unique<vkb::core::CommandPoolCpp>(
*reinterpret_cast<vkb::core::DeviceCpp *>(this),
get_queue_by_flags_impl(vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eCompute, 0).get_family_index());
fence_pool = std::make_unique<vkb::HPPFencePool>(*reinterpret_cast<vkb::core::DeviceCpp *>(this));
}
}
} // namespace core
} // namespace vkb
+77
View File
@@ -0,0 +1,77 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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.
*/
#include "framebuffer.h"
#include "device.h"
namespace vkb
{
VkFramebuffer Framebuffer::get_handle() const
{
return handle;
}
const VkExtent2D &Framebuffer::get_extent() const
{
return extent;
}
Framebuffer::Framebuffer(vkb::core::DeviceC &device, const RenderTarget &render_target, const RenderPass &render_pass) :
device{device},
extent{render_target.get_extent()}
{
std::vector<VkImageView> attachments;
for (auto &view : render_target.get_views())
{
attachments.emplace_back(view.get_handle());
}
VkFramebufferCreateInfo create_info{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
create_info.renderPass = render_pass.get_handle();
create_info.attachmentCount = to_u32(attachments.size());
create_info.pAttachments = attachments.data();
create_info.width = extent.width;
create_info.height = extent.height;
create_info.layers = 1;
auto result = vkCreateFramebuffer(device.get_handle(), &create_info, nullptr, &handle);
if (result != VK_SUCCESS)
{
throw VulkanException{result, "Cannot create Framebuffer"};
}
}
Framebuffer::Framebuffer(Framebuffer &&other) :
device{other.device},
handle{other.handle},
extent{other.extent}
{
other.handle = VK_NULL_HANDLE;
}
Framebuffer::~Framebuffer()
{
if (handle != VK_NULL_HANDLE)
{
vkDestroyFramebuffer(device.get_handle(), handle, nullptr);
}
}
} // namespace vkb
+61
View File
@@ -0,0 +1,61 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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 "common/helpers.h"
#include "common/vk_common.h"
namespace vkb
{
class RenderPass;
class RenderTarget;
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceC = Device<vkb::BindingType::C>;
} // namespace core
class Framebuffer
{
public:
Framebuffer(vkb::core::DeviceC &device, const RenderTarget &render_target, const RenderPass &render_pass);
Framebuffer(const Framebuffer &) = delete;
Framebuffer(Framebuffer &&other);
~Framebuffer();
Framebuffer &operator=(const Framebuffer &) = delete;
Framebuffer &operator=(Framebuffer &&) = delete;
VkFramebuffer get_handle() const;
const VkExtent2D &get_extent() const;
private:
vkb::core::DeviceC &device;
VkFramebuffer handle{VK_NULL_HANDLE};
VkExtent2D extent{};
};
} // namespace vkb
+118
View File
@@ -0,0 +1,118 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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.
*/
#include "core/hpp_debug.h"
#include "core/command_buffer.h"
#include "core/device.h"
namespace vkb
{
namespace core
{
void HPPDebugUtilsExtDebugUtils::set_debug_name(vk::Device device, vk::ObjectType object_type, uint64_t object_handle, const char *name) const
{
vk::DebugUtilsObjectNameInfoEXT name_info{.objectType = object_type, .objectHandle = object_handle, .pObjectName = name};
device.setDebugUtilsObjectNameEXT(name_info);
}
void HPPDebugUtilsExtDebugUtils::set_debug_tag(
vk::Device device, vk::ObjectType object_type, uint64_t object_handle, uint64_t tag_name, const void *tag_data, size_t tag_data_size) const
{
vk::DebugUtilsObjectTagInfoEXT tag_info{.objectType = object_type, .objectHandle = object_handle, .tagName = tag_name, .tagSize = tag_data_size, .pTag = tag_data};
device.setDebugUtilsObjectTagEXT(tag_info);
}
void HPPDebugUtilsExtDebugUtils::cmd_begin_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const color) const
{
vk::DebugUtilsLabelEXT label_info{.pLabelName = name, .color = reinterpret_cast<std::array<float, 4> const &>(*&color[0])};
command_buffer.beginDebugUtilsLabelEXT(label_info);
}
void HPPDebugUtilsExtDebugUtils::cmd_end_label(vk::CommandBuffer command_buffer) const
{
command_buffer.endDebugUtilsLabelEXT();
}
void HPPDebugUtilsExtDebugUtils::cmd_insert_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const color) const
{
vk::DebugUtilsLabelEXT label_info{.pLabelName = name, .color = reinterpret_cast<std::array<float, 4> const &>(*&color[0])};
command_buffer.insertDebugUtilsLabelEXT(label_info);
}
void HPPDebugMarkerExtDebugUtils::set_debug_name(vk::Device device, vk::ObjectType object_type, uint64_t object_handle, const char *name) const
{
vk::DebugMarkerObjectNameInfoEXT name_info{.objectType = vk::debugReportObjectType(object_type), .object = object_handle, .pObjectName = name};
device.debugMarkerSetObjectNameEXT(name_info);
}
void HPPDebugMarkerExtDebugUtils::set_debug_tag(
vk::Device device, vk::ObjectType object_type, uint64_t object_handle, uint64_t tag_name, const void *tag_data, size_t tag_data_size) const
{
vk::DebugMarkerObjectTagInfoEXT tag_info{
.objectType = vk::debugReportObjectType(object_type), .object = object_handle, .tagName = tag_name, .tagSize = tag_data_size, .pTag = tag_data};
device.debugMarkerSetObjectTagEXT(tag_info);
}
void HPPDebugMarkerExtDebugUtils::cmd_begin_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const color) const
{
vk::DebugMarkerMarkerInfoEXT marker_info{.pMarkerName = name, .color = reinterpret_cast<std::array<float, 4> const &>(*&color[0])};
command_buffer.debugMarkerBeginEXT(marker_info);
}
void HPPDebugMarkerExtDebugUtils::cmd_end_label(vk::CommandBuffer command_buffer) const
{
command_buffer.debugMarkerEndEXT();
}
void HPPDebugMarkerExtDebugUtils::cmd_insert_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const color) const
{
vk::DebugMarkerMarkerInfoEXT marker_info{.pMarkerName = name, .color = reinterpret_cast<std::array<float, 4> const &>(*&color[0])};
command_buffer.debugMarkerInsertEXT(marker_info);
}
HPPScopedDebugLabel::HPPScopedDebugLabel(const HPPDebugUtils &debug_utils,
vk::CommandBuffer command_buffer,
std::string const &name,
glm::vec4 const color) :
debug_utils{&debug_utils}, command_buffer{VK_NULL_HANDLE}
{
if (!name.empty())
{
assert(command_buffer);
this->command_buffer = command_buffer;
debug_utils.cmd_begin_label(command_buffer, name.c_str(), color);
}
}
HPPScopedDebugLabel::HPPScopedDebugLabel(const vkb::core::CommandBufferCpp &command_buffer,
std::string const &name,
glm::vec4 const color) :
HPPScopedDebugLabel{command_buffer.get_device().get_debug_utils(), command_buffer.get_handle(), name, color}
{
}
HPPScopedDebugLabel::~HPPScopedDebugLabel()
{
if (command_buffer)
{
debug_utils->cmd_end_label(command_buffer);
}
}
} // namespace core
} // namespace vkb
+152
View File
@@ -0,0 +1,152 @@
/* Copyright (c) 2022-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "common/vk_common.h"
#include <glm/glm.hpp>
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class CommandBuffer;
using CommandBufferCpp = CommandBuffer<vkb::BindingType::Cpp>;
/**
* @brief An interface over platform-specific debug extensions.
*/
class HPPDebugUtils
{
public:
virtual ~HPPDebugUtils() = default;
/**
* @brief Sets the debug name for a Vulkan object.
*/
virtual void set_debug_name(vk::Device device, vk::ObjectType object_type, uint64_t object_handle, const char *name) const = 0;
/**
* @brief Tags the given Vulkan object with some data.
*/
virtual void set_debug_tag(
vk::Device device, vk::ObjectType object_type, uint64_t object_handle, uint64_t tag_name, const void *tag_data, size_t tag_data_size) const = 0;
/**
* @brief Inserts a command to begin a new debug label/marker scope.
*/
virtual void cmd_begin_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const color = {}) const = 0;
/**
* @brief Inserts a command to end the current debug label/marker scope.
*/
virtual void cmd_end_label(vk::CommandBuffer command_buffer) const = 0;
/**
* @brief Inserts a (non-scoped) debug label/marker in the command buffer.
*/
virtual void cmd_insert_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const color = {}) const = 0;
};
/**
* @brief HPPDebugUtils implemented on top of VK_EXT_debug_utils.
*/
class HPPDebugUtilsExtDebugUtils final : public vkb::core::HPPDebugUtils
{
public:
~HPPDebugUtilsExtDebugUtils() override = default;
void set_debug_name(vk::Device device, vk::ObjectType object_type, uint64_t object_handle, const char *name) const override;
void set_debug_tag(
vk::Device device, vk::ObjectType object_type, uint64_t object_handle, uint64_t tag_name, const void *tag_data, size_t tag_data_size) const override;
void cmd_begin_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const color) const override;
void cmd_end_label(vk::CommandBuffer command_buffer) const override;
void cmd_insert_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const color) const override;
};
/**
* @brief HPPDebugUtils implemented on top of VK_EXT_debug_marker.
*/
class HPPDebugMarkerExtDebugUtils final : public vkb::core::HPPDebugUtils
{
public:
~HPPDebugMarkerExtDebugUtils() override = default;
void set_debug_name(vk::Device device, vk::ObjectType object_type, uint64_t object_handle, const char *name) const override;
void set_debug_tag(
vk::Device device, vk::ObjectType object_type, uint64_t object_handle, uint64_t tag_name, const void *tag_data, size_t tag_data_size) const override;
void cmd_begin_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const color) const override;
void cmd_end_label(vk::CommandBuffer command_buffer) const override;
void cmd_insert_label(vk::CommandBuffer command_buffer, const char *name, glm::vec4 const color) const override;
};
/**
* @brief No-op HPPDebugUtils.
*/
class HPPDummyDebugUtils final : public vkb::core::HPPDebugUtils
{
public:
~HPPDummyDebugUtils() override = default;
inline void set_debug_name(vk::Device, vk::ObjectType, uint64_t, const char *) const override
{}
inline void set_debug_tag(vk::Device, vk::ObjectType, uint64_t, uint64_t, const void *, size_t) const override
{}
inline void cmd_begin_label(vk::CommandBuffer, const char *, glm::vec4 const) const override
{}
inline void cmd_end_label(vk::CommandBuffer) const override
{}
inline void cmd_insert_label(vk::CommandBuffer, const char *, glm::vec4 const) const override
{}
};
/**
* @brief A RAII debug label.
* If any of EXT_debug_utils or EXT_debug_marker is available, this:
* - Begins a debug label / marker on construction
* - Ends it on destruction
*/
class HPPScopedDebugLabel final
{
public:
HPPScopedDebugLabel(const vkb::core::HPPDebugUtils &debug_utils, vk::CommandBuffer command_buffer, std::string const &name, glm::vec4 const color = {});
HPPScopedDebugLabel(const vkb::core::CommandBufferCpp &command_buffer, std::string const &name, glm::vec4 const color = {});
~HPPScopedDebugLabel();
private:
const vkb::core::HPPDebugUtils *debug_utils;
vk::CommandBuffer command_buffer;
};
} // namespace core
} // namespace vkb
+48
View File
@@ -0,0 +1,48 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "descriptor_pool.h"
#include <core/hpp_descriptor_set_layout.h>
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceCpp = Device<vkb::BindingType::Cpp>;
using DeviceC = Device<vkb::BindingType::C>;
/**
* @brief facade class around vkb::DescriptorPool, providing a vulkan.hpp-based interface
*
* See vkb::DescriptorPool for documentation
*/
class HPPDescriptorPool : private vkb::DescriptorPool
{
public:
using vkb::DescriptorPool::reset;
HPPDescriptorPool(vkb::core::DeviceCpp &device, const vkb::core::HPPDescriptorSetLayout &descriptor_set_layout, uint32_t pool_size = MAX_SETS_PER_POOL) :
vkb::DescriptorPool(
reinterpret_cast<vkb::core::DeviceC &>(device), reinterpret_cast<vkb::DescriptorSetLayout const &>(descriptor_set_layout), pool_size)
{}
};
} // namespace core
} // namespace vkb
+72
View File
@@ -0,0 +1,72 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "descriptor_set.h"
#include <core/hpp_descriptor_pool.h>
#include <core/hpp_descriptor_set_layout.h>
namespace vkb
{
namespace core
{
/**
* @brief facade class around vkb::DescriptorSet, providing a vulkan.hpp-based interface
*
* See vkb::DescriptorSet for documentation
*/
class HPPDescriptorSet : private vkb::DescriptorSet
{
public:
using vkb::DescriptorSet::apply_writes;
using vkb::DescriptorSet::update;
HPPDescriptorSet(vkb::core::DeviceCpp &device,
const vkb::core::HPPDescriptorSetLayout &descriptor_set_layout,
vkb::core::HPPDescriptorPool &descriptor_pool,
const BindingMap<vk::DescriptorBufferInfo> &buffer_infos = {},
const BindingMap<vk::DescriptorImageInfo> &image_infos = {}) :
vkb::DescriptorSet(reinterpret_cast<vkb::core::DeviceC &>(device),
reinterpret_cast<vkb::DescriptorSetLayout const &>(descriptor_set_layout),
reinterpret_cast<vkb::DescriptorPool &>(descriptor_pool),
reinterpret_cast<BindingMap<VkDescriptorBufferInfo> const &>(buffer_infos),
reinterpret_cast<BindingMap<VkDescriptorImageInfo> const &>(image_infos))
{}
BindingMap<vk::DescriptorBufferInfo> &get_buffer_infos()
{
return reinterpret_cast<BindingMap<vk::DescriptorBufferInfo> &>(vkb::DescriptorSet::get_buffer_infos());
}
vk::DescriptorSet get_handle() const
{
return static_cast<vk::DescriptorSet>(vkb::DescriptorSet::get_handle());
}
BindingMap<vk::DescriptorImageInfo> &get_image_infos()
{
return reinterpret_cast<BindingMap<vk::DescriptorImageInfo> &>(vkb::DescriptorSet::get_image_infos());
}
const vkb::core::HPPDescriptorSetLayout &get_layout() const
{
return reinterpret_cast<vkb::core::HPPDescriptorSetLayout const &>(vkb::DescriptorSet::get_layout());
}
};
} // namespace core
} // namespace vkb
@@ -0,0 +1,73 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "core/descriptor_set_layout.h"
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceCpp = Device<vkb::BindingType::Cpp>;
using DeviceC = Device<vkb::BindingType::C>;
class HPPShaderModule;
struct HPPShaderResource;
/**
* @brief facade class around vkb::DescriptorSetLayout, providing a vulkan.hpp-based interface
*
* See vkb::DescriptorSetLayout for documentation
*/
class HPPDescriptorSetLayout : private vkb::DescriptorSetLayout
{
public:
using vkb::DescriptorSetLayout::get_index;
public:
HPPDescriptorSetLayout(vkb::core::DeviceCpp &device,
const uint32_t set_index,
const std::vector<vkb::core::HPPShaderModule *> &shader_modules,
const std::vector<vkb::core::HPPShaderResource> &resource_set) :
vkb::DescriptorSetLayout(reinterpret_cast<vkb::core::DeviceC &>(device),
set_index,
reinterpret_cast<std::vector<vkb::ShaderModule *> const &>(shader_modules),
reinterpret_cast<std::vector<vkb::ShaderResource> const &>(resource_set))
{}
vk::DescriptorSetLayout get_handle() const
{
return static_cast<vk::DescriptorSetLayout>(vkb::DescriptorSetLayout::get_handle());
}
std::unique_ptr<vk::DescriptorSetLayoutBinding> get_layout_binding(const uint32_t binding_index) const
{
return std::unique_ptr<vk::DescriptorSetLayoutBinding>(
reinterpret_cast<vk::DescriptorSetLayoutBinding *>(vkb::DescriptorSetLayout::get_layout_binding(binding_index).release()));
}
vk::DescriptorBindingFlagsEXT get_layout_binding_flag(const uint32_t binding_index) const
{
return static_cast<vk::DescriptorBindingFlagsEXT>(vkb::DescriptorSetLayout::get_layout_binding_flag(binding_index));
}
};
} // namespace core
} // namespace vkb
+60
View File
@@ -0,0 +1,60 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "core/framebuffer.h"
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace rendering
{
class HPPRenderTarget;
}
namespace core
{
class HPPDevice;
class HPPRenderPass;
/**
* @brief facade class around vkb::Framebuffer, providing a vulkan.hpp-based interface
*
* See vkb::Framebuffer for documentation
*/
class HPPFramebuffer : private vkb::Framebuffer
{
public:
HPPFramebuffer(vkb::core::DeviceCpp &device, const vkb::rendering::HPPRenderTarget &render_target, const vkb::core::HPPRenderPass &render_pass) :
vkb::Framebuffer(reinterpret_cast<vkb::core::DeviceC &>(device),
reinterpret_cast<vkb::RenderTarget const &>(render_target),
reinterpret_cast<vkb::RenderPass const &>(render_pass))
{}
const vk::Extent2D &get_extent() const
{
return reinterpret_cast<vk::Extent2D const &>(vkb::Framebuffer::get_extent());
}
vk::Framebuffer get_handle() const
{
return static_cast<vk::Framebuffer>(vkb::Framebuffer::get_handle());
}
};
} // namespace core
} // namespace vkb
+166
View File
@@ -0,0 +1,166 @@
/* Copyright (c) 2021-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "builder_base.h"
#include "core/allocated.h"
#include "core/vulkan_resource.h"
#include <unordered_set>
namespace vkb
{
namespace core
{
class HPPDevice;
class HPPImageView;
class HPPImage;
using HPPImagePtr = std::unique_ptr<HPPImage>;
struct HPPImageBuilder : public vkb::allocated::BuilderBaseCpp<HPPImageBuilder, vk::ImageCreateInfo>
{
private:
using Parent = vkb::allocated::BuilderBaseCpp<HPPImageBuilder, vk::ImageCreateInfo>;
public:
HPPImageBuilder(vk::Extent3D const &extent) : // Better reasonable defaults than vk::ImageCreateInfo default ctor
Parent(vk::ImageCreateInfo{.imageType = vk::ImageType::e2D, .format = vk::Format::eR8G8B8A8Unorm, .extent = extent, .mipLevels = 1, .arrayLayers = 1})
{
}
HPPImageBuilder(vk::Extent2D const &extent) :
HPPImageBuilder(vk::Extent3D{extent.width, extent.height, 1})
{
}
HPPImageBuilder(uint32_t width, uint32_t height = 1, uint32_t depth = 1) :
HPPImageBuilder(vk::Extent3D{width, height, depth})
{
}
HPPImageBuilder &with_format(vk::Format format)
{
create_info.format = format;
return *this;
}
HPPImageBuilder &with_image_type(vk::ImageType type)
{
create_info.imageType = type;
return *this;
}
HPPImageBuilder &with_array_layers(uint32_t layers)
{
create_info.arrayLayers = layers;
return *this;
}
HPPImageBuilder &with_mip_levels(uint32_t levels)
{
create_info.mipLevels = levels;
return *this;
}
HPPImageBuilder &with_sample_count(vk::SampleCountFlagBits sample_count)
{
create_info.samples = sample_count;
return *this;
}
HPPImageBuilder &with_tiling(vk::ImageTiling tiling)
{
create_info.tiling = tiling;
return *this;
}
HPPImageBuilder &with_usage(vk::ImageUsageFlags usage)
{
create_info.usage = usage;
return *this;
}
HPPImageBuilder &with_flags(vk::ImageCreateFlags flags)
{
create_info.flags = flags;
return *this;
}
HPPImage build(vkb::core::DeviceCpp &device) const;
HPPImagePtr build_unique(vkb::core::DeviceCpp &device) const;
};
class HPPImage : public vkb::allocated::AllocatedCpp<vk::Image>
{
public:
HPPImage(vkb::core::DeviceCpp &device,
vk::Image handle,
const vk::Extent3D &extent,
vk::Format format,
vk::ImageUsageFlags image_usage,
vk::SampleCountFlagBits sample_count = vk::SampleCountFlagBits::e1);
//[[deprecated("Use the HPPImageBuilder ctor instead")]]
HPPImage(vkb::core::DeviceCpp &device,
const vk::Extent3D &extent,
vk::Format format,
vk::ImageUsageFlags image_usage,
VmaMemoryUsage memory_usage = VMA_MEMORY_USAGE_AUTO,
vk::SampleCountFlagBits sample_count = vk::SampleCountFlagBits::e1,
uint32_t mip_levels = 1,
uint32_t array_layers = 1,
vk::ImageTiling tiling = vk::ImageTiling::eOptimal,
vk::ImageCreateFlags flags = {},
uint32_t num_queue_families = 0,
const uint32_t *queue_families = nullptr);
HPPImage(vkb::core::DeviceCpp &device,
HPPImageBuilder const &builder);
HPPImage(const HPPImage &) = delete;
HPPImage(HPPImage &&other) noexcept;
~HPPImage();
HPPImage &operator=(const HPPImage &) = delete;
HPPImage &operator=(HPPImage &&) = delete;
/**
* @brief Maps vulkan memory to an host visible address
* @return Pointer to host visible memory
*/
uint8_t *map();
vk::ImageType get_type() const;
const vk::Extent3D &get_extent() const;
vk::Format get_format() const;
vk::SampleCountFlagBits get_sample_count() const;
vk::ImageUsageFlags get_usage() const;
vk::ImageTiling get_tiling() const;
vk::ImageSubresource get_subresource() const;
uint32_t get_array_layer_count() const;
std::unordered_set<vkb::core::HPPImageView *> &get_views();
private:
vk::ImageCreateInfo create_info;
vk::ImageSubresource subresource;
std::unordered_set<vkb::core::HPPImageView *> views; /// HPPImage views referring to this image
};
} // namespace core
} // namespace vkb
+184
View File
@@ -0,0 +1,184 @@
/* Copyright (c) 2022-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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.
*/
#include "core/device.h"
#include "core/hpp_image.h"
#include "core/hpp_image_view.h"
namespace vkb
{
namespace
{
inline vk::ImageType find_image_type(vk::Extent3D const &extent)
{
uint32_t dim_num = !!extent.width + !!extent.height + (1 < extent.depth);
switch (dim_num)
{
case 1:
return vk::ImageType::e1D;
case 2:
return vk::ImageType::e2D;
case 3:
return vk::ImageType::e3D;
default:
throw std::runtime_error("No image type found.");
return vk::ImageType();
}
}
} // namespace
namespace core
{
HPPImage HPPImageBuilder::build(vkb::core::DeviceCpp &device) const
{
return HPPImage(device, *this);
}
HPPImagePtr HPPImageBuilder::build_unique(vkb::core::DeviceCpp &device) const
{
return std::make_unique<HPPImage>(device, *this);
}
HPPImage::HPPImage(vkb::core::DeviceCpp &device,
const vk::Extent3D &extent,
vk::Format format,
vk::ImageUsageFlags image_usage,
VmaMemoryUsage memory_usage,
vk::SampleCountFlagBits sample_count,
const uint32_t mip_levels,
const uint32_t array_layers,
vk::ImageTiling tiling,
vk::ImageCreateFlags flags,
uint32_t num_queue_families,
const uint32_t *queue_families) :
HPPImage{device,
HPPImageBuilder{extent}
.with_format(format)
.with_mip_levels(mip_levels)
.with_array_layers(array_layers)
.with_sample_count(sample_count)
.with_tiling(tiling)
.with_flags(flags)
.with_usage(image_usage)
.with_queue_families(num_queue_families, queue_families)}
{}
HPPImage::HPPImage(vkb::core::DeviceCpp &device, HPPImageBuilder const &builder) :
vkb::allocated::AllocatedCpp<vk::Image>{builder.get_allocation_create_info(), nullptr, &device}, create_info{builder.get_create_info()}
{
get_handle() = create_image(create_info);
subresource.arrayLayer = create_info.arrayLayers;
subresource.mipLevel = create_info.mipLevels;
if (!builder.get_debug_name().empty())
{
set_debug_name(builder.get_debug_name());
}
}
HPPImage::HPPImage(vkb::core::DeviceCpp &device,
vk::Image handle,
const vk::Extent3D &extent,
vk::Format format,
vk::ImageUsageFlags image_usage,
vk::SampleCountFlagBits sample_count) :
vkb::allocated::AllocatedCpp<vk::Image>{handle, &device}
{
create_info.samples = sample_count;
create_info.format = format;
create_info.extent = extent;
create_info.imageType = find_image_type(extent);
create_info.arrayLayers = 1;
create_info.mipLevels = 1;
subresource.mipLevel = 1;
subresource.arrayLayer = 1;
}
HPPImage::HPPImage(HPPImage &&other) noexcept :
vkb::allocated::AllocatedCpp<vk::Image>{std::move(other)},
create_info(std::exchange(other.create_info, {})),
subresource(std::exchange(other.subresource, {})),
views(std::exchange(other.views, {}))
{
// Update image views references to this image to avoid dangling pointers
for (auto &view : views)
{
view->set_image(*this);
}
}
HPPImage::~HPPImage()
{
destroy_image(get_handle());
}
uint8_t *HPPImage::map()
{
if (create_info.tiling != vk::ImageTiling::eLinear)
{
LOGW("Mapping image memory that is not linear");
}
return vkb::allocated::AllocatedCpp<vk::Image>::map();
}
vk::ImageType HPPImage::get_type() const
{
return create_info.imageType;
}
const vk::Extent3D &HPPImage::get_extent() const
{
return create_info.extent;
}
vk::Format HPPImage::get_format() const
{
return create_info.format;
}
vk::SampleCountFlagBits HPPImage::get_sample_count() const
{
return create_info.samples;
}
vk::ImageUsageFlags HPPImage::get_usage() const
{
return create_info.usage;
}
vk::ImageTiling HPPImage::get_tiling() const
{
return create_info.tiling;
}
vk::ImageSubresource HPPImage::get_subresource() const
{
return subresource;
}
uint32_t HPPImage::get_array_layer_count() const
{
return create_info.arrayLayers;
}
std::unordered_set<vkb::core::HPPImageView *> &HPPImage::get_views()
{
return views;
}
} // namespace core
} // namespace vkb
+105
View File
@@ -0,0 +1,105 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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.
*/
#include "core/hpp_image_view.h"
#include "common/hpp_vk_common.h"
#include "core/device.h"
#include "core/hpp_image.h"
#include <vulkan/vulkan_format_traits.hpp>
namespace vkb
{
namespace core
{
HPPImageView::HPPImageView(vkb::core::HPPImage &img,
vk::ImageViewType view_type,
vk::Format format,
uint32_t mip_level,
uint32_t array_layer,
uint32_t n_mip_levels,
uint32_t n_array_layers) :
VulkanResource{nullptr, &img.get_device()}, image{&img}, format{format}
{
if (format == vk::Format::eUndefined)
{
this->format = format = image->get_format();
}
subresource_range = vk::ImageSubresourceRange{.aspectMask = (std::string(vk::componentName(format, 0)) == "D") ? vk::ImageAspectFlagBits::eDepth : vk::ImageAspectFlagBits::eColor,
.baseMipLevel = mip_level,
.levelCount = n_mip_levels == 0 ? image->get_subresource().mipLevel : n_mip_levels,
.baseArrayLayer = array_layer,
.layerCount = n_array_layers == 0 ? image->get_subresource().arrayLayer : n_array_layers};
vk::ImageViewCreateInfo image_view_create_info{
.image = image->get_handle(), .viewType = view_type, .format = format, .subresourceRange = subresource_range};
set_handle(get_device().get_handle().createImageView(image_view_create_info));
// Register this image view to its image
// in order to be notified when it gets moved
image->get_views().emplace(this);
}
HPPImageView::HPPImageView(HPPImageView &&other) :
VulkanResource{std::move(other)}, image{other.image}, format{other.format}, subresource_range{other.subresource_range}
{
// Remove old view from image set and add this new one
auto &views = image->get_views();
views.erase(&other);
views.emplace(this);
other.set_handle(nullptr);
}
HPPImageView::~HPPImageView()
{
if (get_handle())
{
get_device().get_handle().destroyImageView(get_handle());
}
}
vk::Format HPPImageView::get_format() const
{
return format;
}
const vkb::core::HPPImage &HPPImageView::get_image() const
{
assert(image && "vkb::core::HPPImage view is referring an invalid image");
return *image;
}
void HPPImageView::set_image(vkb::core::HPPImage &img)
{
image = &img;
}
vk::ImageSubresourceLayers HPPImageView::get_subresource_layers() const
{
return vk::ImageSubresourceLayers{
subresource_range.aspectMask, subresource_range.baseMipLevel, subresource_range.baseArrayLayer, subresource_range.layerCount};
}
vk::ImageSubresourceRange HPPImageView::get_subresource_range() const
{
return subresource_range;
}
} // namespace core
} // namespace vkb
+59
View File
@@ -0,0 +1,59 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "core/vulkan_resource.h"
namespace vkb
{
namespace core
{
class HPPImage;
class HPPImageView : public vkb::core::VulkanResourceCpp<vk::ImageView>
{
public:
HPPImageView(vkb::core::HPPImage &image,
vk::ImageViewType view_type,
vk::Format format = vk::Format::eUndefined,
uint32_t base_mip_level = 0,
uint32_t base_array_layer = 0,
uint32_t n_mip_levels = 0,
uint32_t n_array_layers = 0);
HPPImageView(HPPImageView &) = delete;
HPPImageView(HPPImageView &&other);
~HPPImageView() override;
HPPImageView &operator=(const HPPImageView &) = delete;
HPPImageView &operator=(HPPImageView &&) = delete;
vk::Format get_format() const;
vkb::core::HPPImage const &get_image() const;
void set_image(vkb::core::HPPImage &image);
vk::ImageSubresourceLayers get_subresource_layers() const;
vk::ImageSubresourceRange get_subresource_range() const;
private:
vkb::core::HPPImage *image = nullptr;
vk::Format format;
vk::ImageSubresourceRange subresource_range;
};
} // namespace core
} // namespace vkb
+160
View File
@@ -0,0 +1,160 @@
/* Copyright (c) 2022-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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.
*/
#include <core/hpp_physical_device.h>
#include <core/util/logging.hpp>
namespace vkb
{
namespace core
{
HPPPhysicalDevice::HPPPhysicalDevice(vkb::core::InstanceCpp &instance, vk::PhysicalDevice physical_device) :
instance{instance},
handle{physical_device}
{
features = physical_device.getFeatures();
properties = physical_device.getProperties();
memory_properties = physical_device.getMemoryProperties();
LOGI("Found GPU: {}", properties.deviceName.data());
queue_family_properties = physical_device.getQueueFamilyProperties();
device_extensions = physical_device.enumerateDeviceExtensionProperties();
// Display supported extensions
if (device_extensions.size() > 0)
{
LOGD("HPPDevice supports the following extensions:");
for (auto &extension : device_extensions)
{
LOGD(" \t{}", extension.extensionName.data());
}
}
}
DriverVersion HPPPhysicalDevice::get_driver_version() const
{
DriverVersion version;
vk::PhysicalDeviceProperties const &properties = get_properties();
switch (properties.vendorID)
{
case 0x10DE:
// Nvidia
version.major = (properties.driverVersion >> 22) & 0x3ff;
version.minor = (properties.driverVersion >> 14) & 0x0ff;
version.patch = (properties.driverVersion >> 6) & 0x0ff;
// Ignoring optional tertiary info in lower 6 bits
break;
case 0x8086:
version.major = (properties.driverVersion >> 14) & 0x3ffff;
version.minor = properties.driverVersion & 0x3ffff;
break;
default:
version.major = VK_VERSION_MAJOR(properties.driverVersion);
version.minor = VK_VERSION_MINOR(properties.driverVersion);
version.patch = VK_VERSION_PATCH(properties.driverVersion);
break;
}
return version;
}
void *HPPPhysicalDevice::get_extension_feature_chain() const
{
return last_requested_extension_feature;
}
bool HPPPhysicalDevice::is_extension_supported(const std::string &requested_extension) const
{
return std::ranges::find_if(device_extensions,
[requested_extension](auto &device_extension) { return std::strcmp(device_extension.extensionName, requested_extension.c_str()) == 0; }) != device_extensions.end();
}
const vk::PhysicalDeviceFeatures &HPPPhysicalDevice::get_features() const
{
return features;
}
vk::PhysicalDevice HPPPhysicalDevice::get_handle() const
{
return handle;
}
vkb::core::InstanceCpp &HPPPhysicalDevice::get_instance() const
{
return instance;
}
const vk::PhysicalDeviceMemoryProperties &HPPPhysicalDevice::get_memory_properties() const
{
return memory_properties;
}
uint32_t HPPPhysicalDevice::get_memory_type(uint32_t bits, vk::MemoryPropertyFlags properties, vk::Bool32 *memory_type_found) const
{
for (uint32_t i = 0; i < memory_properties.memoryTypeCount; i++)
{
if ((bits & 1) == 1)
{
if ((memory_properties.memoryTypes[i].propertyFlags & properties) == properties)
{
if (memory_type_found)
{
*memory_type_found = true;
}
return i;
}
}
bits >>= 1;
}
if (memory_type_found)
{
*memory_type_found = false;
return ~0;
}
else
{
throw std::runtime_error("Could not find a matching memory type");
}
}
const vk::PhysicalDeviceProperties &HPPPhysicalDevice::get_properties() const
{
return properties;
}
const std::vector<vk::QueueFamilyProperties> &HPPPhysicalDevice::get_queue_family_properties() const
{
return queue_family_properties;
}
const vk::PhysicalDeviceFeatures &HPPPhysicalDevice::get_requested_features() const
{
return requested_features;
}
vk::PhysicalDeviceFeatures &HPPPhysicalDevice::get_mutable_requested_features()
{
return requested_features;
}
} // namespace core
} // namespace vkb
+250
View File
@@ -0,0 +1,250 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "core/instance.h"
#include <map>
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace core
{
struct DriverVersion
{
uint16_t major;
uint16_t minor;
uint16_t patch;
};
/**
* @brief A wrapper class for vk::PhysicalDevice
*
* This class is responsible for handling gpu features, properties, and queue families for the device creation.
*/
class HPPPhysicalDevice
{
public:
HPPPhysicalDevice(vkb::core::InstanceCpp &instance, vk::PhysicalDevice physical_device);
HPPPhysicalDevice(const HPPPhysicalDevice &) = delete;
HPPPhysicalDevice(HPPPhysicalDevice &&) = delete;
HPPPhysicalDevice &operator=(const HPPPhysicalDevice &) = delete;
HPPPhysicalDevice &operator=(HPPPhysicalDevice &&) = delete;
/**
* @return The version of the driver
*/
DriverVersion get_driver_version() const;
/**
* @brief Used at logical device creation to pass the extensions feature chain to vkCreateDevice
* @returns A void pointer to the start of the extension linked list
*/
void *get_extension_feature_chain() const;
bool is_extension_supported(const std::string &requested_extension) const;
const vk::PhysicalDeviceFeatures &get_features() const;
vk::PhysicalDevice get_handle() const;
vkb::core::InstanceCpp &get_instance() const;
const vk::PhysicalDeviceMemoryProperties &get_memory_properties() const;
/**
* @brief Checks that a given memory type is supported by the GPU
* @param bits The memory requirement type bits
* @param properties The memory property to search for
* @param memory_type_found True if found, false if not found
* @returns The memory type index of the found memory type
*/
uint32_t get_memory_type(uint32_t bits, vk::MemoryPropertyFlags properties, vk::Bool32 *memory_type_found = nullptr) const;
const vk::PhysicalDeviceProperties &get_properties() const;
const std::vector<vk::QueueFamilyProperties> &get_queue_family_properties() const;
const vk::PhysicalDeviceFeatures &get_requested_features() const;
vk::PhysicalDeviceFeatures &get_mutable_requested_features();
/**
* @brief Get an extension features struct
*
* Gets the actual extension features struct with the supported flags set.
* The flags you're interested in can be set in a corresponding struct in the structure chain
* by calling PhysicalDevice::add_extension_features()
* @returns The extension feature struct
*/
template <typename HPPStructureType>
HPPStructureType get_extension_features() const
{
// We cannot request extension features if the physical device properties 2 instance extension isn't enabled
if (!instance.is_enabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME))
{
throw std::runtime_error("Couldn't request feature from device as " + std::string(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) +
" isn't enabled!");
}
// Get the extension feature
return handle.getFeatures2KHR<vk::PhysicalDeviceFeatures2KHR, HPPStructureType>().template get<HPPStructureType>();
}
/**
* @brief Add an extension features struct to the structure chain used for device creation
*
* To have the features enabled, this function must be called before the logical device
* is created. To do this request sample specific features inside
* VulkanSample::request_gpu_features(vkb::HPPPhysicalDevice &gpu).
*
* If the feature extension requires you to ask for certain features to be enabled, you can
* modify the struct returned by this function, it will propagate the changes to the logical
* device.
* @returns A reference to the extension feature struct in the structure chain
*/
template <typename HPPStructureType>
HPPStructureType &add_extension_features()
{
// We cannot request extension features if the physical device properties 2 instance extension isn't enabled
if (!instance.is_enabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME))
{
throw std::runtime_error("Couldn't request feature from device as " + std::string(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) +
" isn't enabled!");
}
// Add an (empty) extension features into the map of extension features
auto [it, added] = extension_features.insert({HPPStructureType::structureType, std::make_shared<HPPStructureType>()});
if (added)
{
// if it was actually added, also add it to the structure chain
if (last_requested_extension_feature)
{
static_cast<HPPStructureType *>(it->second.get())->pNext = last_requested_extension_feature;
}
last_requested_extension_feature = it->second.get();
}
return *static_cast<HPPStructureType *>(it->second.get());
}
/**
* @brief Request an optional features flag
*
* Calls get_extension_features to get the support of the requested flag. If it's supported,
* add_extension_features is called, otherwise a log message is generated.
*
* @returns true if the requested feature is supported, otherwise false
*/
template <typename Feature>
vk::Bool32 request_optional_feature(vk::Bool32 Feature::*flag, std::string const &featureName, std::string const &flagName)
{
vk::Bool32 supported = get_extension_features<Feature>().*flag;
if (supported)
{
add_extension_features<Feature>().*flag = true;
}
else
{
LOGI("Requested optional feature <{}::{}> is not supported", featureName, flagName);
}
return supported;
}
/**
* @brief Request a required features flag
*
* Calls get_extension_features to get the support of the requested flag. If it's supported,
* add_extension_features is called, otherwise a runtime_error is thrown.
*/
template <typename Feature>
void request_required_feature(vk::Bool32 Feature::*flag, std::string const &featureName, std::string const &flagName)
{
if (get_extension_features<Feature>().*flag)
{
add_extension_features<Feature>().*flag = true;
}
else
{
throw std::runtime_error(std::string("Requested required feature <") + featureName + "::" + flagName + "> is not supported");
}
}
/**
* @brief Sets whether or not the first graphics queue should have higher priority than other queues.
* Very specific feature which is used by async compute samples.
* @param enable If true, present queue will have prio 1.0 and other queues have prio 0.5.
* Default state is false, where all queues have 0.5 priority.
*/
void set_high_priority_graphics_queue_enable(bool enable)
{
high_priority_graphics_queue = enable;
}
/**
* @brief Returns high priority graphics queue state.
* @return High priority state.
*/
bool has_high_priority_graphics_queue() const
{
return high_priority_graphics_queue;
}
private:
// Handle to the Vulkan instance
vkb::core::InstanceCpp &instance;
// Handle to the Vulkan physical device
vk::PhysicalDevice handle{nullptr};
// The features that this GPU supports
vk::PhysicalDeviceFeatures features;
// The extensions that this GPU supports
std::vector<vk::ExtensionProperties> device_extensions;
// The GPU properties
vk::PhysicalDeviceProperties properties;
// The GPU memory properties
vk::PhysicalDeviceMemoryProperties memory_properties;
// The GPU queue family properties
std::vector<vk::QueueFamilyProperties> queue_family_properties;
// The features that will be requested to be enabled in the logical device
vk::PhysicalDeviceFeatures requested_features;
// The extension feature pointer
void *last_requested_extension_feature{nullptr};
// Holds the extension feature structures, we use a map to retain an order of requested structures
std::map<vk::StructureType, std::shared_ptr<void>> extension_features;
bool high_priority_graphics_queue{false};
};
#define HPP_REQUEST_OPTIONAL_FEATURE(gpu, Feature, flag) gpu.request_optional_feature<Feature>(&Feature::flag, #Feature, #flag)
#define HPP_REQUEST_REQUIRED_FEATURE(gpu, Feature, flag) gpu.request_required_feature<Feature>(&Feature::flag, #Feature, #flag)
} // namespace core
} // namespace vkb
+75
View File
@@ -0,0 +1,75 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "core/pipeline.h"
namespace vkb
{
namespace rendering
{
class HPPPipelineState;
}
namespace core
{
/**
* @brief facade class around vkb::Pipeline, providing a vulkan.hpp-based interface
*
* See vkb::Pipeline for documentation
*/
class HPPPipeline : private vkb::Pipeline
{
public:
vk::Pipeline get_handle() const
{
return static_cast<vk::Pipeline>(vkb::Pipeline::get_handle());
}
};
class HPPComputePipeline : private vkb::ComputePipeline
{
public:
HPPComputePipeline(vkb::core::DeviceCpp &device, vk::PipelineCache pipeline_cache, vkb::rendering::HPPPipelineState &pipeline_state) :
vkb::ComputePipeline(reinterpret_cast<vkb::core::DeviceC &>(device),
static_cast<VkPipelineCache>(pipeline_cache),
reinterpret_cast<vkb::PipelineState &>(pipeline_state))
{}
vk::Pipeline get_handle() const
{
return static_cast<vk::Pipeline>(vkb::ComputePipeline::get_handle());
}
};
class HPPGraphicsPipeline : private vkb::GraphicsPipeline
{
public:
HPPGraphicsPipeline(vkb::core::DeviceCpp &device, vk::PipelineCache pipeline_cache, vkb::rendering::HPPPipelineState &pipeline_state) :
vkb::GraphicsPipeline(reinterpret_cast<vkb::core::DeviceC &>(device),
static_cast<VkPipelineCache>(pipeline_cache),
reinterpret_cast<vkb::PipelineState &>(pipeline_state))
{}
vk::Pipeline get_handle() const
{
return static_cast<vk::Pipeline>(vkb::GraphicsPipeline::get_handle());
}
};
} // namespace core
} // namespace vkb
+197
View File
@@ -0,0 +1,197 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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.
*/
#include "hpp_pipeline_layout.h"
#include "core/device.h"
#include <core/hpp_shader_module.h>
namespace vkb
{
namespace core
{
HPPPipelineLayout::HPPPipelineLayout(vkb::core::DeviceCpp &device, const std::vector<vkb::core::HPPShaderModule *> &shader_modules) :
device{device},
shader_modules{shader_modules}
{
// Collect and combine all the shader resources from each of the shader modules
// Collate them all into a map that is indexed by the name of the resource
for (auto *shader_module : shader_modules)
{
for (const auto &shader_resource : shader_module->get_resources())
{
std::string key = shader_resource.name;
// Since 'Input' and 'Output' resources can have the same name, we modify the key string
if (shader_resource.type == vkb::core::HPPShaderResourceType::Input || shader_resource.type == vkb::core::HPPShaderResourceType::Output)
{
key = std::to_string(static_cast<uint32_t>(shader_resource.stages)) + "_" + key;
}
auto it = shader_resources.find(key);
if (it != shader_resources.end())
{
// Append stage flags if resource already exists
it->second.stages |= shader_resource.stages;
}
else
{
// Create a new entry in the map
shader_resources.emplace(key, shader_resource);
}
}
}
// Sift through the map of name indexed shader resources
// Separate them into their respective sets
for (auto &it : shader_resources)
{
auto &shader_resource = it.second;
// Find binding by set index in the map.
auto it2 = shader_sets.find(shader_resource.set);
if (it2 != shader_sets.end())
{
// Add resource to the found set index
it2->second.push_back(shader_resource);
}
else
{
// Create a new set index and with the first resource
shader_sets.emplace(shader_resource.set, std::vector<vkb::core::HPPShaderResource>{shader_resource});
}
}
// Create a descriptor set layout for each shader set in the shader modules
for (auto &shader_set_it : shader_sets)
{
descriptor_set_layouts.emplace_back(
&device.get_resource_cache().request_descriptor_set_layout(shader_set_it.first, shader_modules, shader_set_it.second));
}
// Collect all the descriptor set layout handles, maintaining set order
std::vector<vk::DescriptorSetLayout> descriptor_set_layout_handles;
for (auto descriptor_set_layout : descriptor_set_layouts)
{
descriptor_set_layout_handles.push_back(descriptor_set_layout ? descriptor_set_layout->get_handle() : nullptr);
}
// Collect all the push constant shader resources
std::vector<vk::PushConstantRange> push_constant_ranges;
for (auto &push_constant_resource : get_resources(vkb::core::HPPShaderResourceType::PushConstant))
{
push_constant_ranges.push_back({push_constant_resource.stages, push_constant_resource.offset, push_constant_resource.size});
}
vk::PipelineLayoutCreateInfo create_info{.setLayoutCount = static_cast<uint32_t>(descriptor_set_layout_handles.size()),
.pSetLayouts = descriptor_set_layout_handles.data(),
.pushConstantRangeCount = static_cast<uint32_t>(push_constant_ranges.size()),
.pPushConstantRanges = push_constant_ranges.data()};
// Create the Vulkan pipeline layout handle
handle = device.get_handle().createPipelineLayout(create_info);
}
HPPPipelineLayout::HPPPipelineLayout(HPPPipelineLayout &&other) :
device{other.device},
handle{other.handle},
shader_modules{std::move(other.shader_modules)},
shader_resources{std::move(other.shader_resources)},
shader_sets{std::move(other.shader_sets)},
descriptor_set_layouts{std::move(other.descriptor_set_layouts)}
{
other.handle = nullptr;
}
HPPPipelineLayout::~HPPPipelineLayout()
{
// Destroy pipeline layout
if (handle)
{
device.get_handle().destroyPipelineLayout(handle);
}
}
vkb::core::HPPDescriptorSetLayout const &HPPPipelineLayout::get_descriptor_set_layout(const uint32_t set_index) const
{
auto it = std::ranges::find_if(descriptor_set_layouts,
[&set_index](auto const *descriptor_set_layout) { return descriptor_set_layout->get_index() == set_index; });
if (it == descriptor_set_layouts.end())
{
throw std::runtime_error("Couldn't find descriptor set layout at set index " + to_string(set_index));
}
return **it;
}
vk::PipelineLayout HPPPipelineLayout::get_handle() const
{
return handle;
}
vk::ShaderStageFlags HPPPipelineLayout::get_push_constant_range_stage(uint32_t size, uint32_t offset) const
{
vk::ShaderStageFlags stages;
for (auto &push_constant_resource : get_resources(vkb::core::HPPShaderResourceType::PushConstant))
{
if (push_constant_resource.offset <= offset && offset + size <= push_constant_resource.offset + push_constant_resource.size)
{
stages |= push_constant_resource.stages;
}
}
return stages;
}
std::vector<vkb::core::HPPShaderResource> HPPPipelineLayout::get_resources(const vkb::core::HPPShaderResourceType &type, vk::ShaderStageFlagBits stage) const
{
std::vector<vkb::core::HPPShaderResource> found_resources;
for (auto &it : shader_resources)
{
auto &shader_resource = it.second;
if (shader_resource.type == type || type == vkb::core::HPPShaderResourceType::All)
{
if (shader_resource.stages == stage || stage == vk::ShaderStageFlagBits::eAll)
{
found_resources.push_back(shader_resource);
}
}
}
return found_resources;
}
const std::vector<vkb::core::HPPShaderModule *> &HPPPipelineLayout::get_shader_modules() const
{
return shader_modules;
}
const std::unordered_map<uint32_t, std::vector<vkb::core::HPPShaderResource>> &HPPPipelineLayout::get_shader_sets() const
{
return shader_sets;
}
bool HPPPipelineLayout::has_descriptor_set_layout(const uint32_t set_index) const
{
return set_index < descriptor_set_layouts.size();
}
} // namespace core
} // namespace vkb
+64
View File
@@ -0,0 +1,64 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 <core/hpp_shader_module.h>
#include <vector>
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace core
{
class HPPDevice;
class HPPDescriptorSetLayout;
/**
* @brief A wrapper class for vk::HPPPipelineLayout
*
*/
class HPPPipelineLayout
{
public:
HPPPipelineLayout(vkb::core::DeviceCpp &device, const std::vector<vkb::core::HPPShaderModule *> &shader_modules);
HPPPipelineLayout(const HPPPipelineLayout &) = delete;
HPPPipelineLayout(HPPPipelineLayout &&other);
~HPPPipelineLayout();
HPPPipelineLayout &operator=(const HPPPipelineLayout &) = delete;
HPPPipelineLayout &operator=(HPPPipelineLayout &&) = delete;
vkb::core::HPPDescriptorSetLayout const &get_descriptor_set_layout(const uint32_t set_index) const;
vk::PipelineLayout get_handle() const;
vk::ShaderStageFlags get_push_constant_range_stage(uint32_t size, uint32_t offset = 0) const;
std::vector<vkb::core::HPPShaderResource> get_resources(const vkb::core::HPPShaderResourceType &type = vkb::core::HPPShaderResourceType::All,
vk::ShaderStageFlagBits stage = vk::ShaderStageFlagBits::eAll) const;
const std::vector<vkb::core::HPPShaderModule *> &get_shader_modules() const;
const std::unordered_map<uint32_t, std::vector<vkb::core::HPPShaderResource>> &get_shader_sets() const;
bool has_descriptor_set_layout(const uint32_t set_index) const;
private:
vkb::core::DeviceCpp &device;
vk::PipelineLayout handle;
std::vector<vkb::core::HPPShaderModule *> shader_modules; // The shader modules that this pipeline layout uses
std::unordered_map<std::string, vkb::core::HPPShaderResource> shader_resources; // The shader resources that this pipeline layout uses, indexed by their name
std::unordered_map<uint32_t, std::vector<vkb::core::HPPShaderResource>> shader_sets; // A map of each set and the resources it owns used by the pipeline layout
std::vector<vkb::core::HPPDescriptorSetLayout *> descriptor_set_layouts; // The different descriptor set layouts for this pipeline layout
};
} // namespace core
} // namespace vkb
+40
View File
@@ -0,0 +1,40 @@
/* Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
*
* 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 "core/query_pool.h"
namespace vkb
{
namespace core
{
/**
* @brief facade class around vkb::QueryPool, providing a vulkan.hpp-based interface
*
* See vkb::QueryPool for documentation
*/
class HPPQueryPool : private vkb::QueryPool
{
public:
vk::QueryPool get_handle() const
{
return static_cast<vk::QueryPool>(vkb::QueryPool::get_handle());
}
};
} // namespace core
} // namespace vkb
+88
View File
@@ -0,0 +1,88 @@
/* Copyright (c) 2022-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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.
*/
#include "core/hpp_queue.h"
#include "core/command_buffer.h"
#include "core/device.h"
namespace vkb
{
namespace core
{
HPPQueue::HPPQueue(vkb::core::DeviceCpp &device, uint32_t family_index, vk::QueueFamilyProperties const &properties, vk::Bool32 can_present, uint32_t index) :
device{device}, family_index{family_index}, index{index}, can_present{can_present}, properties{properties}
{
handle = device.get_handle().getQueue(family_index, index);
}
HPPQueue::HPPQueue(HPPQueue &&other) :
device(other.device),
handle(std::exchange(other.handle, {})),
family_index(std::exchange(other.family_index, {})),
index(std::exchange(other.index, 0)),
can_present(std::exchange(other.can_present, false)),
properties(std::exchange(other.properties, {}))
{}
const vkb::core::DeviceCpp &HPPQueue::get_device() const
{
return device;
}
vk::Queue HPPQueue::get_handle() const
{
return handle;
}
uint32_t HPPQueue::get_family_index() const
{
return family_index;
}
uint32_t HPPQueue::get_index() const
{
return index;
}
const vk::QueueFamilyProperties &HPPQueue::get_properties() const
{
return properties;
}
vk::Bool32 HPPQueue::support_present() const
{
return can_present;
}
void HPPQueue::submit(const vkb::core::CommandBufferCpp &command_buffer, vk::Fence fence) const
{
vk::CommandBuffer commandBuffer = command_buffer.get_handle();
vk::SubmitInfo submit_info{.commandBufferCount = 1, .pCommandBuffers = &commandBuffer};
handle.submit(submit_info, fence);
}
vk::Result HPPQueue::present(const vk::PresentInfoKHR &present_info) const
{
if (!can_present)
{
return vk::Result::eErrorIncompatibleDisplayKHR;
}
return handle.presentKHR(present_info);
}
} // namespace core
} // namespace vkb
+82
View File
@@ -0,0 +1,82 @@
/* Copyright (c) 2021-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "common/vk_common.h"
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceCpp = Device<vkb::BindingType::Cpp>;
template <vkb::BindingType bindingType>
class CommandBuffer;
using CommandBufferCpp = CommandBuffer<vkb::BindingType::Cpp>;
/**
* @brief A wrapper class for vk::Queue
*
*/
class HPPQueue
{
public:
HPPQueue(vkb::core::DeviceCpp &device, uint32_t family_index, vk::QueueFamilyProperties const &properties, vk::Bool32 can_present, uint32_t index);
HPPQueue(const HPPQueue &) = default;
HPPQueue(HPPQueue &&other);
HPPQueue &operator=(const HPPQueue &) = delete;
HPPQueue &operator=(HPPQueue &&) = delete;
const vkb::core::DeviceCpp &get_device() const;
vk::Queue get_handle() const;
uint32_t get_family_index() const;
uint32_t get_index() const;
const vk::QueueFamilyProperties &get_properties() const;
vk::Bool32 support_present() const;
void submit(const vkb::core::CommandBufferCpp &command_buffer, vk::Fence fence) const;
vk::Result present(const vk::PresentInfoKHR &present_infos) const;
private:
vkb::core::DeviceCpp &device;
vk::Queue handle;
uint32_t family_index{0};
uint32_t index{0};
vk::Bool32 can_present = false;
vk::QueueFamilyProperties properties{};
};
} // namespace core
} // namespace vkb
+82
View File
@@ -0,0 +1,82 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "core/render_pass.h"
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace common
{
struct HPPLoadStoreInfo;
}
namespace rendering
{
struct HPPAttachment;
}
namespace core
{
/**
* @brief facade class around vkb::RenderPass, providing a vulkan.hpp-based interface
*
* See vkb::RenderPass for documentation
*/
struct HPPSubpassInfo
{
std::vector<uint32_t> input_attachments;
std::vector<uint32_t> output_attachments;
std::vector<uint32_t> color_resolve_attachments;
bool disable_depth_stencil_attachment;
uint32_t depth_stencil_resolve_attachment;
vk::ResolveModeFlagBits depth_stencil_resolve_mode;
std::string debug_name;
};
class HPPRenderPass : private vkb::RenderPass
{
public:
using vkb::RenderPass::get_color_output_count;
public:
HPPRenderPass(vkb::core::DeviceCpp &device,
const std::vector<vkb::rendering::HPPAttachment> &attachments,
const std::vector<vkb::common::HPPLoadStoreInfo> &load_store_infos,
const std::vector<vkb::core::HPPSubpassInfo> &subpasses) :
vkb::RenderPass(reinterpret_cast<vkb::core::DeviceC &>(device),
reinterpret_cast<std::vector<vkb::Attachment> const &>(attachments),
reinterpret_cast<std::vector<vkb::LoadStoreInfo> const &>(load_store_infos),
reinterpret_cast<std::vector<vkb::SubpassInfo> const &>(subpasses))
{}
vk::RenderPass get_handle() const
{
return static_cast<vk::RenderPass>(vkb::RenderPass::get_handle());
}
vk::Extent2D get_render_area_granularity() const
{
VkExtent2D extent = vkb::RenderPass::get_render_area_granularity();
return *reinterpret_cast<vk::Extent2D *>(&extent);
}
};
} // namespace core
} // namespace vkb
+42
View File
@@ -0,0 +1,42 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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.
*/
#include "hpp_sampler.h"
#include "core/device.h"
namespace vkb
{
namespace core
{
HPPSampler::HPPSampler(vkb::core::DeviceCpp &device, const vk::SamplerCreateInfo &info) :
vkb::core::VulkanResourceCpp<vk::Sampler>{device.get_handle().createSampler(info), &device}
{}
HPPSampler::HPPSampler(HPPSampler &&other) :
VulkanResource(std::move(other))
{}
HPPSampler::~HPPSampler()
{
if (get_handle())
{
get_device().get_handle().destroySampler(get_handle());
}
}
} // namespace core
} // namespace vkb
+50
View File
@@ -0,0 +1,50 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "core/vulkan_resource.h"
namespace vkb
{
namespace core
{
/**
* @brief Represents a Vulkan Sampler, using Vulkan-Hpp
*/
class HPPSampler : public vkb::core::VulkanResourceCpp<vk::Sampler>
{
public:
/**
* @brief Creates a Vulkan HPPSampler
* @param device The device to use
* @param info Creation details
*/
HPPSampler(vkb::core::DeviceCpp &device, const vk::SamplerCreateInfo &info);
HPPSampler(const HPPSampler &) = delete;
HPPSampler(HPPSampler &&sampler);
~HPPSampler();
HPPSampler &operator=(const HPPSampler &) = delete;
HPPSampler &operator=(HPPSampler &&) = delete;
};
} // namespace core
} // namespace vkb
+120
View File
@@ -0,0 +1,120 @@
/* Copyright (c) 2023-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "core/shader_module.h"
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceCpp = Device<vkb::BindingType::Cpp>;
using DeviceC = Device<vkb::BindingType::C>;
/**
* @brief facade class around vkb::ShaderModule, providing a vulkan.hpp-based interface
*
* See vkb::ShaderModule for documentation
*/
/// Types of shader resources
enum class HPPShaderResourceType
{
Input,
InputAttachment,
Output,
Image,
ImageSampler,
ImageStorage,
Sampler,
BufferUniform,
BufferStorage,
PushConstant,
SpecializationConstant,
All
};
/// This determines the type and method of how descriptor set should be created and bound
enum class HPPShaderResourceMode
{
Static,
Dynamic,
UpdateAfterBind
};
/// Store shader resource data.
/// Used by the shader module.
struct HPPShaderResource
{
vk::ShaderStageFlags stages;
HPPShaderResourceType type;
HPPShaderResourceMode mode;
uint32_t set;
uint32_t binding;
uint32_t location;
uint32_t input_attachment_index;
uint32_t vec_size;
uint32_t columns;
uint32_t array_size;
uint32_t offset;
uint32_t size;
uint32_t constant_id;
uint32_t qualifiers;
std::string name;
};
class HPPShaderSource : private vkb::ShaderSource
{
public:
HPPShaderSource(const std::string &filename) :
vkb::ShaderSource(filename)
{}
};
class HPPShaderVariant : private vkb::ShaderVariant
{};
class HPPShaderModule : private vkb::ShaderModule
{
public:
using vkb::ShaderModule::get_id;
public:
HPPShaderModule(vkb::core::DeviceCpp &device,
vk::ShaderStageFlagBits stage,
const vkb::core::HPPShaderSource &glsl_source,
const std::string &entry_point,
const vkb::core::HPPShaderVariant &shader_variant) :
vkb::ShaderModule(reinterpret_cast<vkb::core::DeviceC &>(device),
static_cast<VkShaderStageFlagBits>(stage),
reinterpret_cast<vkb::ShaderSource const &>(glsl_source),
entry_point,
reinterpret_cast<vkb::ShaderVariant const &>(shader_variant))
{}
const std::vector<vkb::core::HPPShaderResource> &get_resources() const
{
return reinterpret_cast<std::vector<vkb::core::HPPShaderResource> const &>(vkb::ShaderModule::get_resources());
}
};
} // namespace core
} // namespace vkb
+416
View File
@@ -0,0 +1,416 @@
/* Copyright (c) 2022-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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.
*/
#include "core/hpp_swapchain.h"
#include "core/device.h"
#include "core/hpp_physical_device.h"
namespace vkb
{
namespace
{
template <class T>
constexpr const T &clamp(const T &v, const T &lo, const T &hi)
{
return (v < lo) ? lo : ((hi < v) ? hi : v);
}
vk::Extent2D choose_extent(vk::Extent2D request_extent,
const vk::Extent2D &min_image_extent,
const vk::Extent2D &max_image_extent,
const vk::Extent2D &current_extent)
{
if (current_extent.width == 0xFFFFFFFF)
{
return request_extent;
}
if (request_extent.width < 1 || request_extent.height < 1)
{
LOGW("(HPPSwapchain) Image extent ({}, {}) not supported. Selecting ({}, {}).",
request_extent.width,
request_extent.height,
current_extent.width,
current_extent.height);
return current_extent;
}
request_extent.width = clamp(request_extent.width, min_image_extent.width, max_image_extent.width);
request_extent.height = clamp(request_extent.height, min_image_extent.height, max_image_extent.height);
return request_extent;
}
vk::PresentModeKHR choose_present_mode(vk::PresentModeKHR request_present_mode,
const std::vector<vk::PresentModeKHR> &available_present_modes,
const std::vector<vk::PresentModeKHR> &present_mode_priority_list)
{
// Try to find the requested present mode in the available present modes
auto const present_mode_it = std::ranges::find(available_present_modes, request_present_mode);
if (present_mode_it == available_present_modes.end())
{
// If the requested present mode isn't found, then try to find a mode from the priority list
auto const chosen_present_mode_it =
std::ranges::find_if(present_mode_priority_list,
[&available_present_modes](vk::PresentModeKHR present_mode) { return std::ranges::find(available_present_modes, present_mode) != available_present_modes.end(); });
// If nothing found, always default to FIFO
vk::PresentModeKHR const chosen_present_mode = (chosen_present_mode_it != present_mode_priority_list.end()) ? *chosen_present_mode_it : vk::PresentModeKHR::eFifo;
LOGW("(HPPSwapchain) Present mode '{}' not supported. Selecting '{}'.", vk::to_string(request_present_mode), vk::to_string(chosen_present_mode));
return chosen_present_mode;
}
else
{
LOGI("(HPPSwapchain) Present mode selected: {}", to_string(request_present_mode));
return request_present_mode;
}
}
vk::SurfaceFormatKHR choose_surface_format(const vk::SurfaceFormatKHR requested_surface_format,
const std::vector<vk::SurfaceFormatKHR> &available_surface_formats,
const std::vector<vk::SurfaceFormatKHR> &surface_format_priority_list)
{
// Try to find the requested surface format in the available surface formats
auto const surface_format_it = std::ranges::find(available_surface_formats, requested_surface_format);
// If the requested surface format isn't found, then try to request a format from the priority list
if (surface_format_it == available_surface_formats.end())
{
auto const chosen_surface_format_it =
std::ranges::find_if(surface_format_priority_list,
[&available_surface_formats](vk::SurfaceFormatKHR surface_format) { return std::ranges::find(available_surface_formats, surface_format) != available_surface_formats.end(); });
// If nothing found, default to the first available format
vk::SurfaceFormatKHR const &chosen_surface_format = (chosen_surface_format_it != surface_format_priority_list.end()) ? *chosen_surface_format_it : available_surface_formats[0];
LOGW("(HPPSwapchain) Surface format ({}) not supported. Selecting ({}).",
vk::to_string(requested_surface_format.format) + ", " + vk::to_string(requested_surface_format.colorSpace),
vk::to_string(chosen_surface_format.format) + ", " + vk::to_string(chosen_surface_format.colorSpace));
return chosen_surface_format;
}
else
{
LOGI("(HPPSwapchain) Surface format selected: {}",
vk::to_string(requested_surface_format.format) + ", " + vk::to_string(requested_surface_format.colorSpace));
return requested_surface_format;
}
}
vk::SurfaceTransformFlagBitsKHR choose_transform(vk::SurfaceTransformFlagBitsKHR request_transform,
vk::SurfaceTransformFlagsKHR supported_transform,
vk::SurfaceTransformFlagBitsKHR current_transform)
{
if (request_transform & supported_transform)
{
return request_transform;
}
LOGW("(HPPSwapchain) Surface transform '{}' not supported. Selecting '{}'.", vk::to_string(request_transform), vk::to_string(current_transform));
return current_transform;
}
vk::CompositeAlphaFlagBitsKHR choose_composite_alpha(vk::CompositeAlphaFlagBitsKHR request_composite_alpha,
vk::CompositeAlphaFlagsKHR supported_composite_alpha)
{
if (request_composite_alpha & supported_composite_alpha)
{
return request_composite_alpha;
}
static const std::vector<vk::CompositeAlphaFlagBitsKHR> composite_alpha_priority_list = {vk::CompositeAlphaFlagBitsKHR::eOpaque,
vk::CompositeAlphaFlagBitsKHR::ePreMultiplied,
vk::CompositeAlphaFlagBitsKHR::ePostMultiplied,
vk::CompositeAlphaFlagBitsKHR::eInherit};
auto const chosen_composite_alpha_it =
std::find_if(composite_alpha_priority_list.begin(),
composite_alpha_priority_list.end(),
[&supported_composite_alpha](vk::CompositeAlphaFlagBitsKHR composite_alpha) { return composite_alpha & supported_composite_alpha; });
if (chosen_composite_alpha_it == composite_alpha_priority_list.end())
{
throw std::runtime_error("No compatible composite alpha found.");
}
else
{
LOGW("(HPPSwapchain) Composite alpha '{}' not supported. Selecting '{}.", vk::to_string(request_composite_alpha), vk::to_string(*chosen_composite_alpha_it));
return *chosen_composite_alpha_it;
}
}
bool validate_format_feature(vk::ImageUsageFlagBits image_usage, vk::FormatFeatureFlags supported_features)
{
return (image_usage != vk::ImageUsageFlagBits::eStorage) || (supported_features & vk::FormatFeatureFlagBits::eStorageImage);
}
std::set<vk::ImageUsageFlagBits> choose_image_usage(const std::set<vk::ImageUsageFlagBits> &requested_image_usage_flags,
vk::ImageUsageFlags supported_image_usage,
vk::FormatFeatureFlags supported_features)
{
std::set<vk::ImageUsageFlagBits> validated_image_usage_flags;
for (auto flag : requested_image_usage_flags)
{
if ((flag & supported_image_usage) && validate_format_feature(flag, supported_features))
{
validated_image_usage_flags.insert(flag);
}
else
{
LOGW("(HPPSwapchain) Image usage ({}) requested but not supported.", vk::to_string(flag));
}
}
if (validated_image_usage_flags.empty())
{
// Pick the first format from list of defaults, if supported
static const std::vector<vk::ImageUsageFlagBits> image_usage_priority_list = {
vk::ImageUsageFlagBits::eColorAttachment, vk::ImageUsageFlagBits::eStorage, vk::ImageUsageFlagBits::eSampled, vk::ImageUsageFlagBits::eTransferDst};
auto const priority_list_it =
std::ranges::find_if(image_usage_priority_list,
[&supported_image_usage, &supported_features](auto const image_usage) { return ((image_usage & supported_image_usage) && validate_format_feature(image_usage, supported_features)); });
if (priority_list_it != image_usage_priority_list.end())
{
validated_image_usage_flags.insert(*priority_list_it);
}
}
if (validated_image_usage_flags.empty())
{
throw std::runtime_error("No compatible image usage found.");
}
else
{
// Log image usage flags used
std::string usage_list;
for (vk::ImageUsageFlagBits image_usage : validated_image_usage_flags)
{
usage_list += to_string(image_usage) + " ";
}
LOGI("(HPPSwapchain) Image usage flags: {}", usage_list);
}
return validated_image_usage_flags;
}
vk::ImageUsageFlags composite_image_flags(std::set<vk::ImageUsageFlagBits> &image_usage_flags)
{
vk::ImageUsageFlags image_usage;
for (auto flag : image_usage_flags)
{
image_usage |= flag;
}
return image_usage;
}
} // namespace
namespace core
{
HPPSwapchain::HPPSwapchain(HPPSwapchain &old_swapchain, const vk::Extent2D &extent) :
HPPSwapchain{old_swapchain.device,
old_swapchain.surface,
old_swapchain.properties.present_mode,
old_swapchain.present_mode_priority_list,
old_swapchain.surface_format_priority_list,
extent,
old_swapchain.properties.image_count,
old_swapchain.properties.pre_transform,
old_swapchain.image_usage_flags,
old_swapchain.get_handle()}
{}
HPPSwapchain::HPPSwapchain(HPPSwapchain &old_swapchain, const uint32_t image_count) :
HPPSwapchain{old_swapchain.device,
old_swapchain.surface,
old_swapchain.properties.present_mode,
old_swapchain.present_mode_priority_list,
old_swapchain.surface_format_priority_list,
old_swapchain.properties.extent,
image_count,
old_swapchain.properties.pre_transform,
old_swapchain.image_usage_flags,
old_swapchain.get_handle()}
{}
HPPSwapchain::HPPSwapchain(HPPSwapchain &old_swapchain, const std::set<vk::ImageUsageFlagBits> &image_usage_flags) :
HPPSwapchain{old_swapchain.device,
old_swapchain.surface,
old_swapchain.properties.present_mode,
old_swapchain.present_mode_priority_list,
old_swapchain.surface_format_priority_list,
old_swapchain.properties.extent,
old_swapchain.properties.image_count,
old_swapchain.properties.pre_transform,
image_usage_flags,
old_swapchain.get_handle()}
{}
HPPSwapchain::HPPSwapchain(HPPSwapchain &old_swapchain, const vk::Extent2D &extent, const vk::SurfaceTransformFlagBitsKHR transform) :
HPPSwapchain{old_swapchain.device,
old_swapchain.surface,
old_swapchain.properties.present_mode,
old_swapchain.present_mode_priority_list,
old_swapchain.surface_format_priority_list,
extent,
old_swapchain.properties.image_count,
transform,
old_swapchain.image_usage_flags,
old_swapchain.get_handle()}
{}
HPPSwapchain::HPPSwapchain(vkb::core::DeviceCpp &device,
vk::SurfaceKHR surface,
const vk::PresentModeKHR present_mode,
const std::vector<vk::PresentModeKHR> &present_mode_priority_list,
const std::vector<vk::SurfaceFormatKHR> &surface_format_priority_list,
const vk::Extent2D &extent,
const uint32_t image_count,
const vk::SurfaceTransformFlagBitsKHR transform,
const std::set<vk::ImageUsageFlagBits> &image_usage_flags,
vk::SwapchainKHR old_swapchain) :
device{device},
surface{surface}
{
this->present_mode_priority_list = present_mode_priority_list;
this->surface_format_priority_list = surface_format_priority_list;
std::vector<vk::SurfaceFormatKHR> surface_formats = device.get_gpu().get_handle().getSurfaceFormatsKHR(surface);
LOGI("Surface supports the following surface formats:");
for (auto &surface_format : surface_formats)
{
LOGI(" \t{}", vk::to_string(surface_format.format) + ", " + vk::to_string(surface_format.colorSpace));
}
std::vector<vk::PresentModeKHR> present_modes = device.get_gpu().get_handle().getSurfacePresentModesKHR(surface);
LOGI("Surface supports the following present modes:");
for (auto &present_mode : present_modes)
{
LOGI(" \t{}", to_string(present_mode));
}
// Choose best properties based on surface capabilities
vk::SurfaceCapabilitiesKHR const surface_capabilities = device.get_gpu().get_handle().getSurfaceCapabilitiesKHR(surface);
properties.old_swapchain = old_swapchain;
properties.image_count = clamp(image_count,
surface_capabilities.minImageCount,
surface_capabilities.maxImageCount ? surface_capabilities.maxImageCount : std::numeric_limits<uint32_t>::max());
properties.extent = choose_extent(extent, surface_capabilities.minImageExtent, surface_capabilities.maxImageExtent, surface_capabilities.currentExtent);
properties.surface_format = choose_surface_format(properties.surface_format, surface_formats, surface_format_priority_list);
properties.array_layers = 1;
vk::FormatProperties const format_properties = device.get_gpu().get_handle().getFormatProperties(properties.surface_format.format);
this->image_usage_flags = choose_image_usage(image_usage_flags, surface_capabilities.supportedUsageFlags, format_properties.optimalTilingFeatures);
properties.image_usage = composite_image_flags(this->image_usage_flags);
properties.pre_transform = choose_transform(transform, surface_capabilities.supportedTransforms, surface_capabilities.currentTransform);
properties.composite_alpha = choose_composite_alpha(vk::CompositeAlphaFlagBitsKHR::eInherit, surface_capabilities.supportedCompositeAlpha);
properties.present_mode = choose_present_mode(present_mode, present_modes, present_mode_priority_list);
vk::SwapchainCreateInfoKHR const create_info{.surface = surface,
.minImageCount = properties.image_count,
.imageFormat = properties.surface_format.format,
.imageColorSpace = properties.surface_format.colorSpace,
.imageExtent = properties.extent,
.imageArrayLayers = properties.array_layers,
.imageUsage = properties.image_usage,
.preTransform = properties.pre_transform,
.compositeAlpha = properties.composite_alpha,
.presentMode = properties.present_mode,
.oldSwapchain = properties.old_swapchain};
handle = device.get_handle().createSwapchainKHR(create_info);
images = device.get_handle().getSwapchainImagesKHR(handle);
}
HPPSwapchain::~HPPSwapchain()
{
if (handle)
{
device.get_handle().destroySwapchainKHR(handle);
}
}
HPPSwapchain::HPPSwapchain(HPPSwapchain &&other) :
device{other.device},
surface{std::exchange(other.surface, nullptr)},
handle{std::exchange(other.handle, nullptr)},
images{std::exchange(other.images, {})},
properties{std::exchange(other.properties, {})},
present_mode_priority_list{std::exchange(other.present_mode_priority_list, {})},
surface_format_priority_list{std::exchange(other.surface_format_priority_list, {})},
image_usage_flags{std::move(other.image_usage_flags)}
{}
bool HPPSwapchain::is_valid() const
{
return !!handle;
}
vkb::core::DeviceCpp const &HPPSwapchain::get_device() const
{
return device;
}
vk::SwapchainKHR HPPSwapchain::get_handle() const
{
return handle;
}
std::pair<vk::Result, uint32_t> HPPSwapchain::acquire_next_image(vk::Semaphore image_acquired_semaphore, vk::Fence fence) const
{
vk::ResultValue<uint32_t> rv = device.get_handle().acquireNextImageKHR(handle, std::numeric_limits<uint64_t>::max(), image_acquired_semaphore, fence);
return std::make_pair(rv.result, rv.value);
}
const vk::Extent2D &HPPSwapchain::get_extent() const
{
return properties.extent;
}
vk::Format HPPSwapchain::get_format() const
{
return properties.surface_format.format;
}
const std::vector<vk::Image> &HPPSwapchain::get_images() const
{
return images;
}
vk::SurfaceTransformFlagBitsKHR HPPSwapchain::get_transform() const
{
return properties.pre_transform;
}
vk::SurfaceKHR HPPSwapchain::get_surface() const
{
return surface;
}
vk::ImageUsageFlags HPPSwapchain::get_usage() const
{
return properties.image_usage;
}
vk::PresentModeKHR HPPSwapchain::get_present_mode() const
{
return properties.present_mode;
}
} // namespace core
} // namespace vkb
+139
View File
@@ -0,0 +1,139 @@
/* Copyright (c) 2021-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "common/vk_common.h"
#include <set>
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceCpp = Device<vkb::BindingType::Cpp>;
struct HPPSwapchainProperties
{
vk::SwapchainKHR old_swapchain;
uint32_t image_count{3};
vk::Extent2D extent;
vk::SurfaceFormatKHR surface_format;
uint32_t array_layers;
vk::ImageUsageFlags image_usage;
vk::SurfaceTransformFlagBitsKHR pre_transform;
vk::CompositeAlphaFlagBitsKHR composite_alpha;
vk::PresentModeKHR present_mode;
};
class HPPSwapchain
{
public:
/**
* @brief Constructor to create a swapchain by changing the extent
* only and preserving the configuration from the old swapchain.
*/
HPPSwapchain(HPPSwapchain &old_swapchain, const vk::Extent2D &extent);
/**
* @brief Constructor to create a swapchain by changing the image count
* only and preserving the configuration from the old swapchain.
*/
HPPSwapchain(HPPSwapchain &old_swapchain, const uint32_t image_count);
/**
* @brief Constructor to create a swapchain by changing the image usage
* only and preserving the configuration from the old swapchain.
*/
HPPSwapchain(HPPSwapchain &old_swapchain, const std::set<vk::ImageUsageFlagBits> &image_usage_flags);
/**
* @brief Constructor to create a swapchain by changing the extent
* and transform only and preserving the configuration from the old swapchain.
*/
HPPSwapchain(HPPSwapchain &swapchain, const vk::Extent2D &extent, const vk::SurfaceTransformFlagBitsKHR transform);
/**
* @brief Constructor to create a swapchain.
*/
HPPSwapchain(vkb::core::DeviceCpp &device,
vk::SurfaceKHR surface,
const vk::PresentModeKHR present_mode,
const std::vector<vk::PresentModeKHR> &present_mode_priority_list = {vk::PresentModeKHR::eFifo, vk::PresentModeKHR::eMailbox},
const std::vector<vk::SurfaceFormatKHR> &surface_format_priority_list = {{vk::Format::eR8G8B8A8Srgb, vk::ColorSpaceKHR::eSrgbNonlinear},
{vk::Format::eB8G8R8A8Srgb, vk::ColorSpaceKHR::eSrgbNonlinear}},
const vk::Extent2D &extent = {},
const uint32_t image_count = 3,
const vk::SurfaceTransformFlagBitsKHR transform = vk::SurfaceTransformFlagBitsKHR::eIdentity,
const std::set<vk::ImageUsageFlagBits> &image_usage_flags = {vk::ImageUsageFlagBits::eColorAttachment, vk::ImageUsageFlagBits::eTransferSrc},
vk::SwapchainKHR old_swapchain = nullptr);
HPPSwapchain(const HPPSwapchain &) = delete;
HPPSwapchain(HPPSwapchain &&other);
~HPPSwapchain();
HPPSwapchain &operator=(const HPPSwapchain &) = delete;
HPPSwapchain &operator=(HPPSwapchain &&) = delete;
bool is_valid() const;
vkb::core::DeviceCpp const &get_device() const;
vk::SwapchainKHR get_handle() const;
std::pair<vk::Result, uint32_t> acquire_next_image(vk::Semaphore image_acquired_semaphore, vk::Fence fence = nullptr) const;
const vk::Extent2D &get_extent() const;
vk::Format get_format() const;
const std::vector<vk::Image> &get_images() const;
vk::SurfaceTransformFlagBitsKHR get_transform() const;
vk::SurfaceKHR get_surface() const;
vk::ImageUsageFlags get_usage() const;
vk::PresentModeKHR get_present_mode() const;
private:
vkb::core::DeviceCpp &device;
vk::SurfaceKHR surface;
vk::SwapchainKHR handle;
std::vector<vk::Image> images;
HPPSwapchainProperties properties;
// A list of present modes in order of priority (vector[0] has high priority, vector[size-1] has low priority)
std::vector<vk::PresentModeKHR> present_mode_priority_list;
// A list of surface formats in order of priority (vector[0] has high priority, vector[size-1] has low priority)
std::vector<vk::SurfaceFormatKHR> surface_format_priority_list;
std::set<vk::ImageUsageFlagBits> image_usage_flags;
};
} // namespace core
} // namespace vkb
+192
View File
@@ -0,0 +1,192 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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 <unordered_set>
#include "builder_base.h"
#include "common/helpers.h"
#include "common/vk_common.h"
#include "core/allocated.h"
#include "core/vulkan_resource.h"
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceC = Device<vkb::BindingType::C>;
class Image;
using ImagePtr = std::unique_ptr<Image>;
struct ImageBuilder : public vkb::allocated::BuilderBaseC<ImageBuilder, VkImageCreateInfo>
{
private:
using Parent = vkb::allocated::BuilderBaseC<ImageBuilder, VkImageCreateInfo>;
public:
ImageBuilder(VkExtent3D const &extent) :
Parent(VkImageCreateInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, nullptr})
{
VkImageCreateInfo &create_info = get_create_info();
create_info.extent = extent;
create_info.arrayLayers = 1;
create_info.mipLevels = 1;
create_info.imageType = VK_IMAGE_TYPE_2D;
create_info.format = VK_FORMAT_R8G8B8A8_UNORM;
create_info.samples = VK_SAMPLE_COUNT_1_BIT;
}
ImageBuilder(uint32_t width, uint32_t height = 1, uint32_t depth = 1) :
ImageBuilder(VkExtent3D{width, height, depth})
{
}
ImageBuilder &with_format(VkFormat format)
{
get_create_info().format = format;
return *this;
}
ImageBuilder &with_usage(VkImageUsageFlags usage)
{
get_create_info().usage = usage;
return *this;
}
ImageBuilder &with_flags(VkImageCreateFlags flags)
{
get_create_info().flags = flags;
return *this;
}
ImageBuilder &with_image_type(VkImageType type)
{
get_create_info().imageType = type;
return *this;
}
ImageBuilder &with_array_layers(uint32_t layers)
{
get_create_info().arrayLayers = layers;
return *this;
}
ImageBuilder &with_mip_levels(uint32_t levels)
{
get_create_info().mipLevels = levels;
return *this;
}
ImageBuilder &with_sample_count(VkSampleCountFlagBits sample_count)
{
get_create_info().samples = sample_count;
return *this;
}
ImageBuilder &with_tiling(VkImageTiling tiling)
{
get_create_info().tiling = tiling;
return *this;
}
template <typename ExtensionType>
ImageBuilder &with_extension(ExtensionType &extension)
{
extension.pNext = create_info.pNext;
create_info.pNext = &extension;
return *this;
}
Image build(Device<vkb::BindingType::C> &device) const;
ImagePtr build_unique(Device<vkb::BindingType::C> &device) const;
};
class ImageView;
class Image : public vkb::allocated::AllocatedC<VkImage>
{
public:
Image(vkb::core::DeviceC &device,
VkImage handle,
const VkExtent3D &extent,
VkFormat format,
VkImageUsageFlags image_usage,
VkSampleCountFlagBits sample_count = VK_SAMPLE_COUNT_1_BIT);
// [[deprecated("Use the ImageBuilder ctor instead")]]
Image(
vkb::core::DeviceC &device,
const VkExtent3D &extent,
VkFormat format,
VkImageUsageFlags image_usage,
VmaMemoryUsage memory_usage = VMA_MEMORY_USAGE_AUTO,
VkSampleCountFlagBits sample_count = VK_SAMPLE_COUNT_1_BIT,
uint32_t mip_levels = 1,
uint32_t array_layers = 1,
VkImageTiling tiling = VK_IMAGE_TILING_OPTIMAL,
VkImageCreateFlags flags = 0,
uint32_t num_queue_families = 0,
const uint32_t *queue_families = nullptr);
Image(vkb::core::DeviceC &device, ImageBuilder const &builder);
Image(const Image &) = delete;
Image(Image &&other) noexcept;
~Image() override;
Image &operator=(const Image &) = delete;
Image &operator=(Image &&) = delete;
VkImageType get_type() const;
const VkExtent3D &get_extent() const;
VkFormat get_format() const;
VkSampleCountFlagBits get_sample_count() const;
VkImageUsageFlags get_usage() const;
VkImageTiling get_tiling() const;
const VkImageSubresource &get_subresource() const;
uint32_t get_array_layer_count() const;
std::unordered_set<ImageView *> &get_views();
VkDeviceSize get_image_required_size() const;
VkImageCompressionPropertiesEXT get_applied_compression() const;
private:
/// Image views referring to this image
VkImageCreateInfo create_info{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
VkImageSubresource subresource{};
std::unordered_set<ImageView *> views;
};
} // namespace core
} // namespace vkb
+210
View File
@@ -0,0 +1,210 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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.
*/
#include "image.h"
#include "device.h"
#include "image_view.h"
namespace vkb
{
namespace
{
inline VkImageType find_image_type(VkExtent3D extent)
{
VkImageType result{};
uint32_t dim_num{0};
if (extent.width >= 1)
{
dim_num++;
}
if (extent.height >= 1)
{
dim_num++;
}
if (extent.depth > 1)
{
dim_num++;
}
switch (dim_num)
{
case 1:
result = VK_IMAGE_TYPE_1D;
break;
case 2:
result = VK_IMAGE_TYPE_2D;
break;
case 3:
result = VK_IMAGE_TYPE_3D;
break;
default:
throw std::runtime_error("No image type found.");
break;
}
return result;
}
} // namespace
namespace core
{
Image ImageBuilder::build(vkb::core::DeviceC &device) const
{
return Image(device, *this);
}
ImagePtr ImageBuilder::build_unique(vkb::core::DeviceC &device) const
{
return std::make_unique<Image>(device, *this);
}
Image::Image(vkb::core::DeviceC &device,
const VkExtent3D &extent,
VkFormat format,
VkImageUsageFlags image_usage,
VmaMemoryUsage memory_usage,
VkSampleCountFlagBits sample_count,
const uint32_t mip_levels,
const uint32_t array_layers,
VkImageTiling tiling,
VkImageCreateFlags flags,
uint32_t num_queue_families,
const uint32_t *queue_families) :
// Pass through to the ImageBuilder ctor
Image(device,
ImageBuilder(extent)
.with_format(format)
.with_image_type(find_image_type(extent))
.with_usage(image_usage)
.with_mip_levels(mip_levels)
.with_array_layers(array_layers)
.with_tiling(tiling)
.with_flags(flags)
.with_vma_usage(memory_usage)
.with_sample_count(sample_count)
.with_queue_families(num_queue_families, queue_families)
.with_implicit_sharing_mode())
{
}
Image::Image(vkb::core::DeviceC &device, ImageBuilder const &builder) :
vkb::allocated::AllocatedC<VkImage>{builder.get_allocation_create_info(), VK_NULL_HANDLE, &device}, create_info(builder.get_create_info())
{
set_handle(create_image(create_info));
subresource.arrayLayer = create_info.arrayLayers;
subresource.mipLevel = create_info.mipLevels;
if (!builder.get_debug_name().empty())
{
set_debug_name(builder.get_debug_name());
}
}
Image::Image(
vkb::core::DeviceC &device, VkImage handle, const VkExtent3D &extent, VkFormat format, VkImageUsageFlags image_usage, VkSampleCountFlagBits sample_count) :
vkb::allocated::AllocatedC<VkImage>{handle, &device}
{
create_info.extent = extent;
create_info.imageType = find_image_type(extent);
create_info.format = format;
create_info.samples = sample_count;
create_info.usage = image_usage;
subresource.arrayLayer = create_info.arrayLayers = 1;
subresource.mipLevel = create_info.mipLevels = 1;
}
Image::Image(Image &&other) noexcept
:
vkb::allocated::AllocatedC<VkImage>{std::move(other)}, create_info{std::exchange(other.create_info, {})}, subresource{std::exchange(other.subresource, {})}, views(std::exchange(other.views, {}))
{
// Update image views references to this image to avoid dangling pointers
for (auto &view : views)
{
view->set_image(*this);
}
}
Image::~Image()
{
destroy_image(get_handle());
}
VkImageType Image::get_type() const
{
return create_info.imageType;
}
const VkExtent3D &Image::get_extent() const
{
return create_info.extent;
}
VkFormat Image::get_format() const
{
return create_info.format;
}
VkSampleCountFlagBits Image::get_sample_count() const
{
return create_info.samples;
}
VkImageUsageFlags Image::get_usage() const
{
return create_info.usage;
}
VkImageTiling Image::get_tiling() const
{
return create_info.tiling;
}
const VkImageSubresource &Image::get_subresource() const
{
return subresource;
}
uint32_t Image::get_array_layer_count() const
{
return create_info.arrayLayers;
}
std::unordered_set<ImageView *> &Image::get_views()
{
return views;
}
VkDeviceSize Image::get_image_required_size() const
{
VkMemoryRequirements memory_requirements;
vkGetImageMemoryRequirements(get_device().get_handle(), get_handle(), &memory_requirements);
return memory_requirements.size;
}
VkImageCompressionPropertiesEXT Image::get_applied_compression() const
{
return query_applied_compression(get_device().get_handle(), get_handle());
}
} // namespace core
} // namespace vkb
+122
View File
@@ -0,0 +1,122 @@
/* Copyright (c) 2019-2024, Arm Limited and Contributors
*
* 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.
*/
#include "image_view.h"
#include "core/image.h"
#include "device.h"
namespace vkb
{
namespace core
{
ImageView::ImageView(Image &img, VkImageViewType view_type, VkFormat format,
uint32_t mip_level, uint32_t array_layer,
uint32_t n_mip_levels, uint32_t n_array_layers) :
VulkanResource{VK_NULL_HANDLE, &img.get_device()},
image{&img},
format{format}
{
if (format == VK_FORMAT_UNDEFINED)
{
this->format = format = image->get_format();
}
subresource_range.baseMipLevel = mip_level;
subresource_range.baseArrayLayer = array_layer;
subresource_range.levelCount = n_mip_levels == 0 ? image->get_subresource().mipLevel : n_mip_levels;
subresource_range.layerCount = n_array_layers == 0 ? image->get_subresource().arrayLayer : n_array_layers;
if (is_depth_format(format))
{
subresource_range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
}
else
{
subresource_range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
}
VkImageViewCreateInfo view_info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
view_info.image = image->get_handle();
view_info.viewType = view_type;
view_info.format = format;
view_info.subresourceRange = subresource_range;
auto result = vkCreateImageView(get_device().get_handle(), &view_info, nullptr, &get_handle());
if (result != VK_SUCCESS)
{
throw VulkanException{result, "Cannot create ImageView"};
}
// Register this image view to its image
// in order to be notified when it gets moved
image->get_views().emplace(this);
}
ImageView::ImageView(ImageView &&other) :
VulkanResource{std::move(other)},
image{other.image},
format{other.format},
subresource_range{other.subresource_range}
{
// Remove old view from image set and add this new one
auto &views = image->get_views();
views.erase(&other);
views.emplace(this);
}
ImageView::~ImageView()
{
if (has_device())
{
vkDestroyImageView(get_device().get_handle(), get_handle(), nullptr);
}
}
const Image &ImageView::get_image() const
{
assert(image && "Image view is referring an invalid image");
return *image;
}
void ImageView::set_image(Image &img)
{
image = &img;
}
VkFormat ImageView::get_format() const
{
return format;
}
VkImageSubresourceRange ImageView::get_subresource_range() const
{
return subresource_range;
}
VkImageSubresourceLayers ImageView::get_subresource_layers() const
{
VkImageSubresourceLayers subresource{};
subresource.aspectMask = subresource_range.aspectMask;
subresource.baseArrayLayer = subresource_range.baseArrayLayer;
subresource.layerCount = subresource_range.layerCount;
subresource.mipLevel = subresource_range.baseMipLevel;
return subresource;
}
} // namespace core
} // namespace vkb
+69
View File
@@ -0,0 +1,69 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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 "common/helpers.h"
#include "common/vk_common.h"
#include "core/vulkan_resource.h"
namespace vkb
{
namespace core
{
class Image;
class ImageView : public vkb::core::VulkanResourceC<VkImageView>
{
public:
ImageView(Image &image, VkImageViewType view_type, VkFormat format = VK_FORMAT_UNDEFINED,
uint32_t base_mip_level = 0, uint32_t base_array_layer = 0,
uint32_t n_mip_levels = 0, uint32_t n_array_layers = 0);
ImageView(ImageView &) = delete;
ImageView(ImageView &&other);
~ImageView() override;
ImageView &operator=(const ImageView &) = delete;
ImageView &operator=(ImageView &&) = delete;
const Image &get_image() const;
/**
* @brief Update the image this view is referring to
* Used on image move
*/
void set_image(Image &image);
VkFormat get_format() const;
VkImageSubresourceRange get_subresource_range() const;
VkImageSubresourceLayers get_subresource_layers() const;
private:
Image *image{};
VkFormat format{};
VkImageSubresourceRange subresource_range{};
};
} // namespace core
} // namespace vkb
+769
View File
@@ -0,0 +1,769 @@
/* Copyright (c) 2018-2025, Arm Limited and Contributors
* Copyright (c) 2022-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "common/vk_common.h"
#include <ranges>
#include <vulkan/vulkan.hpp>
#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)
# define USE_VALIDATION_LAYERS
#endif
#if defined(USE_VALIDATION_LAYERS) && \
(defined(VKB_VALIDATION_LAYERS_GPU_ASSISTED) || defined(VKB_VALIDATION_LAYERS_BEST_PRACTICES) || defined(VKB_VALIDATION_LAYERS_SYNCHRONIZATION))
# define USE_VALIDATION_LAYER_FEATURES
#endif
namespace vkb
{
class PhysicalDevice;
namespace core
{
class HPPPhysicalDevice;
/**
* @brief A wrapper class for InstanceType
*
* This class is responsible for initializing the dispatcher, enumerating over all available extensions and validation layers
* enabling them if they exist, setting up debug messaging and querying all the physical devices existing on the machine.
*/
template <vkb::BindingType bindingType>
class Instance
{
public:
using InstanceType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Instance, VkInstance>::type;
using SurfaceType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::SurfaceKHR, VkSurfaceKHR>::type;
using PhysicalDeviceType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vkb::core::HPPPhysicalDevice, vkb::PhysicalDevice>::type;
public:
/**
* @brief Can be set from the GPU selection plugin to explicitly select a GPU instead
*/
inline static uint32_t selected_gpu_index = ~0;
/**
* @brief Initializes the connection to Vulkan
* @param application_name The name of the application
* @param requested_extensions The extensions requested to be enabled
* @param requested_layers The validation layers to be enabled
* @param requested_layer_settings The layer settings to be enabled
* @param api_version The Vulkan API version that the instance will be using
* @throws runtime_error if the required extensions and validation layers are not found
*/
Instance(std::string const &application_name,
std::unordered_map<char const *, bool> const &requested_extensions = {},
std::unordered_map<char const *, bool> const &requested_layers = {},
const std::vector<vk::LayerSettingEXT> &requested_layer_settings = {},
uint32_t api_version = VK_API_VERSION_1_1);
Instance(std::string const &application_name,
std::unordered_map<char const *, bool> const &requested_extensions,
std::unordered_map<char const *, bool> const &requested_layers,
std::vector<VkLayerSettingEXT> const &requested_layer_settings,
uint32_t api_version = VK_API_VERSION_1_1);
/**
* @brief Queries the GPUs of a InstanceType that is already created
* @param instance A valid InstanceType
* @param externally_enabled_extensions List of extensions that have been enabled, used for following checks e.g. during device creation
* @param needsToInitializeDispatcher If the sample uses the C-bindings and some "non-standard" initialization, the dispatcher needs to be initialized
*/
Instance(vk::Instance instance, const std::vector<const char *> &externally_enabled_extensions = {}, bool needsToInitializeDispatcher = false);
Instance(VkInstance instance, const std::vector<const char *> &externally_enabled_extensions = {});
Instance(Instance const &) = delete;
Instance(Instance &&) = delete;
~Instance();
Instance &operator=(Instance const &) = delete;
Instance &operator=(Instance &&) = delete;
const std::vector<const char *> &get_extensions();
/**
* @brief Tries to find the first available discrete GPU
* @returns A valid physical device
*/
PhysicalDeviceType &get_first_gpu();
InstanceType get_handle() const;
/**
* @brief Tries to find the first available discrete GPU that can render to the given surface
* @param surface to test against
* @param headless_surface Is surface created with VK_EXT_headless_surface
* @returns A valid physical device
*/
PhysicalDeviceType &get_suitable_gpu(SurfaceType surface, bool headless_surface);
/**
* @brief Checks if the given extension is enabled in the InstanceType
* @param extension An extension to check
*/
bool is_enabled(char const *extension) const;
private:
/**
* @brief Queries the instance for the physical devices on the machine
*/
void query_gpus();
private:
std::vector<const char *> enabled_extensions; // The enabled extensions
std::vector<std::unique_ptr<vkb::core::HPPPhysicalDevice>> gpus; // The physical devices found on the machine
vk::Instance handle; // The Vulkan instance
#if defined(VKB_DEBUG) || defined(VKB_VALIDATION_LAYERS)
vk::DebugReportCallbackEXT debug_report_callback; // The debug report callback
vk::DebugUtilsMessengerEXT debug_utils_messenger; // Debug utils messenger callback for VK_EXT_Debug_Utils
#endif
};
using InstanceC = Instance<vkb::BindingType::C>;
using InstanceCpp = Instance<vkb::BindingType::Cpp>;
} // namespace core
} // namespace vkb
#include "core/hpp_physical_device.h"
namespace vkb
{
namespace core
{
namespace
{
#ifdef USE_VALIDATION_LAYERS
inline VKAPI_ATTR vk::Bool32 VKAPI_CALL debug_utils_messenger_callback(vk::DebugUtilsMessageSeverityFlagBitsEXT message_severity,
vk::DebugUtilsMessageTypeFlagsEXT message_type,
vk::DebugUtilsMessengerCallbackDataEXT const *callback_data,
void *user_data)
{
// Log debug message
if (message_severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning)
{
LOGW("{} - {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage);
}
else if (message_severity & vk::DebugUtilsMessageSeverityFlagBitsEXT::eError)
{
LOGE("{} - {}: {}", callback_data->messageIdNumber, callback_data->pMessageIdName, callback_data->pMessage);
}
return false;
}
inline VKAPI_ATTR vk::Bool32 VKAPI_CALL debug_callback(vk::DebugReportFlagsEXT flags,
vk::DebugReportObjectTypeEXT /*type*/,
uint64_t /*object*/,
size_t /*location*/,
int32_t /*message_code*/,
char const *layer_prefix,
char const *message,
void * /*user_data*/)
{
if (flags & vk::DebugReportFlagBitsEXT::eError)
{
LOGE("{}: {}", layer_prefix, message);
}
else if (flags & vk::DebugReportFlagBitsEXT::eWarning)
{
LOGW("{}: {}", layer_prefix, message);
}
else if (flags & vk::DebugReportFlagBitsEXT::ePerformanceWarning)
{
LOGW("{}: {}", layer_prefix, message);
}
else
{
LOGI("{}: {}", layer_prefix, message);
}
return false;
}
#endif
inline bool enable_extension(char const *requested_extension,
std::vector<vk::ExtensionProperties> const &available_extensions,
std::vector<char const *> &enabled_extensions)
{
bool is_available = std::ranges::any_of(available_extensions,
[&requested_extension](auto const &available_extension) { return strcmp(requested_extension, available_extension.extensionName) == 0; });
if (is_available)
{
bool is_already_enabled = std::ranges::any_of(
enabled_extensions, [&requested_extension](auto const &enabled_extension) { return strcmp(requested_extension, enabled_extension) == 0; });
if (!is_already_enabled)
{
LOGI("Extension {} available, enabling it", requested_extension);
enabled_extensions.emplace_back(requested_extension);
}
}
else
{
LOGI("Extension {} not available", requested_extension);
}
return is_available;
}
inline bool enable_layer(char const *requested_layer, std::vector<vk::LayerProperties> const &available_layers, std::vector<char const *> &enabled_layers)
{
bool is_available = std::ranges::any_of(
available_layers, [&requested_layer](auto const &available_layer) { return strcmp(requested_layer, available_layer.layerName) == 0; });
if (is_available)
{
bool is_already_enabled =
std::ranges::any_of(enabled_layers, [&requested_layer](auto const &enabled_layer) { return strcmp(requested_layer, enabled_layer) == 0; });
if (!is_already_enabled)
{
LOGI("Layer {} available, enabling it", requested_layer);
enabled_layers.emplace_back(requested_layer);
}
}
else
{
LOGI("Layer {} not available", requested_layer);
}
return is_available;
}
inline bool enable_layer_setting(vk::LayerSettingEXT const &requested_layer_setting,
std::vector<char const *> const &enabled_layers,
std::vector<vk::LayerSettingEXT> &enabled_layer_settings)
{
// We are checking if the layer is available.
// Vulkan does not provide a reflection API for layer settings. Layer settings are described in each layer JSON manifest.
bool is_available = std::ranges::any_of(
enabled_layers, [&requested_layer_setting](auto const &available_layer) { return strcmp(available_layer, requested_layer_setting.pLayerName) == 0; });
#if defined(PLATFORM__MACOS)
// On Apple platforms the MoltenVK layer is implicitly enabled and available, and cannot be explicitly added or checked via enabled_layers.
is_available = is_available || strcmp(requested_layer_setting.pLayerName, "MoltenVK") == 0;
#endif
if (!is_available)
{
LOGW("Layer: {} not found. Disabling layer setting: {}", requested_layer_setting.pLayerName, requested_layer_setting.pSettingName);
return false;
}
bool is_already_enabled = std::ranges::any_of(enabled_layer_settings,
[&requested_layer_setting](VkLayerSettingEXT const &enabled_layer_setting) {
return (strcmp(requested_layer_setting.pLayerName, enabled_layer_setting.pLayerName) == 0) &&
(strcmp(requested_layer_setting.pSettingName, enabled_layer_setting.pSettingName) == 0);
});
if (is_already_enabled)
{
LOGW("Ignoring duplicated layer setting {} in layer {}.", requested_layer_setting.pSettingName, requested_layer_setting.pLayerName);
return false;
}
LOGI("Enabling layer setting {} in layer {}.", requested_layer_setting.pSettingName, requested_layer_setting.pLayerName);
enabled_layer_settings.push_back(requested_layer_setting);
return true;
}
inline bool enable_layer_setting(VkLayerSettingEXT const &requested_layer_setting,
std::vector<char const *> const &enabled_layers,
std::vector<vk::LayerSettingEXT> &enabled_layer_settings)
{
return enable_layer_setting(reinterpret_cast<vk::LayerSettingEXT const &>(requested_layer_setting), enabled_layers, enabled_layer_settings);
}
inline bool validate_layers(std::vector<char const *> const &required, std::vector<vk::LayerProperties> const &available)
{
for (auto layer : required)
{
bool found = false;
for (auto &available_layer : available)
{
if (strcmp(available_layer.layerName, layer) == 0)
{
found = true;
break;
}
}
if (!found)
{
LOGE("Validation Layer {} not found", layer);
return false;
}
}
return true;
}
} // namespace
template <vkb::BindingType bindingType>
inline Instance<bindingType>::Instance(std::string const &application_name,
std::unordered_map<char const *, bool> const &requested_extensions,
std::unordered_map<char const *, bool> const &requested_layers,
std::vector<vk::LayerSettingEXT> const &requested_layer_settings,
uint32_t api_version)
{
std::vector<vk::ExtensionProperties> available_instance_extensions = vk::enumerateInstanceExtensionProperties();
#ifdef USE_VALIDATION_LAYERS
// Check if VK_EXT_debug_utils is supported, which supersedes VK_EXT_Debug_Report
const bool has_debug_utils = enable_extension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, available_instance_extensions, enabled_extensions);
bool has_debug_report = false;
if (!has_debug_utils)
{
has_debug_report = enable_extension(VK_EXT_DEBUG_REPORT_EXTENSION_NAME, available_instance_extensions, enabled_extensions);
if (!has_debug_report)
{
LOGW("Neither of {} or {} are available; disabling debug reporting", VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_DEBUG_REPORT_EXTENSION_NAME);
}
}
#endif
#if (defined(VKB_ENABLE_PORTABILITY))
enable_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, available_instance_extensions, enabled_extensions);
bool portability_enumeration_available = enable_extension(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, available_instance_extensions, enabled_extensions);
#endif
#ifdef USE_VALIDATION_LAYERS
const char *validation_layer_name = "VK_LAYER_KHRONOS_validation";
# ifdef USE_VALIDATION_LAYER_FEATURES
bool validation_features = false;
{
std::vector<vk::ExtensionProperties> available_layer_instance_extensions = vk::enumerateInstanceExtensionProperties(std::string(validation_layer_name));
validation_features = enable_extension(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME, available_layer_instance_extensions, enabled_extensions);
}
# endif // USE_VALIDATION_LAYER_FEATURES
#endif // USE_VALIDATION_LAYERS
// Specific surface extensions are obtained from Window::get_required_surface_extensions
// They are already added to requested_extensions by VulkanSample::prepare
// Even for a headless surface a swapchain is still required
enable_extension(VK_KHR_SURFACE_EXTENSION_NAME, available_instance_extensions, enabled_extensions);
// VK_KHR_get_physical_device_properties2 is a prerequisite of VK_KHR_performance_query
// which will be used for stats gathering where available.
enable_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, available_instance_extensions, enabled_extensions);
for (auto requested_extension : requested_extensions)
{
auto const &extension_name = requested_extension.first;
auto extension_is_optional = requested_extension.second;
if (!enable_extension(extension_name, available_instance_extensions, enabled_extensions))
{
if (extension_is_optional)
{
LOGW("Optional instance extension {} not available, some features may be disabled", extension_name);
}
else
{
LOGE("Required instance extension {} not available, cannot run", extension_name);
throw std::runtime_error("Required instance extensions are missing.");
}
}
}
std::vector<vk::LayerProperties> supported_layers = vk::enumerateInstanceLayerProperties();
std::vector<char const *> enabled_layers;
auto layer_error = false;
for (auto const &requested_layer : requested_layers)
{
auto const &layer_name = requested_layer.first;
auto layer_is_optional = requested_layer.second;
if (!enable_layer(layer_name, supported_layers, enabled_layers))
{
if (layer_is_optional)
{
LOGW("Optional layer {} not available, some features may be disabled", layer_name);
}
else
{
LOGE("Required layer {} not available, cannot run", layer_name);
throw std::runtime_error("Required layers are missing.");
}
}
}
#ifdef USE_VALIDATION_LAYERS
// NOTE: It's important to have the validation layer as the last one here!!!!
// Otherwise, device creation fails !?!
enable_layer(validation_layer_name, supported_layers, enabled_layers);
#endif
vk::ApplicationInfo app_info{.pApplicationName = application_name.c_str(), .pEngineName = "Vulkan Samples", .apiVersion = api_version};
vk::InstanceCreateInfo instance_info{.pApplicationInfo = &app_info,
.enabledLayerCount = static_cast<uint32_t>(enabled_layers.size()),
.ppEnabledLayerNames = enabled_layers.data(),
.enabledExtensionCount = static_cast<uint32_t>(enabled_extensions.size()),
.ppEnabledExtensionNames = enabled_extensions.data()};
std::vector<vk::LayerSettingEXT> enabled_layer_settings;
for (auto const &layer_setting : requested_layer_settings)
{
enable_layer_setting(layer_setting, enabled_layers, enabled_layer_settings);
}
#ifdef USE_VALIDATION_LAYERS
vk::DebugUtilsMessengerCreateInfoEXT debug_utils_create_info;
vk::DebugReportCallbackCreateInfoEXT debug_report_create_info;
if (has_debug_utils)
{
debug_utils_create_info = vk::DebugUtilsMessengerCreateInfoEXT{
.messageSeverity = vk::DebugUtilsMessageSeverityFlagBitsEXT::eError | vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning,
.messageType = vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance,
.pfnUserCallback = debug_utils_messenger_callback};
instance_info.pNext = &debug_utils_create_info;
}
else if (has_debug_report)
{
debug_report_create_info = {.flags =
vk::DebugReportFlagBitsEXT::eError | vk::DebugReportFlagBitsEXT::eWarning | vk::DebugReportFlagBitsEXT::ePerformanceWarning,
.pfnCallback = debug_callback};
instance_info.pNext = &debug_report_create_info;
}
#endif
#if (defined(VKB_ENABLE_PORTABILITY))
if (portability_enumeration_available)
{
instance_info.flags |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR;
}
#endif
// Some of the specialized layers need to be enabled explicitly
// The validation layer does not need to be enabled in code and it can also be configured using the vulkan configurator.
#ifdef USE_VALIDATION_LAYER_FEATURES
# if defined(VKB_VALIDATION_LAYERS_GPU_ASSISTED)
const vk::Bool32 setting_validate_gpuav = true;
if (validation_features)
{
enable_layer_setting(vk::LayerSettingEXT(validation_layer_name, "gpuav_enable", vk::LayerSettingTypeEXT::eBool32, 1, &setting_validate_gpuav), enabled_layers, enabled_layer_settings);
}
# endif
# if defined(VKB_VALIDATION_LAYERS_BEST_PRACTICES)
const vk::Bool32 setting_validate_best_practices = true;
const vk::Bool32 setting_validate_best_practices_arm = true;
const vk::Bool32 setting_validate_best_practices_amd = true;
const vk::Bool32 setting_validate_best_practices_img = true;
const vk::Bool32 setting_validate_best_practices_nvidia = true;
if (validation_features)
{
enable_layer_setting(
vk::LayerSettingEXT(validation_layer_name, "validate_best_practices", vk::LayerSettingTypeEXT::eBool32, 1, &setting_validate_best_practices),
enabled_layers,
enabled_layer_settings);
enable_layer_setting(
vk::LayerSettingEXT(validation_layer_name, "validate_best_practices_arm", vk::LayerSettingTypeEXT::eBool32, 1, &setting_validate_best_practices_arm),
enabled_layers,
enabled_layer_settings);
enable_layer_setting(
vk::LayerSettingEXT(validation_layer_name, "validate_best_practices_amd", vk::LayerSettingTypeEXT::eBool32, 1, &setting_validate_best_practices_amd),
enabled_layers,
enabled_layer_settings);
enable_layer_setting(
vk::LayerSettingEXT(validation_layer_name, "validate_best_practices_img", vk::LayerSettingTypeEXT::eBool32, 1, &setting_validate_best_practices_img),
enabled_layers,
enabled_layer_settings);
enable_layer_setting(
vk::LayerSettingEXT(
validation_layer_name, "validate_best_practices_nvidia", vk::LayerSettingTypeEXT::eBool32, 1, &setting_validate_best_practices_nvidia),
enabled_layers,
enabled_layer_settings);
}
# endif
# if defined(VKB_VALIDATION_LAYERS_SYNCHRONIZATION)
const vk::Bool32 setting_validate_sync = true;
const vk::Bool32 setting_validate_sync_heuristics = true;
if (validation_features)
{
enable_layer_setting(vk::LayerSettingEXT(validation_layer_name, "validate_sync", vk::LayerSettingTypeEXT::eBool32, 1, &setting_validate_sync),
enabled_layers,
enabled_layer_settings);
enable_layer_setting(
vk::LayerSettingEXT(
validation_layer_name, "syncval_shader_accesses_heuristic", vk::LayerSettingTypeEXT::eBool32, 1, &setting_validate_sync_heuristics),
enabled_layers,
enabled_layer_settings);
}
# endif
#endif
vk::LayerSettingsCreateInfoEXT layerSettingsCreateInfo;
// If layer settings are defined, then activate the sample's required layer settings during instance creation
if (enabled_layer_settings.size() > 0)
{
layerSettingsCreateInfo.settingCount = static_cast<uint32_t>(enabled_layer_settings.size());
layerSettingsCreateInfo.pSettings = enabled_layer_settings.data();
layerSettingsCreateInfo.pNext = instance_info.pNext;
instance_info.pNext = &layerSettingsCreateInfo;
}
// Create the Vulkan instance
handle = vk::createInstance(instance_info);
// initialize the Vulkan-Hpp default dispatcher on the instance
VULKAN_HPP_DEFAULT_DISPATCHER.init(handle);
// Need to load volk for all the not-yet Vulkan-Hpp calls
volkLoadInstance(handle);
#ifdef USE_VALIDATION_LAYERS
if (has_debug_utils)
{
debug_utils_messenger = handle.createDebugUtilsMessengerEXT(debug_utils_create_info);
}
else if (has_debug_report)
{
debug_report_callback = handle.createDebugReportCallbackEXT(debug_report_create_info);
}
#endif
query_gpus();
}
template <vkb::BindingType bindingType>
inline Instance<bindingType>::Instance(std::string const &application_name,
std::unordered_map<char const *, bool> const &requested_extensions,
std::unordered_map<char const *, bool> const &requested_layers,
std::vector<VkLayerSettingEXT> const &requested_layer_settings,
uint32_t api_version) :
Instance<bindingType>(application_name,
requested_extensions,
requested_layers,
reinterpret_cast<std::vector<vk::LayerSettingEXT> const &>(requested_layer_settings),
api_version)
{}
template <vkb::BindingType bindingType>
inline Instance<bindingType>::Instance(vk::Instance instance, const std::vector<const char *> &externally_enabled_extensions, bool needsToInitializeDispatcher) :
handle{instance}
{
if (needsToInitializeDispatcher)
{
#if defined(_HPP_VULKAN_LIBRARY)
static vk::detail::DynamicLoader dl(_HPP_VULKAN_LIBRARY);
#else
static vk::detail::DynamicLoader dl;
#endif
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = dl.getProcAddress<PFN_vkGetInstanceProcAddr>("vkGetInstanceProcAddr");
VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr);
VULKAN_HPP_DEFAULT_DISPATCHER.init(instance);
}
// Some parts of the framework will check for certain extensions to be enabled
// To make those work we need to copy over externally enabled extensions into this class
for (auto extension : externally_enabled_extensions)
{
enabled_extensions.push_back(extension);
}
if (handle)
{
query_gpus();
}
else
{
throw std::runtime_error("Instance not valid");
}
}
template <vkb::BindingType bindingType>
inline Instance<bindingType>::Instance(VkInstance instance, const std::vector<const char *> &externally_enabled_extensions) :
Instance<bindingType>(static_cast<vk::Instance>(instance), externally_enabled_extensions, true)
{}
template <vkb::BindingType bindingType>
inline Instance<bindingType>::~Instance()
{
#ifdef USE_VALIDATION_LAYERS
if (debug_utils_messenger)
{
handle.destroyDebugUtilsMessengerEXT(debug_utils_messenger);
}
if (debug_report_callback)
{
handle.destroyDebugReportCallbackEXT(debug_report_callback);
}
#endif
if (handle)
{
handle.destroy();
}
}
#if defined(USE_VALIDATION_LAYERS)
# undef USE_VALIDATION_LAYERS
#endif
#if defined(USE_VALIDATION_LAYER_FEATURES)
# undef USE_VALIDATION_LAYER_FEATURES
#endif
template <vkb::BindingType bindingType>
inline typename Instance<bindingType>::PhysicalDeviceType &Instance<bindingType>::get_first_gpu()
{
assert(!gpus.empty() && "No physical devices were found on the system.");
// Find a discrete GPU
auto gpuIt = std::ranges::find_if(gpus, [](auto const &gpu) { return gpu->get_properties().deviceType == vk::PhysicalDeviceType::eDiscreteGpu; });
if (gpuIt == gpus.end())
{
LOGW("Couldn't find a discrete physical device, picking default GPU");
gpuIt = gpus.begin();
}
if constexpr (bindingType == BindingType::Cpp)
{
return **gpuIt;
}
else
{
return reinterpret_cast<vkb::PhysicalDevice &>(**gpuIt);
}
}
template <vkb::BindingType bindingType>
inline typename Instance<bindingType>::InstanceType Instance<bindingType>::get_handle() const
{
if constexpr (bindingType == BindingType::Cpp)
{
return handle;
}
else
{
return static_cast<VkInstance>(handle);
}
return handle;
}
template <vkb::BindingType bindingType>
inline typename Instance<bindingType>::PhysicalDeviceType &Instance<bindingType>::get_suitable_gpu(SurfaceType surface, bool headless_surface)
{
assert(!gpus.empty() && "No physical devices were found on the system.");
// A GPU can be explicitly selected via the command line (see plugins/gpu_selection.cpp), this overrides the below GPU selection algorithm
auto gpuIt = gpus.begin();
if (selected_gpu_index != ~0)
{
LOGI("Explicitly selecting GPU {}", selected_gpu_index);
if (selected_gpu_index > gpus.size() - 1)
{
throw std::runtime_error("Selected GPU index is not within no. of available GPUs");
}
gpuIt = gpus.begin() + selected_gpu_index;
}
else
{
if (headless_surface)
{
LOGW("Using headless surface with multiple GPUs. Considered explicitly selecting the target GPU.")
}
// Find a discrete GPU
#if 0
gpuIt = std::ranges::find_if(gpus,
[&surface](auto const &gpu) {
return (gpu->get_properties().deviceType == vk::PhysicalDeviceType::eDiscreteGpu) &&
std::ranges::any_of(std::views::iota(size_t(0), gpu->get_queue_family_properties().size()),
[&gpu, &surface](auto const &queue_idx) {
return gpu->get_handle().getSurfaceSupportKHR(static_cast<uint32_t>(queue_idx), surface);
});
});
#else
gpuIt = std::ranges::find_if(gpus,
[&surface](auto const &gpu) {
auto gpu_supports_surface = [&gpu, &surface]() {
for (uint32_t queue_idx = 0; queue_idx < gpu->get_queue_family_properties().size(); ++queue_idx)
{
if (gpu->get_handle().getSurfaceSupportKHR(queue_idx, surface))
{
return true;
}
}
return false;
};
return (gpu->get_properties().deviceType == vk::PhysicalDeviceType::eDiscreteGpu) && gpu_supports_surface();
});
#endif
if (gpuIt == gpus.end())
{
LOGW("Couldn't find a discrete physical device that supports the surface, picking default GPU");
gpuIt = gpus.begin();
}
}
if constexpr (bindingType == BindingType::Cpp)
{
return **gpuIt;
}
else
{
return reinterpret_cast<vkb::PhysicalDevice &>(**gpuIt);
}
}
template <vkb::BindingType bindingType>
inline bool Instance<bindingType>::is_enabled(char const *extension) const
{
return std::ranges::find_if(enabled_extensions, [extension](char const *enabled_extension) { return strcmp(extension, enabled_extension) == 0; }) !=
enabled_extensions.end();
}
template <vkb::BindingType bindingType>
inline void Instance<bindingType>::query_gpus()
{
// Querying valid physical devices on the machine
std::vector<vk::PhysicalDevice> physical_devices = handle.enumeratePhysicalDevices();
if (physical_devices.empty())
{
throw std::runtime_error("Couldn't find a physical device that supports Vulkan.");
}
// Create gpus wrapper objects from the vk::PhysicalDevice's
for (auto &physical_device : physical_devices)
{
if constexpr (bindingType == BindingType::Cpp)
{
gpus.push_back(std::make_unique<vkb::core::HPPPhysicalDevice>(*this, physical_device));
}
else
{
gpus.push_back(std::make_unique<vkb::core::HPPPhysicalDevice>(*reinterpret_cast<vkb::core::InstanceCpp *>(this), physical_device));
}
}
}
template <vkb::BindingType bindingType>
inline std::vector<char const *> const &Instance<bindingType>::get_extensions()
{
return enabled_extensions;
}
} // namespace core
} // namespace vkb
+203
View File
@@ -0,0 +1,203 @@
/* Copyright (c) 2020-2025, Arm Limited and Contributors
*
* 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.
*/
#include "physical_device.h"
namespace vkb
{
PhysicalDevice::PhysicalDevice(vkb::core::InstanceC &instance, VkPhysicalDevice physical_device) :
instance{instance},
handle{physical_device}
{
vkGetPhysicalDeviceFeatures(physical_device, &features);
vkGetPhysicalDeviceProperties(physical_device, &properties);
vkGetPhysicalDeviceMemoryProperties(physical_device, &memory_properties);
LOGI("Found GPU: {}", properties.deviceName);
uint32_t queue_family_properties_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_properties_count, nullptr);
queue_family_properties = std::vector<VkQueueFamilyProperties>(queue_family_properties_count);
vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_properties_count, queue_family_properties.data());
uint32_t device_extension_count;
VK_CHECK(vkEnumerateDeviceExtensionProperties(get_handle(), nullptr, &device_extension_count, nullptr));
device_extensions = std::vector<VkExtensionProperties>(device_extension_count);
VK_CHECK(vkEnumerateDeviceExtensionProperties(get_handle(), nullptr, &device_extension_count, device_extensions.data()));
// Display supported extensions
if (device_extensions.size() > 0)
{
LOGD("Device supports the following extensions:");
for (auto &extension : device_extensions)
{
LOGD(" \t{}", extension.extensionName);
}
}
}
DriverVersion PhysicalDevice::get_driver_version() const
{
DriverVersion version;
VkPhysicalDeviceProperties const &properties = get_properties();
switch (properties.vendorID)
{
case 0x10DE:
// Nvidia
version.major = (properties.driverVersion >> 22) & 0x3ff;
version.minor = (properties.driverVersion >> 14) & 0x0ff;
version.patch = (properties.driverVersion >> 6) & 0x0ff;
// Ignoring optional tertiary info in lower 6 bits
break;
case 0x8086:
version.major = (properties.driverVersion >> 14) & 0x3ffff;
version.minor = properties.driverVersion & 0x3ffff;
break;
default:
version.major = VK_VERSION_MAJOR(properties.driverVersion);
version.minor = VK_VERSION_MINOR(properties.driverVersion);
version.patch = VK_VERSION_PATCH(properties.driverVersion);
break;
}
return version;
}
vkb::core::InstanceC &PhysicalDevice::get_instance() const
{
return instance;
}
VkBool32 PhysicalDevice::is_present_supported(VkSurfaceKHR surface, uint32_t queue_family_index) const
{
VkBool32 present_supported{VK_FALSE};
if (surface != VK_NULL_HANDLE)
{
VK_CHECK(vkGetPhysicalDeviceSurfaceSupportKHR(handle, queue_family_index, surface, &present_supported));
}
return present_supported;
}
bool PhysicalDevice::is_extension_supported(const std::string &requested_extension) const
{
return std::ranges::find_if(device_extensions,
[requested_extension](auto &device_extension) {
return std::strcmp(device_extension.extensionName, requested_extension.c_str()) == 0;
}) != device_extensions.end();
}
const VkFormatProperties PhysicalDevice::get_format_properties(VkFormat format) const
{
VkFormatProperties format_properties;
vkGetPhysicalDeviceFormatProperties(handle, format, &format_properties);
return format_properties;
}
VkPhysicalDevice PhysicalDevice::get_handle() const
{
return handle;
}
const VkPhysicalDeviceFeatures &PhysicalDevice::get_features() const
{
return features;
}
const VkPhysicalDeviceProperties &PhysicalDevice::get_properties() const
{
return properties;
}
const VkPhysicalDeviceMemoryProperties &PhysicalDevice::get_memory_properties() const
{
return memory_properties;
}
uint32_t PhysicalDevice::get_memory_type(uint32_t bits, VkMemoryPropertyFlags properties, VkBool32 *memory_type_found) const
{
for (uint32_t i = 0; i < memory_properties.memoryTypeCount; i++)
{
if ((bits & 1) == 1)
{
if ((memory_properties.memoryTypes[i].propertyFlags & properties) == properties)
{
if (memory_type_found)
{
*memory_type_found = true;
}
return i;
}
}
bits >>= 1;
}
if (memory_type_found)
{
*memory_type_found = false;
return ~0;
}
else
{
throw std::runtime_error("Could not find a matching memory type");
}
}
const std::vector<VkQueueFamilyProperties> &PhysicalDevice::get_queue_family_properties() const
{
return queue_family_properties;
}
uint32_t PhysicalDevice::get_queue_family_performance_query_passes(
const VkQueryPoolPerformanceCreateInfoKHR *perf_query_create_info) const
{
uint32_t passes_needed;
vkGetPhysicalDeviceQueueFamilyPerformanceQueryPassesKHR(get_handle(), perf_query_create_info,
&passes_needed);
return passes_needed;
}
void PhysicalDevice::enumerate_queue_family_performance_query_counters(
uint32_t queue_family_index,
uint32_t *count,
VkPerformanceCounterKHR *counters,
VkPerformanceCounterDescriptionKHR *descriptions) const
{
VK_CHECK(vkEnumeratePhysicalDeviceQueueFamilyPerformanceQueryCountersKHR(
get_handle(), queue_family_index, count, counters, descriptions));
}
const VkPhysicalDeviceFeatures PhysicalDevice::get_requested_features() const
{
return requested_features;
}
VkPhysicalDeviceFeatures &PhysicalDevice::get_mutable_requested_features()
{
return requested_features;
}
void *PhysicalDevice::get_extension_feature_chain() const
{
return last_requested_extension_feature;
}
} // namespace vkb
+255
View File
@@ -0,0 +1,255 @@
/* Copyright (c) 2020-2025, Arm Limited and Contributors
*
* 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 "core/instance.h"
namespace vkb
{
struct DriverVersion
{
uint16_t major;
uint16_t minor;
uint16_t patch;
};
/**
* @brief A wrapper class for VkPhysicalDevice
*
* This class is responsible for handling gpu features, properties, and queue families for the device creation.
*/
class PhysicalDevice
{
public:
PhysicalDevice(vkb::core::InstanceC &instance, VkPhysicalDevice physical_device);
PhysicalDevice(const PhysicalDevice &) = delete;
PhysicalDevice(PhysicalDevice &&) = delete;
PhysicalDevice &operator=(const PhysicalDevice &) = delete;
PhysicalDevice &operator=(PhysicalDevice &&) = delete;
DriverVersion get_driver_version() const;
vkb::core::InstanceC &get_instance() const;
VkBool32 is_present_supported(VkSurfaceKHR surface, uint32_t queue_family_index) const;
bool is_extension_supported(const std::string &extension) const;
const VkFormatProperties get_format_properties(VkFormat format) const;
VkPhysicalDevice get_handle() const;
const VkPhysicalDeviceFeatures &get_features() const;
const VkPhysicalDeviceProperties &get_properties() const;
const VkPhysicalDeviceMemoryProperties &get_memory_properties() const;
uint32_t get_memory_type(uint32_t bits, VkMemoryPropertyFlags properties, VkBool32 *memory_type_found = nullptr) const;
const std::vector<VkQueueFamilyProperties> &get_queue_family_properties() const;
uint32_t get_queue_family_performance_query_passes(
const VkQueryPoolPerformanceCreateInfoKHR *perf_query_create_info) const;
void enumerate_queue_family_performance_query_counters(
uint32_t queue_family_index,
uint32_t *count,
VkPerformanceCounterKHR *counters,
VkPerformanceCounterDescriptionKHR *descriptions) const;
const VkPhysicalDeviceFeatures get_requested_features() const;
VkPhysicalDeviceFeatures &get_mutable_requested_features();
/**
* @brief Used at logical device creation to pass the extensions feature chain to vkCreateDevice
* @returns A void pointer to the start of the extension linked list
*/
void *get_extension_feature_chain() const;
/**
* @brief Get an extension features struct
*
* Gets the actual extension features struct with the supported flags set.
* The flags you're interested in can be set in a corresponding struct in the structure chain
* by calling PhysicalDevice::add_extension_features()
* @param type The VkStructureType for the extension you are requesting
* @returns The extension feature struct
*/
template <typename T>
T get_extension_features(VkStructureType type)
{
// We cannot request extension features if the physical device properties 2 instance extension isn't enabled
if (!instance.is_enabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME))
{
throw std::runtime_error("Couldn't request feature from device as " + std::string(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) +
" isn't enabled!");
}
// Get the extension features
T features{type};
VkPhysicalDeviceFeatures2KHR physical_device_features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR};
physical_device_features.pNext = &features;
vkGetPhysicalDeviceFeatures2KHR(handle, &physical_device_features);
return features;
}
/**
* @brief Add an extension features struct to the structure chain used for device creation
*
* To have the features enabled, this function must be called before the logical device
* is created. To do this request sample specific features inside
* VulkanSample::request_gpu_features(vkb::PhysicalDevice &gpu).
*
* If the feature extension requires you to ask for certain features to be enabled, you can
* modify the struct returned by this function, it will propagate the changes to the logical
* device.
* @param type The VkStructureType for the extension you are requesting
* @returns A reference to extension feature struct in the structure chain
*/
template <typename T>
T &add_extension_features(VkStructureType type)
{
// We cannot request extension features if the physical device properties 2 instance extension isn't enabled
if (!instance.is_enabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME))
{
throw std::runtime_error("Couldn't request feature from device as " + std::string(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME) +
" isn't enabled!");
}
// Add an (empty) extension features into the map of extension features
auto [it, added] = extension_features.insert({type, std::make_shared<T>(T{type})});
if (added)
{
// if it was actually added, also add it to the structure chain
if (last_requested_extension_feature)
{
static_cast<T *>(it->second.get())->pNext = last_requested_extension_feature;
}
last_requested_extension_feature = it->second.get();
}
return *static_cast<T *>(it->second.get());
}
/**
* @brief Request an optional features flag
*
* Calls get_extension_features to get the support of the requested flag. If it's supported,
* add_extension_features is called, otherwise a log message is generated.
*
* @returns true if the requested feature is supported, otherwise false
*/
template <typename Feature>
VkBool32 request_optional_feature(VkStructureType type, VkBool32 Feature::*flag, std::string const &featureName, std::string const &flagName)
{
VkBool32 supported = get_extension_features<Feature>(type).*flag;
if (supported)
{
add_extension_features<Feature>(type).*flag = true;
}
else
{
LOGI("Requested optional feature <{}::{}> is not supported", featureName, flagName);
}
return supported;
}
/**
* @brief Request a required features flag
*
* Calls get_extension_features to get the support of the requested flag. If it's supported,
* add_extension_features is called, otherwise a runtime_error is thrown.
*/
template <typename Feature>
void request_required_feature(VkStructureType type, VkBool32 Feature::*flag, std::string const &featureName, std::string const &flagName)
{
if (get_extension_features<Feature>(type).*flag)
{
add_extension_features<Feature>(type).*flag = true;
}
else
{
throw std::runtime_error(std::string("Requested required feature <") + featureName + "::" + flagName + "> is not supported");
}
}
/**
* @brief Sets whether or not the first graphics queue should have higher priority than other queues.
* Very specific feature which is used by async compute samples.
* @param enable If true, present queue will have prio 1.0 and other queues have prio 0.5.
* Default state is false, where all queues have 0.5 priority.
*/
void set_high_priority_graphics_queue_enable(bool enable)
{
high_priority_graphics_queue = enable;
}
/**
* @brief Returns high priority graphics queue state.
* @return High priority state.
*/
bool has_high_priority_graphics_queue() const
{
return high_priority_graphics_queue;
}
private:
// Handle to the Vulkan instance
vkb::core::InstanceC &instance;
// Handle to the Vulkan physical device
VkPhysicalDevice handle{VK_NULL_HANDLE};
// The features that this GPU supports
VkPhysicalDeviceFeatures features{};
// The extensions that this GPU supports
std::vector<VkExtensionProperties> device_extensions;
// The GPU properties
VkPhysicalDeviceProperties properties;
// The GPU memory properties
VkPhysicalDeviceMemoryProperties memory_properties;
// The GPU queue family properties
std::vector<VkQueueFamilyProperties> queue_family_properties;
// The features that will be requested to be enabled in the logical device
VkPhysicalDeviceFeatures requested_features{};
// The extension feature pointer
void *last_requested_extension_feature{nullptr};
// Holds the extension feature structures, we use a map to retain an order of requested structures
std::map<VkStructureType, std::shared_ptr<void>> extension_features;
bool high_priority_graphics_queue{};
};
#define REQUEST_OPTIONAL_FEATURE(gpu, Feature, type, flag) gpu.request_optional_feature<Feature>(type, &Feature::flag, #Feature, #flag)
#define REQUEST_REQUIRED_FEATURE(gpu, Feature, type, flag) gpu.request_required_feature<Feature>(type, &Feature::flag, #Feature, #flag)
} // namespace vkb
+307
View File
@@ -0,0 +1,307 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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.
*/
#include "pipeline.h"
#include "debug.h"
#include "device.h"
#include "pipeline_layout.h"
#include "shader_module.h"
namespace vkb
{
Pipeline::Pipeline(vkb::core::DeviceC &device) :
device{device}
{}
Pipeline::Pipeline(Pipeline &&other) :
device{other.device},
handle{other.handle},
state{other.state}
{
other.handle = VK_NULL_HANDLE;
}
Pipeline::~Pipeline()
{
// Destroy pipeline
if (handle != VK_NULL_HANDLE)
{
vkDestroyPipeline(device.get_handle(), handle, nullptr);
}
}
VkPipeline Pipeline::get_handle() const
{
return handle;
}
const PipelineState &Pipeline::get_state() const
{
return state;
}
ComputePipeline::ComputePipeline(vkb::core::DeviceC &device,
VkPipelineCache pipeline_cache,
PipelineState &pipeline_state) :
Pipeline{device}
{
const ShaderModule *shader_module = pipeline_state.get_pipeline_layout().get_shader_modules().front();
if (shader_module->get_stage() != VK_SHADER_STAGE_COMPUTE_BIT)
{
throw VulkanException{VK_ERROR_INVALID_SHADER_NV, "Shader module stage is not compute"};
}
VkPipelineShaderStageCreateInfo stage{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
stage.stage = shader_module->get_stage();
stage.pName = shader_module->get_entry_point().c_str();
// Create the Vulkan handle
VkShaderModuleCreateInfo vk_create_info{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
vk_create_info.codeSize = shader_module->get_binary().size() * sizeof(uint32_t);
vk_create_info.pCode = shader_module->get_binary().data();
VkResult result = vkCreateShaderModule(device.get_handle(), &vk_create_info, nullptr, &stage.module);
if (result != VK_SUCCESS)
{
throw VulkanException{result};
}
device.get_debug_utils().set_debug_name(device.get_handle(),
VK_OBJECT_TYPE_SHADER_MODULE, reinterpret_cast<uint64_t>(stage.module),
shader_module->get_debug_name().c_str());
// Create specialization info from tracked state.
std::vector<uint8_t> data{};
std::vector<VkSpecializationMapEntry> map_entries{};
const auto specialization_constant_state = pipeline_state.get_specialization_constant_state().get_specialization_constant_state();
for (const auto specialization_constant : specialization_constant_state)
{
map_entries.push_back({specialization_constant.first, to_u32(data.size()), specialization_constant.second.size()});
data.insert(data.end(), specialization_constant.second.begin(), specialization_constant.second.end());
}
VkSpecializationInfo specialization_info{};
specialization_info.mapEntryCount = to_u32(map_entries.size());
specialization_info.pMapEntries = map_entries.data();
specialization_info.dataSize = data.size();
specialization_info.pData = data.data();
stage.pSpecializationInfo = &specialization_info;
VkComputePipelineCreateInfo create_info{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
create_info.layout = pipeline_state.get_pipeline_layout().get_handle();
create_info.stage = stage;
result = vkCreateComputePipelines(device.get_handle(), pipeline_cache, 1, &create_info, nullptr, &handle);
if (result != VK_SUCCESS)
{
throw VulkanException{result, "Cannot create ComputePipelines"};
}
vkDestroyShaderModule(device.get_handle(), stage.module, nullptr);
}
GraphicsPipeline::GraphicsPipeline(vkb::core::DeviceC &device,
VkPipelineCache pipeline_cache,
PipelineState &pipeline_state) :
Pipeline{device}
{
std::vector<VkShaderModule> shader_modules;
std::vector<VkPipelineShaderStageCreateInfo> stage_create_infos;
// Create specialization info from tracked state. This is shared by all shaders.
std::vector<uint8_t> data{};
std::vector<VkSpecializationMapEntry> map_entries{};
const auto specialization_constant_state = pipeline_state.get_specialization_constant_state().get_specialization_constant_state();
for (const auto specialization_constant : specialization_constant_state)
{
map_entries.push_back({specialization_constant.first, to_u32(data.size()), specialization_constant.second.size()});
data.insert(data.end(), specialization_constant.second.begin(), specialization_constant.second.end());
}
VkSpecializationInfo specialization_info{};
specialization_info.mapEntryCount = to_u32(map_entries.size());
specialization_info.pMapEntries = map_entries.data();
specialization_info.dataSize = data.size();
specialization_info.pData = data.data();
for (const ShaderModule *shader_module : pipeline_state.get_pipeline_layout().get_shader_modules())
{
VkPipelineShaderStageCreateInfo stage_create_info{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO};
stage_create_info.stage = shader_module->get_stage();
stage_create_info.pName = shader_module->get_entry_point().c_str();
// Create the Vulkan handle
VkShaderModuleCreateInfo vk_create_info{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
vk_create_info.codeSize = shader_module->get_binary().size() * sizeof(uint32_t);
vk_create_info.pCode = shader_module->get_binary().data();
VkResult result = vkCreateShaderModule(device.get_handle(), &vk_create_info, nullptr, &stage_create_info.module);
if (result != VK_SUCCESS)
{
throw VulkanException{result};
}
device.get_debug_utils().set_debug_name(device.get_handle(),
VK_OBJECT_TYPE_SHADER_MODULE, reinterpret_cast<uint64_t>(stage_create_info.module),
shader_module->get_debug_name().c_str());
stage_create_info.pSpecializationInfo = &specialization_info;
stage_create_infos.push_back(stage_create_info);
shader_modules.push_back(stage_create_info.module);
}
VkGraphicsPipelineCreateInfo create_info{VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO};
create_info.stageCount = to_u32(stage_create_infos.size());
create_info.pStages = stage_create_infos.data();
VkPipelineVertexInputStateCreateInfo vertex_input_state{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
vertex_input_state.pVertexAttributeDescriptions = pipeline_state.get_vertex_input_state().attributes.data();
vertex_input_state.vertexAttributeDescriptionCount = to_u32(pipeline_state.get_vertex_input_state().attributes.size());
vertex_input_state.pVertexBindingDescriptions = pipeline_state.get_vertex_input_state().bindings.data();
vertex_input_state.vertexBindingDescriptionCount = to_u32(pipeline_state.get_vertex_input_state().bindings.size());
VkPipelineInputAssemblyStateCreateInfo input_assembly_state{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
input_assembly_state.topology = pipeline_state.get_input_assembly_state().topology;
input_assembly_state.primitiveRestartEnable = pipeline_state.get_input_assembly_state().primitive_restart_enable;
VkPipelineViewportStateCreateInfo viewport_state{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
viewport_state.viewportCount = pipeline_state.get_viewport_state().viewport_count;
viewport_state.scissorCount = pipeline_state.get_viewport_state().scissor_count;
VkPipelineRasterizationStateCreateInfo rasterization_state{VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO};
rasterization_state.depthClampEnable = pipeline_state.get_rasterization_state().depth_clamp_enable;
rasterization_state.rasterizerDiscardEnable = pipeline_state.get_rasterization_state().rasterizer_discard_enable;
rasterization_state.polygonMode = pipeline_state.get_rasterization_state().polygon_mode;
rasterization_state.cullMode = pipeline_state.get_rasterization_state().cull_mode;
rasterization_state.frontFace = pipeline_state.get_rasterization_state().front_face;
rasterization_state.depthBiasEnable = pipeline_state.get_rasterization_state().depth_bias_enable;
rasterization_state.depthBiasClamp = 1.0f;
rasterization_state.depthBiasSlopeFactor = 1.0f;
rasterization_state.lineWidth = 1.0f;
VkPipelineMultisampleStateCreateInfo multisample_state{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
multisample_state.sampleShadingEnable = pipeline_state.get_multisample_state().sample_shading_enable;
multisample_state.rasterizationSamples = pipeline_state.get_multisample_state().rasterization_samples;
multisample_state.minSampleShading = pipeline_state.get_multisample_state().min_sample_shading;
multisample_state.alphaToCoverageEnable = pipeline_state.get_multisample_state().alpha_to_coverage_enable;
multisample_state.alphaToOneEnable = pipeline_state.get_multisample_state().alpha_to_one_enable;
if (pipeline_state.get_multisample_state().sample_mask)
{
multisample_state.pSampleMask = &pipeline_state.get_multisample_state().sample_mask;
}
VkPipelineDepthStencilStateCreateInfo depth_stencil_state{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
depth_stencil_state.depthTestEnable = pipeline_state.get_depth_stencil_state().depth_test_enable;
depth_stencil_state.depthWriteEnable = pipeline_state.get_depth_stencil_state().depth_write_enable;
depth_stencil_state.depthCompareOp = pipeline_state.get_depth_stencil_state().depth_compare_op;
depth_stencil_state.depthBoundsTestEnable = pipeline_state.get_depth_stencil_state().depth_bounds_test_enable;
depth_stencil_state.stencilTestEnable = pipeline_state.get_depth_stencil_state().stencil_test_enable;
depth_stencil_state.front.failOp = pipeline_state.get_depth_stencil_state().front.fail_op;
depth_stencil_state.front.passOp = pipeline_state.get_depth_stencil_state().front.pass_op;
depth_stencil_state.front.depthFailOp = pipeline_state.get_depth_stencil_state().front.depth_fail_op;
depth_stencil_state.front.compareOp = pipeline_state.get_depth_stencil_state().front.compare_op;
depth_stencil_state.front.compareMask = ~0U;
depth_stencil_state.front.writeMask = ~0U;
depth_stencil_state.front.reference = ~0U;
depth_stencil_state.back.failOp = pipeline_state.get_depth_stencil_state().back.fail_op;
depth_stencil_state.back.passOp = pipeline_state.get_depth_stencil_state().back.pass_op;
depth_stencil_state.back.depthFailOp = pipeline_state.get_depth_stencil_state().back.depth_fail_op;
depth_stencil_state.back.compareOp = pipeline_state.get_depth_stencil_state().back.compare_op;
depth_stencil_state.back.compareMask = ~0U;
depth_stencil_state.back.writeMask = ~0U;
depth_stencil_state.back.reference = ~0U;
VkPipelineColorBlendStateCreateInfo color_blend_state{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
color_blend_state.logicOpEnable = pipeline_state.get_color_blend_state().logic_op_enable;
color_blend_state.logicOp = pipeline_state.get_color_blend_state().logic_op;
color_blend_state.attachmentCount = to_u32(pipeline_state.get_color_blend_state().attachments.size());
color_blend_state.pAttachments = reinterpret_cast<const VkPipelineColorBlendAttachmentState *>(pipeline_state.get_color_blend_state().attachments.data());
color_blend_state.blendConstants[0] = 1.0f;
color_blend_state.blendConstants[1] = 1.0f;
color_blend_state.blendConstants[2] = 1.0f;
color_blend_state.blendConstants[3] = 1.0f;
std::array<VkDynamicState, 9> dynamic_states{
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR,
VK_DYNAMIC_STATE_LINE_WIDTH,
VK_DYNAMIC_STATE_DEPTH_BIAS,
VK_DYNAMIC_STATE_BLEND_CONSTANTS,
VK_DYNAMIC_STATE_DEPTH_BOUNDS,
VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK,
VK_DYNAMIC_STATE_STENCIL_REFERENCE,
};
VkPipelineDynamicStateCreateInfo dynamic_state{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO};
dynamic_state.pDynamicStates = dynamic_states.data();
dynamic_state.dynamicStateCount = to_u32(dynamic_states.size());
create_info.pVertexInputState = &vertex_input_state;
create_info.pInputAssemblyState = &input_assembly_state;
create_info.pViewportState = &viewport_state;
create_info.pRasterizationState = &rasterization_state;
create_info.pMultisampleState = &multisample_state;
create_info.pDepthStencilState = &depth_stencil_state;
create_info.pColorBlendState = &color_blend_state;
create_info.pDynamicState = &dynamic_state;
create_info.layout = pipeline_state.get_pipeline_layout().get_handle();
create_info.renderPass = pipeline_state.get_render_pass()->get_handle();
create_info.subpass = pipeline_state.get_subpass_index();
auto result = vkCreateGraphicsPipelines(device.get_handle(), pipeline_cache, 1, &create_info, nullptr, &handle);
if (result != VK_SUCCESS)
{
throw VulkanException{result, "Cannot create GraphicsPipelines"};
}
for (auto shader_module : shader_modules)
{
vkDestroyShaderModule(device.get_handle(), shader_module, nullptr);
}
state = pipeline_state;
}
} // namespace vkb
+76
View File
@@ -0,0 +1,76 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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 "common/helpers.h"
#include "common/vk_common.h"
#include "rendering/pipeline_state.h"
namespace vkb
{
class Pipeline
{
public:
Pipeline(vkb::core::DeviceC &device);
Pipeline(const Pipeline &) = delete;
Pipeline(Pipeline &&other);
virtual ~Pipeline();
Pipeline &operator=(const Pipeline &) = delete;
Pipeline &operator=(Pipeline &&) = delete;
VkPipeline get_handle() const;
const PipelineState &get_state() const;
protected:
vkb::core::DeviceC &device;
VkPipeline handle = VK_NULL_HANDLE;
PipelineState state;
};
class ComputePipeline : public Pipeline
{
public:
ComputePipeline(ComputePipeline &&) = default;
virtual ~ComputePipeline() = default;
ComputePipeline(vkb::core::DeviceC &device,
VkPipelineCache pipeline_cache,
PipelineState &pipeline_state);
};
class GraphicsPipeline : public Pipeline
{
public:
GraphicsPipeline(GraphicsPipeline &&) = default;
virtual ~GraphicsPipeline() = default;
GraphicsPipeline(vkb::core::DeviceC &device,
VkPipelineCache pipeline_cache,
PipelineState &pipeline_state);
};
} // namespace vkb
+210
View File
@@ -0,0 +1,210 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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.
*/
#include "pipeline_layout.h"
#include "descriptor_set_layout.h"
#include "device.h"
#include "pipeline.h"
#include "resource_cache.h"
#include "shader_module.h"
namespace vkb
{
PipelineLayout::PipelineLayout(vkb::core::DeviceC &device, const std::vector<ShaderModule *> &shader_modules) :
device{device},
shader_modules{shader_modules}
{
// Collect and combine all the shader resources from each of the shader modules
// Collate them all into a map that is indexed by the name of the resource
for (auto *shader_module : shader_modules)
{
for (const auto &shader_resource : shader_module->get_resources())
{
std::string key = shader_resource.name;
// Since 'Input' and 'Output' resources can have the same name, we modify the key string
if (shader_resource.type == ShaderResourceType::Input || shader_resource.type == ShaderResourceType::Output)
{
key = std::to_string(shader_resource.stages) + "_" + key;
}
auto it = shader_resources.find(key);
if (it != shader_resources.end())
{
// Append stage flags if resource already exists
it->second.stages |= shader_resource.stages;
}
else
{
// Create a new entry in the map
shader_resources.emplace(key, shader_resource);
}
}
}
// Sift through the map of name indexed shader resources
// Separate them into their respective sets
for (auto &it : shader_resources)
{
auto &shader_resource = it.second;
// Find binding by set index in the map.
auto it2 = shader_sets.find(shader_resource.set);
if (it2 != shader_sets.end())
{
// Add resource to the found set index
it2->second.push_back(shader_resource);
}
else
{
// Create a new set index and with the first resource
shader_sets.emplace(shader_resource.set, std::vector<ShaderResource>{shader_resource});
}
}
// Create a descriptor set layout for each shader set in the shader modules
for (auto &shader_set_it : shader_sets)
{
descriptor_set_layouts.emplace_back(&device.get_resource_cache().request_descriptor_set_layout(shader_set_it.first, shader_modules, shader_set_it.second));
}
// Collect all the descriptor set layout handles, maintaining set order
std::vector<VkDescriptorSetLayout> descriptor_set_layout_handles;
for (uint32_t i = 0; i < descriptor_set_layouts.size(); ++i)
{
if (descriptor_set_layouts[i])
{
descriptor_set_layout_handles.push_back(descriptor_set_layouts[i]->get_handle());
}
else
{
descriptor_set_layout_handles.push_back(VK_NULL_HANDLE);
}
}
// Collect all the push constant shader resources
std::vector<VkPushConstantRange> push_constant_ranges;
for (auto &push_constant_resource : get_resources(ShaderResourceType::PushConstant))
{
push_constant_ranges.push_back({push_constant_resource.stages, push_constant_resource.offset, push_constant_resource.size});
}
VkPipelineLayoutCreateInfo create_info{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
create_info.setLayoutCount = to_u32(descriptor_set_layout_handles.size());
create_info.pSetLayouts = descriptor_set_layout_handles.data();
create_info.pushConstantRangeCount = to_u32(push_constant_ranges.size());
create_info.pPushConstantRanges = push_constant_ranges.data();
// Create the Vulkan pipeline layout handle
auto result = vkCreatePipelineLayout(device.get_handle(), &create_info, nullptr, &handle);
if (result != VK_SUCCESS)
{
throw VulkanException{result, "Cannot create PipelineLayout"};
}
}
PipelineLayout::PipelineLayout(PipelineLayout &&other) :
device{other.device},
handle{other.handle},
shader_modules{std::move(other.shader_modules)},
shader_resources{std::move(other.shader_resources)},
shader_sets{std::move(other.shader_sets)},
descriptor_set_layouts{std::move(other.descriptor_set_layouts)}
{
other.handle = VK_NULL_HANDLE;
}
PipelineLayout::~PipelineLayout()
{
// Destroy pipeline layout
if (handle != VK_NULL_HANDLE)
{
vkDestroyPipelineLayout(device.get_handle(), handle, nullptr);
}
}
VkPipelineLayout PipelineLayout::get_handle() const
{
return handle;
}
const std::vector<ShaderModule *> &PipelineLayout::get_shader_modules() const
{
return shader_modules;
}
const std::vector<ShaderResource> PipelineLayout::get_resources(const ShaderResourceType &type, VkShaderStageFlagBits stage) const
{
std::vector<ShaderResource> found_resources;
for (auto &it : shader_resources)
{
auto &shader_resource = it.second;
if (shader_resource.type == type || type == ShaderResourceType::All)
{
if (shader_resource.stages == stage || stage == VK_SHADER_STAGE_ALL)
{
found_resources.push_back(shader_resource);
}
}
}
return found_resources;
}
const std::unordered_map<uint32_t, std::vector<ShaderResource>> &PipelineLayout::get_shader_sets() const
{
return shader_sets;
}
bool PipelineLayout::has_descriptor_set_layout(const uint32_t set_index) const
{
return set_index < descriptor_set_layouts.size();
}
DescriptorSetLayout &PipelineLayout::get_descriptor_set_layout(const uint32_t set_index) const
{
for (auto &descriptor_set_layout : descriptor_set_layouts)
{
if (descriptor_set_layout->get_index() == set_index)
{
return *descriptor_set_layout;
}
}
throw std::runtime_error("Couldn't find descriptor set layout at set index " + to_string(set_index));
}
VkShaderStageFlags PipelineLayout::get_push_constant_range_stage(uint32_t size, uint32_t offset) const
{
VkShaderStageFlags stages = 0;
for (auto &push_constant_resource : get_resources(ShaderResourceType::PushConstant))
{
if (offset >= push_constant_resource.offset && offset + size <= push_constant_resource.offset + push_constant_resource.size)
{
stages |= push_constant_resource.stages;
}
}
return stages;
}
} // namespace vkb
+76
View File
@@ -0,0 +1,76 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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 "common/helpers.h"
#include "common/vk_common.h"
#include "core/descriptor_set_layout.h"
#include "core/shader_module.h"
namespace vkb
{
class ShaderModule;
class DescriptorSetLayout;
class PipelineLayout
{
public:
PipelineLayout(vkb::core::DeviceC &device, const std::vector<ShaderModule *> &shader_modules);
PipelineLayout(const PipelineLayout &) = delete;
PipelineLayout(PipelineLayout &&other);
~PipelineLayout();
PipelineLayout &operator=(const PipelineLayout &) = delete;
PipelineLayout &operator=(PipelineLayout &&) = delete;
VkPipelineLayout get_handle() const;
const std::vector<ShaderModule *> &get_shader_modules() const;
const std::vector<ShaderResource> get_resources(const ShaderResourceType &type = ShaderResourceType::All, VkShaderStageFlagBits stage = VK_SHADER_STAGE_ALL) const;
const std::unordered_map<uint32_t, std::vector<ShaderResource>> &get_shader_sets() const;
bool has_descriptor_set_layout(const uint32_t set_index) const;
DescriptorSetLayout &get_descriptor_set_layout(const uint32_t set_index) const;
VkShaderStageFlags get_push_constant_range_stage(uint32_t size, uint32_t offset = 0) const;
private:
vkb::core::DeviceC &device;
VkPipelineLayout handle{VK_NULL_HANDLE};
// The shader modules that this pipeline layout uses
std::vector<ShaderModule *> shader_modules;
// The shader resources that this pipeline layout uses, indexed by their name
std::unordered_map<std::string, ShaderResource> shader_resources;
// A map of each set and the resources it owns used by the pipeline layout
std::unordered_map<uint32_t, std::vector<ShaderResource>> shader_sets;
// The different descriptor set layouts for this pipeline layout
std::vector<DescriptorSetLayout *> descriptor_set_layouts;
};
} // namespace vkb
+67
View File
@@ -0,0 +1,67 @@
/* Copyright (c) 2020-2025, Broadcom Inc. and Contributors
*
* 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.
*/
#include "query_pool.h"
#include "device.h"
namespace vkb
{
QueryPool::QueryPool(vkb::core::DeviceC &d, const VkQueryPoolCreateInfo &info) :
device{d}
{
VK_CHECK(vkCreateQueryPool(device.get_handle(), &info, nullptr, &handle));
}
QueryPool::QueryPool(QueryPool &&other) :
device{other.device},
handle{other.handle}
{
other.handle = VK_NULL_HANDLE;
}
QueryPool::~QueryPool()
{
if (handle != VK_NULL_HANDLE)
{
vkDestroyQueryPool(device.get_handle(), handle, nullptr);
}
}
VkQueryPool QueryPool::get_handle() const
{
assert(handle != VK_NULL_HANDLE && "QueryPool handle is invalid");
return handle;
}
void QueryPool::host_reset(uint32_t first_query, uint32_t query_count)
{
assert(device.is_extension_enabled("VK_EXT_host_query_reset") &&
"VK_EXT_host_query_reset needs to be enabled to call QueryPool::host_reset");
vkResetQueryPoolEXT(device.get_handle(), get_handle(), first_query, query_count);
}
VkResult QueryPool::get_results(uint32_t first_query, uint32_t num_queries,
size_t result_bytes, void *results, VkDeviceSize stride,
VkQueryResultFlags flags)
{
return vkGetQueryPoolResults(device.get_handle(), get_handle(), first_query, num_queries,
result_bytes, results, stride, flags);
}
} // namespace vkb
+85
View File
@@ -0,0 +1,85 @@
/* Copyright (c) 2020-2025, Broadcom Inc. and Contributors
*
* 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 "common/helpers.h"
#include "common/vk_common.h"
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceC = Device<vkb::BindingType::C>;
} // namespace core
/**
* @brief Represents a Vulkan Query Pool
*/
class QueryPool
{
public:
/**
* @brief Creates a Vulkan Query Pool
* @param d The device to use
* @param info Creation details
*/
QueryPool(vkb::core::DeviceC &d, const VkQueryPoolCreateInfo &info);
QueryPool(const QueryPool &) = delete;
QueryPool(QueryPool &&pool);
~QueryPool();
QueryPool &operator=(const QueryPool &) = delete;
QueryPool &operator=(QueryPool &&) = delete;
/**
* @return The vulkan query pool handle
*/
VkQueryPool get_handle() const;
/**
* @brief Reset a range of queries in the query pool. Only call if VK_EXT_host_query_reset is enabled.
* @param first_query The first query to reset
* @param query_count The number of queries to reset
*/
void host_reset(uint32_t first_query, uint32_t query_count);
/**
* @brief Get query pool results
* @param first_query The initial query index
* @param num_queries The number of queries to read
* @param result_bytes The number of bytes in the results array
* @param results Array of bytes result_bytes long
* @param stride The stride in bytes between results for individual queries
* @param flags A bitmask of VkQueryResultFlagBits
*/
VkResult get_results(uint32_t first_query, uint32_t num_queries,
size_t result_bytes, void *results, VkDeviceSize stride,
VkQueryResultFlags flags);
private:
vkb::core::DeviceC &device;
VkQueryPool handle{VK_NULL_HANDLE};
};
} // namespace vkb
+104
View File
@@ -0,0 +1,104 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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.
*/
#include "queue.h"
#include "command_buffer.h"
#include "device.h"
namespace vkb
{
Queue::Queue(vkb::core::DeviceC &device, uint32_t family_index, VkQueueFamilyProperties properties, VkBool32 can_present, uint32_t index) :
device{device}, family_index{family_index}, index{index}, can_present{can_present}, properties{properties}
{
vkGetDeviceQueue(device.get_handle(), family_index, index, &handle);
}
Queue::Queue(Queue &&other) :
device{other.device},
handle{other.handle},
family_index{other.family_index},
index{other.index},
can_present{other.can_present},
properties{other.properties}
{
other.handle = VK_NULL_HANDLE;
other.family_index = {};
other.properties = {};
other.can_present = VK_FALSE;
other.index = 0;
}
const vkb::core::DeviceC &Queue::get_device() const
{
return device;
}
VkQueue Queue::get_handle() const
{
return handle;
}
uint32_t Queue::get_family_index() const
{
return family_index;
}
uint32_t Queue::get_index() const
{
return index;
}
const VkQueueFamilyProperties &Queue::get_properties() const
{
return properties;
}
VkBool32 Queue::support_present() const
{
return can_present;
}
VkResult Queue::submit(const std::vector<VkSubmitInfo> &submit_infos, VkFence fence) const
{
return vkQueueSubmit(handle, to_u32(submit_infos.size()), submit_infos.data(), fence);
}
VkResult Queue::submit(const vkb::core::CommandBufferC &command_buffer, VkFence fence) const
{
VkSubmitInfo submit_info{VK_STRUCTURE_TYPE_SUBMIT_INFO};
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &command_buffer.get_handle();
return submit({submit_info}, fence);
}
VkResult Queue::present(const VkPresentInfoKHR &present_info) const
{
if (!can_present)
{
return VK_ERROR_INCOMPATIBLE_DISPLAY_KHR;
}
return vkQueuePresentKHR(handle, &present_info);
} // namespace vkb
VkResult Queue::wait_idle() const
{
return vkQueueWaitIdle(handle);
}
} // namespace vkb
+80
View File
@@ -0,0 +1,80 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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 "common/helpers.h"
#include "common/vk_common.h"
#include "core/swapchain.h"
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class CommandBuffer;
using CommandBufferC = CommandBuffer<vkb::BindingType::C>;
} // namespace core
class Queue
{
public:
Queue(vkb::core::DeviceC &device, uint32_t family_index, VkQueueFamilyProperties properties, VkBool32 can_present, uint32_t index);
Queue(const Queue &) = default;
Queue(Queue &&other);
Queue &operator=(const Queue &) = delete;
Queue &operator=(Queue &&) = delete;
const vkb::core::DeviceC &get_device() const;
VkQueue get_handle() const;
uint32_t get_family_index() const;
uint32_t get_index() const;
const VkQueueFamilyProperties &get_properties() const;
VkBool32 support_present() const;
VkResult submit(const std::vector<VkSubmitInfo> &submit_infos, VkFence fence) const;
VkResult submit(const vkb::core::CommandBufferC &command_buffer, VkFence fence) const;
VkResult present(const VkPresentInfoKHR &present_infos) const;
VkResult wait_idle() const;
private:
vkb::core::DeviceC &device;
VkQueue handle{VK_NULL_HANDLE};
uint32_t family_index{0};
uint32_t index{0};
VkBool32 can_present{VK_FALSE};
VkQueueFamilyProperties properties{};
};
} // namespace vkb
+559
View File
@@ -0,0 +1,559 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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.
*/
#include "render_pass.h"
#include <numeric>
#include <span>
#include "device.h"
#include "rendering/render_target.h"
namespace vkb
{
namespace
{
inline void set_structure_type(VkAttachmentDescription &attachment)
{
// VkAttachmentDescription has no sType field
}
inline void set_structure_type(VkAttachmentDescription2KHR &attachment)
{
attachment.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2_KHR;
}
inline void set_structure_type(VkAttachmentReference &reference)
{
// VkAttachmentReference has no sType field
}
inline void set_structure_type(VkAttachmentReference2KHR &reference)
{
reference.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
}
inline void set_structure_type(VkRenderPassCreateInfo &create_info)
{
create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
}
inline void set_structure_type(VkRenderPassCreateInfo2KHR &create_info)
{
create_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR;
}
inline void set_structure_type(VkSubpassDescription &description)
{
// VkSubpassDescription has no sType field
}
inline void set_structure_type(VkSubpassDescription2KHR &description)
{
description.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR;
}
inline void set_pointer_next(VkSubpassDescription &subpass_description, VkSubpassDescriptionDepthStencilResolveKHR &depth_resolve, VkAttachmentReference &depth_resolve_attachment)
{
// VkSubpassDescription cannot have pNext point to a VkSubpassDescriptionDepthStencilResolveKHR containing a VkAttachmentReference
}
inline void set_pointer_next(VkSubpassDescription2KHR &subpass_description, VkSubpassDescriptionDepthStencilResolveKHR &depth_resolve, VkAttachmentReference2KHR &depth_resolve_attachment)
{
depth_resolve.pDepthStencilResolveAttachment = &depth_resolve_attachment;
subpass_description.pNext = &depth_resolve;
}
inline const VkAttachmentReference2KHR *get_depth_resolve_reference(const VkSubpassDescription &subpass_description)
{
// VkSubpassDescription cannot have pNext point to a VkSubpassDescriptionDepthStencilResolveKHR containing a VkAttachmentReference2KHR
return nullptr;
}
inline const VkAttachmentReference2KHR *get_depth_resolve_reference(const VkSubpassDescription2KHR &subpass_description)
{
auto description_depth_resolve = static_cast<const VkSubpassDescriptionDepthStencilResolveKHR *>(subpass_description.pNext);
const VkAttachmentReference2KHR *depth_resolve_attachment = nullptr;
if (description_depth_resolve)
{
depth_resolve_attachment = description_depth_resolve->pDepthStencilResolveAttachment;
}
return depth_resolve_attachment;
}
inline VkResult create_vk_renderpass(VkDevice device, VkRenderPassCreateInfo &create_info, VkRenderPass *handle)
{
return vkCreateRenderPass(device, &create_info, nullptr, handle);
}
inline VkResult create_vk_renderpass(VkDevice device, VkRenderPassCreateInfo2KHR &create_info, VkRenderPass *handle)
{
return vkCreateRenderPass2KHR(device, &create_info, nullptr, handle);
}
} // namespace
template <typename T>
std::vector<T> get_attachment_descriptions(const std::vector<Attachment> &attachments, const std::vector<LoadStoreInfo> &load_store_infos)
{
std::vector<T> attachment_descriptions;
for (size_t i = 0U; i < attachments.size(); ++i)
{
T attachment{};
set_structure_type(attachment);
attachment.format = attachments[i].format;
attachment.samples = attachments[i].samples;
attachment.initialLayout = attachments[i].initial_layout;
attachment.finalLayout =
vkb::is_depth_format(attachment.format) ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
if (i < load_store_infos.size())
{
attachment.loadOp = load_store_infos[i].load_op;
attachment.storeOp = load_store_infos[i].store_op;
attachment.stencilLoadOp = load_store_infos[i].load_op;
attachment.stencilStoreOp = load_store_infos[i].store_op;
}
attachment_descriptions.push_back(std::move(attachment));
}
return attachment_descriptions;
}
template <typename T_SubpassDescription, typename T_AttachmentDescription, typename T_AttachmentReference>
void set_attachment_layouts(std::vector<T_SubpassDescription> &subpass_descriptions, std::vector<T_AttachmentDescription> &attachment_descriptions)
{
// Make the initial layout same as in the first subpass using that attachment
for (auto &subpass : subpass_descriptions)
{
for (size_t k = 0U; k < subpass.colorAttachmentCount; ++k)
{
auto &reference = subpass.pColorAttachments[k];
// Set it only if not defined yet
if (attachment_descriptions[reference.attachment].initialLayout == VK_IMAGE_LAYOUT_UNDEFINED)
{
attachment_descriptions[reference.attachment].initialLayout = reference.layout;
}
}
for (size_t k = 0U; k < subpass.inputAttachmentCount; ++k)
{
auto &reference = subpass.pInputAttachments[k];
// Set it only if not defined yet
if (attachment_descriptions[reference.attachment].initialLayout == VK_IMAGE_LAYOUT_UNDEFINED)
{
attachment_descriptions[reference.attachment].initialLayout = reference.layout;
}
}
if (subpass.pDepthStencilAttachment)
{
auto &reference = *subpass.pDepthStencilAttachment;
// Set it only if not defined yet
if (attachment_descriptions[reference.attachment].initialLayout == VK_IMAGE_LAYOUT_UNDEFINED)
{
attachment_descriptions[reference.attachment].initialLayout = reference.layout;
}
}
if (subpass.pResolveAttachments)
{
for (size_t k = 0U; k < subpass.colorAttachmentCount; ++k)
{
auto &reference = subpass.pResolveAttachments[k];
// Set it only if not defined yet
if (attachment_descriptions[reference.attachment].initialLayout == VK_IMAGE_LAYOUT_UNDEFINED)
{
attachment_descriptions[reference.attachment].initialLayout = reference.layout;
}
}
}
if (const auto depth_resolve = get_depth_resolve_reference(subpass))
{
// Set it only if not defined yet
if (attachment_descriptions[depth_resolve->attachment].initialLayout == VK_IMAGE_LAYOUT_UNDEFINED)
{
attachment_descriptions[depth_resolve->attachment].initialLayout = depth_resolve->layout;
}
}
}
// Make the final layout same as the last subpass layout
{
auto &subpass = subpass_descriptions.back();
for (size_t k = 0U; k < subpass.colorAttachmentCount; ++k)
{
const auto &reference = subpass.pColorAttachments[k];
attachment_descriptions[reference.attachment].finalLayout = reference.layout;
}
for (size_t k = 0U; k < subpass.inputAttachmentCount; ++k)
{
const auto &reference = subpass.pInputAttachments[k];
attachment_descriptions[reference.attachment].finalLayout = reference.layout;
// Do not use depth attachment if used as input
if (vkb::is_depth_format(attachment_descriptions[reference.attachment].format))
{
subpass.pDepthStencilAttachment = nullptr;
}
}
if (subpass.pDepthStencilAttachment)
{
const auto &reference = *subpass.pDepthStencilAttachment;
attachment_descriptions[reference.attachment].finalLayout = reference.layout;
}
if (subpass.pResolveAttachments)
{
for (size_t k = 0U; k < subpass.colorAttachmentCount; ++k)
{
const auto &reference = subpass.pResolveAttachments[k];
attachment_descriptions[reference.attachment].finalLayout = reference.layout;
}
}
if (const auto depth_resolve = get_depth_resolve_reference(subpass))
{
attachment_descriptions[depth_resolve->attachment].finalLayout = depth_resolve->layout;
}
}
}
/**
* @brief Assuming there is only one depth attachment
*/
template <typename T_SubpassDescription, typename T_AttachmentDescription>
bool is_depth_a_dependency(std::vector<T_SubpassDescription> &subpass_descriptions, std::vector<T_AttachmentDescription> &attachment_descriptions)
{
// More than 1 subpass uses depth
if (std::ranges::count_if(subpass_descriptions,
[](auto const &subpass) {
return subpass.pDepthStencilAttachment != nullptr;
}) > 1)
{
return true;
}
// Otherwise check if any uses depth as an input
return std::ranges::any_of(
subpass_descriptions,
[&attachment_descriptions](auto const &subpass) {
return std::ranges::any_of(
std::span{subpass.pInputAttachments, subpass.inputAttachmentCount},
[&attachment_descriptions](auto const &reference) {
return vkb::is_depth_format(attachment_descriptions[reference.attachment].format);
});
});
return false;
}
template <typename T>
std::vector<T> get_subpass_dependencies(const size_t subpass_count, bool depth_stencil_dependency)
{
std::vector<T> dependencies{};
if (subpass_count > 1)
{
for (uint32_t subpass_id = 0; subpass_id < to_u32(subpass_count - 1); ++subpass_id)
{
T color_dep{};
color_dep.srcSubpass = subpass_id;
color_dep.dstSubpass = subpass_id + 1;
color_dep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
color_dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
color_dep.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
color_dep.dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
color_dep.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
dependencies.push_back(color_dep);
if (depth_stencil_dependency)
{
T depth_dep{};
depth_dep.srcSubpass = subpass_id;
depth_dep.dstSubpass = subpass_id + 1;
depth_dep.srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
depth_dep.dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
depth_dep.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
depth_dep.dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
depth_dep.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
dependencies.push_back(depth_dep);
}
}
}
return dependencies;
}
template <typename T>
T get_attachment_reference(const uint32_t attachment, const VkImageLayout layout)
{
T reference{};
set_structure_type(reference);
reference.attachment = attachment;
reference.layout = layout;
return reference;
}
template <typename T_SubpassDescription, typename T_AttachmentDescription, typename T_AttachmentReference, typename T_SubpassDependency, typename T_RenderPassCreateInfo>
void RenderPass::create_renderpass(const std::vector<Attachment> &attachments, const std::vector<LoadStoreInfo> &load_store_infos, const std::vector<SubpassInfo> &subpasses)
{
if (attachments.size() != load_store_infos.size())
{
LOGW("Render Pass creation: size of attachment list and load/store info list does not match: {} vs {}", attachments.size(), load_store_infos.size());
}
auto attachment_descriptions = get_attachment_descriptions<T_AttachmentDescription>(attachments, load_store_infos);
// Store attachments for every subpass
std::vector<std::vector<T_AttachmentReference>> input_attachments{subpass_count};
std::vector<std::vector<T_AttachmentReference>> color_attachments{subpass_count};
std::vector<std::vector<T_AttachmentReference>> depth_stencil_attachments{subpass_count};
std::vector<std::vector<T_AttachmentReference>> color_resolve_attachments{subpass_count};
std::vector<std::vector<T_AttachmentReference>> depth_resolve_attachments{subpass_count};
std::string new_debug_name{};
const bool needs_debug_name = get_debug_name().empty();
if (needs_debug_name)
{
new_debug_name = fmt::format("RP with {} subpasses:\n", subpasses.size());
}
for (size_t i = 0; i < subpasses.size(); ++i)
{
auto &subpass = subpasses[i];
if (needs_debug_name)
{
new_debug_name += fmt::format("\t[{}]: {}\n", i, subpass.debug_name);
}
// Fill color attachments references
for (auto o_attachment : subpass.output_attachments)
{
auto initial_layout = attachments[o_attachment].initial_layout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : attachments[o_attachment].initial_layout;
auto &description = attachment_descriptions[o_attachment];
if (!is_depth_format(description.format))
{
color_attachments[i].push_back(get_attachment_reference<T_AttachmentReference>(o_attachment, initial_layout));
}
}
// Fill input attachments references
for (auto i_attachment : subpass.input_attachments)
{
auto initial_layout = vkb::is_depth_format(attachments[i_attachment].format) ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
input_attachments[i].push_back(get_attachment_reference<T_AttachmentReference>(i_attachment, initial_layout));
}
for (auto r_attachment : subpass.color_resolve_attachments)
{
auto initial_layout = attachments[r_attachment].initial_layout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : attachments[r_attachment].initial_layout;
color_resolve_attachments[i].push_back(get_attachment_reference<T_AttachmentReference>(r_attachment, initial_layout));
}
if (!subpass.disable_depth_stencil_attachment)
{
// Assumption: depth stencil attachment appears in the list before any depth stencil resolve attachment
auto it = find_if(attachments.begin(), attachments.end(), [](const Attachment attachment) { return is_depth_format(attachment.format); });
if (it != attachments.end())
{
auto i_depth_stencil = vkb::to_u32(std::distance(attachments.begin(), it));
auto initial_layout = it->initial_layout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : it->initial_layout;
depth_stencil_attachments[i].push_back(get_attachment_reference<T_AttachmentReference>(i_depth_stencil, initial_layout));
if (subpass.depth_stencil_resolve_mode != VK_RESOLVE_MODE_NONE)
{
auto i_depth_stencil_resolve = subpass.depth_stencil_resolve_attachment;
initial_layout = attachments[i_depth_stencil_resolve].initial_layout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : attachments[i_depth_stencil_resolve].initial_layout;
depth_resolve_attachments[i].push_back(get_attachment_reference<T_AttachmentReference>(i_depth_stencil_resolve, initial_layout));
}
}
}
}
std::vector<T_SubpassDescription> subpass_descriptions;
subpass_descriptions.reserve(subpass_count);
VkSubpassDescriptionDepthStencilResolveKHR depth_resolve{};
for (size_t i = 0; i < subpasses.size(); ++i)
{
auto &subpass = subpasses[i];
T_SubpassDescription subpass_description{};
set_structure_type(subpass_description);
subpass_description.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass_description.pInputAttachments = input_attachments[i].empty() ? nullptr : input_attachments[i].data();
subpass_description.inputAttachmentCount = to_u32(input_attachments[i].size());
subpass_description.pColorAttachments = color_attachments[i].empty() ? nullptr : color_attachments[i].data();
subpass_description.colorAttachmentCount = to_u32(color_attachments[i].size());
subpass_description.pResolveAttachments = color_resolve_attachments[i].empty() ? nullptr : color_resolve_attachments[i].data();
subpass_description.pDepthStencilAttachment = nullptr;
if (!depth_stencil_attachments[i].empty())
{
subpass_description.pDepthStencilAttachment = depth_stencil_attachments[i].data();
if (!depth_resolve_attachments[i].empty())
{
// If the pNext list of VkSubpassDescription2 includes a VkSubpassDescriptionDepthStencilResolve structure,
// then that structure describes multisample resolve operations for the depth/stencil attachment in a subpass
depth_resolve.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE_KHR;
depth_resolve.depthResolveMode = subpass.depth_stencil_resolve_mode;
set_pointer_next(subpass_description, depth_resolve, depth_resolve_attachments[i][0]);
auto &reference = depth_resolve_attachments[i][0];
// Set it only if not defined yet
if (attachment_descriptions[reference.attachment].initialLayout == VK_IMAGE_LAYOUT_UNDEFINED)
{
attachment_descriptions[reference.attachment].initialLayout = reference.layout;
}
}
}
subpass_descriptions.push_back(subpass_description);
}
// Default subpass
if (subpasses.empty())
{
T_SubpassDescription subpass_description{};
set_structure_type(subpass_description);
subpass_description.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
uint32_t default_depth_stencil_attachment{VK_ATTACHMENT_UNUSED};
for (uint32_t k = 0U; k < to_u32(attachment_descriptions.size()); ++k)
{
if (vkb::is_depth_format(attachments[k].format))
{
if (default_depth_stencil_attachment == VK_ATTACHMENT_UNUSED)
{
default_depth_stencil_attachment = k;
}
continue;
}
color_attachments[0].push_back(get_attachment_reference<T_AttachmentReference>(k, VK_IMAGE_LAYOUT_GENERAL));
}
subpass_description.pColorAttachments = color_attachments[0].data();
if (default_depth_stencil_attachment != VK_ATTACHMENT_UNUSED)
{
depth_stencil_attachments[0].push_back(get_attachment_reference<T_AttachmentReference>(default_depth_stencil_attachment, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL));
subpass_description.pDepthStencilAttachment = depth_stencil_attachments[0].data();
}
subpass_descriptions.push_back(subpass_description);
}
set_attachment_layouts<T_SubpassDescription, T_AttachmentDescription, T_AttachmentReference>(subpass_descriptions, attachment_descriptions);
color_output_count.reserve(subpass_count);
for (size_t i = 0; i < subpass_count; i++)
{
color_output_count.push_back(to_u32(color_attachments[i].size()));
}
const auto &subpass_dependencies = get_subpass_dependencies<T_SubpassDependency>(subpass_count, is_depth_a_dependency(subpass_descriptions, attachment_descriptions));
T_RenderPassCreateInfo create_info{};
set_structure_type(create_info);
create_info.attachmentCount = to_u32(attachment_descriptions.size());
create_info.pAttachments = attachment_descriptions.data();
create_info.subpassCount = to_u32(subpass_descriptions.size());
create_info.pSubpasses = subpass_descriptions.data();
create_info.dependencyCount = to_u32(subpass_dependencies.size());
create_info.pDependencies = subpass_dependencies.data();
auto result = create_vk_renderpass(get_device().get_handle(), create_info, &get_handle());
if (result != VK_SUCCESS)
{
throw VulkanException{result, "Cannot create RenderPass"};
}
if (needs_debug_name)
{
set_debug_name(new_debug_name);
}
}
RenderPass::RenderPass(vkb::core::DeviceC &device,
const std::vector<Attachment> &attachments,
const std::vector<LoadStoreInfo> &load_store_infos,
const std::vector<SubpassInfo> &subpasses) :
VulkanResource{VK_NULL_HANDLE, &device}, subpass_count{std::max<size_t>(1, subpasses.size())}, // At least 1 subpass
color_output_count{}
{
if (device.is_extension_enabled(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME))
{
create_renderpass<VkSubpassDescription2KHR, VkAttachmentDescription2KHR, VkAttachmentReference2KHR, VkSubpassDependency2KHR, VkRenderPassCreateInfo2KHR>(
attachments, load_store_infos, subpasses);
}
else
{
create_renderpass<VkSubpassDescription, VkAttachmentDescription, VkAttachmentReference, VkSubpassDependency, VkRenderPassCreateInfo>(
attachments, load_store_infos, subpasses);
}
}
RenderPass::RenderPass(RenderPass &&other) :
VulkanResource{std::move(other)},
subpass_count{other.subpass_count},
color_output_count{other.color_output_count}
{}
RenderPass::~RenderPass()
{
// Destroy render pass
if (has_device())
{
vkDestroyRenderPass(get_device().get_handle(), get_handle(), nullptr);
}
}
const uint32_t RenderPass::get_color_output_count(uint32_t subpass_index) const
{
return color_output_count[subpass_index];
}
VkExtent2D RenderPass::get_render_area_granularity() const
{
VkExtent2D render_area_granularity = {};
vkGetRenderAreaGranularity(get_device().get_handle(), get_handle(), &render_area_granularity);
return render_area_granularity;
}
} // namespace vkb
+80
View File
@@ -0,0 +1,80 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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 "core/vulkan_resource.h"
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceC = Device<vkb::BindingType::C>;
} // namespace core
struct Attachment;
struct SubpassInfo
{
std::vector<uint32_t> input_attachments;
std::vector<uint32_t> output_attachments;
std::vector<uint32_t> color_resolve_attachments;
bool disable_depth_stencil_attachment;
uint32_t depth_stencil_resolve_attachment;
VkResolveModeFlagBits depth_stencil_resolve_mode;
std::string debug_name;
};
class RenderPass : public vkb::core::VulkanResourceC<VkRenderPass>
{
public:
RenderPass(vkb::core::DeviceC &device,
const std::vector<Attachment> &attachments,
const std::vector<LoadStoreInfo> &load_store_infos,
const std::vector<SubpassInfo> &subpasses);
RenderPass(const RenderPass &) = delete;
RenderPass(RenderPass &&other);
~RenderPass();
RenderPass &operator=(const RenderPass &) = delete;
RenderPass &operator=(RenderPass &&) = delete;
const uint32_t get_color_output_count(uint32_t subpass_index) const;
VkExtent2D get_render_area_granularity() const;
private:
size_t subpass_count;
template <typename T_SubpassDescription, typename T_AttachmentDescription, typename T_AttachmentReference, typename T_SubpassDependency, typename T_RenderPassCreateInfo>
void create_renderpass(const std::vector<Attachment> &attachments, const std::vector<LoadStoreInfo> &load_store_infos, const std::vector<SubpassInfo> &subpasses);
std::vector<uint32_t> color_output_count;
};
} // namespace vkb
+105
View File
@@ -0,0 +1,105 @@
/* Copyright (c) 2021-2023, Arm Limited and Contributors
*
* 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.
*/
#include "sampled_image.h"
#include "rendering/render_target.h"
namespace vkb
{
namespace core
{
SampledImage::SampledImage(const core::ImageView &image_view, Sampler *sampler) :
image_view{&image_view},
target_attachment{0},
render_target{nullptr},
sampler{sampler},
isDepthResolve{false}
{}
SampledImage::SampledImage(uint32_t target_attachment, RenderTarget *render_target, Sampler *sampler, bool isDepthResolve) :
image_view{nullptr},
target_attachment{target_attachment},
render_target{render_target},
sampler{sampler},
isDepthResolve{isDepthResolve}
{}
SampledImage::SampledImage(const SampledImage &to_copy) :
image_view{to_copy.image_view},
target_attachment{to_copy.target_attachment},
render_target{to_copy.render_target},
sampler{to_copy.sampler},
isDepthResolve{false}
{}
SampledImage &SampledImage::operator=(const SampledImage &to_copy)
{
image_view = to_copy.image_view;
target_attachment = to_copy.target_attachment;
render_target = to_copy.render_target;
sampler = to_copy.sampler;
isDepthResolve = to_copy.isDepthResolve;
return *this;
}
SampledImage::SampledImage(SampledImage &&to_move) :
image_view{std::move(to_move.image_view)},
target_attachment{std::move(to_move.target_attachment)},
render_target{std::move(to_move.render_target)},
sampler{std::move(to_move.sampler)},
isDepthResolve{std::move(to_move.isDepthResolve)}
{}
SampledImage &SampledImage::operator=(SampledImage &&to_move)
{
image_view = std::move(to_move.image_view);
target_attachment = std::move(to_move.target_attachment);
render_target = std::move(to_move.render_target);
sampler = std::move(to_move.sampler);
isDepthResolve = std::move(to_move.isDepthResolve);
return *this;
}
const core::ImageView &SampledImage::get_image_view(const RenderTarget &default_target) const
{
if (image_view != nullptr)
{
return *image_view;
}
else
{
const auto &target = render_target ? *render_target : default_target;
assert(target_attachment < target.get_views().size());
return target.get_views()[target_attachment];
}
}
const uint32_t *SampledImage::get_target_attachment() const
{
if (image_view != nullptr)
{
return nullptr;
}
else
{
return &target_attachment;
}
}
} // namespace core
} // namespace vkb
+143
View File
@@ -0,0 +1,143 @@
/* Copyright (c) 2021, Arm Limited and Contributors
*
* 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 "core/image.h"
#include "core/sampler.h"
#include <memory>
namespace vkb
{
class RenderTarget;
namespace core
{
/**
* @brief A reference to a vkb::core::ImageView, plus an optional sampler for it
* - either coming from a vkb::RenderTarget or from a user-created Image.
*/
class SampledImage
{
public:
/**
* @brief Constructs a SampledImage referencing the given image and with the given sampler.
* @remarks If the sampler is null, a default sampler will be used.
*/
SampledImage(const ImageView &image_view, Sampler *sampler = nullptr);
/**
* @brief Constructs a SampledImage referencing a certain attachment of a render target.
* @remarks If the render target is null, the default is assumed.
* If the sampler is null, a default sampler is used.
*/
SampledImage(uint32_t target_attachment, RenderTarget *render_target = nullptr, Sampler *sampler = nullptr, bool isDepthResolve = false);
SampledImage(const SampledImage &to_copy);
SampledImage &operator=(const SampledImage &to_copy);
SampledImage(SampledImage &&to_move);
SampledImage &operator=(SampledImage &&to_move);
~SampledImage() = default;
/**
* @brief Replaces the current image view with the given one.
*/
inline void set_image_view(const ImageView &new_view)
{
image_view = &new_view;
}
/**
* @brief Replaces the image view with an attachment of the PostProcessingPipeline's render target.
*/
inline void set_image_view(uint32_t new_attachment)
{
image_view = nullptr;
target_attachment = new_attachment;
}
/**
* @brief If this view refers to a render target attachment, returns a pointer to its index;
* otherwise, returns `null`.
* @remarks The lifetime of the returned pointer matches that of this `SampledImage`.
*/
const uint32_t *get_target_attachment() const;
/**
* @brief Returns either the ImageView, if set, or the image view for the set target attachment.
* If the view has no render target associated with it, default_target is used.
*/
const ImageView &get_image_view(const vkb::RenderTarget &default_target) const;
/**
* @brief Returns the currently-set sampler, if any.
*/
inline Sampler *get_sampler() const
{
return sampler;
}
/**
* @brief Sets the sampler for this SampledImage.
*/
inline void set_sampler(Sampler *new_sampler)
{
sampler = new_sampler;
}
/**
* @brief Returns the RenderTarget, if set.
*/
inline RenderTarget *get_render_target() const
{
return render_target;
}
/**
* @brief Returns either the RenderTarget, if set, or - if not - the given fallback render target.
*/
inline RenderTarget &get_render_target(RenderTarget &fallback) const
{
return render_target ? *render_target : fallback;
}
/**
* @brief Sets the sampler for this SampledImage.
* Setting it to null will make it use the default instead.
*/
inline void set_render_target(RenderTarget *new_render_target)
{
render_target = std::move(new_render_target);
}
inline bool is_depth_resolve() const
{
return isDepthResolve;
}
private:
const ImageView *image_view;
uint32_t target_attachment;
RenderTarget * render_target;
Sampler * sampler;
bool isDepthResolve;
};
} // namespace core
} // namespace vkb
+55
View File
@@ -0,0 +1,55 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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 "common/helpers.h"
#include "common/vk_common.h"
#include "core/vulkan_resource.h"
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceC = Device<vkb::BindingType::C>;
/**
* @brief Represents a Vulkan Sampler
*/
class Sampler : public VulkanResourceC<VkSampler>
{
public:
/**
* @brief Creates a Vulkan Sampler
* @param d The device to use
* @param info Creation details
*/
Sampler(vkb::core::DeviceC &d, const VkSamplerCreateInfo &info);
Sampler(const Sampler &) = delete;
Sampler(Sampler &&sampler);
~Sampler();
Sampler &operator=(const Sampler &) = delete;
Sampler &operator=(Sampler &&) = delete;
};
} // namespace core
} // namespace vkb
+46
View File
@@ -0,0 +1,46 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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.
*/
#include "sampler.h"
#include "device.h"
namespace vkb
{
namespace core
{
Sampler::Sampler(vkb::core::DeviceC &d, const VkSamplerCreateInfo &info) :
VulkanResource{VK_NULL_HANDLE, &d}
{
VK_CHECK(vkCreateSampler(get_device().get_handle(), &info, nullptr, &get_handle()));
}
Sampler::Sampler(Sampler &&other) :
VulkanResource{std::move(other)}
{
}
Sampler::~Sampler()
{
if (has_device())
{
vkDestroySampler(get_device().get_handle(), get_handle(), nullptr);
}
}
} // namespace core
} // namespace vkb
+181
View File
@@ -0,0 +1,181 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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.
*/
#include "shader_module.h"
#include "core/util/logging.hpp"
#include "device.h"
#include "filesystem/legacy.h"
#include "spirv_reflection.h"
namespace vkb
{
ShaderModule::ShaderModule(vkb::core::DeviceC &device,
VkShaderStageFlagBits stage,
const ShaderSource &shader_source,
const std::string &entry_point,
const ShaderVariant &shader_variant) :
device{device}, stage{stage}, entry_point{entry_point}
{
debug_name = fmt::format("{} [variant {:X}] [entrypoint {}]", shader_source.get_filename(), shader_variant.get_id(), entry_point);
// Shaders in binary SPIR-V format can be loaded directly
spirv = vkb::fs::read_shader_binary_u32(shader_source.get_filename());
// Reflection is used to dynamically create descriptor bindings
SPIRVReflection spirv_reflection;
// Reflect all shader resources
if (!spirv_reflection.reflect_shader_resources(stage, spirv, resources, shader_variant))
{
throw VulkanException{VK_ERROR_INITIALIZATION_FAILED};
}
// Generate a unique id, determined by source and variant
std::hash<std::string> hasher{};
id = hasher(std::string{reinterpret_cast<const char *>(spirv.data()),
reinterpret_cast<const char *>(spirv.data() + spirv.size())});
}
ShaderModule::ShaderModule(ShaderModule &&other) :
device{other.device},
id{other.id},
stage{other.stage},
entry_point{other.entry_point},
debug_name{other.debug_name},
spirv{other.spirv},
resources{other.resources}
{
other.stage = {};
}
size_t ShaderModule::get_id() const
{
return id;
}
VkShaderStageFlagBits ShaderModule::get_stage() const
{
return stage;
}
const std::string &ShaderModule::get_entry_point() const
{
return entry_point;
}
const std::vector<ShaderResource> &ShaderModule::get_resources() const
{
return resources;
}
const std::vector<uint32_t> &ShaderModule::get_binary() const
{
return spirv;
}
void ShaderModule::set_resource_mode(const std::string &resource_name, const ShaderResourceMode &resource_mode)
{
auto it = std::ranges::find_if(resources, [&resource_name](const ShaderResource &resource) { return resource.name == resource_name; });
if (it != resources.end())
{
if (resource_mode == ShaderResourceMode::Dynamic)
{
if (it->type == ShaderResourceType::BufferUniform || it->type == ShaderResourceType::BufferStorage)
{
it->mode = resource_mode;
}
else
{
LOGW("Resource `{}` does not support dynamic.", resource_name);
}
}
else
{
it->mode = resource_mode;
}
}
else
{
LOGW("Resource `{}` not found for shader.", resource_name);
}
}
size_t ShaderVariant::get_id() const
{
return id;
}
void ShaderVariant::add_runtime_array_size(const std::string &runtime_array_name, size_t size)
{
if (runtime_array_sizes.find(runtime_array_name) == runtime_array_sizes.end())
{
runtime_array_sizes.insert({runtime_array_name, size});
}
else
{
runtime_array_sizes[runtime_array_name] = size;
}
}
void ShaderVariant::set_runtime_array_sizes(const std::unordered_map<std::string, size_t> &sizes)
{
this->runtime_array_sizes = sizes;
}
const std::unordered_map<std::string, size_t> &ShaderVariant::get_runtime_array_sizes() const
{
return runtime_array_sizes;
}
void ShaderVariant::clear()
{
runtime_array_sizes.clear();
id = 0;
}
ShaderSource::ShaderSource(const std::string &filename) :
filename{filename},
source{fs::read_text_file(filename)}
{
std::hash<std::string> hasher{};
id = hasher(std::string{this->source.cbegin(), this->source.cend()});
}
size_t ShaderSource::get_id() const
{
return id;
}
const std::string &ShaderSource::get_filename() const
{
return filename;
}
void ShaderSource::set_source(const std::string &source_)
{
source = source_;
std::hash<std::string> hasher{};
id = hasher(std::string{this->source.cbegin(), this->source.cend()});
}
const std::string &ShaderSource::get_source() const
{
return source;
}
} // namespace vkb
+234
View File
@@ -0,0 +1,234 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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 "common/helpers.h"
#include "common/vk_common.h"
#if defined(VK_USE_PLATFORM_XLIB_KHR)
# undef None
#endif
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceC = Device<vkb::BindingType::C>;
} // namespace core
/// Types of shader resources
enum class ShaderResourceType
{
Input,
InputAttachment,
Output,
Image,
ImageSampler,
ImageStorage,
Sampler,
BufferUniform,
BufferStorage,
PushConstant,
SpecializationConstant,
All
};
/// This determines the type and method of how descriptor set should be created and bound
enum class ShaderResourceMode
{
Static,
Dynamic,
UpdateAfterBind
};
/// A bitmask of qualifiers applied to a resource
struct ShaderResourceQualifiers
{
enum : uint32_t
{
None = 0,
NonReadable = 1,
NonWritable = 2,
};
};
/// Store shader resource data.
/// Used by the shader module.
struct ShaderResource
{
VkShaderStageFlags stages;
ShaderResourceType type;
ShaderResourceMode mode;
uint32_t set;
uint32_t binding;
uint32_t location;
uint32_t input_attachment_index;
uint32_t vec_size;
uint32_t columns;
uint32_t array_size;
uint32_t offset;
uint32_t size;
uint32_t constant_id;
uint32_t qualifiers;
std::string name;
};
/**
* @brief Adds support for C style preprocessor macros to glsl shaders
* enabling you to define or undefine certain symbols
*/
class ShaderVariant
{
public:
ShaderVariant() = default;
size_t get_id() const;
/**
* @brief Specifies the size of a named runtime array for automatic reflection. If already specified, overrides the size.
* @param runtime_array_name String under which the runtime array is named in the shader
* @param size Integer specifying the wanted size of the runtime array (in number of elements, not size in bytes), used for automatic allocation of buffers.
* See get_declared_struct_size_runtime_array() in spirv_cross.h
*/
void add_runtime_array_size(const std::string &runtime_array_name, size_t size);
void set_runtime_array_sizes(const std::unordered_map<std::string, size_t> &sizes);
const std::unordered_map<std::string, size_t> &get_runtime_array_sizes() const;
void clear();
private:
size_t id;
std::unordered_map<std::string, size_t> runtime_array_sizes;
};
class ShaderSource
{
public:
ShaderSource() = default;
ShaderSource(const std::string &filename);
size_t get_id() const;
const std::string &get_filename() const;
void set_source(const std::string &source);
const std::string &get_source() const;
private:
size_t id;
std::string filename;
std::string source;
};
/**
* @brief Contains shader code, with an entry point, for a specific shader stage.
* It is needed by a PipelineLayout to create a Pipeline.
* ShaderModule can do auto-pairing between shader code and textures.
* The low level code can change bindings, just keeping the name of the texture.
* Variants for each texture are also generated, such as HAS_BASE_COLOR_TEX.
* It works similarly for attribute locations. A current limitation is that only set 0
* is considered. Uniform buffers are currently hardcoded as well.
*/
class ShaderModule
{
public:
ShaderModule(vkb::core::DeviceC &device,
VkShaderStageFlagBits stage,
const ShaderSource &shader_source,
const std::string &entry_point,
const ShaderVariant &shader_variant);
ShaderModule(const ShaderModule &) = delete;
ShaderModule(ShaderModule &&other);
ShaderModule &operator=(const ShaderModule &) = delete;
ShaderModule &operator=(ShaderModule &&) = delete;
size_t get_id() const;
VkShaderStageFlagBits get_stage() const;
const std::string &get_entry_point() const;
const std::vector<ShaderResource> &get_resources() const;
const std::vector<uint32_t> &get_binary() const;
inline const std::string &get_debug_name() const
{
return debug_name;
}
inline void set_debug_name(const std::string &name)
{
debug_name = name;
}
/**
* @brief Flags a resource to use a different method of being bound to the shader
* @param resource_name The name of the shader resource
* @param resource_mode The mode of how the shader resource will be bound
*/
void set_resource_mode(const std::string &resource_name, const ShaderResourceMode &resource_mode);
private:
vkb::core::DeviceC &device;
/// Shader unique id
size_t id;
/// Stage of the shader (vertex, fragment, etc)
VkShaderStageFlagBits stage{};
/// Name of the main function
std::string entry_point;
/// Human-readable name for the shader
std::string debug_name;
/// Compiled source
std::vector<uint32_t> spirv;
std::vector<ShaderResource> resources;
};
} // namespace vkb
+645
View File
@@ -0,0 +1,645 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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.
*/
#include "core/swapchain.h"
#include "core/util/logging.hpp"
#include "device.h"
#include "image.h"
namespace vkb
{
namespace
{
inline uint32_t choose_image_count(
uint32_t request_image_count,
uint32_t min_image_count,
uint32_t max_image_count)
{
if (max_image_count != 0)
{
request_image_count = std::min(request_image_count, max_image_count);
}
request_image_count = std::max(request_image_count, min_image_count);
return request_image_count;
}
inline uint32_t choose_image_array_layers(
uint32_t request_image_array_layers,
uint32_t max_image_array_layers)
{
request_image_array_layers = std::min(request_image_array_layers, max_image_array_layers);
request_image_array_layers = std::max(request_image_array_layers, 1u);
return request_image_array_layers;
}
inline VkExtent2D choose_extent(
VkExtent2D request_extent,
const VkExtent2D &min_image_extent,
const VkExtent2D &max_image_extent,
const VkExtent2D &current_extent)
{
if (current_extent.width == 0xFFFFFFFF)
{
return request_extent;
}
if (request_extent.width < 1 || request_extent.height < 1)
{
LOGW("(Swapchain) Image extent ({}, {}) not supported. Selecting ({}, {}).", request_extent.width, request_extent.height, current_extent.width, current_extent.height);
return current_extent;
}
request_extent.width = std::max(request_extent.width, min_image_extent.width);
request_extent.width = std::min(request_extent.width, max_image_extent.width);
request_extent.height = std::max(request_extent.height, min_image_extent.height);
request_extent.height = std::min(request_extent.height, max_image_extent.height);
return request_extent;
}
inline VkPresentModeKHR choose_present_mode(
VkPresentModeKHR request_present_mode,
const std::vector<VkPresentModeKHR> &available_present_modes,
const std::vector<VkPresentModeKHR> &present_mode_priority_list)
{
auto present_mode_it = std::ranges::find(available_present_modes, request_present_mode);
if (present_mode_it == available_present_modes.end())
{
// If nothing found, always default to FIFO
VkPresentModeKHR chosen_present_mode = VK_PRESENT_MODE_FIFO_KHR;
for (auto &present_mode : present_mode_priority_list)
{
if (std::ranges::find(available_present_modes, present_mode) != available_present_modes.end())
{
chosen_present_mode = present_mode;
break;
}
}
LOGW("(Swapchain) Present mode '{}' not supported. Selecting '{}'.", to_string(request_present_mode), to_string(chosen_present_mode));
return chosen_present_mode;
}
else
{
LOGI("(Swapchain) Present mode selected: {}", to_string(request_present_mode));
return *present_mode_it;
}
}
inline VkSurfaceFormatKHR choose_surface_format(
const VkSurfaceFormatKHR requested_surface_format,
const std::vector<VkSurfaceFormatKHR> &available_surface_formats,
const std::vector<VkSurfaceFormatKHR> &surface_format_priority_list)
{
// Try to find the requested surface format in the supported surface formats
auto surface_format_it = std::ranges::find_if(
available_surface_formats,
[&requested_surface_format](const VkSurfaceFormatKHR &surface) {
if (surface.format == requested_surface_format.format &&
surface.colorSpace == requested_surface_format.colorSpace)
{
return true;
}
return false;
});
// If the requested surface format isn't found, then try to request a format from the priority list
if (surface_format_it == available_surface_formats.end())
{
for (auto &surface_format : surface_format_priority_list)
{
surface_format_it = std::ranges::find_if(
available_surface_formats,
[&surface_format](const VkSurfaceFormatKHR &surface) {
if (surface.format == surface_format.format &&
surface.colorSpace == surface_format.colorSpace)
{
return true;
}
return false;
});
if (surface_format_it != available_surface_formats.end())
{
LOGW("(Swapchain) Surface format ({}) not supported. Selecting ({}).", to_string(requested_surface_format), to_string(*surface_format_it));
return *surface_format_it;
}
}
// If nothing found, default to the first supported surface format
surface_format_it = available_surface_formats.begin();
LOGW("(Swapchain) Surface format ({}) not supported. Selecting ({}).", to_string(requested_surface_format), to_string(*surface_format_it));
}
else
{
LOGI("(Swapchain) Surface format selected: {}", to_string(requested_surface_format));
}
return *surface_format_it;
}
inline VkSurfaceTransformFlagBitsKHR choose_transform(
VkSurfaceTransformFlagBitsKHR request_transform,
VkSurfaceTransformFlagsKHR supported_transform,
VkSurfaceTransformFlagBitsKHR current_transform)
{
if (request_transform & supported_transform)
{
return request_transform;
}
LOGW("(Swapchain) Surface transform '{}' not supported. Selecting '{}'.", to_string(request_transform), to_string(current_transform));
return current_transform;
}
inline VkCompositeAlphaFlagBitsKHR choose_composite_alpha(VkCompositeAlphaFlagBitsKHR request_composite_alpha, VkCompositeAlphaFlagsKHR supported_composite_alpha)
{
if (request_composite_alpha & supported_composite_alpha)
{
return request_composite_alpha;
}
static const std::vector<VkCompositeAlphaFlagBitsKHR> composite_alpha_flags = {
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR};
for (VkCompositeAlphaFlagBitsKHR composite_alpha : composite_alpha_flags)
{
if (composite_alpha & supported_composite_alpha)
{
LOGW("(Swapchain) Composite alpha '{}' not supported. Selecting '{}.", to_string(request_composite_alpha), to_string(composite_alpha));
return composite_alpha;
}
}
throw std::runtime_error("No compatible composite alpha found.");
}
inline bool validate_format_feature(VkImageUsageFlagBits image_usage, VkFormatFeatureFlags supported_features)
{
switch (image_usage)
{
case VK_IMAGE_USAGE_STORAGE_BIT:
return VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT & supported_features;
default:
return true;
}
}
inline std::set<VkImageUsageFlagBits> choose_image_usage(const std::set<VkImageUsageFlagBits> &requested_image_usage_flags, VkImageUsageFlags supported_image_usage, VkFormatFeatureFlags supported_features)
{
std::set<VkImageUsageFlagBits> validated_image_usage_flags;
for (auto flag : requested_image_usage_flags)
{
if ((flag & supported_image_usage) && validate_format_feature(flag, supported_features))
{
validated_image_usage_flags.insert(flag);
}
else
{
LOGW("(Swapchain) Image usage ({}) requested but not supported.", to_string(flag));
}
}
if (validated_image_usage_flags.empty())
{
// Pick the first format from list of defaults, if supported
static const std::vector<VkImageUsageFlagBits> image_usage_flags = {
VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
VK_IMAGE_USAGE_STORAGE_BIT,
VK_IMAGE_USAGE_SAMPLED_BIT,
VK_IMAGE_USAGE_TRANSFER_DST_BIT};
for (VkImageUsageFlagBits image_usage : image_usage_flags)
{
if ((image_usage & supported_image_usage) && validate_format_feature(image_usage, supported_features))
{
validated_image_usage_flags.insert(image_usage);
break;
}
}
}
if (!validated_image_usage_flags.empty())
{
// Log image usage flags used
std::string usage_list;
for (VkImageUsageFlagBits image_usage : validated_image_usage_flags)
{
usage_list += to_string(image_usage) + " ";
}
LOGI("(Swapchain) Image usage flags: {}", usage_list);
}
else
{
throw std::runtime_error("No compatible image usage found.");
}
return validated_image_usage_flags;
}
inline VkImageUsageFlags composite_image_flags(std::set<VkImageUsageFlagBits> &image_usage_flags)
{
VkImageUsageFlags image_usage{};
for (auto flag : image_usage_flags)
{
image_usage |= flag;
}
return image_usage;
}
} // namespace
Swapchain::Swapchain(Swapchain &old_swapchain, const VkExtent2D &extent) :
Swapchain{old_swapchain,
old_swapchain.device,
old_swapchain.surface,
old_swapchain.properties.present_mode,
old_swapchain.present_mode_priority_list,
old_swapchain.surface_format_priority_list,
extent,
old_swapchain.properties.image_count,
old_swapchain.properties.pre_transform,
old_swapchain.image_usage_flags,
old_swapchain.requested_compression,
old_swapchain.requested_compression_fixed_rate}
{}
Swapchain::Swapchain(Swapchain &old_swapchain, const uint32_t image_count) :
Swapchain{old_swapchain,
old_swapchain.device,
old_swapchain.surface,
old_swapchain.properties.present_mode,
old_swapchain.present_mode_priority_list,
old_swapchain.surface_format_priority_list,
old_swapchain.properties.extent,
image_count,
old_swapchain.properties.pre_transform,
old_swapchain.image_usage_flags,
old_swapchain.requested_compression,
old_swapchain.requested_compression_fixed_rate}
{}
Swapchain::Swapchain(Swapchain &old_swapchain, const std::set<VkImageUsageFlagBits> &image_usage_flags) :
Swapchain{old_swapchain,
old_swapchain.device,
old_swapchain.surface,
old_swapchain.properties.present_mode,
old_swapchain.present_mode_priority_list,
old_swapchain.surface_format_priority_list,
old_swapchain.properties.extent,
old_swapchain.properties.image_count,
old_swapchain.properties.pre_transform,
image_usage_flags,
old_swapchain.requested_compression,
old_swapchain.requested_compression_fixed_rate}
{}
Swapchain::Swapchain(Swapchain &old_swapchain, const VkExtent2D &extent, const VkSurfaceTransformFlagBitsKHR transform) :
Swapchain{old_swapchain,
old_swapchain.device,
old_swapchain.surface,
old_swapchain.properties.present_mode,
old_swapchain.present_mode_priority_list,
old_swapchain.surface_format_priority_list,
extent,
old_swapchain.properties.image_count,
transform,
old_swapchain.image_usage_flags,
old_swapchain.requested_compression,
old_swapchain.requested_compression_fixed_rate}
{}
Swapchain::Swapchain(Swapchain &old_swapchain, const VkImageCompressionFlagsEXT requested_compression, const VkImageCompressionFixedRateFlagsEXT requested_compression_fixed_rate) :
Swapchain{old_swapchain,
old_swapchain.device,
old_swapchain.surface,
old_swapchain.properties.present_mode,
old_swapchain.present_mode_priority_list,
old_swapchain.surface_format_priority_list,
old_swapchain.properties.extent,
old_swapchain.properties.image_count,
old_swapchain.properties.pre_transform,
old_swapchain.image_usage_flags,
requested_compression,
requested_compression_fixed_rate}
{}
Swapchain::Swapchain(vkb::core::DeviceC &device,
VkSurfaceKHR surface,
const VkPresentModeKHR present_mode,
std::vector<VkPresentModeKHR> const &present_mode_priority_list,
const std::vector<VkSurfaceFormatKHR> &surface_format_priority_list,
const VkExtent2D &extent,
const uint32_t image_count,
const VkSurfaceTransformFlagBitsKHR transform,
const std::set<VkImageUsageFlagBits> &image_usage_flags,
const VkImageCompressionFlagsEXT requested_compression,
const VkImageCompressionFixedRateFlagsEXT requested_compression_fixed_rate) :
Swapchain{*this, device, surface, present_mode, present_mode_priority_list, surface_format_priority_list, extent, image_count, transform, image_usage_flags}
{
}
Swapchain::Swapchain(Swapchain &old_swapchain,
vkb::core::DeviceC &device,
VkSurfaceKHR surface,
const VkPresentModeKHR present_mode,
std::vector<VkPresentModeKHR> const &present_mode_priority_list,
const std::vector<VkSurfaceFormatKHR> &surface_format_priority_list,
const VkExtent2D &extent,
const uint32_t image_count,
const VkSurfaceTransformFlagBitsKHR transform,
const std::set<VkImageUsageFlagBits> &image_usage_flags,
const VkImageCompressionFlagsEXT requested_compression,
const VkImageCompressionFixedRateFlagsEXT requested_compression_fixed_rate) :
device{device},
surface{surface},
requested_compression{requested_compression},
requested_compression_fixed_rate{requested_compression_fixed_rate}
{
this->present_mode_priority_list = present_mode_priority_list;
this->surface_format_priority_list = surface_format_priority_list;
VkSurfaceCapabilitiesKHR surface_capabilities{};
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(this->device.get_gpu().get_handle(), surface, &surface_capabilities);
uint32_t surface_format_count{0U};
VK_CHECK(vkGetPhysicalDeviceSurfaceFormatsKHR(this->device.get_gpu().get_handle(), surface, &surface_format_count, nullptr));
std::vector<VkSurfaceFormatKHR> surface_formats(surface_format_count);
VK_CHECK(vkGetPhysicalDeviceSurfaceFormatsKHR(this->device.get_gpu().get_handle(), surface, &surface_format_count, surface_formats.data()));
LOGI("Surface supports the following surface formats:");
for (auto &surface_format : surface_formats)
{
LOGI(" \t{}", to_string(surface_format));
}
uint32_t present_mode_count{0U};
VK_CHECK(vkGetPhysicalDeviceSurfacePresentModesKHR(this->device.get_gpu().get_handle(), surface, &present_mode_count, nullptr));
std::vector<VkPresentModeKHR> present_modes(present_mode_count);
VK_CHECK(vkGetPhysicalDeviceSurfacePresentModesKHR(this->device.get_gpu().get_handle(), surface, &present_mode_count, present_modes.data()));
LOGI("Surface supports the following present modes:");
for (auto &pm : present_modes)
{
LOGI(" \t{}", to_string(pm));
}
// Choose best properties based on surface capabilities
properties.old_swapchain = old_swapchain.get_handle();
properties.image_count = choose_image_count(image_count, surface_capabilities.minImageCount, surface_capabilities.maxImageCount);
properties.extent = choose_extent(extent, surface_capabilities.minImageExtent, surface_capabilities.maxImageExtent, surface_capabilities.currentExtent);
properties.surface_format = choose_surface_format(properties.surface_format, surface_formats, surface_format_priority_list);
properties.array_layers = choose_image_array_layers(1U, surface_capabilities.maxImageArrayLayers);
VkFormatProperties format_properties;
vkGetPhysicalDeviceFormatProperties(this->device.get_gpu().get_handle(), properties.surface_format.format, &format_properties);
this->image_usage_flags = choose_image_usage(image_usage_flags, surface_capabilities.supportedUsageFlags, format_properties.optimalTilingFeatures);
properties.image_usage = composite_image_flags(this->image_usage_flags);
properties.pre_transform = choose_transform(transform, surface_capabilities.supportedTransforms, surface_capabilities.currentTransform);
properties.composite_alpha = choose_composite_alpha(VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, surface_capabilities.supportedCompositeAlpha);
properties.present_mode = choose_present_mode(present_mode, present_modes, present_mode_priority_list);
VkSwapchainCreateInfoKHR create_info{VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR};
create_info.minImageCount = properties.image_count;
create_info.imageExtent = properties.extent;
create_info.presentMode = properties.present_mode;
create_info.imageFormat = properties.surface_format.format;
create_info.imageColorSpace = properties.surface_format.colorSpace;
create_info.imageArrayLayers = properties.array_layers;
create_info.imageUsage = properties.image_usage;
create_info.preTransform = properties.pre_transform;
create_info.compositeAlpha = properties.composite_alpha;
create_info.oldSwapchain = properties.old_swapchain;
create_info.surface = surface;
auto fixed_rate_flags = requested_compression_fixed_rate;
VkImageCompressionControlEXT compression_control{VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT};
compression_control.flags = requested_compression;
if (device.is_extension_enabled(VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_EXTENSION_NAME))
{
create_info.pNext = &compression_control;
if (VK_IMAGE_COMPRESSION_FIXED_RATE_EXPLICIT_EXT == requested_compression)
{
// Do not support compression for multi-planar formats
compression_control.compressionControlPlaneCount = 1;
compression_control.pFixedRateFlags = &fixed_rate_flags;
}
else if (VK_IMAGE_COMPRESSION_DISABLED_EXT == requested_compression)
{
LOGW("(Swapchain) Disabling default (lossless) compression, which can negatively impact performance")
}
}
else
{
if (VK_IMAGE_COMPRESSION_DEFAULT_EXT != requested_compression)
{
LOGW("(Swapchain) Compression cannot be controlled because VK_EXT_image_compression_control_swapchain is not enabled")
this->requested_compression = VK_IMAGE_COMPRESSION_DEFAULT_EXT;
this->requested_compression_fixed_rate = VK_IMAGE_COMPRESSION_FIXED_RATE_NONE_EXT;
}
}
VkResult result = vkCreateSwapchainKHR(device.get_handle(), &create_info, nullptr, &handle);
if (result != VK_SUCCESS)
{
throw VulkanException{result, "Cannot create Swapchain"};
}
uint32_t image_available{0u};
VK_CHECK(vkGetSwapchainImagesKHR(device.get_handle(), handle, &image_available, nullptr));
images.resize(image_available);
VK_CHECK(vkGetSwapchainImagesKHR(device.get_handle(), handle, &image_available, images.data()));
if (device.is_extension_enabled(VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_EXTENSION_NAME) &&
VK_IMAGE_COMPRESSION_FIXED_RATE_EXPLICIT_EXT == requested_compression)
{
// Check if fixed-rate compression was applied
const auto applied_compression_fixed_rate = vkb::query_applied_compression(device.get_handle(), images[0]).imageCompressionFixedRateFlags;
if (applied_compression_fixed_rate != requested_compression_fixed_rate)
{
LOGW("(Swapchain) Requested fixed-rate compression ({}) was not applied, instead images use {}",
image_compression_fixed_rate_flags_to_string(requested_compression_fixed_rate),
image_compression_fixed_rate_flags_to_string(applied_compression_fixed_rate));
this->requested_compression_fixed_rate = applied_compression_fixed_rate;
if (VK_IMAGE_COMPRESSION_FIXED_RATE_NONE_EXT == applied_compression_fixed_rate)
{
this->requested_compression = VK_IMAGE_COMPRESSION_DEFAULT_EXT;
}
}
else
{
LOGI("(Swapchain) Applied fixed-rate compression: {}",
image_compression_fixed_rate_flags_to_string(applied_compression_fixed_rate));
}
}
}
Swapchain::~Swapchain()
{
if (handle != VK_NULL_HANDLE)
{
vkDestroySwapchainKHR(device.get_handle(), handle, nullptr);
}
}
Swapchain::Swapchain(Swapchain &&other) :
device{other.device},
surface{std::exchange(other.surface, VK_NULL_HANDLE)},
handle{std::exchange(other.handle, VK_NULL_HANDLE)},
images{std::exchange(other.images, {})},
properties{std::exchange(other.properties, {})},
present_mode_priority_list{std::exchange(other.present_mode_priority_list, {})},
surface_format_priority_list{std::exchange(other.surface_format_priority_list, {})},
image_usage_flags{std::move(other.image_usage_flags)}
{
}
bool Swapchain::is_valid() const
{
return handle != VK_NULL_HANDLE;
}
vkb::core::DeviceC &Swapchain::get_device()
{
return device;
}
VkSwapchainKHR Swapchain::get_handle() const
{
return handle;
}
VkResult Swapchain::acquire_next_image(uint32_t &image_index, VkSemaphore image_acquired_semaphore, VkFence fence) const
{
return vkAcquireNextImageKHR(device.get_handle(), handle, std::numeric_limits<uint64_t>::max(), image_acquired_semaphore, fence, &image_index);
}
const VkExtent2D &Swapchain::get_extent() const
{
return properties.extent;
}
VkFormat Swapchain::get_format() const
{
return properties.surface_format.format;
}
VkSurfaceFormatKHR Swapchain::get_surface_format() const
{
return properties.surface_format;
}
const std::vector<VkImage> &Swapchain::get_images() const
{
return images;
}
VkSurfaceTransformFlagBitsKHR Swapchain::get_transform() const
{
return properties.pre_transform;
}
VkSurfaceKHR Swapchain::get_surface() const
{
return surface;
}
VkImageUsageFlags Swapchain::get_usage() const
{
return properties.image_usage;
}
VkPresentModeKHR Swapchain::get_present_mode() const
{
return properties.present_mode;
}
VkImageCompressionFlagsEXT Swapchain::get_applied_compression() const
{
return vkb::query_applied_compression(device.get_handle(), get_images()[0]).imageCompressionFlags;
}
std::vector<Swapchain::SurfaceFormatCompression> Swapchain::query_supported_fixed_rate_compression(vkb::core::DeviceC &device, const VkSurfaceKHR &surface)
{
std::vector<SurfaceFormatCompression> surface_format_compression_list;
if (device.is_extension_enabled(VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_EXTENSION_NAME))
{
if (device.get_gpu().get_instance().is_enabled(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME))
{
VkPhysicalDeviceSurfaceInfo2KHR surface_info{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR};
surface_info.surface = surface;
uint32_t surface_format_count{0U};
VK_CHECK(vkGetPhysicalDeviceSurfaceFormats2KHR(device.get_gpu().get_handle(), &surface_info, &surface_format_count, nullptr));
std::vector<VkSurfaceFormat2KHR> surface_formats;
surface_formats.resize(surface_format_count, {VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR});
std::vector<VkImageCompressionPropertiesEXT> compression_properties;
compression_properties.resize(surface_format_count, {VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_PROPERTIES_EXT});
for (uint32_t i = 0; i < surface_format_count; i++)
{
surface_formats[i].pNext = &compression_properties[i];
}
VK_CHECK(vkGetPhysicalDeviceSurfaceFormats2KHR(device.get_gpu().get_handle(), &surface_info, &surface_format_count, surface_formats.data()));
surface_format_compression_list.reserve(surface_format_count);
for (uint32_t i = 0; i < surface_format_count; i++)
{
surface_format_compression_list.push_back({surface_formats[i], compression_properties[i]});
}
}
else
{
LOGW("(Swapchain) To query fixed-rate compression support, instance extension VK_KHR_get_surface_capabilities2 must be enabled")
}
}
else
{
LOGW("(Swapchain) To query fixed-rate compression support, device extension VK_EXT_image_compression_control_swapchain must be enabled")
}
return surface_format_compression_list;
}
} // namespace vkb
+194
View File
@@ -0,0 +1,194 @@
/* Copyright (c) 2019-2025, Arm Limited and Contributors
*
* 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 "common/helpers.h"
#include "common/vk_common.h"
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceC = Device<vkb::BindingType::C>;
} // namespace core
enum ImageFormat
{
sRGB,
UNORM
};
struct SwapchainProperties
{
VkSwapchainKHR old_swapchain;
uint32_t image_count{3};
VkExtent2D extent{};
VkSurfaceFormatKHR surface_format{};
uint32_t array_layers;
VkImageUsageFlags image_usage;
VkSurfaceTransformFlagBitsKHR pre_transform;
VkCompositeAlphaFlagBitsKHR composite_alpha;
VkPresentModeKHR present_mode;
};
class Swapchain
{
public:
/**
* @brief Constructor to create a swapchain by changing the extent
* only and preserving the configuration from the old swapchain.
*/
Swapchain(Swapchain &old_swapchain, const VkExtent2D &extent);
/**
* @brief Constructor to create a swapchain by changing the image count
* only and preserving the configuration from the old swapchain.
*/
Swapchain(Swapchain &old_swapchain, const uint32_t image_count);
/**
* @brief Constructor to create a swapchain by changing the image usage
* only and preserving the configuration from the old swapchain.
*/
Swapchain(Swapchain &old_swapchain, const std::set<VkImageUsageFlagBits> &image_usage_flags);
/**
* @brief Constructor to create a swapchain by changing the extent
* and transform only and preserving the configuration from the old swapchain.
*/
Swapchain(Swapchain &swapchain, const VkExtent2D &extent, const VkSurfaceTransformFlagBitsKHR transform);
/**
* @brief Constructor to create a swapchain by changing the compression settings
* only and preserving the configuration from the old swapchain.
*/
Swapchain(Swapchain &swapchain, const VkImageCompressionFlagsEXT requested_compression, const VkImageCompressionFixedRateFlagsEXT requested_compression_fixed_rate);
/**
* @brief Constructor to create a swapchain.
*/
Swapchain(vkb::core::DeviceC &device,
VkSurfaceKHR surface,
const VkPresentModeKHR present_mode,
const std::vector<VkPresentModeKHR> &present_mode_priority_list = {VK_PRESENT_MODE_FIFO_KHR,
VK_PRESENT_MODE_MAILBOX_KHR},
const std::vector<VkSurfaceFormatKHR> &surface_format_priority_list = {{VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
{VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}},
const VkExtent2D &extent = {},
const uint32_t image_count = 3,
const VkSurfaceTransformFlagBitsKHR transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
const std::set<VkImageUsageFlagBits> &image_usage_flags = {VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_IMAGE_USAGE_TRANSFER_SRC_BIT},
const VkImageCompressionFlagsEXT requested_compression = VK_IMAGE_COMPRESSION_DEFAULT_EXT,
const VkImageCompressionFixedRateFlagsEXT requested_compression_fixed_rate = VK_IMAGE_COMPRESSION_FIXED_RATE_NONE_EXT);
/**
* @brief Constructor to create a swapchain from the old swapchain
* by configuring all parameters.
*/
Swapchain(Swapchain &old_swapchain,
vkb::core::DeviceC &device,
VkSurfaceKHR surface,
const VkPresentModeKHR present_mode,
const std::vector<VkPresentModeKHR> &present_mode_priority_list = {VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_MAILBOX_KHR},
const std::vector<VkSurfaceFormatKHR> &surface_format_priority_list = {{VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
{VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}},
const VkExtent2D &extent = {},
const uint32_t image_count = 3,
const VkSurfaceTransformFlagBitsKHR transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
const std::set<VkImageUsageFlagBits> &image_usage_flags = {VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_IMAGE_USAGE_TRANSFER_SRC_BIT},
const VkImageCompressionFlagsEXT requested_compression = VK_IMAGE_COMPRESSION_DEFAULT_EXT,
const VkImageCompressionFixedRateFlagsEXT requested_compression_fixed_rate = VK_IMAGE_COMPRESSION_FIXED_RATE_NONE_EXT);
Swapchain(const Swapchain &) = delete;
Swapchain(Swapchain &&other);
~Swapchain();
Swapchain &operator=(const Swapchain &) = delete;
Swapchain &operator=(Swapchain &&) = delete;
bool is_valid() const;
vkb::core::DeviceC &get_device();
VkSwapchainKHR get_handle() const;
VkResult acquire_next_image(uint32_t &image_index, VkSemaphore image_acquired_semaphore, VkFence fence = VK_NULL_HANDLE) const;
const VkExtent2D &get_extent() const;
VkFormat get_format() const;
VkSurfaceFormatKHR get_surface_format() const;
const std::vector<VkImage> &get_images() const;
VkSurfaceTransformFlagBitsKHR get_transform() const;
VkSurfaceKHR get_surface() const;
VkImageUsageFlags get_usage() const;
VkPresentModeKHR get_present_mode() const;
VkImageCompressionFlagsEXT get_applied_compression() const;
/**
* Helper functions for compression controls
*/
struct SurfaceFormatCompression
{
VkSurfaceFormat2KHR surface_format{VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR};
VkImageCompressionPropertiesEXT compression_properties{VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_PROPERTIES_EXT};
};
static std::vector<SurfaceFormatCompression> query_supported_fixed_rate_compression(vkb::core::DeviceC &device, const VkSurfaceKHR &surface);
private:
vkb::core::DeviceC &device;
VkSurfaceKHR surface{VK_NULL_HANDLE};
VkSwapchainKHR handle{VK_NULL_HANDLE};
std::vector<VkImage> images;
SwapchainProperties properties;
// A list of present modes in order of priority (vector[0] has high priority, vector[size-1] has low priority)
std::vector<VkPresentModeKHR> present_mode_priority_list = {
VK_PRESENT_MODE_FIFO_KHR,
VK_PRESENT_MODE_MAILBOX_KHR};
// A list of surface formats in order of priority (vector[0] has high priority, vector[size-1] has low priority)
std::vector<VkSurfaceFormatKHR> surface_format_priority_list = {
{VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR},
{VK_FORMAT_B8G8R8A8_SRGB, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}};
std::set<VkImageUsageFlagBits> image_usage_flags;
VkImageCompressionFlagsEXT requested_compression{VK_IMAGE_COMPRESSION_DEFAULT_EXT};
VkImageCompressionFixedRateFlagsEXT requested_compression_fixed_rate{VK_IMAGE_COMPRESSION_FIXED_RATE_NONE_EXT};
};
} // namespace vkb
+235
View File
@@ -0,0 +1,235 @@
/* Copyright (c) 2021-2025, Arm Limited and Contributors
* Copyright (c) 2024-2025, NVIDIA CORPORATION. All rights reserved.
*
* 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 "common/vk_common.h"
#include "core/debug.h"
#include "vulkan_type_mapping.h"
#include <utility>
#include <vulkan/vulkan.hpp>
namespace vkb
{
namespace core
{
template <vkb::BindingType bindingType>
class Device;
using DeviceC = Device<vkb::BindingType::C>;
using DeviceCpp = Device<vkb::BindingType::Cpp>;
/// Inherit this for any Vulkan object with a handle of type `Handle`.
///
/// This allows the derived class to store a Vulkan handle, and also a pointer to the parent vkb::Device.
/// It also allows to set a debug name for any Vulkan object.
template <vkb::BindingType bindingType, typename Handle>
class VulkanResource
{
public:
// we always want to store a vk::Handle as a resource, so we have to figure out that type, depending on the BindingType!
using ResourceType = typename vkb::VulkanTypeMapping<bindingType, Handle>::Type;
public:
using ObjectType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::ObjectType, VkObjectType>::type;
VulkanResource(Handle handle = nullptr, Device<bindingType> *device_ = nullptr);
VulkanResource(const VulkanResource &) = delete;
VulkanResource &operator=(const VulkanResource &) = delete;
VulkanResource(VulkanResource &&other);
VulkanResource &operator=(VulkanResource &&other);
virtual ~VulkanResource() = default;
const std::string &get_debug_name() const;
vkb::core::Device<bindingType> &get_device();
vkb::core::Device<bindingType> const &get_device() const;
Handle &get_handle();
const Handle &get_handle() const;
uint64_t get_handle_u64() const;
ObjectType get_object_type() const;
ResourceType const &get_resource() const;
bool has_device() const;
bool has_handle() const;
void set_debug_name(const std::string &name);
void set_handle(Handle hdl);
private:
std::string debug_name;
vkb::core::DeviceCpp *device;
ResourceType handle;
};
template <typename Handle>
using VulkanResourceC = VulkanResource<vkb::BindingType::C, Handle>;
template <typename Handle>
using VulkanResourceCpp = VulkanResource<vkb::BindingType::Cpp, Handle>;
template <vkb::BindingType bindingType, typename Handle>
inline VulkanResource<bindingType, Handle>::VulkanResource(Handle handle_, Device<bindingType> *device_) :
handle{handle_}
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
device = device_;
}
else
{
device = reinterpret_cast<vkb::core::DeviceCpp *>(device_);
}
}
template <vkb::BindingType bindingType, typename Handle>
inline VulkanResource<bindingType, Handle>::VulkanResource(VulkanResource &&other) :
handle(std::exchange(other.handle, {})),
device(std::exchange(other.device, {})),
debug_name(std::exchange(other.debug_name, {}))
{}
template <vkb::BindingType bindingType, typename Handle>
inline VulkanResource<bindingType, Handle> &VulkanResource<bindingType, Handle>::operator=(VulkanResource &&other)
{
handle = std::exchange(other.handle, {});
device = std::exchange(other.device, {});
debug_name = std::exchange(other.debug_name, {});
return *this;
}
template <vkb::BindingType bindingType, typename Handle>
inline const std::string &VulkanResource<bindingType, Handle>::get_debug_name() const
{
return debug_name;
}
template <vkb::BindingType bindingType, typename Handle>
inline Device<bindingType> &VulkanResource<bindingType, Handle>::get_device()
{
assert(device && "VKBDevice handle not set");
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return *device;
}
else
{
return *reinterpret_cast<vkb::core::DeviceC *>(device);
}
}
template <vkb::BindingType bindingType, typename Handle>
inline Device<bindingType> const &VulkanResource<bindingType, Handle>::get_device() const
{
assert(device && "VKBDevice handle not set");
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return *device;
}
else
{
return *reinterpret_cast<vkb::core::DeviceC const *>(device);
}
}
template <vkb::BindingType bindingType, typename Handle>
inline Handle &VulkanResource<bindingType, Handle>::get_handle()
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return handle;
}
else
{
return *reinterpret_cast<typename ResourceType::NativeType *>(&handle);
}
}
template <vkb::BindingType bindingType, typename Handle>
inline const Handle &VulkanResource<bindingType, Handle>::get_handle() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return handle;
}
else
{
return *reinterpret_cast<typename ResourceType::NativeType const *>(&handle);
}
}
template <vkb::BindingType bindingType, typename Handle>
inline uint64_t VulkanResource<bindingType, Handle>::get_handle_u64() const
{
// See https://github.com/KhronosGroup/Vulkan-Docs/issues/368 .
// Dispatchable and non-dispatchable handle types are *not* necessarily binary-compatible!
// Non-dispatchable handles _might_ be only 32-bit long. This is because, on 32-bit machines, they might be a typedef to a 32-bit pointer.
using UintHandle = typename std::conditional<sizeof(ResourceType) == sizeof(uint32_t), uint32_t, uint64_t>::type;
return static_cast<uint64_t>(*reinterpret_cast<UintHandle const *>(&handle));
}
template <vkb::BindingType bindingType, typename Handle>
inline typename VulkanResource<bindingType, Handle>::ObjectType VulkanResource<bindingType, Handle>::get_object_type() const
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return ResourceType::objectType;
}
else
{
return static_cast<VkObjectType>(ResourceType::objectType);
}
}
template <vkb::BindingType bindingType, typename Handle>
inline typename VulkanResource<bindingType, Handle>::ResourceType const &VulkanResource<bindingType, Handle>::get_resource() const
{
return handle;
}
template <vkb::BindingType bindingType, typename Handle>
inline bool VulkanResource<bindingType, Handle>::has_device() const
{
return device != nullptr;
}
template <vkb::BindingType bindingType, typename Handle>
inline bool VulkanResource<bindingType, Handle>::has_handle() const
{
return handle != nullptr;
}
template <vkb::BindingType bindingType, typename Handle>
inline void VulkanResource<bindingType, Handle>::set_debug_name(const std::string &name)
{
debug_name = name;
if (device && !debug_name.empty())
{
get_device().get_debug_utils().set_debug_name(get_device().get_handle(), get_object_type(), get_handle_u64(), debug_name.c_str());
}
}
template <vkb::BindingType bindingType, typename Handle>
inline void VulkanResource<bindingType, Handle>::set_handle(Handle hdl)
{
handle = hdl;
}
} // namespace core
} // namespace vkb
+41
View File
@@ -0,0 +1,41 @@
/* Copyright (c) 2019-2023, Arm Limited and Contributors
*
* 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.
*/
#include "debug_info.h"
namespace vkb
{
const std::vector<std::unique_ptr<field::Base>> &DebugInfo::get_fields() const
{
return fields;
}
float DebugInfo::get_longest_label() const
{
float column_width = 0.0f;
for (auto &field : fields)
{
const std::string &label = field->label;
if (label.size() > column_width)
{
column_width = static_cast<float>(label.size());
}
}
return column_width;
}
} // namespace vkb

Some files were not shown because too many files have changed in this diff Show More