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
@@ -0,0 +1,40 @@
# Copyright (c) 2019-2025, Sascha Willems
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 the "License";
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH)
get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME)
add_sample_with_tags(
ID ${FOLDER_NAME}
CATEGORY ${CATEGORY_NAME}
AUTHOR "Sascha Willems"
NAME "Ray tracing basic"
DESCRIPTION "Basic example for hardware accelerated ray tracing"
SHADER_FILES_GLSL
"ray_tracing_basic/glsl/raygen.rgen"
"ray_tracing_basic/glsl/miss.rmiss"
"ray_tracing_basic/glsl/closesthit.rchit"
GLSLC_ADDITIONAL_ARGUMENTS
"--target-spv=spv1.4"
SHADER_FILES_HLSL
"ray_tracing_basic/hlsl/raygen.rgen.hlsl"
"ray_tracing_basic/hlsl/miss.rmiss.hlsl"
"ray_tracing_basic/hlsl/closesthit.rchit.hlsl"
DXC_ADDITIONAL_ARGUMENTS
"-fspv-extension=SPV_KHR_ray_query"
)
@@ -0,0 +1,31 @@
////
- Copyright (c) 2020-2023, The Khronos Group
-
- 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.
-
////
= Basic hardware accelerated ray tracing
ifdef::site-gen-antora[]
TIP: The source for this sample can be found in the https://github.com/KhronosGroup/Vulkan-Samples/tree/main/samples/extensions/ray_tracing_basic[Khronos Vulkan samples github repository].
endif::[]
*Extensions*: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VK_KHR_ray_tracing_pipeline[`VK_KHR_ray_tracing_pipeline`], https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VK_KHR_acceleration_structure[`VK_KHR_acceleration_structure`]
Render a basic scene using the official cross-vendor ray tracing extension.
Shows how to setup all data structures required for ray tracing, including the bottom and top level acceleration structures for the geometry, the shader binding table and the ray tracing pipelines with shader groups for ray generation, ray hits, and ray misses.
After dispatching the rays, the final result is copied to the swapchain image.
@@ -0,0 +1,896 @@
/* Copyright (c) 2019-2025, Sascha Willems
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Basic example for hardware accelerated ray tracing using VK_KHR_ray_tracing_pipeline and VK_KHR_acceleration_structure
*/
#include "ray_tracing_basic.h"
RaytracingBasic::RaytracingBasic()
{
title = "Hardware accelerated ray tracing";
// SPIRV 1.4 requires Vulkan 1.1
set_api_version(VK_API_VERSION_1_1);
// Ray tracing related extensions required by this sample
add_device_extension(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME);
add_device_extension(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME);
// Required by VK_KHR_acceleration_structure
add_device_extension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
add_device_extension(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME);
add_device_extension(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
// Required for VK_KHR_ray_tracing_pipeline
add_device_extension(VK_KHR_SPIRV_1_4_EXTENSION_NAME);
// Required by VK_KHR_spirv_1_4
add_device_extension(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
}
RaytracingBasic::~RaytracingBasic()
{
if (has_device())
{
vkDestroyPipeline(get_device().get_handle(), pipeline, nullptr);
vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr);
vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layout, nullptr);
vkDestroyImageView(get_device().get_handle(), storage_image.view, nullptr);
vkDestroyImage(get_device().get_handle(), storage_image.image, nullptr);
vkFreeMemory(get_device().get_handle(), storage_image.memory, nullptr);
delete_acceleration_structure(top_level_acceleration_structure);
delete_acceleration_structure(bottom_level_acceleration_structure);
vertex_buffer.reset();
index_buffer.reset();
ubo.reset();
}
}
void RaytracingBasic::request_gpu_features(vkb::PhysicalDevice &gpu)
{
// Enable extension features required by this sample
// These are passed to device creation via a pNext structure chain
REQUEST_REQUIRED_FEATURE(gpu,
VkPhysicalDeviceBufferDeviceAddressFeatures,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES,
bufferDeviceAddress);
REQUEST_REQUIRED_FEATURE(gpu,
VkPhysicalDeviceRayTracingPipelineFeaturesKHR,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR,
rayTracingPipeline);
REQUEST_REQUIRED_FEATURE(gpu,
VkPhysicalDeviceAccelerationStructureFeaturesKHR,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR,
accelerationStructure);
}
/*
Set up a storage image that the ray generation shader will be writing to
*/
void RaytracingBasic::create_storage_image()
{
storage_image.width = width;
storage_image.height = height;
VkImageCreateInfo image = vkb::initializers::image_create_info();
image.imageType = VK_IMAGE_TYPE_2D;
image.format = VK_FORMAT_B8G8R8A8_UNORM;
image.extent.width = storage_image.width;
image.extent.height = storage_image.height;
image.extent.depth = 1;
image.mipLevels = 1;
image.arrayLayers = 1;
image.samples = VK_SAMPLE_COUNT_1_BIT;
image.tiling = VK_IMAGE_TILING_OPTIMAL;
image.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_STORAGE_BIT;
image.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VK_CHECK(vkCreateImage(get_device().get_handle(), &image, nullptr, &storage_image.image));
VkMemoryRequirements memory_requirements;
vkGetImageMemoryRequirements(get_device().get_handle(), storage_image.image, &memory_requirements);
VkMemoryAllocateInfo memory_allocate_info = vkb::initializers::memory_allocate_info();
memory_allocate_info.allocationSize = memory_requirements.size;
memory_allocate_info.memoryTypeIndex = get_device().get_gpu().get_memory_type(memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VK_CHECK(vkAllocateMemory(get_device().get_handle(), &memory_allocate_info, nullptr, &storage_image.memory));
VK_CHECK(vkBindImageMemory(get_device().get_handle(), storage_image.image, storage_image.memory, 0));
VkImageViewCreateInfo color_image_view = vkb::initializers::image_view_create_info();
color_image_view.viewType = VK_IMAGE_VIEW_TYPE_2D;
color_image_view.format = VK_FORMAT_B8G8R8A8_UNORM;
color_image_view.subresourceRange = {};
color_image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
color_image_view.subresourceRange.baseMipLevel = 0;
color_image_view.subresourceRange.levelCount = 1;
color_image_view.subresourceRange.baseArrayLayer = 0;
color_image_view.subresourceRange.layerCount = 1;
color_image_view.image = storage_image.image;
VK_CHECK(vkCreateImageView(get_device().get_handle(), &color_image_view, nullptr, &storage_image.view));
VkCommandBuffer command_buffer = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
vkb::image_layout_transition(command_buffer,
storage_image.image,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
{},
{},
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_GENERAL,
{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1});
get_device().flush_command_buffer(command_buffer, queue);
}
/*
Gets the device address from a buffer that's needed in many places during the ray tracing setup
*/
uint64_t RaytracingBasic::get_buffer_device_address(VkBuffer buffer)
{
VkBufferDeviceAddressInfoKHR buffer_device_address_info{};
buffer_device_address_info.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
buffer_device_address_info.buffer = buffer;
return vkGetBufferDeviceAddressKHR(get_device().get_handle(), &buffer_device_address_info);
}
/*
Create buffer and allocate memory for a temporary scratch buffer
*/
ScratchBuffer RaytracingBasic::create_scratch_buffer(VkDeviceSize size)
{
ScratchBuffer scratch_buffer{};
VkBufferCreateInfo buffer_create_info = {};
buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buffer_create_info.size = size;
buffer_create_info.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
VK_CHECK(vkCreateBuffer(get_device().get_handle(), &buffer_create_info, nullptr, &scratch_buffer.handle));
VkMemoryRequirements memory_requirements = {};
vkGetBufferMemoryRequirements(get_device().get_handle(), scratch_buffer.handle, &memory_requirements);
VkMemoryAllocateFlagsInfo memory_allocate_flags_info = {};
memory_allocate_flags_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO;
memory_allocate_flags_info.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR;
VkMemoryAllocateInfo memory_allocate_info = {};
memory_allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memory_allocate_info.pNext = &memory_allocate_flags_info;
memory_allocate_info.allocationSize = memory_requirements.size;
memory_allocate_info.memoryTypeIndex = get_device().get_gpu().get_memory_type(memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VK_CHECK(vkAllocateMemory(get_device().get_handle(), &memory_allocate_info, nullptr, &scratch_buffer.memory));
VK_CHECK(vkBindBufferMemory(get_device().get_handle(), scratch_buffer.handle, scratch_buffer.memory, 0));
VkBufferDeviceAddressInfoKHR buffer_device_address_info{};
buffer_device_address_info.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
buffer_device_address_info.buffer = scratch_buffer.handle;
scratch_buffer.device_address = vkGetBufferDeviceAddressKHR(get_device().get_handle(), &buffer_device_address_info);
return scratch_buffer;
}
void RaytracingBasic::delete_scratch_buffer(ScratchBuffer &scratch_buffer)
{
if (scratch_buffer.memory != VK_NULL_HANDLE)
{
vkFreeMemory(get_device().get_handle(), scratch_buffer.memory, nullptr);
}
if (scratch_buffer.handle != VK_NULL_HANDLE)
{
vkDestroyBuffer(get_device().get_handle(), scratch_buffer.handle, nullptr);
}
}
/*
Create the bottom level acceleration structure that contains the scene's geometry (triangles)
*/
void RaytracingBasic::create_bottom_level_acceleration_structure()
{
// Setup vertices and indices for a single triangle
struct Vertex
{
float pos[3];
};
std::vector<Vertex> vertices = {
{{1.0f, 1.0f, 0.0f}},
{{-1.0f, 1.0f, 0.0f}},
{{0.0f, -1.0f, 0.0f}}};
std::vector<uint32_t> indices = {0, 1, 2};
auto vertex_buffer_size = vertices.size() * sizeof(Vertex);
auto index_buffer_size = indices.size() * sizeof(uint32_t);
// Create buffers for the bottom level geometry
// For the sake of simplicity we won't stage the vertex data to the GPU memory
// Note that the buffer usage flags for buffers consumed by the bottom level acceleration structure require special flags
const VkBufferUsageFlags buffer_usage_flags = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
vertex_buffer = std::make_unique<vkb::core::BufferC>(get_device(), vertex_buffer_size, buffer_usage_flags, VMA_MEMORY_USAGE_CPU_TO_GPU);
vertex_buffer->update(vertices.data(), vertex_buffer_size);
index_buffer = std::make_unique<vkb::core::BufferC>(get_device(), index_buffer_size, buffer_usage_flags, VMA_MEMORY_USAGE_CPU_TO_GPU);
index_buffer->update(indices.data(), index_buffer_size);
// Setup a single transformation matrix that can be used to transform the whole geometry for a single bottom level acceleration structure
VkTransformMatrixKHR transform_matrix = {
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f};
std::unique_ptr<vkb::core::BufferC> transform_matrix_buffer = std::make_unique<vkb::core::BufferC>(get_device(), sizeof(transform_matrix), buffer_usage_flags, VMA_MEMORY_USAGE_CPU_TO_GPU);
transform_matrix_buffer->update(&transform_matrix, sizeof(transform_matrix));
VkDeviceOrHostAddressConstKHR vertex_data_device_address{};
VkDeviceOrHostAddressConstKHR index_data_device_address{};
VkDeviceOrHostAddressConstKHR transform_matrix_device_address{};
vertex_data_device_address.deviceAddress = get_buffer_device_address(vertex_buffer->get_handle());
index_data_device_address.deviceAddress = get_buffer_device_address(index_buffer->get_handle());
transform_matrix_device_address.deviceAddress = get_buffer_device_address(transform_matrix_buffer->get_handle());
// The bottom level acceleration structure contains one set of triangles as the input geometry
VkAccelerationStructureGeometryKHR acceleration_structure_geometry{};
acceleration_structure_geometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
acceleration_structure_geometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR;
acceleration_structure_geometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR;
acceleration_structure_geometry.geometry.triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR;
acceleration_structure_geometry.geometry.triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT;
acceleration_structure_geometry.geometry.triangles.vertexData = vertex_data_device_address;
acceleration_structure_geometry.geometry.triangles.maxVertex = 3;
acceleration_structure_geometry.geometry.triangles.vertexStride = sizeof(Vertex);
acceleration_structure_geometry.geometry.triangles.indexType = VK_INDEX_TYPE_UINT32;
acceleration_structure_geometry.geometry.triangles.indexData = index_data_device_address;
acceleration_structure_geometry.geometry.triangles.transformData = transform_matrix_device_address;
// Get the size requirements for buffers involved in the acceleration structure build process
VkAccelerationStructureBuildGeometryInfoKHR acceleration_structure_build_geometry_info{};
acceleration_structure_build_geometry_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
acceleration_structure_build_geometry_info.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
acceleration_structure_build_geometry_info.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
acceleration_structure_build_geometry_info.geometryCount = 1;
acceleration_structure_build_geometry_info.pGeometries = &acceleration_structure_geometry;
const uint32_t primitive_count = 1;
VkAccelerationStructureBuildSizesInfoKHR acceleration_structure_build_sizes_info{};
acceleration_structure_build_sizes_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR;
vkGetAccelerationStructureBuildSizesKHR(
get_device().get_handle(),
VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
&acceleration_structure_build_geometry_info,
&primitive_count,
&acceleration_structure_build_sizes_info);
// Create a buffer to hold the acceleration structure
bottom_level_acceleration_structure.buffer = std::make_unique<vkb::core::BufferC>(
get_device(),
acceleration_structure_build_sizes_info.accelerationStructureSize,
VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
VMA_MEMORY_USAGE_GPU_ONLY);
// Create the acceleration structure
VkAccelerationStructureCreateInfoKHR acceleration_structure_create_info{};
acceleration_structure_create_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR;
acceleration_structure_create_info.buffer = bottom_level_acceleration_structure.buffer->get_handle();
acceleration_structure_create_info.size = acceleration_structure_build_sizes_info.accelerationStructureSize;
acceleration_structure_create_info.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
vkCreateAccelerationStructureKHR(get_device().get_handle(), &acceleration_structure_create_info, nullptr, &bottom_level_acceleration_structure.handle);
// The actual build process starts here
// Create a scratch buffer as a temporary storage for the acceleration structure build
ScratchBuffer scratch_buffer = create_scratch_buffer(acceleration_structure_build_sizes_info.buildScratchSize);
VkAccelerationStructureBuildGeometryInfoKHR acceleration_build_geometry_info{};
acceleration_build_geometry_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
acceleration_build_geometry_info.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
acceleration_build_geometry_info.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
acceleration_build_geometry_info.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
acceleration_build_geometry_info.dstAccelerationStructure = bottom_level_acceleration_structure.handle;
acceleration_build_geometry_info.geometryCount = 1;
acceleration_build_geometry_info.pGeometries = &acceleration_structure_geometry;
acceleration_build_geometry_info.scratchData.deviceAddress = scratch_buffer.device_address;
VkAccelerationStructureBuildRangeInfoKHR acceleration_structure_build_range_info;
acceleration_structure_build_range_info.primitiveCount = 1;
acceleration_structure_build_range_info.primitiveOffset = 0;
acceleration_structure_build_range_info.firstVertex = 0;
acceleration_structure_build_range_info.transformOffset = 0;
std::vector<VkAccelerationStructureBuildRangeInfoKHR *> acceleration_build_structure_range_infos = {&acceleration_structure_build_range_info};
// Build the acceleration structure on the device via a one-time command buffer submission
// Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds
VkCommandBuffer command_buffer = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
vkCmdBuildAccelerationStructuresKHR(
command_buffer,
1,
&acceleration_build_geometry_info,
acceleration_build_structure_range_infos.data());
get_device().flush_command_buffer(command_buffer, queue);
delete_scratch_buffer(scratch_buffer);
// Get the bottom acceleration structure's handle, which will be used during the top level acceleration build
VkAccelerationStructureDeviceAddressInfoKHR acceleration_device_address_info{};
acceleration_device_address_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR;
acceleration_device_address_info.accelerationStructure = bottom_level_acceleration_structure.handle;
bottom_level_acceleration_structure.device_address =
vkGetAccelerationStructureDeviceAddressKHR(get_device().get_handle(), &acceleration_device_address_info);
}
/*
Create the top level acceleration structure containing geometry instances of the bottom level acceleration structure(s)
*/
void RaytracingBasic::create_top_level_acceleration_structure()
{
VkTransformMatrixKHR transform_matrix = {
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f};
VkAccelerationStructureInstanceKHR acceleration_structure_instance{};
acceleration_structure_instance.transform = transform_matrix;
acceleration_structure_instance.instanceCustomIndex = 0;
acceleration_structure_instance.mask = 0xFF;
acceleration_structure_instance.instanceShaderBindingTableRecordOffset = 0;
acceleration_structure_instance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
acceleration_structure_instance.accelerationStructureReference = bottom_level_acceleration_structure.device_address;
std::unique_ptr<vkb::core::BufferC> instances_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
sizeof(VkAccelerationStructureInstanceKHR),
VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
VMA_MEMORY_USAGE_CPU_TO_GPU);
instances_buffer->update(&acceleration_structure_instance, sizeof(VkAccelerationStructureInstanceKHR));
VkDeviceOrHostAddressConstKHR instance_data_device_address{};
instance_data_device_address.deviceAddress = get_buffer_device_address(instances_buffer->get_handle());
// The top level acceleration structure contains (bottom level) instance as the input geometry
VkAccelerationStructureGeometryKHR acceleration_structure_geometry{};
acceleration_structure_geometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
acceleration_structure_geometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR;
acceleration_structure_geometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR;
acceleration_structure_geometry.geometry.instances.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR;
acceleration_structure_geometry.geometry.instances.arrayOfPointers = VK_FALSE;
acceleration_structure_geometry.geometry.instances.data = instance_data_device_address;
// Get the size requirements for buffers involved in the acceleration structure build process
VkAccelerationStructureBuildGeometryInfoKHR acceleration_structure_build_geometry_info{};
acceleration_structure_build_geometry_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
acceleration_structure_build_geometry_info.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
acceleration_structure_build_geometry_info.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
acceleration_structure_build_geometry_info.geometryCount = 1;
acceleration_structure_build_geometry_info.pGeometries = &acceleration_structure_geometry;
const uint32_t primitive_count = 1;
VkAccelerationStructureBuildSizesInfoKHR acceleration_structure_build_sizes_info{};
acceleration_structure_build_sizes_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR;
vkGetAccelerationStructureBuildSizesKHR(
get_device().get_handle(), VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
&acceleration_structure_build_geometry_info,
&primitive_count,
&acceleration_structure_build_sizes_info);
// Create a buffer to hold the acceleration structure
top_level_acceleration_structure.buffer = std::make_unique<vkb::core::BufferC>(
get_device(),
acceleration_structure_build_sizes_info.accelerationStructureSize,
VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT,
VMA_MEMORY_USAGE_GPU_ONLY);
// Create the acceleration structure
VkAccelerationStructureCreateInfoKHR acceleration_structure_create_info{};
acceleration_structure_create_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR;
acceleration_structure_create_info.buffer = top_level_acceleration_structure.buffer->get_handle();
acceleration_structure_create_info.size = acceleration_structure_build_sizes_info.accelerationStructureSize;
acceleration_structure_create_info.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
vkCreateAccelerationStructureKHR(get_device().get_handle(), &acceleration_structure_create_info, nullptr, &top_level_acceleration_structure.handle);
// The actual build process starts here
// Create a scratch buffer as a temporary storage for the acceleration structure build
ScratchBuffer scratch_buffer = create_scratch_buffer(acceleration_structure_build_sizes_info.buildScratchSize);
VkAccelerationStructureBuildGeometryInfoKHR acceleration_build_geometry_info{};
acceleration_build_geometry_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
acceleration_build_geometry_info.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR;
acceleration_build_geometry_info.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
acceleration_build_geometry_info.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
acceleration_build_geometry_info.dstAccelerationStructure = top_level_acceleration_structure.handle;
acceleration_build_geometry_info.geometryCount = 1;
acceleration_build_geometry_info.pGeometries = &acceleration_structure_geometry;
acceleration_build_geometry_info.scratchData.deviceAddress = scratch_buffer.device_address;
VkAccelerationStructureBuildRangeInfoKHR acceleration_structure_build_range_info;
acceleration_structure_build_range_info.primitiveCount = 1;
acceleration_structure_build_range_info.primitiveOffset = 0;
acceleration_structure_build_range_info.firstVertex = 0;
acceleration_structure_build_range_info.transformOffset = 0;
std::vector<VkAccelerationStructureBuildRangeInfoKHR *> acceleration_build_structure_range_infos = {&acceleration_structure_build_range_info};
// Build the acceleration structure on the device via a one-time command buffer submission
// Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds
VkCommandBuffer command_buffer = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true);
vkCmdBuildAccelerationStructuresKHR(
command_buffer,
1,
&acceleration_build_geometry_info,
acceleration_build_structure_range_infos.data());
get_device().flush_command_buffer(command_buffer, queue);
delete_scratch_buffer(scratch_buffer);
// Get the top acceleration structure's handle, which will be used to setup it's descriptor
VkAccelerationStructureDeviceAddressInfoKHR acceleration_device_address_info{};
acceleration_device_address_info.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR;
acceleration_device_address_info.accelerationStructure = top_level_acceleration_structure.handle;
top_level_acceleration_structure.device_address =
vkGetAccelerationStructureDeviceAddressKHR(get_device().get_handle(), &acceleration_device_address_info);
}
inline uint32_t aligned_size(uint32_t value, uint32_t alignment)
{
return (value + alignment - 1) & ~(alignment - 1);
}
/*
Create scene geometry and ray tracing acceleration structures
*/
void RaytracingBasic::create_scene()
{
create_bottom_level_acceleration_structure();
create_top_level_acceleration_structure();
}
/*
Create the Shader Binding Tables that connects the ray tracing pipelines' programs and the top-level acceleration structure
SBT Layout used in this sample:
/-----------\
| raygen |
|-----------|
| miss |
|-----------|
| hit |
\-----------/
*/
void RaytracingBasic::create_shader_binding_tables()
{
const uint32_t handle_size = ray_tracing_pipeline_properties.shaderGroupHandleSize;
const uint32_t handle_size_aligned = aligned_size(ray_tracing_pipeline_properties.shaderGroupHandleSize, ray_tracing_pipeline_properties.shaderGroupHandleAlignment);
const uint32_t handle_alignment = ray_tracing_pipeline_properties.shaderGroupHandleAlignment;
const uint32_t group_count = static_cast<uint32_t>(shader_groups.size());
const uint32_t sbt_size = group_count * handle_size_aligned;
const VkBufferUsageFlags sbt_buffer_usage_flags = VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
const VmaMemoryUsage sbt_memory_usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
// Raygen
// Create binding table buffers for each shader type
raygen_shader_binding_table = std::make_unique<vkb::core::BufferC>(get_device(), handle_size, sbt_buffer_usage_flags, sbt_memory_usage, 0);
miss_shader_binding_table = std::make_unique<vkb::core::BufferC>(get_device(), handle_size, sbt_buffer_usage_flags, sbt_memory_usage, 0);
hit_shader_binding_table = std::make_unique<vkb::core::BufferC>(get_device(), handle_size, sbt_buffer_usage_flags, sbt_memory_usage, 0);
// Copy the pipeline's shader handles into a host buffer
std::vector<uint8_t> shader_handle_storage(sbt_size);
VK_CHECK(vkGetRayTracingShaderGroupHandlesKHR(get_device().get_handle(), pipeline, 0, group_count, sbt_size, shader_handle_storage.data()));
// Copy the shader handles from the host buffer to the binding tables
uint8_t *data = static_cast<uint8_t *>(raygen_shader_binding_table->map());
memcpy(data, shader_handle_storage.data(), handle_size);
data = static_cast<uint8_t *>(miss_shader_binding_table->map());
memcpy(data, shader_handle_storage.data() + handle_size_aligned, handle_size);
data = static_cast<uint8_t *>(hit_shader_binding_table->map());
memcpy(data, shader_handle_storage.data() + handle_size_aligned * 2, handle_size);
raygen_shader_binding_table->unmap();
miss_shader_binding_table->unmap();
hit_shader_binding_table->unmap();
}
/*
Create the descriptor sets used for the ray tracing dispatch
*/
void RaytracingBasic::create_descriptor_sets()
{
std::vector<VkDescriptorPoolSize> pool_sizes = {
{VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1},
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1},
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1}};
VkDescriptorPoolCreateInfo descriptor_pool_create_info = vkb::initializers::descriptor_pool_create_info(pool_sizes, 1);
VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool));
VkDescriptorSetAllocateInfo descriptor_set_allocate_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, &descriptor_set_layout, 1);
VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &descriptor_set_allocate_info, &descriptor_set));
// Setup the descriptor for binding our top level acceleration structure to the ray tracing shaders
VkWriteDescriptorSetAccelerationStructureKHR descriptor_acceleration_structure_info{};
descriptor_acceleration_structure_info.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR;
descriptor_acceleration_structure_info.accelerationStructureCount = 1;
descriptor_acceleration_structure_info.pAccelerationStructures = &top_level_acceleration_structure.handle;
VkWriteDescriptorSet acceleration_structure_write{};
acceleration_structure_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
acceleration_structure_write.dstSet = descriptor_set;
acceleration_structure_write.dstBinding = 0;
acceleration_structure_write.descriptorCount = 1;
acceleration_structure_write.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR;
// The acceleration structure descriptor has to be chained via pNext
acceleration_structure_write.pNext = &descriptor_acceleration_structure_info;
VkDescriptorImageInfo image_descriptor{};
image_descriptor.imageView = storage_image.view;
image_descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
VkDescriptorBufferInfo buffer_descriptor = create_descriptor(*ubo);
VkWriteDescriptorSet result_image_write = vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &image_descriptor);
VkWriteDescriptorSet uniform_buffer_write = vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &buffer_descriptor);
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
acceleration_structure_write,
result_image_write,
uniform_buffer_write};
vkUpdateDescriptorSets(get_device().get_handle(), static_cast<uint32_t>(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, VK_NULL_HANDLE);
}
/*
Create our ray tracing pipeline
*/
void RaytracingBasic::create_ray_tracing_pipeline()
{
// Slot for binding top level acceleration structures to the ray generation shader
VkDescriptorSetLayoutBinding acceleration_structure_layout_binding{};
acceleration_structure_layout_binding.binding = 0;
acceleration_structure_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR;
acceleration_structure_layout_binding.descriptorCount = 1;
acceleration_structure_layout_binding.stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR;
VkDescriptorSetLayoutBinding result_image_layout_binding{};
result_image_layout_binding.binding = 1;
result_image_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
result_image_layout_binding.descriptorCount = 1;
result_image_layout_binding.stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR;
VkDescriptorSetLayoutBinding uniform_buffer_binding{};
uniform_buffer_binding.binding = 2;
uniform_buffer_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
uniform_buffer_binding.descriptorCount = 1;
uniform_buffer_binding.stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR;
std::vector<VkDescriptorSetLayoutBinding> bindings = {
acceleration_structure_layout_binding,
result_image_layout_binding,
uniform_buffer_binding};
VkDescriptorSetLayoutCreateInfo layout_info{};
layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layout_info.bindingCount = static_cast<uint32_t>(bindings.size());
layout_info.pBindings = bindings.data();
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &layout_info, nullptr, &descriptor_set_layout));
VkPipelineLayoutCreateInfo pipeline_layout_create_info{};
pipeline_layout_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipeline_layout_create_info.setLayoutCount = 1;
pipeline_layout_create_info.pSetLayouts = &descriptor_set_layout;
VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layout));
/*
Setup ray tracing shader groups
Each shader group points at the corresponding shader in the pipeline
*/
std::vector<VkPipelineShaderStageCreateInfo> shader_stages;
// Ray generation group
{
shader_stages.push_back(load_shader("ray_tracing_basic", "raygen.rgen.spv", VK_SHADER_STAGE_RAYGEN_BIT_KHR));
VkRayTracingShaderGroupCreateInfoKHR raygen_group_ci{};
raygen_group_ci.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
raygen_group_ci.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
raygen_group_ci.generalShader = static_cast<uint32_t>(shader_stages.size()) - 1;
raygen_group_ci.closestHitShader = VK_SHADER_UNUSED_KHR;
raygen_group_ci.anyHitShader = VK_SHADER_UNUSED_KHR;
raygen_group_ci.intersectionShader = VK_SHADER_UNUSED_KHR;
shader_groups.push_back(raygen_group_ci);
}
// Ray miss group
{
shader_stages.push_back(load_shader("ray_tracing_basic", "miss.rmiss.spv", VK_SHADER_STAGE_MISS_BIT_KHR));
VkRayTracingShaderGroupCreateInfoKHR miss_group_ci{};
miss_group_ci.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
miss_group_ci.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
miss_group_ci.generalShader = static_cast<uint32_t>(shader_stages.size()) - 1;
miss_group_ci.closestHitShader = VK_SHADER_UNUSED_KHR;
miss_group_ci.anyHitShader = VK_SHADER_UNUSED_KHR;
miss_group_ci.intersectionShader = VK_SHADER_UNUSED_KHR;
shader_groups.push_back(miss_group_ci);
}
// Ray closest hit group
{
shader_stages.push_back(load_shader("ray_tracing_basic", "closesthit.rchit.spv", VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR));
VkRayTracingShaderGroupCreateInfoKHR closes_hit_group_ci{};
closes_hit_group_ci.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
closes_hit_group_ci.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR;
closes_hit_group_ci.generalShader = VK_SHADER_UNUSED_KHR;
closes_hit_group_ci.closestHitShader = static_cast<uint32_t>(shader_stages.size()) - 1;
closes_hit_group_ci.anyHitShader = VK_SHADER_UNUSED_KHR;
closes_hit_group_ci.intersectionShader = VK_SHADER_UNUSED_KHR;
shader_groups.push_back(closes_hit_group_ci);
}
/*
Create the ray tracing pipeline
*/
VkRayTracingPipelineCreateInfoKHR raytracing_pipeline_create_info{};
raytracing_pipeline_create_info.sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR;
raytracing_pipeline_create_info.stageCount = static_cast<uint32_t>(shader_stages.size());
raytracing_pipeline_create_info.pStages = shader_stages.data();
raytracing_pipeline_create_info.groupCount = static_cast<uint32_t>(shader_groups.size());
raytracing_pipeline_create_info.pGroups = shader_groups.data();
raytracing_pipeline_create_info.maxPipelineRayRecursionDepth = 1;
raytracing_pipeline_create_info.layout = pipeline_layout;
VK_CHECK(vkCreateRayTracingPipelinesKHR(get_device().get_handle(), VK_NULL_HANDLE, VK_NULL_HANDLE, 1, &raytracing_pipeline_create_info, nullptr, &pipeline));
}
/*
Deletes all resources acquired by an acceleration structure
*/
void RaytracingBasic::delete_acceleration_structure(AccelerationStructure &acceleration_structure)
{
if (acceleration_structure.buffer)
{
acceleration_structure.buffer.reset();
}
if (acceleration_structure.handle)
{
vkDestroyAccelerationStructureKHR(get_device().get_handle(), acceleration_structure.handle, nullptr);
}
}
/*
Create the uniform buffer used to pass matrices to the ray tracing ray generation shader
*/
void RaytracingBasic::create_uniform_buffer()
{
ubo = std::make_unique<vkb::core::BufferC>(get_device(),
sizeof(uniform_data),
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VMA_MEMORY_USAGE_CPU_TO_GPU);
ubo->convert_and_update(uniform_data);
update_uniform_buffers();
}
/*
Command buffer generation
*/
void RaytracingBasic::build_command_buffers()
{
if (width != storage_image.width || height != storage_image.height)
{
// If the view port size has changed, we need to recreate the storage image
vkDestroyImageView(get_device().get_handle(), storage_image.view, nullptr);
vkDestroyImage(get_device().get_handle(), storage_image.image, nullptr);
vkFreeMemory(get_device().get_handle(), storage_image.memory, nullptr);
create_storage_image();
// The descriptor also needs to be updated to reference the new image
VkDescriptorImageInfo image_descriptor{};
image_descriptor.imageView = storage_image.view;
image_descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
VkWriteDescriptorSet result_image_write = vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &image_descriptor);
vkUpdateDescriptorSets(get_device().get_handle(), 1, &result_image_write, 0, VK_NULL_HANDLE);
}
VkCommandBufferBeginInfo command_buffer_begin_info = vkb::initializers::command_buffer_begin_info();
VkImageSubresourceRange subresource_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
{
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
/*
Setup the strided device address regions pointing at the shader identifiers in the shader binding table
*/
const uint32_t handle_size_aligned = aligned_size(ray_tracing_pipeline_properties.shaderGroupHandleSize, ray_tracing_pipeline_properties.shaderGroupHandleAlignment);
VkStridedDeviceAddressRegionKHR raygen_shader_sbt_entry{};
raygen_shader_sbt_entry.deviceAddress = get_buffer_device_address(raygen_shader_binding_table->get_handle());
raygen_shader_sbt_entry.stride = handle_size_aligned;
raygen_shader_sbt_entry.size = handle_size_aligned;
VkStridedDeviceAddressRegionKHR miss_shader_sbt_entry{};
miss_shader_sbt_entry.deviceAddress = get_buffer_device_address(miss_shader_binding_table->get_handle());
miss_shader_sbt_entry.stride = handle_size_aligned;
miss_shader_sbt_entry.size = handle_size_aligned;
VkStridedDeviceAddressRegionKHR hit_shader_sbt_entry{};
hit_shader_sbt_entry.deviceAddress = get_buffer_device_address(hit_shader_binding_table->get_handle());
hit_shader_sbt_entry.stride = handle_size_aligned;
hit_shader_sbt_entry.size = handle_size_aligned;
VkStridedDeviceAddressRegionKHR callable_shader_sbt_entry{};
/*
Dispatch the ray tracing commands
*/
vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline);
vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline_layout, 0, 1, &descriptor_set, 0, 0);
vkCmdTraceRaysKHR(
draw_cmd_buffers[i],
&raygen_shader_sbt_entry,
&miss_shader_sbt_entry,
&hit_shader_sbt_entry,
&callable_shader_sbt_entry,
width,
height,
1);
/*
Copy ray tracing output to swap chain image
*/
// Prepare current swap chain image as transfer destination
vkb::image_layout_transition(draw_cmd_buffers[i],
get_render_context().get_swapchain().get_images()[i],
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
// Prepare ray tracing output image as transfer source
vkb::image_layout_transition(draw_cmd_buffers[i],
storage_image.image,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
{},
VK_ACCESS_TRANSFER_READ_BIT,
VK_IMAGE_LAYOUT_GENERAL,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
subresource_range);
VkImageCopy copy_region{};
copy_region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
copy_region.srcOffset = {0, 0, 0};
copy_region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
copy_region.dstOffset = {0, 0, 0};
copy_region.extent = {width, height, 1};
vkCmdCopyImage(draw_cmd_buffers[i], storage_image.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
get_render_context().get_swapchain().get_images()[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy_region);
// Transition swap chain image back for presentation
vkb::image_layout_transition(draw_cmd_buffers[i],
get_render_context().get_swapchain().get_images()[i],
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
// Transition ray tracing output image back to general layout
vkb::image_layout_transition(draw_cmd_buffers[i],
storage_image.image,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_ACCESS_TRANSFER_READ_BIT,
{},
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_IMAGE_LAYOUT_GENERAL,
subresource_range);
/*
Start a new render pass to draw the UI overlay on top of the ray traced image
*/
VkClearValue clear_values[2];
clear_values[0].color = {{0.0f, 0.0f, 0.033f, 0.0f}};
clear_values[1].depthStencil = {0.0f, 0};
VkRenderPassBeginInfo render_pass_begin_info = vkb::initializers::render_pass_begin_info();
render_pass_begin_info.renderPass = render_pass;
render_pass_begin_info.framebuffer = framebuffers[i];
render_pass_begin_info.renderArea.extent.width = width;
render_pass_begin_info.renderArea.extent.height = height;
render_pass_begin_info.clearValueCount = 2;
render_pass_begin_info.pClearValues = clear_values;
vkCmdBeginRenderPass(draw_cmd_buffers[i], &render_pass_begin_info, VK_SUBPASS_CONTENTS_INLINE);
draw_ui(draw_cmd_buffers[i]);
vkCmdEndRenderPass(draw_cmd_buffers[i]);
VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i]));
}
}
void RaytracingBasic::update_uniform_buffers()
{
uniform_data.proj_inverse = glm::inverse(camera.matrices.perspective);
uniform_data.view_inverse = glm::inverse(camera.matrices.view);
ubo->convert_and_update(uniform_data);
}
bool RaytracingBasic::prepare(const vkb::ApplicationOptions &options)
{
if (!ApiVulkanSample::prepare(options))
{
return false;
}
// This sample copies the ray traced output to the swap chain image, so we need to enable the required image usage flags
const std::set<VkImageUsageFlagBits> image_usage_flags = {VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_IMAGE_USAGE_TRANSFER_DST_BIT};
update_swapchain_image_usage_flags(image_usage_flags);
// This sample renders the UI overlay on top of the ray tracing output, so we need to disable color attachment clears
update_render_pass_flags(RenderPassCreateFlags::ColorAttachmentLoad);
// Get the ray tracing pipeline properties, which we'll need later on in the sample
ray_tracing_pipeline_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR;
VkPhysicalDeviceProperties2 device_properties{};
device_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
device_properties.pNext = &ray_tracing_pipeline_properties;
vkGetPhysicalDeviceProperties2(get_device().get_gpu().get_handle(), &device_properties);
// Get the acceleration structure features, which we'll need later on in the sample
acceleration_structure_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR;
VkPhysicalDeviceFeatures2 device_features{};
device_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
device_features.pNext = &acceleration_structure_features;
vkGetPhysicalDeviceFeatures2(get_device().get_gpu().get_handle(), &device_features);
camera.type = vkb::CameraType::LookAt;
camera.set_perspective(60.0f, static_cast<float>(width) / static_cast<float>(height), 0.1f, 512.0f);
camera.set_rotation(glm::vec3(0.0f, 0.0f, 0.0f));
camera.set_translation(glm::vec3(0.0f, 0.0f, -2.5f));
create_storage_image();
create_scene();
create_uniform_buffer();
create_ray_tracing_pipeline();
create_shader_binding_tables();
create_descriptor_sets();
build_command_buffers();
prepared = true;
return true;
}
void RaytracingBasic::draw()
{
ApiVulkanSample::prepare_frame();
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer];
VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE));
ApiVulkanSample::submit_frame();
}
void RaytracingBasic::render(float delta_time)
{
if (!prepared)
{
return;
}
draw();
if (camera.updated)
{
update_uniform_buffers();
}
}
std::unique_ptr<vkb::VulkanSampleC> create_ray_tracing_basic()
{
return std::make_unique<RaytracingBasic>();
}
@@ -0,0 +1,105 @@
/* Copyright (c) 2019-2025, Sascha Willems
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Basic example for hardware accelerated ray tracing using VK_KHR_ray_tracing_pipeline and VK_KHR_acceleration_structure
*/
#pragma once
#include "api_vulkan_sample.h"
// Holds data for a scratch buffer used as a temporary storage during acceleration structure builds
struct ScratchBuffer
{
uint64_t device_address;
VkBuffer handle;
VkDeviceMemory memory;
};
// Wraps all data required for an acceleration structure
struct AccelerationStructure
{
VkAccelerationStructureKHR handle;
uint64_t device_address;
std::unique_ptr<vkb::core::BufferC> buffer;
};
class RaytracingBasic : public ApiVulkanSample
{
public:
VkPhysicalDeviceRayTracingPipelinePropertiesKHR ray_tracing_pipeline_properties{};
VkPhysicalDeviceAccelerationStructureFeaturesKHR acceleration_structure_features{};
AccelerationStructure bottom_level_acceleration_structure;
AccelerationStructure top_level_acceleration_structure;
std::unique_ptr<vkb::core::BufferC> vertex_buffer;
std::unique_ptr<vkb::core::BufferC> index_buffer;
uint32_t index_count;
std::vector<VkRayTracingShaderGroupCreateInfoKHR> shader_groups{};
std::unique_ptr<vkb::core::BufferC> raygen_shader_binding_table;
std::unique_ptr<vkb::core::BufferC> miss_shader_binding_table;
std::unique_ptr<vkb::core::BufferC> hit_shader_binding_table;
struct StorageImage
{
VkDeviceMemory memory;
VkImage image = VK_NULL_HANDLE;
VkImageView view;
VkFormat format;
uint32_t width;
uint32_t height;
} storage_image;
struct UniformData
{
glm::mat4 view_inverse;
glm::mat4 proj_inverse;
} uniform_data;
std::unique_ptr<vkb::core::BufferC> ubo;
VkPipeline pipeline;
VkPipelineLayout pipeline_layout;
VkDescriptorSet descriptor_set;
VkDescriptorSetLayout descriptor_set_layout;
RaytracingBasic();
~RaytracingBasic();
void request_gpu_features(vkb::PhysicalDevice &gpu) override;
uint64_t get_buffer_device_address(VkBuffer buffer);
ScratchBuffer create_scratch_buffer(VkDeviceSize size);
void delete_scratch_buffer(ScratchBuffer &scratch_buffer);
void create_storage_image();
void create_bottom_level_acceleration_structure();
void create_top_level_acceleration_structure();
void delete_acceleration_structure(AccelerationStructure &acceleration_structure);
void create_scene();
void create_shader_binding_tables();
void create_descriptor_sets();
void create_ray_tracing_pipeline();
void create_uniform_buffer();
void build_command_buffers() override;
void update_uniform_buffers();
void draw();
bool prepare(const vkb::ApplicationOptions &options) override;
virtual void render(float delta_time) override;
};
std::unique_ptr<vkb::VulkanSampleC> create_ray_tracing_basic();