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) 2024-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(
ID ${FOLDER_NAME}
CATEGORY ${CATEGORY_NAME}
AUTHOR "Sascha Willems"
NAME "Ray tracing position fetch"
DESCRIPTION "Uses the VK_KHR_ray_tracing_position_fetch to fetch vertex positions from an acceleration structure"
SHADER_FILES_GLSL
"ray_tracing_position_fetch/glsl/closesthit.rchit"
"ray_tracing_position_fetch/glsl/miss.rmiss"
"ray_tracing_position_fetch/glsl/raygen.rgen"
GLSLC_ADDITIONAL_ARGUMENTS
"--target-spv=spv1.4"
SHADER_FILES_HLSL
"ray_tracing_position_fetch/hlsl/closesthit.rchit.hlsl"
"ray_tracing_position_fetch/hlsl/miss.rmiss.hlsl"
"ray_tracing_position_fetch/hlsl/raygen.rgen.hlsl"
DXC_ADDITIONAL_ARGUMENTS
"-fspv-extension=SPV_KHR_ray_query -fspv-extension=SPV_KHR_ray_tracing"
)
@@ -0,0 +1,84 @@
////
- Copyright (c) 2023-2024, Sascha Willems
-
- SPDX-License-Identifier: Apache-2.0
-
- Licensed under the Apache License, Version 2.0 the "License";
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-
////
= Ray tracing position fetch
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_position_fetch[Khronos Vulkan samples github repository].
endif::[]
== Background
Accessing per-vertex attributes in a ray tracing shader stage is a common use case, but often requires passing those attributes via some buffer and then doing manual indexing (and unpacking) in the shader. Even if it's something already stored in the acceleration structure like vertex positions.
Using the `VK_KHR_ray_tracing_position_fetch` extension it's now possible to directly access vertex positions from an acceleration structure
image::./images/sample.png[Ray tracing position fetch sample]
_Model "PICA PICA - Robot 01" by SEED.EA, acquired from https://sketchfab.com/3d-models/pica-pica-robot-01-438e3a1589a6411a8e704471930389e1[this url]_
== In the application
Enabling vertex position fetch in the application is straight-forward:
- Enable the `VK_KHR_ray_tracing_position_fetch` device extension
- Enable the `rayTracingPositionFetch` feature in the `VkPhysicalDeviceRayTracingPositionFetchFeaturesKHR` struct
- Add the `VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_DATA_ACCESS_KHR` flag (`VkBuildAccelerationStructureFlagsKHR`) to the build flags of the bottom level acceleration structure to allow access to the positions from shaders
== In the shader
As part of this extension, `SPV_KHR_ray_tracing_position_fetch` adds new built-ins for accessing vertex positions to the any-hit and closest-hit ray tracing shaders.
=== GLSL
[,glsl]
----
// This extension is required for fetching position data in the closest hit shader
#extension GL_EXT_ray_tracing_position_fetch : require
...
// Get the vertex positions of the hit triangle
vec3 pos0 = gl_HitTriangleVertexPositionsEXT[0];
vec3 pos1 = gl_HitTriangleVertexPositionsEXT[1];
vec3 pos2 = gl_HitTriangleVertexPositionsEXT[2];
----
=== HLSL
[,hlsl]
----
// For HLSL, we need to use SPIR-V intrinsics for vertex position fetch
#define HitTriangleVertexPositionsKHR 5335
#define RayTracingPositionFetchKHR 5336
[[vk::ext_extension("SPV_KHR_ray_tracing_position_fetch")]]
[[vk::ext_capability(RayTracingPositionFetchKHR)]]
[[vk::ext_builtin_input(HitTriangleVertexPositionsKHR)]]
const static float3 gl_HitTriangleVertexPositions[3];
...
// Get the vertex positions of the hit triangle
float3 pos0 = gl_HitTriangleVertexPositions[0];
float3 pos1 = gl_HitTriangleVertexPositions[1];
float3 pos2 = gl_HitTriangleVertexPositions[2];
----
== Additional resources
* https://www.khronos.org/blog/introducing-vulkan-ray-tracing-position-fetch-extension[Introducing Vulkan Ray Tracing Position Fetch Extension]
Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

