1013 lines
50 KiB
C++
1013 lines
50 KiB
C++
/*
|
|
* Copyright (c) 2021-2025, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/*
|
|
* More complex example for hardware accelerated ray tracing using VK_KHR_ray_tracing_pipeline and VK_KHR_acceleration_structure
|
|
*/
|
|
|
|
#define TINYOBJLOADER_IMPLEMENTATION
|
|
|
|
#include "ray_tracing_reflection.h"
|
|
#include <glm/gtc/type_ptr.hpp>
|
|
|
|
struct ObjPlane : ObjModelCpu
|
|
{
|
|
ObjPlane()
|
|
{
|
|
vertices = {
|
|
{{+1, 0, +1}, {0, 1, 0}},
|
|
{{-1, 0, +1}, {0, 1, 0}},
|
|
{{+1, 0, -1}, {0, 1, 0}},
|
|
{{-1, 0, -1}, {0, 1, 0}},
|
|
};
|
|
indices = {0, 1, 2, 1, 2, 3};
|
|
mat_index = {0, 0};
|
|
}
|
|
};
|
|
|
|
struct ObjCube : ObjModelCpu
|
|
{
|
|
ObjCube()
|
|
{
|
|
vertices = {
|
|
{{+0.5f, +0.5f, +0.5f}, {+0.f, +1.f, +0.f}}, // Top
|
|
{{-0.5f, +0.5f, +0.5f}, {+0.f, +1.f, +0.f}},
|
|
{{+0.5f, +0.5f, -0.5f}, {+0.f, +1.f, +0.f}},
|
|
{{-0.5f, +0.5f, -0.5f}, {+0.f, +1.f, +0.f}},
|
|
{{+0.5f, -0.5f, +0.5f}, {+0.f, -1.f, +0.f}}, // Bottom
|
|
{{-0.5f, -0.5f, +0.5f}, {+0.f, -1.f, +0.f}},
|
|
{{+0.5f, -0.5f, -0.5f}, {+0.f, -1.f, +0.f}},
|
|
{{-0.5f, -0.5f, -0.5f}, {+0.f, -1.f, +0.f}},
|
|
{{+0.5f, +0.5f, +0.5f}, {+1.f, +0.f, +0.f}}, // Right
|
|
{{+0.5f, +0.5f, -0.5f}, {+1.f, +0.f, +0.f}},
|
|
{{+0.5f, -0.5f, -0.5f}, {+1.f, +0.f, +0.f}},
|
|
{{+0.5f, -0.5f, +0.5f}, {+1.f, +0.f, +0.f}},
|
|
{{-0.5f, +0.5f, +0.5f}, {-1.f, +0.f, +0.f}}, // left
|
|
{{-0.5f, +0.5f, -0.5f}, {-1.f, +0.f, +0.f}},
|
|
{{-0.5f, -0.5f, -0.5f}, {-1.f, +0.f, +0.f}},
|
|
{{-0.5f, -0.5f, +0.5f}, {-1.f, +0.f, +0.f}},
|
|
{{-0.5f, +0.5f, +0.5f}, {+0.f, +0.f, +1.f}}, // front
|
|
{{+0.5f, +0.5f, +0.5f}, {+0.f, +0.f, +1.f}},
|
|
{{+0.5f, -0.5f, +0.5f}, {+0.f, +0.f, +1.f}},
|
|
{{-0.5f, -0.5f, +0.5f}, {+0.f, +0.f, +1.f}},
|
|
{{-0.5f, +0.5f, -0.5f}, {+0.f, +0.f, -1.f}}, // back
|
|
{{+0.5f, +0.5f, -0.5f}, {+0.f, +0.f, -1.f}},
|
|
{{+0.5f, -0.5f, -0.5f}, {+0.f, +0.f, -1.f}},
|
|
{{-0.5f, -0.5f, -0.5f}, {+0.f, +0.f, -1.f}},
|
|
|
|
};
|
|
indices = {
|
|
0, 1, 2, 1, 2, 3, /*top*/
|
|
4, 5, 6, 5, 6, 7, /*bottom*/
|
|
8, 9, 10, 8, 10, 11, /*right*/
|
|
12, 13, 14, 12, 14, 15, /*left*/
|
|
16, 17, 18, 16, 18, 19, /*front*/
|
|
20, 21, 22, 20, 22, 23, /*back*/
|
|
};
|
|
mat_index = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5};
|
|
}
|
|
};
|
|
|
|
RaytracingReflection::RaytracingReflection()
|
|
{
|
|
title = "Hardware accelerated ray tracing";
|
|
|
|
set_api_version(VK_API_VERSION_1_2);
|
|
|
|
// 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);
|
|
}
|
|
|
|
RaytracingReflection::~RaytracingReflection()
|
|
{
|
|
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);
|
|
for (auto &b : bottom_level_acceleration_structure)
|
|
{
|
|
delete_acceleration_structure(b);
|
|
}
|
|
|
|
for (auto &obj : obj_models)
|
|
{
|
|
obj.vertex_buffer.reset();
|
|
obj.index_buffer.reset();
|
|
obj.mat_color_buffer.reset();
|
|
obj.mat_index_buffer.reset();
|
|
}
|
|
|
|
ubo.reset();
|
|
}
|
|
}
|
|
|
|
/*
|
|
Enable extension features required by this sample
|
|
These are passed to device creation via a pNext structure chain
|
|
*/
|
|
void RaytracingReflection::request_gpu_features(vkb::PhysicalDevice &gpu)
|
|
{
|
|
REQUEST_REQUIRED_FEATURE(gpu, VkPhysicalDeviceVulkan12Features, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, bufferDeviceAddress);
|
|
REQUEST_REQUIRED_FEATURE(gpu, VkPhysicalDeviceVulkan12Features, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, scalarBlockLayout);
|
|
REQUEST_REQUIRED_FEATURE(gpu, VkPhysicalDeviceAccelerationStructureFeaturesKHR, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR, accelerationStructure);
|
|
REQUEST_REQUIRED_FEATURE(gpu, VkPhysicalDeviceRayTracingPipelineFeaturesKHR, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR, rayTracingPipeline);
|
|
|
|
if (gpu.get_features().shaderInt64)
|
|
{
|
|
gpu.get_mutable_requested_features().shaderInt64 = VK_TRUE;
|
|
}
|
|
else
|
|
{
|
|
throw std::runtime_error("Requested required feature <VkPhysicalDeviceFeatures::shaderInt64> is not supported");
|
|
}
|
|
}
|
|
|
|
/*
|
|
Set up a storage image that the ray generation shader will be writing to
|
|
*/
|
|
void RaytracingReflection::create_storage_image()
|
|
{
|
|
storage_image.width = width;
|
|
storage_image.height = height;
|
|
|
|
VkImageCreateInfo image{VK_STRUCTURE_TYPE_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{VK_STRUCTURE_TYPE_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{VK_STRUCTURE_TYPE_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 (triangles)
|
|
*/
|
|
void RaytracingReflection::create_bottom_level_acceleration_structure(ObjModelGpu &obj_model)
|
|
{
|
|
// 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;
|
|
|
|
// 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 = obj_model.vertex_buffer->get_device_address();
|
|
index_data_device_address.deviceAddress = obj_model.index_buffer->get_device_address();
|
|
transform_matrix_device_address.deviceAddress = transform_matrix_buffer->get_device_address();
|
|
|
|
VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR};
|
|
triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR;
|
|
triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT;
|
|
triangles.vertexData = vertex_data_device_address;
|
|
triangles.maxVertex = obj_model.nb_vertices;
|
|
triangles.vertexStride = sizeof(ObjVertex);
|
|
triangles.indexType = VK_INDEX_TYPE_UINT32;
|
|
triangles.indexData = index_data_device_address;
|
|
triangles.transformData = transform_matrix_device_address;
|
|
|
|
// The bottom level acceleration structure contains one set of triangles as the input geometry
|
|
VkAccelerationStructureGeometryKHR acceleration_structure_geometry{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 = triangles;
|
|
|
|
// Get the size requirements for buffers involved in the acceleration structure build process
|
|
VkAccelerationStructureBuildGeometryInfoKHR acceleration_structure_build_geometry_info{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 triangle_count = obj_model.nb_indices / 3;
|
|
|
|
VkAccelerationStructureBuildSizesInfoKHR acceleration_structure_build_sizes_info{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,
|
|
&triangle_count,
|
|
&acceleration_structure_build_sizes_info);
|
|
|
|
// Create a buffer to hold the acceleration structure
|
|
AccelerationStructure blas;
|
|
blas.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{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR};
|
|
acceleration_structure_create_info.buffer = blas.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, &blas.handle);
|
|
|
|
// The actual build process starts here
|
|
|
|
// Create a scratch buffer as a temporary storage for the acceleration structure build
|
|
std::unique_ptr<vkb::core::BufferC> sc_buffer;
|
|
sc_buffer = std::make_unique<vkb::core::BufferC>(get_device(), acceleration_structure_build_sizes_info.buildScratchSize,
|
|
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
|
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
|
|
|
VkAccelerationStructureBuildGeometryInfoKHR acceleration_build_geometry_info{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 = blas.handle;
|
|
acceleration_build_geometry_info.geometryCount = 1;
|
|
acceleration_build_geometry_info.pGeometries = &acceleration_structure_geometry;
|
|
acceleration_build_geometry_info.scratchData.deviceAddress = sc_buffer->get_device_address();
|
|
|
|
VkAccelerationStructureBuildRangeInfoKHR acceleration_structure_build_range_info;
|
|
acceleration_structure_build_range_info.primitiveCount = triangle_count;
|
|
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);
|
|
sc_buffer.reset();
|
|
|
|
// Store the blas to be re-used as instance
|
|
bottom_level_acceleration_structure.push_back(std::move(blas));
|
|
}
|
|
|
|
/*
|
|
Create the top level acceleration structure containing geometry instances of the bottom level acceleration structure(s)
|
|
*/
|
|
void RaytracingReflection::create_top_level_acceleration_structure(std::vector<VkAccelerationStructureInstanceKHR> &blas_instances)
|
|
{
|
|
std::unique_ptr<vkb::core::BufferC> instances_buffer = std::make_unique<vkb::core::BufferC>(get_device(),
|
|
sizeof(VkAccelerationStructureInstanceKHR) * blas_instances.size(),
|
|
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(blas_instances.data(), sizeof(VkAccelerationStructureInstanceKHR) * blas_instances.size());
|
|
|
|
VkDeviceOrHostAddressConstKHR instance_data_device_address{};
|
|
instance_data_device_address.deviceAddress = instances_buffer->get_device_address();
|
|
|
|
// The top level acceleration structure contains (bottom level) instance as the input geometry
|
|
VkAccelerationStructureGeometryKHR acceleration_structure_geometry{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{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 auto primitive_count = static_cast<uint32_t>(blas_instances.size());
|
|
|
|
VkAccelerationStructureBuildSizesInfoKHR acceleration_structure_build_sizes_info{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{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
|
|
std::unique_ptr<vkb::core::BufferC> sc_buffer;
|
|
sc_buffer = std::make_unique<vkb::core::BufferC>(get_device(), acceleration_structure_build_sizes_info.buildScratchSize,
|
|
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
|
|
VMA_MEMORY_USAGE_CPU_TO_GPU);
|
|
|
|
VkAccelerationStructureBuildGeometryInfoKHR acceleration_build_geometry_info{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 = sc_buffer->get_device_address();
|
|
|
|
VkAccelerationStructureBuildRangeInfoKHR acceleration_structure_build_range_info;
|
|
acceleration_structure_build_range_info.primitiveCount = primitive_count;
|
|
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);
|
|
sc_buffer.reset();
|
|
}
|
|
|
|
inline uint32_t aligned_size(uint32_t value, uint32_t alignment)
|
|
{
|
|
return (value + alignment - 1) & ~(alignment - 1);
|
|
}
|
|
|
|
/*
|
|
Create the GPU representation of the model
|
|
*/
|
|
void RaytracingReflection::create_model(ObjModelCpu &obj, const std::vector<ObjMaterial> &materials)
|
|
{
|
|
ObjModelGpu model;
|
|
model.nb_indices = static_cast<uint32_t>(obj.indices.size());
|
|
model.nb_vertices = static_cast<uint32_t>(obj.vertices.size());
|
|
|
|
auto vertex_buffer_size = obj.vertices.size() * sizeof(ObjVertex);
|
|
auto index_buffer_size = obj.indices.size() * sizeof(uint32_t);
|
|
auto mat_index_buffer_size = obj.mat_index.size() * sizeof(int32_t);
|
|
auto mat_buffer_size = materials.size() * sizeof(ObjMaterial);
|
|
|
|
// Making sure the material triangle index don't exceed the number of materials
|
|
auto max_index = static_cast<int32_t>(materials.size() - 1);
|
|
std::vector<int32_t> mat_index(obj.mat_index.size());
|
|
for (auto i = 0; i < obj.mat_index.size(); i++)
|
|
{
|
|
mat_index[i] = std::min(max_index, obj.mat_index[i]);
|
|
}
|
|
|
|
// Note that the buffer usage flags for buffers consumed by the bottom level acceleration structure require special flags
|
|
VkBufferUsageFlags buffer_usage_flags = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
|
|
|
|
model.vertex_buffer = std::make_unique<vkb::core::BufferC>(get_device(), vertex_buffer_size, buffer_usage_flags, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
|
model.vertex_buffer->update(obj.vertices.data(), vertex_buffer_size);
|
|
|
|
model.index_buffer = std::make_unique<vkb::core::BufferC>(get_device(), index_buffer_size, buffer_usage_flags, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
|
model.index_buffer->update(obj.indices.data(), index_buffer_size);
|
|
|
|
// Acceleration structure flag is not needed for the rest
|
|
buffer_usage_flags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
|
|
|
|
model.mat_index_buffer = std::make_unique<vkb::core::BufferC>(get_device(), mat_index_buffer_size, buffer_usage_flags, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
|
model.mat_index_buffer->update(mat_index.data(), mat_index_buffer_size);
|
|
|
|
model.mat_color_buffer = std::make_unique<vkb::core::BufferC>(get_device(), mat_buffer_size, buffer_usage_flags, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
|
model.mat_color_buffer->update(reinterpret_cast<const uint8_t *>(materials.data()), mat_buffer_size);
|
|
|
|
obj_models.push_back(std::move(model));
|
|
}
|
|
|
|
auto RaytracingReflection::create_blas_instance(uint32_t blas_id, glm::mat4 &mat)
|
|
{
|
|
VkTransformMatrixKHR transform_matrix;
|
|
glm::mat3x4 rtxT = glm::transpose(mat);
|
|
memcpy(&transform_matrix, glm::value_ptr(rtxT), sizeof(VkTransformMatrixKHR));
|
|
|
|
AccelerationStructure &blas = bottom_level_acceleration_structure[blas_id];
|
|
|
|
// Get the bottom acceleration structure's handle, which will be used during the top level acceleration build
|
|
VkAccelerationStructureDeviceAddressInfoKHR acceleration_device_address_info{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR};
|
|
acceleration_device_address_info.accelerationStructure = blas.handle;
|
|
auto device_address = vkGetAccelerationStructureDeviceAddressKHR(get_device().get_handle(), &acceleration_device_address_info);
|
|
|
|
VkAccelerationStructureInstanceKHR blas_instance{};
|
|
blas_instance.transform = transform_matrix;
|
|
blas_instance.instanceCustomIndex = blas_id;
|
|
blas_instance.mask = 0xFF;
|
|
blas_instance.instanceShaderBindingTableRecordOffset = 0;
|
|
blas_instance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
|
|
blas_instance.accelerationStructureReference = device_address;
|
|
|
|
return blas_instance;
|
|
}
|
|
|
|
/*
|
|
Create a buffer holding the address of model buffers (buffer reference)
|
|
*/
|
|
void RaytracingReflection::create_buffer_references()
|
|
{
|
|
// For each model that was created, we retrieved the address of buffers
|
|
// used by them. So in the shader, we have direct access to the data
|
|
std::vector<ObjBuffers> obj_data;
|
|
auto nbObj = static_cast<uint32_t>(obj_models.size());
|
|
for (uint32_t i = 0; i < nbObj; ++i)
|
|
{
|
|
ObjBuffers data;
|
|
data.vertices = obj_models[i].vertex_buffer->get_device_address();
|
|
data.indices = obj_models[i].index_buffer->get_device_address();
|
|
data.materials = obj_models[i].mat_color_buffer->get_device_address();
|
|
data.materialIndices = obj_models[i].mat_index_buffer->get_device_address();
|
|
obj_data.emplace_back(data);
|
|
}
|
|
VkBufferUsageFlags buffer_usage_flags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
|
|
scene_desc = std::make_unique<vkb::core::BufferC>(get_device(), nbObj * sizeof(ObjBuffers), buffer_usage_flags, VMA_MEMORY_USAGE_CPU_TO_GPU);
|
|
scene_desc->update(obj_data.data(), nbObj * sizeof(ObjBuffers));
|
|
}
|
|
|
|
/*
|
|
Create scene geometry and ray tracing acceleration structures
|
|
*/
|
|
void RaytracingReflection::create_scene()
|
|
{
|
|
// Materials
|
|
ObjMaterial mat_red = {{1, 0, 0}, {1, 1, 1}, 0.0f};
|
|
ObjMaterial mat_green = {{0, 1, 0}, {1, 1, 1}, 0.0f};
|
|
ObjMaterial mat_blue = {{0, 0, 1}, {1, 1, 1}, 0.0f};
|
|
ObjMaterial mat_yellow = {{1, 1, 0}, {1, 1, 1}, 0.0f};
|
|
ObjMaterial mat_cyan = {{0, 1, 1}, {1, 1, 1}, 0.0f};
|
|
ObjMaterial mat_magenta = {{1, 0, 1}, {1, 1, 1}, 0.0f};
|
|
ObjMaterial mat_grey = {{0.7f, 0.7f, 0.7f}, {0.9f, 0.9f, 0.9f}, 0.1f}; // Slightly reflective
|
|
ObjMaterial mat_mirror = {{0.3f, 0.9f, 1.0f}, {0.9f, 0.9f, 0.9f}, 0.9f}; // Mirror Slightly blue
|
|
|
|
// Geometries
|
|
auto cube = ObjCube();
|
|
auto plane = ObjPlane();
|
|
|
|
// Upload geometries to GPU
|
|
create_model(cube, {mat_red, mat_green, mat_blue, mat_yellow, mat_cyan, mat_magenta}); // 6 color faces
|
|
create_model(plane, {mat_grey});
|
|
create_model(cube, {mat_mirror});
|
|
|
|
// Create a buffer holding the address of model buffers (buffer reference)
|
|
create_buffer_references();
|
|
|
|
// Create as many bottom acceleration structures (blas) as there are geometries/models
|
|
create_bottom_level_acceleration_structure(obj_models[0]);
|
|
create_bottom_level_acceleration_structure(obj_models[1]);
|
|
create_bottom_level_acceleration_structure(obj_models[2]);
|
|
|
|
// Matrices to position the instances
|
|
glm::mat4 m_mirror_back = glm::scale(glm::translate(glm::mat4(1.f), glm::vec3(0.0f, 0.0f, -7.0f)), glm::vec3(5.0f, 5.0f, 0.1f));
|
|
glm::mat4 m_mirror_front = glm::scale(glm::translate(glm::mat4(1.f), glm::vec3(0.0f, 0.0f, 7.0f)), glm::vec3(5.0f, 5.0f, 0.1f));
|
|
glm::mat4 m_plane = glm::scale(glm::translate(glm::mat4(1.f), glm::vec3(0.0f, -1.0f, 0.0f)), glm::vec3(15.0f, 15.0f, 15.0f));
|
|
glm::mat4 m_cube_left = glm::translate(glm::mat4(1.f), glm::vec3(-1.0f, 0.0f, 0.0f));
|
|
glm::mat4 m_cube_right = glm::translate(glm::mat4(1.f), glm::vec3(1.0f, 0.0f, 0.0f));
|
|
|
|
// Creating instances of the blas to the top level acceleration structure
|
|
std::vector<VkAccelerationStructureInstanceKHR> blas_instances;
|
|
blas_instances.push_back(create_blas_instance(0, m_cube_left));
|
|
blas_instances.push_back(create_blas_instance(0, m_cube_right));
|
|
blas_instances.push_back(create_blas_instance(1, m_plane));
|
|
blas_instances.push_back(create_blas_instance(2, m_mirror_back));
|
|
blas_instances.push_back(create_blas_instance(2, m_mirror_front));
|
|
|
|
// Building the TLAS
|
|
create_top_level_acceleration_structure(blas_instances);
|
|
}
|
|
|
|
/*
|
|
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 |
|
|
|-------------|
|
|
| miss shadow |
|
|
|-------------|
|
|
| hit |
|
|
\-------------/
|
|
*/
|
|
|
|
void RaytracingReflection::create_shader_binding_tables()
|
|
{
|
|
// Index position of the groups in the generated ray tracing pipeline
|
|
// To be generic, this should be pass in parameters
|
|
std::vector<uint32_t> rgen_index{0};
|
|
std::vector<uint32_t> miss_index{1, 2};
|
|
std::vector<uint32_t> hit_index{3};
|
|
|
|
const uint32_t handle_size = ray_tracing_pipeline_properties.shaderGroupHandleSize;
|
|
const uint32_t handle_alignment = ray_tracing_pipeline_properties.shaderGroupHandleAlignment;
|
|
const uint32_t handle_size_aligned = aligned_size(handle_size, handle_alignment);
|
|
|
|
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;
|
|
|
|
// Create binding table buffers for each shader type
|
|
raygen_shader_binding_table = std::make_unique<vkb::core::BufferC>(get_device(), handle_size_aligned * rgen_index.size(), sbt_buffer_usage_flags, sbt_memory_usage, 0);
|
|
miss_shader_binding_table = std::make_unique<vkb::core::BufferC>(get_device(), handle_size_aligned * miss_index.size(), sbt_buffer_usage_flags, sbt_memory_usage, 0);
|
|
hit_shader_binding_table = std::make_unique<vkb::core::BufferC>(get_device(), handle_size_aligned * hit_index.size(), sbt_buffer_usage_flags, sbt_memory_usage, 0);
|
|
|
|
// Copy the pipeline's shader handles into a host buffer
|
|
const auto group_count = static_cast<uint32_t>(rgen_index.size() + miss_index.size() + hit_index.size());
|
|
const auto sbt_size = group_count * handle_size_aligned;
|
|
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()));
|
|
|
|
// Write the handles in the SBT buffer
|
|
auto copyHandles = [&](auto &buffer, std::vector<uint32_t> &indices, uint32_t stride) {
|
|
auto *pBuffer = static_cast<uint8_t *>(buffer->map());
|
|
for (uint32_t index = 0; index < static_cast<uint32_t>(indices.size()); index++)
|
|
{
|
|
auto *pStart = pBuffer;
|
|
// Copy the handle
|
|
memcpy(pBuffer, shader_handle_storage.data() + (indices[index] * handle_size), handle_size);
|
|
pBuffer = pStart + stride; // Jumping to next group
|
|
}
|
|
buffer->unmap();
|
|
};
|
|
|
|
copyHandles(raygen_shader_binding_table, rgen_index, handle_size_aligned);
|
|
copyHandles(miss_shader_binding_table, miss_index, handle_size_aligned);
|
|
copyHandles(hit_shader_binding_table, hit_index, handle_size_aligned);
|
|
}
|
|
|
|
/*
|
|
Create the descriptor sets used for the ray tracing dispatch
|
|
*/
|
|
void RaytracingReflection::create_descriptor_sets()
|
|
{
|
|
uint32_t nbObj = static_cast<uint32_t>(obj_models.size());
|
|
|
|
std::vector<VkDescriptorPoolSize> pool_sizes = {
|
|
{VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1},
|
|
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1},
|
|
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1},
|
|
{VK_DESCRIPTOR_TYPE_STORAGE_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{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{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 uniform_descriptor = create_descriptor(*ubo);
|
|
VkDescriptorBufferInfo scene_descriptor = create_descriptor(*scene_desc);
|
|
|
|
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, &uniform_descriptor);
|
|
VkWriteDescriptorSet scene_buffer_write = vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3, &scene_descriptor);
|
|
|
|
std::vector<VkWriteDescriptorSet> write_descriptor_sets = {
|
|
acceleration_structure_write,
|
|
result_image_write,
|
|
uniform_buffer_write,
|
|
scene_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 RaytracingReflection::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 | VK_SHADER_STAGE_CLOSEST_HIT_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;
|
|
|
|
// Scene description
|
|
VkDescriptorSetLayoutBinding scene_buffer_binding{};
|
|
scene_buffer_binding.binding = 3;
|
|
scene_buffer_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
scene_buffer_binding.descriptorCount = 1;
|
|
scene_buffer_binding.stageFlags = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR;
|
|
|
|
std::vector<VkDescriptorSetLayoutBinding> bindings = {
|
|
acceleration_structure_layout_binding,
|
|
result_image_layout_binding,
|
|
uniform_buffer_binding,
|
|
scene_buffer_binding,
|
|
};
|
|
|
|
VkDescriptorSetLayoutCreateInfo layout_info{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{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_reflection/raygen.rgen.spv", VK_SHADER_STAGE_RAYGEN_BIT_KHR));
|
|
VkRayTracingShaderGroupCreateInfoKHR raygen_group_ci{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_reflection/miss.rmiss.spv", VK_SHADER_STAGE_MISS_BIT_KHR));
|
|
VkRayTracingShaderGroupCreateInfoKHR miss_group_ci{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 miss (shadow) group
|
|
{
|
|
shader_stages.push_back(load_shader("ray_tracing_reflection/missShadow.rmiss.spv", VK_SHADER_STAGE_MISS_BIT_KHR));
|
|
VkRayTracingShaderGroupCreateInfoKHR miss_group_ci{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_reflection/closesthit.rchit.spv", VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR));
|
|
VkRayTracingShaderGroupCreateInfoKHR closes_hit_group_ci{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{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 = 2;
|
|
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 RaytracingReflection::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 RaytracingReflection::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 RaytracingReflection::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{VK_STRUCTURE_TYPE_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 = raygen_shader_binding_table->get_device_address();
|
|
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 = miss_shader_binding_table->get_device_address();
|
|
miss_shader_sbt_entry.stride = handle_size_aligned;
|
|
miss_shader_sbt_entry.size = handle_size_aligned * 2;
|
|
|
|
VkStridedDeviceAddressRegionKHR hit_shader_sbt_entry{};
|
|
hit_shader_sbt_entry.deviceAddress = hit_shader_binding_table->get_device_address();
|
|
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, ©_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 RaytracingReflection::update_uniform_buffers()
|
|
{
|
|
auto mat = camera.matrices.perspective;
|
|
mat[1][1] *= -1; // Flipping Y axis
|
|
|
|
uniform_data.proj_inverse = glm::inverse(mat);
|
|
uniform_data.view_inverse = glm::inverse(camera.matrices.view);
|
|
ubo->convert_and_update(uniform_data);
|
|
}
|
|
|
|
bool RaytracingReflection::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
|
|
VkPhysicalDeviceProperties2 device_properties{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
|
|
VkPhysicalDeviceFeatures2 device_features{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 RaytracingReflection::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 RaytracingReflection::render(float delta_time)
|
|
{
|
|
if (!prepared)
|
|
{
|
|
return;
|
|
}
|
|
draw();
|
|
if (camera.updated)
|
|
{
|
|
update_uniform_buffers();
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<vkb::VulkanSampleC> create_ray_tracing_reflection()
|
|
{
|
|
return std::make_unique<RaytracingReflection>();
|
|
}
|