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
+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