@@ -0,0 +1,618 @@
/* Copyright (c) 2024-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 "ray_tracing_position_fetch.h"
#include "scene_graph/components/mesh.h"
RayTracingPositionFetch::RayTracingPositionFetch()
{
title = "Ray tracing position fetch";
// 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);
// Sample specific extension
add_device_extension(VK_KHR_RAY_TRACING_POSITION_FETCH_EXTENSION_NAME);
}
RayTracingPositionFetch::~RayTracingPositionFetch()
{
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);
vertex_buffer.reset();
index_buffer.reset();
ubo.reset();
}
}
void RayTracingPositionFetch::request_gpu_features(vkb::PhysicalDevice &gpu)
{
// Features required for ray tracing
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);
// Sample sepcific feature
REQUEST_REQUIRED_FEATURE(gpu,
VkPhysicalDeviceRayTracingPositionFetchFeaturesKHR,
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_POSITION_FETCH_FEATURES_KHR,
rayTracingPositionFetch);
}
/*
Set up a storage image that the ray generation shader will be writing to
*/
void RayTracingPositionFetch::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);
}
/*
Create the bottom level acceleration structure that contains the scene's geometry
*/
void RayTracingPositionFetch::create_bottom_level_acceleration_structure()
{
// Setup a single transformation matrix that can be used to transform the whole geometry for a single bottom level acceleration structure
// Note: We flip the Y-Axis to match the glTF coordinate system and also offset the model to center it
VkTransformMatrixKHR transform_matrix = {
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, -1.0f, 0.0f, 2.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), VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU);
transform_matrix_buffer->update(&transform_matrix, sizeof(transform_matrix));
bottom_level_acceleration_structure = std::make_unique<vkb::core::AccelerationStructure>(get_device(), VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR);
// For ray tracing, the vertex and index buffers of the glTF scene need to be used for acceleration structure builds and getting device addresses, so we provide additional flags in this sample
const VkBufferUsageFlags additional_buffer_usage_flags = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
// Add all parts of the glTF scene to the bottom level architecture
vkb::GLTFLoader loader{get_device()};
auto scene = loader.read_scene_from_file("scenes/pica_pica_robot/scene.gltf", -1, additional_buffer_usage_flags);
for (auto &&mesh : scene->get_components<vkb::sg::Mesh>())
{
for (auto &&sub_mesh : mesh->get_submeshes())
{
const auto num_vertices = sub_mesh->vertices_count - 1;
const auto num_triangles = sub_mesh->vertex_indices / 3;
vkb::sg::VertexAttribute attrib;
sub_mesh->get_attribute("position", attrib);
bottom_level_acceleration_structure->add_triangle_geometry(
sub_mesh->vertex_buffers.at("position"),
*sub_mesh->index_buffer,
*transform_matrix_buffer,
num_triangles,
num_vertices,
attrib.stride,
0,
attrib.format,
sub_mesh->index_type,
VK_GEOMETRY_OPAQUE_BIT_KHR);
}
}
// To access vertex positions from a shader, we need to set the VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_DATA_ACCESS_KHR for the bottom level acceleration structure
VkBuildAccelerationStructureFlagsKHR acceleration_build_flags = VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_DATA_ACCESS_KHR | VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR;
bottom_level_acceleration_structure->build(queue, acceleration_build_flags, VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR);
}
/*
Create the top level acceleration structure containing geometry instances of the bottom level acceleration structure(s)
*/
void RayTracingPositionFetch::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->get_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));
top_level_acceleration_structure = std::make_unique<vkb::core::AccelerationStructure>(get_device(), VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR);
top_level_acceleration_structure->add_instance_geometry(instances_buffer, 1);
top_level_acceleration_structure->build(queue);
}
/*
Create scene geometry and ray tracing acceleration structures
*/
void RayTracingPositionFetch::create_scene()
{
create_bottom_level_acceleration_structure();
create_top_level_acceleration_structure();
}
inline uint32_t aligned_size(uint32_t value, uint32_t alignment)
{
return (value + alignment - 1) & ~(alignment - 1);
}
/*
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 RayTracingPositionFetch::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 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 RayTracingPositionFetch::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;
auto as_handle = top_level_acceleration_structure->get_handle();
descriptor_acceleration_structure_info.pAccelerationStructures = &as_handle;
descriptor_acceleration_structure_info.pAccelerationStructures = &as_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 RayTracingPositionFetch::create_ray_tracing_pipeline()
{
std::vector<VkDescriptorSetLayoutBinding> bindings = {
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, VK_SHADER_STAGE_RAYGEN_BIT_KHR, 0, 1),
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_RAYGEN_BIT_KHR, 1, 1),
vkb::initializers::descriptor_set_layout_binding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, 2, 1)};
VkDescriptorSetLayoutCreateInfo layout_info = vkb::initializers::descriptor_set_layout_create_info(bindings.data(), static_cast<uint32_t>(bindings.size()));
VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &layout_info, nullptr, &descriptor_set_layout));
VkPipelineLayoutCreateInfo pipeline_layout_create_info = vkb::initializers::pipeline_layout_create_info(&descriptor_set_layout, 1);
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_position_fetch", "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_position_fetch", "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_position_fetch", "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));
}
/*
Create the uniform buffer used to pass matrices to the ray tracing ray generation shader
*/
void RayTracingPositionFetch::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 RayTracingPositionFetch::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};
/*
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);
const VkStridedDeviceAddressRegionKHR raygen_shader_sbt_entry{
raygen_shader_binding_table->get_device_address(),
handle_size_aligned,
handle_size_aligned};
const VkStridedDeviceAddressRegionKHR miss_shader_sbt_entry{
miss_shader_binding_table->get_device_address(),
handle_size_aligned,
handle_size_aligned};
const VkStridedDeviceAddressRegionKHR hit_shader_sbt_entry{
hit_shader_binding_table->get_device_address(),
handle_size_aligned,
handle_size_aligned};
const VkStridedDeviceAddressRegionKHR callable_shader_sbt_entry{};
for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i)
{
VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info));
/*
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 RayTracingPositionFetch::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 RayTracingPositionFetch::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, 15.0f, 0.0f));
camera.set_translation(glm::vec3(0.0f, 0.0f, -6.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 RayTracingPositionFetch::on_update_ui_overlay(vkb::Drawer &drawer)
{
const std::vector<std::string> display_mode_names{"Geometric normal", "Vertex position"};
drawer.combo_box("Display mode", &uniform_data.display_mode, display_mode_names);
}
void RayTracingPositionFetch::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 RayTracingPositionFetch::render(float delta_time)
{
if (!prepared)
{
return;
}
update_uniform_buffers();
draw();
}
std::unique_ptr<vkb::VulkanSample<vkb::BindingType::C>> create_ray_tracing_position_fetch()
{
return std::make_unique<RayTracingPositionFetch>();
}
@@ -0,0 +1,83 @@
/* Copyright (c) 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 "api_vulkan_sample.h"
#include <core/acceleration_structure.h>
class RayTracingPositionFetch : public ApiVulkanSample
{
public:
VkPhysicalDeviceRayTracingPipelinePropertiesKHR ray_tracing_pipeline_properties{};
VkPhysicalDeviceAccelerationStructureFeaturesKHR acceleration_structure_features{};
std::unique_ptr<vkb::core::AccelerationStructure> bottom_level_acceleration_structure{nullptr};
std::unique_ptr<vkb::core::AccelerationStructure> top_level_acceleration_structure{nullptr};
std::unique_ptr<vkb::core::BufferC> vertex_buffer;
std::unique_ptr<vkb::core::BufferC> index_buffer;
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;
VkImageView view;
VkFormat format;
uint32_t width;
uint32_t height;
} storage_image{};
struct UniformData
{
glm::mat4 view_inverse{1.0f};
glm::mat4 proj_inverse{1.0f};
int32_t display_mode{0};
} uniform_data;
std::unique_ptr<vkb::core::BufferC> ubo;
VkPipeline pipeline{VK_NULL_HANDLE};
VkPipelineLayout pipeline_layout{VK_NULL_HANDLE};
VkDescriptorSet descriptor_set{VK_NULL_HANDLE};
VkDescriptorSetLayout descriptor_set_layout{VK_NULL_HANDLE};
RayTracingPositionFetch();
virtual ~RayTracingPositionFetch();
void request_gpu_features(vkb::PhysicalDevice &gpu) override;
void create_storage_image();
void create_bottom_level_acceleration_structure();
void create_top_level_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();
virtual void on_update_ui_overlay(vkb::Drawer &drawer) override;
bool prepare(const vkb::ApplicationOptions &options) override;
virtual void render(float delta_time) override;
};
std::unique_ptr<vkb::VulkanSample<vkb::BindingType::C>> create_ray_tracing_position_fetch();