diff options
55 files changed, 4816 insertions, 127 deletions
@@ -5,3 +5,4 @@ # of contributors, see the revision history in source control. Google LLC +Advanced Micro Devices, Inc. @@ -22,6 +22,7 @@ LOCAL_CXXFLAGS:=-std=c++11 -fno-exceptions -fno-rtti \ -DAMBER_ENABLE_SHADERC=1 \ -DAMBER_ENGINE_VULKAN=1 LOCAL_SRC_FILES:= \ + src/acceleration_structure.cc \ src/amber.cc \ src/amberscript/parser.cc \ src/buffer.cc \ @@ -53,6 +54,7 @@ LOCAL_SRC_FILES:= \ src/vkscript/section_parser.cc \ src/vulkan/buffer_descriptor.cc \ src/vulkan/buffer_backed_descriptor.cc \ + src/vulkan/blas.cc \ src/vulkan/command_buffer.cc \ src/vulkan/command_pool.cc \ src/vulkan/compute_pipeline.cc \ @@ -65,9 +67,13 @@ LOCAL_SRC_FILES:= \ src/vulkan/index_buffer.cc \ src/vulkan/pipeline.cc \ src/vulkan/push_constant.cc \ + src/vulkan/raytracing_pipeline.cc \ src/vulkan/resource.cc \ src/vulkan/sampler.cc \ src/vulkan/sampler_descriptor.cc \ + src/vulkan/sbt.cc \ + src/vulkan/tlas.cc \ + src/vulkan/tlas_descriptor.cc \ src/vulkan/transfer_buffer.cc \ src/vulkan/transfer_image.cc \ src/vulkan/vertex_buffer.cc \ diff --git a/docs/amber_script.md b/docs/amber_script.md index 9bd58d7..7918764 100644 --- a/docs/amber_script.md +++ b/docs/amber_script.md @@ -149,11 +149,18 @@ SHADER {shader_type} {shader_name} {shader_format} [ TARGET_ENV {target_env} ] V * `tessellation_evaluation` * `tessellation_control` * `compute` + * `ray_generation` + * `any_hit` + * `closest_hit` + * `miss` + * `intersection` + * `callable` * `multi` The compute pipeline can only contain compute shaders. The graphics pipeline can not contain compute shaders, and must contain a vertex shader and a fragment -shader. +shader. Ray tracing pipeline can contain only shaders of ray tracing types: +ray generation, any hit, closest hit, miss, intersection, and callable shaders. The provided `multi` shader can only be used with `SPIRV-ASM` and `SPIRV-HEX` and allows for providing multiple shaders in a single module (so the `vertex` @@ -389,15 +396,107 @@ Note: currently the border color is always transparent black. Note: the addressing mode is used for all coordinates currently. Arrayed images should use `clamp_to_edge` for the array index. +### Acceleration Structures + +Acceleration structures are used to enumerate geometries to describe a scene. +There are two kinds of acceleration structures: + * Bottom level + * Top level + +#### Bottom Level + +Bottom level acceleration structures consists of a set of geometries. +Each bottom level acceleration structure can consists either of triangle or +axis aligned bounding box (AABB) geometries. It is prohibited to mix triangle +geometries and AABBs inside same bottom level acceleration structures. + +A bottom level acceleration structure consisting of triangle geometries is defined as: + +```groovy + # Bottom level acceleration structure consisting of triangles + ACCELERATION_STRUCTURE BOTTOM_LEVEL {name_of_bottom_level_acceleration_structure} + {GEOMETRY TRIANGLES + {x0 y0 z0 + x1 y1 z1 + x2 y2 z2}+ + END}+ + END +``` + +A bottom level acceleration structure consisting of axis aligned bounding boxes is defined as: + +```groovy + # Bottom level acceleration structure consisting of AABBs + ACCELERATION_STRUCTURE BOTTOM_LEVEL {name_of_bottom_level_acceleration_structure} + {GEOMETRY AABBS + {x0 y0 z0 x1 y1 z1}+ + END}+ + END +``` + +Each coordinate |x{n}|, |y{n}|, and |z{n}| should be floating point values. + +#### Top Level + +Top level acceleration structures consists of a set of instances of bottom +level acceleration structures. + +```groovy + # Acceleration structure with instance defined in one line + ACCELERATION_STRUCTURE TOP_LEVEL {name_of_top_level_acceleration_structure} + {BLAS_INSTANCE USE {name_of_bottom_level_acceleration_structure}}+ + END + + # Acceleration structure with instance defined in multiple lines + ACCELERATION_STRUCTURE TOP_LEVEL {name_of_top_level_acceleration_structure} + {BOTTOM_LEVEL_INSTANCE {name_of_bottom_level_acceleration_structure} + [INDEX {index}] + [OFFSET {offset}] + [FLAGS {flags}] + [MASK {mask}] + [TRANSFORM \ + {transform} \ + END] + END}+ + END +``` + +The value of |index| should be an integer in range of [0..16,777,215] is a 24-bit user-specified +index value accessible to ray shaders in the InstanceCustomIndexKHR built-in. + +The value of |offset| should be an integer in range of [0..16,777,215] is a 24-bit offset used +in calculating the hit shader binding table index. + +The value of |mask| should be an integer in range of [0..255] (may be specified as 0xNN) is an +8-bit visibility mask for the geometry. + +The value of |flags| is space-separated or EOL-separated list of following: + * `TRIANGLE_FACING_CULL_DISABLE` + * `TRIANGLE_FLIP_FACING` + * `FORCE_OPAQUE` + * `FORCE_NO_OPAQUE` + * `FORCE_OPACITY_MICROMAP_2_STATE` + * `DISABLE_OPACITY_MICROMAPS` + * <any integer number> + +If |flags| is a EOL-separated list it should be ended with END statement. +If |flags| is a space-separated list it should not be ended with END statement. + +The |transform| is 12 space-separated values describing a 3x4 row-major affine transformation matrix applied to +the acceleration structure. + + ### Pipelines #### Pipeline type * `compute` * `graphics` - + * `ray_tracing` + ```groovy -# The PIPELINE command creates a pipeline. This can be either compute or -# graphics. Shaders are attached to the pipeline at pipeline creation time. +# The PIPELINE command creates a pipeline. This can be either compute, +# graphics, or ray_tracing. Shaders are attached to the pipeline +# at pipeline creation time. PIPELINE {pipeline_type} {pipeline_name} ... END @@ -458,6 +557,38 @@ The following commands are all specified within the `PIPELINE` command. PATCH_CONTROL_POINTS {control_points} ``` +Ray tracing pipelines do not attach shaders directly like compute or graphics pipelines. +Ray tracing pipelines organize shaders into shader groups in one of four ways +depending on shader types used: + +```groovy + # Four possible shader group definitions + SHADER_GROUP {group_name_1} {ray_generation_shader_name} + SHADER_GROUP {group_name_2} {miss_shader_name} + SHADER_GROUP {group_name_3} {call_shader_name} + SHADER_GROUP {group_name_4} [closest_hit_shader_name] [any_hit_shader_name] [intersection_shader_name] +``` + +Shader group cannot be empty. +Each group name must be unique within a pipeline. The same shader can be used within one or more +shader groups. The shader group order is important, further commands as shader code might refer +them directly. With the shader groups defined, they are then added into shader binding tables: + +```groovy + # Create shader binding tables and set shader groups into it + SHADER_BINDING_TABLE {sbt_name} + {group_name_1} + [ | {group_name_n}] + END +``` + +Generally a program needs three shader binding tables: + * ray generation shader binding table with one ray generation shader group + * miss shader binding table containing one or more miss shader groups + * hit shader binding table containing one or more hit shader groups + +Shader binding tables for call shaders are optional. + #### Compare operations * `never` * `less` @@ -746,6 +877,13 @@ ranges can be used also with dynamic buffers. INDEX_DATA {buffer_name} ``` +Ray tracing pipelines allow bind top level acceleration structures. + +```groovy + # Bind the top level acceleration structure at the given descriptor set and binding. + BIND ACCELERATION_STRUCTURE {tlas_name} DESCRIPTOR_SET _set_id_ BINDING _id_ +``` + #### OpenCL Plain-Old-Data Arguments OpenCL kernels can have plain-old-data (pod or pod_ubo in the desriptor map) arguments set their data via this command. Amber will generate the appropriate @@ -845,6 +983,25 @@ RUN {pipeline_name} DRAW_ARRAY AS {topology} INDEXED \ [ INSTANCE_COUNT _inst_count_value_ (default 1) ] ``` +```groovy +# Run the |pipeline_name| which must be a `ray tracing` pipeline. +# Next four shader binding table names should be specified: +# * RAYGEN |ray_gen_sbt_name| - shader binding table containing ray generation shader group +# * MISS |miss_sbt_name| - shader binding table containing one or more miss shader groups +# * HIT |hit_sbt_name| - shader binding table containing one or more hit shader groups +# * CALL |call_sbt_name| - shader binding table containing one or more call shader groups +# RAYGEN is required, other shader binding tables (MISS, HIT and CALL) are optional. +# +# The pipeline will be run with the given ray tracing dimensions |x|, |y|, |z|. +# Each of the x, y and z values must be a uint32. +RUN {pipeline_name} \ + RAYGEN {ray_gen_sbt_name} \ + [MISS {miss_sbt_name}] \ + [HIT {hit_sbt_name}] \ + [CALL {call_sbt_name}] \ + _x_ _y_ _z_ +``` + ### Repeating commands ```groovy diff --git a/include/amber/amber_vulkan.h b/include/amber/amber_vulkan.h index d031579..307a58a 100644 --- a/include/amber/amber_vulkan.h +++ b/include/amber/amber_vulkan.h @@ -49,6 +49,18 @@ struct VulkanEngineConfig : public EngineConfig { /// the extension is not enabled, |available_features| will be used. VkPhysicalDeviceFeatures2KHR available_features2; + /// Physical device properties available for |physical_device|. The + /// |available_properties| will be ignored if + /// VK_KHR_get_physical_device_properties2 is enabled, |available_properties2| + /// will be used in that case. + VkPhysicalDeviceProperties available_properties; + + /// Physical device properties for |physical_device|.The + /// |available_properties2| will only be used if + /// VK_KHR_get_physical_device_properties2 is enabled. If the extension is not + /// enabled, |available_properties| will be used. + VkPhysicalDeviceProperties2KHR available_properties2; + /// Instance extensions available. std::vector<std::string> available_instance_extensions; diff --git a/include/amber/recipe.h b/include/amber/recipe.h index 0fd2445..4b8d877 100644 --- a/include/amber/recipe.h +++ b/include/amber/recipe.h @@ -35,6 +35,9 @@ class RecipeImpl { /// Returns required features in the given recipe. virtual std::vector<std::string> GetRequiredFeatures() const = 0; + /// Returns required features in the given recipe. + virtual std::vector<std::string> GetRequiredProperties() const = 0; + /// Returns required device extensions in the given recipe. virtual std::vector<std::string> GetRequiredDeviceExtensions() const = 0; @@ -67,6 +70,9 @@ class Recipe { /// Returns required features in the given recipe. std::vector<std::string> GetRequiredFeatures() const; + /// Returns required properties in the given recipe. + std::vector<std::string> GetRequiredProperties() const; + /// Returns required device extensions in the given recipe. std::vector<std::string> GetRequiredDeviceExtensions() const; diff --git a/include/amber/shader_info.h b/include/amber/shader_info.h index b5ce751..84ee153 100644 --- a/include/amber/shader_info.h +++ b/include/amber/shader_info.h @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -38,6 +39,12 @@ enum ShaderType { kShaderTypeVertex, kShaderTypeTessellationControl, kShaderTypeTessellationEvaluation, + kShaderTypeRayGeneration, + kShaderTypeAnyHit, + kShaderTypeClosestHit, + kShaderTypeMiss, + kShaderTypeIntersection, + kShaderTypeCall, kShaderTypeMulti, }; diff --git a/samples/config_helper_vulkan.cc b/samples/config_helper_vulkan.cc index 104e1dc..95900e3 100644 --- a/samples/config_helper_vulkan.cc +++ b/samples/config_helper_vulkan.cc @@ -72,6 +72,13 @@ const char kComputeFullSubgroups[] = "SubgroupSizeControl.computeFullSubgroups"; const char kShaderSubgroupExtendedTypes[] = "ShaderSubgroupExtendedTypesFeatures.shaderSubgroupExtendedTypes"; +const char kAccelerationStructure[] = + "AccelerationStructureFeaturesKHR.accelerationStructure"; +const char kBufferDeviceAddress[] = + "BufferDeviceAddressFeatures.bufferDeviceAddress"; +const char kRayTracingPipeline[] = + "RayTracingPipelineFeaturesKHR.rayTracingPipeline"; + const char kExtensionForValidationLayer[] = "VK_EXT_debug_report"; VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugReportFlagsEXT flag, @@ -649,7 +656,13 @@ ConfigHelperVulkan::ConfigHelperVulkan() storage_8bit_feature_(VkPhysicalDevice8BitStorageFeaturesKHR()), storage_16bit_feature_(VkPhysicalDevice16BitStorageFeaturesKHR()), subgroup_size_control_feature_( - VkPhysicalDeviceSubgroupSizeControlFeaturesEXT()) {} + VkPhysicalDeviceSubgroupSizeControlFeaturesEXT()), + acceleration_structure_feature_( + VkPhysicalDeviceAccelerationStructureFeaturesKHR()), + buffer_device_address_feature_( + VkPhysicalDeviceBufferDeviceAddressFeatures()), + ray_tracing_pipeline_feature_( + VkPhysicalDeviceRayTracingPipelineFeaturesKHR()) {} ConfigHelperVulkan::~ConfigHelperVulkan() { if (vulkan_device_) @@ -795,6 +808,12 @@ amber::Result ConfigHelperVulkan::CheckVulkanPhysicalDeviceRequirements( supports_shader_subgroup_extended_types_ = true; else if (ext == "VK_KHR_variable_pointers") supports_variable_pointers_ = true; + else if (ext == "VK_KHR_acceleration_structure") + supports_acceleration_structure_ = true; + else if (ext == "VK_KHR_buffer_device_address") + supports_buffer_device_address_ = true; + else if (ext == "VK_KHR_ray_tracing_pipeline") + supports_ray_tracing_pipeline_ = true; } VkPhysicalDeviceFeatures required_vulkan_features = @@ -809,39 +828,71 @@ amber::Result ConfigHelperVulkan::CheckVulkanPhysicalDeviceRequirements( VkPhysicalDeviceFloat16Int8FeaturesKHR float16_int8_features = {}; VkPhysicalDevice8BitStorageFeaturesKHR storage_8bit_features = {}; VkPhysicalDevice16BitStorageFeaturesKHR storage_16bit_features = {}; + VkPhysicalDeviceAccelerationStructureFeaturesKHR + acceleration_structure_features = {}; + VkPhysicalDeviceBufferDeviceAddressFeatures buffer_device_address_features = + {}; + VkPhysicalDeviceRayTracingPipelineFeaturesKHR + ray_tracing_pipeline_features = {}; + void* next_ptr = nullptr; - subgroup_size_control_features.sType = - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES_EXT; - subgroup_size_control_features.pNext = nullptr; + if (supports_subgroup_size_control_) { + subgroup_size_control_features.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES_EXT; + subgroup_size_control_features.pNext = next_ptr; + next_ptr = &subgroup_size_control_features; + } - // Add subgroup size control struct into the chain only if - // VK_EXT_subgroup_size_control is supported. variable_pointers_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VARIABLE_POINTER_FEATURES_KHR; - variable_pointers_features.pNext = supports_subgroup_size_control_ - ? &subgroup_size_control_features - : nullptr; + variable_pointers_features.pNext = next_ptr; + next_ptr = &variable_pointers_features; shader_subgroup_extended_types_features.sType = // NOLINTNEXTLINE(whitespace/line_length) VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES; - shader_subgroup_extended_types_features.pNext = &variable_pointers_features; + shader_subgroup_extended_types_features.pNext = next_ptr; + next_ptr = &shader_subgroup_extended_types_features; float16_int8_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR; - float16_int8_features.pNext = &shader_subgroup_extended_types_features; + float16_int8_features.pNext = next_ptr; + next_ptr = &float16_int8_features; storage_8bit_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES_KHR; - storage_8bit_features.pNext = &float16_int8_features; + storage_8bit_features.pNext = next_ptr; + next_ptr = &storage_8bit_features; storage_16bit_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES_KHR; - storage_16bit_features.pNext = &storage_8bit_features; + storage_16bit_features.pNext = next_ptr; + next_ptr = &storage_16bit_features; + + if (supports_acceleration_structure_) { + acceleration_structure_features.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR; + acceleration_structure_features.pNext = next_ptr; + next_ptr = &acceleration_structure_features; + } + + if (supports_buffer_device_address_) { + buffer_device_address_features.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES; + buffer_device_address_features.pNext = next_ptr; + next_ptr = &buffer_device_address_features; + } + + if (supports_ray_tracing_pipeline_) { + ray_tracing_pipeline_features.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR; + ray_tracing_pipeline_features.pNext = next_ptr; + next_ptr = &ray_tracing_pipeline_features; + } VkPhysicalDeviceFeatures2KHR features2 = VkPhysicalDeviceFeatures2KHR(); features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR; - features2.pNext = &storage_16bit_features; + features2.pNext = next_ptr; auto vkGetPhysicalDeviceFeatures2KHR = reinterpret_cast<PFN_vkGetPhysicalDeviceFeatures2KHR>( @@ -889,7 +940,13 @@ amber::Result ConfigHelperVulkan::CheckVulkanPhysicalDeviceRequirements( VK_FALSE) || (feature == kShaderSubgroupExtendedTypes && shader_subgroup_extended_types_features - .shaderSubgroupExtendedTypes == VK_FALSE)) { + .shaderSubgroupExtendedTypes == VK_FALSE) || + (feature == kAccelerationStructure && + acceleration_structure_features.accelerationStructure == VK_FALSE) || + (feature == kBufferDeviceAddress && + buffer_device_address_features.bufferDeviceAddress == VK_FALSE) || + (feature == kRayTracingPipeline && + ray_tracing_pipeline_features.rayTracingPipeline == VK_FALSE)) { return amber::Result("Device does not support all required features"); } } @@ -1047,6 +1104,18 @@ amber::Result ConfigHelperVulkan::CreateDeviceWithFeatures2( VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_SUBGROUP_EXTENDED_TYPES_FEATURES; shader_subgroup_extended_types_feature_.pNext = nullptr; + acceleration_structure_feature_.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR; + acceleration_structure_feature_.pNext = nullptr; + + buffer_device_address_feature_.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES; + buffer_device_address_feature_.pNext = nullptr; + + ray_tracing_pipeline_feature_.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR; + ray_tracing_pipeline_feature_.pNext = nullptr; + std::vector<std::string> exts = required_extensions; void* pnext = nullptr; @@ -1114,6 +1183,36 @@ amber::Result ConfigHelperVulkan::CreateDeviceWithFeatures2( next_ptr = &shader_subgroup_extended_types_feature_.pNext; } + if (supports_acceleration_structure_) { + if (pnext == nullptr) { + pnext = &acceleration_structure_feature_; + } + if (next_ptr != nullptr) { + *next_ptr = &acceleration_structure_feature_; + } + next_ptr = &acceleration_structure_feature_.pNext; + } + + if (supports_buffer_device_address_) { + if (pnext == nullptr) { + pnext = &buffer_device_address_feature_; + } + if (next_ptr != nullptr) { + *next_ptr = &buffer_device_address_feature_; + } + next_ptr = &buffer_device_address_feature_.pNext; + } + + if (supports_ray_tracing_pipeline_) { + if (pnext == nullptr) { + pnext = &ray_tracing_pipeline_feature_; + } + if (next_ptr != nullptr) { + *next_ptr = &ray_tracing_pipeline_feature_; + } + next_ptr = &ray_tracing_pipeline_feature_.pNext; + } + available_features2_.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR; available_features2_.pNext = pnext; @@ -1163,6 +1262,12 @@ amber::Result ConfigHelperVulkan::CreateDeviceWithFeatures2( else if (feature == kShaderSubgroupExtendedTypes) shader_subgroup_extended_types_feature_.shaderSubgroupExtendedTypes = VK_TRUE; + else if (feature == kAccelerationStructure) + acceleration_structure_feature_.accelerationStructure = VK_TRUE; + else if (feature == kBufferDeviceAddress) + buffer_device_address_feature_.bufferDeviceAddress = VK_TRUE; + else if (feature == kRayTracingPipeline) + ray_tracing_pipeline_feature_.rayTracingPipeline = VK_TRUE; } VkPhysicalDeviceFeatures required_vulkan_features = diff --git a/samples/config_helper_vulkan.h b/samples/config_helper_vulkan.h index 6fe3283..4cb90ff 100644 --- a/samples/config_helper_vulkan.h +++ b/samples/config_helper_vulkan.h @@ -119,6 +119,9 @@ class ConfigHelperVulkan : public ConfigHelperImpl { bool supports_shader_16bit_storage_ = false; bool supports_subgroup_size_control_ = false; bool supports_shader_subgroup_extended_types_ = false; + bool supports_acceleration_structure_ = false; + bool supports_buffer_device_address_ = false; + bool supports_ray_tracing_pipeline_ = false; VkPhysicalDeviceFeatures available_features_; VkPhysicalDeviceFeatures2KHR available_features2_; VkPhysicalDeviceVariablePointerFeaturesKHR variable_pointers_feature_; @@ -128,6 +131,10 @@ class ConfigHelperVulkan : public ConfigHelperImpl { VkPhysicalDeviceSubgroupSizeControlFeaturesEXT subgroup_size_control_feature_; VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures shader_subgroup_extended_types_feature_; + VkPhysicalDeviceAccelerationStructureFeaturesKHR + acceleration_structure_feature_; + VkPhysicalDeviceBufferDeviceAddressFeatures buffer_device_address_feature_; + VkPhysicalDeviceRayTracingPipelineFeaturesKHR ray_tracing_pipeline_feature_; }; } // namespace sample diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3b7a4a5..0819840 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2018 The Amber Authors. +# Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -13,6 +14,7 @@ # limitations under the License. set(AMBER_SOURCES + acceleration_structure.cc amber.cc amberscript/parser.cc buffer.cc @@ -141,12 +143,14 @@ if (${AMBER_ENABLE_TESTS}) amberscript/parser_copy_test.cc amberscript/parser_depth_test.cc amberscript/parser_device_feature_test.cc + amberscript/parser_device_property_test.cc amberscript/parser_expect_test.cc amberscript/parser_extension_test.cc amberscript/parser_framebuffer_test.cc amberscript/parser_image_test.cc amberscript/parser_pipeline_test.cc amberscript/parser_pipeline_set_test.cc + amberscript/parser_raytracing_test.cc amberscript/parser_repeat_test.cc amberscript/parser_run_test.cc amberscript/parser_sampler_test.cc diff --git a/src/acceleration_structure.cc b/src/acceleration_structure.cc new file mode 100644 index 0000000..4507b5e --- /dev/null +++ b/src/acceleration_structure.cc @@ -0,0 +1,51 @@ +// Copyright 2024 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. + +#include "src/acceleration_structure.h" + +#include <algorithm> +#include <cassert> +#include <cmath> +#include <cstring> + +namespace amber { + +Geometry::Geometry() = default; +Geometry::~Geometry() = default; + +BLAS::BLAS() = default; +BLAS::~BLAS() = default; + +BLASInstance::~BLASInstance() = default; + +TLAS::TLAS() = default; +TLAS::~TLAS() = default; + +ShaderGroup::ShaderGroup() + : name_(), + generalShader_(nullptr), + closestHitShader_(nullptr), + anyHitShader_(nullptr), + intersectionShader_(nullptr) {} + +ShaderGroup::~ShaderGroup() = default; + +SBTRecord::SBTRecord() = default; +SBTRecord::~SBTRecord() = default; + +SBT::SBT() = default; +SBT::~SBT() = default; + +} // namespace amber diff --git a/src/acceleration_structure.h b/src/acceleration_structure.h new file mode 100644 index 0000000..a5a3218 --- /dev/null +++ b/src/acceleration_structure.h @@ -0,0 +1,257 @@ +// Copyright 2024 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. + +#ifndef SRC_ACCELERATION_STRUCTURE_H_ +#define SRC_ACCELERATION_STRUCTURE_H_ + +#include <cstdint> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "amber/amber.h" +#include "amber/result.h" +#include "amber/value.h" +#include "src/format.h" +#include "src/image.h" + +namespace amber { + +enum class GeometryType : int8_t { + kUnknown = 0, + kTriangle, + kAABB, +}; + +class Shader; + +class Geometry { + public: + Geometry(); + ~Geometry(); + + void SetType(GeometryType type) { type_ = type; } + GeometryType GetType() { return type_; } + + void SetData(std::vector<float>& data) { data_.swap(data); } + std::vector<float>& GetData() { return data_; } + + size_t getVertexCount() const { + return data_.size() / 3; // Three floats to define vertex + } + + size_t getPrimitiveCount() const { + return IsTriangle() ? (getVertexCount() / 3) // 3 vertices per triangle + : IsAABB() ? (getVertexCount() / 2) // 2 vertices per AABB + : 0; + } + + bool IsTriangle() const { return type_ == GeometryType::kTriangle; } + bool IsAABB() const { return type_ == GeometryType::kAABB; } + + private: + GeometryType type_ = GeometryType::kUnknown; + std::vector<float> data_; +}; + +class BLAS { + public: + BLAS(); + ~BLAS(); + + void SetName(const std::string& name) { name_ = name; } + std::string GetName() const { return name_; } + + void AddGeometry(std::unique_ptr<Geometry>* geometry) { + geometry_.push_back(std::move(*geometry)); + } + size_t GetGeometrySize() { return geometry_.size(); } + std::vector<std::unique_ptr<Geometry>>& GetGeometries() { return geometry_; } + + private: + std::string name_; + std::vector<std::unique_ptr<Geometry>> geometry_; +}; + +class BLASInstance { + public: + BLASInstance() + : used_blas_name_(), + used_blas_(nullptr), + transform_(0), + instance_custom_index_(0), + mask_(0xFF), + instanceShaderBindingTableRecordOffset_(0), + flags_(0) {} + ~BLASInstance(); + + void SetUsedBLAS(const std::string& name, BLAS* blas) { + used_blas_name_ = name; + used_blas_ = blas; + } + std::string GetUsedBLASName() const { return used_blas_name_; } + BLAS* GetUsedBLAS() const { return used_blas_; } + + void SetTransform(const std::vector<float>& transform) { + transform_ = transform; + } + const float* GetTransform() const { return transform_.data(); } + + void SetInstanceIndex(uint32_t instance_custom_index) { + instance_custom_index_ = instance_custom_index; + // Make sure argument was not cut off + assert(instance_custom_index_ == instance_custom_index); + } + uint32_t GetInstanceIndex() const { return instance_custom_index_; } + + void SetMask(uint32_t mask) { + mask_ = mask; + // Make sure argument was not cut off + assert(mask_ == mask); + } + uint32_t GetMask() const { return mask_; } + + void SetOffset(uint32_t offset) { + instanceShaderBindingTableRecordOffset_ = offset; + // Make sure argument was not cut off + assert(instanceShaderBindingTableRecordOffset_ == offset); + } + uint32_t GetOffset() const { return instanceShaderBindingTableRecordOffset_; } + + void SetFlags(uint32_t flags) { + flags_ = flags; + // Make sure argument was not cut off + assert(flags_ == flags); + } + uint32_t GetFlags() const { return flags_; } + + private: + std::string used_blas_name_; + BLAS* used_blas_; + std::vector<float> transform_; + uint32_t instance_custom_index_ : 24; + uint32_t mask_ : 8; + uint32_t instanceShaderBindingTableRecordOffset_ : 24; + uint32_t flags_ : 8; +}; + +class TLAS { + public: + TLAS(); + ~TLAS(); + + void SetName(const std::string& name) { name_ = name; } + std::string GetName() const { return name_; } + + void AddInstance(std::unique_ptr<BLASInstance> instance) { + blas_instances_.push_back( + std::unique_ptr<BLASInstance>(instance.release())); + } + size_t GetInstanceSize() { return blas_instances_.size(); } + std::vector<std::unique_ptr<BLASInstance>>& GetInstances() { + return blas_instances_; + } + + private: + std::string name_; + std::vector<std::unique_ptr<BLASInstance>> blas_instances_; +}; + +class ShaderGroup { + public: + ShaderGroup(); + ~ShaderGroup(); + + void SetName(const std::string& name) { name_ = name; } + std::string GetName() const { return name_; } + + void SetGeneralShader(Shader* shader) { generalShader_ = shader; } + Shader* GetGeneralShader() const { return generalShader_; } + + void SetClosestHitShader(Shader* shader) { closestHitShader_ = shader; } + Shader* GetClosestHitShader() const { return closestHitShader_; } + + void SetAnyHitShader(Shader* shader) { anyHitShader_ = shader; } + Shader* GetAnyHitShader() const { return anyHitShader_; } + + void SetIntersectionShader(Shader* shader) { intersectionShader_ = shader; } + Shader* GetIntersectionShader() const { return intersectionShader_; } + + bool IsGeneralGroup() const { return generalShader_ != nullptr; } + bool IsHitGroup() const { + return closestHitShader_ != nullptr || anyHitShader_ != nullptr || + intersectionShader_ != nullptr; + } + + private: + std::string name_; + Shader* generalShader_; + Shader* closestHitShader_; + Shader* anyHitShader_; + Shader* intersectionShader_; +}; + +class SBTRecord { + public: + SBTRecord(); + ~SBTRecord(); + + void SetUsedShaderGroupName(const std::string& shader_group_name, + uint32_t pipeline_index) { + used_shader_group_name_ = shader_group_name; + pipeline_index_ = pipeline_index; + } + std::string GetUsedShaderGroupName() const { return used_shader_group_name_; } + uint32_t GetUsedShaderGroupPipelineIndex() const { return pipeline_index_; } + + void SetCount(const uint32_t count) { count_ = count; } + uint32_t GetCount() const { return count_; } + + private: + std::string used_shader_group_name_; + uint32_t count_ = 1; + uint32_t pipeline_index_ = static_cast<uint32_t>(-1); +}; + +class SBT { + public: + SBT(); + ~SBT(); + + void SetName(const std::string& name) { name_ = name; } + std::string GetName() const { return name_; } + + void AddSBTRecord(std::unique_ptr<SBTRecord> record) { + records_.push_back(std::move(record)); + } + size_t GetSBTRecordCount() { return records_.size(); } + std::vector<std::unique_ptr<SBTRecord>>& GetSBTRecords() { return records_; } + uint32_t GetSBTSize() { + uint32_t size = 0; + for (auto& x : records_) + size += x->GetCount(); + + return size; + } + + private: + std::string name_; + std::vector<std::unique_ptr<SBTRecord>> records_; +}; + +} // namespace amber + +#endif // SRC_ACCELERATION_STRUCTURE_H_ diff --git a/src/amber.cc b/src/amber.cc index 9bf806e..ef5658f 100644 --- a/src/amber.cc +++ b/src/amber.cc @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -133,6 +134,7 @@ Result CreateEngineAndCheckRequirements(const Recipe* recipe, // much else. Refactor this if they end up doing to much here. Result r = engine->Initialize(opts->config, delegate, script->GetRequiredFeatures(), + script->GetRequiredProperties(), script->GetRequiredInstanceExtensions(), script->GetRequiredDeviceExtensions()); if (!r.IsSuccess()) diff --git a/src/amberscript/parser.cc b/src/amberscript/parser.cc index a2e837f..14bd386 100644 --- a/src/amberscript/parser.cc +++ b/src/amberscript/parser.cc @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -22,6 +23,7 @@ #include <utility> #include <vector> +#include "amber/vulkan_header.h" #include "src/image.h" #include "src/make_unique.h" #include "src/sampler.h" @@ -304,6 +306,8 @@ Result Parser::Parse(const std::string& data) { r = ParseDeviceFeature(); } else if (tok == "DEVICE_EXTENSION") { r = ParseDeviceExtension(); + } else if (tok == "DEVICE_PROPERTY") { + r = ParseDeviceProperty(); } else if (tok == "IMAGE") { r = ParseImage(); } else if (tok == "INSTANCE_EXTENSION") { @@ -322,6 +326,8 @@ Result Parser::Parse(const std::string& data) { r = ParseSampler(); } else if (tok == "VIRTUAL_FILE") { r = ParseVirtualFile(); + } else if (tok == "ACCELERATION_STRUCTURE") { + r = ParseAS(); } else { r = Result("unknown token: " + tok); } @@ -404,6 +410,18 @@ Result Parser::ToShaderType(const std::string& str, ShaderType* type) { *type = kShaderTypeTessellationControl; else if (str == "compute") *type = kShaderTypeCompute; + else if (str == "ray_generation") + *type = kShaderTypeRayGeneration; + else if (str == "any_hit") + *type = kShaderTypeAnyHit; + else if (str == "closest_hit") + *type = kShaderTypeClosestHit; + else if (str == "miss") + *type = kShaderTypeMiss; + else if (str == "intersection") + *type = kShaderTypeIntersection; + else if (str == "callable") + *type = kShaderTypeCall; else if (str == "multi") *type = kShaderTypeMulti; else @@ -436,6 +454,8 @@ Result Parser::ToPipelineType(const std::string& str, PipelineType* type) { *type = PipelineType::kCompute; else if (str == "graphics") *type = PipelineType::kGraphics; + else if (str == "raytracing") + *type = PipelineType::kRayTracing; else return Result("unknown pipeline type: " + str); return {}; @@ -548,6 +568,9 @@ Result Parser::ParseShaderBlock() { if (!token->IsIdentifier() || token->AsString() != "END") return Result("SHADER missing END command"); + if (shader->GetTargetEnv().empty() && IsRayTracingShader(type)) + shader->SetTargetEnv("spv1.4"); + r = script_->AddShader(std::move(shader)); if (!r.IsSuccess()) return r; @@ -624,6 +647,10 @@ Result Parser::ParsePipelineBody(const std::string& cmd_name, r = ParsePipelinePatchControlPoints(pipeline.get()); } else if (tok == "BLEND") { r = ParsePipelineBlend(pipeline.get()); + } else if (tok == "SHADER_GROUP") { + r = ParsePipelineShaderGroup(pipeline.get()); + } else if (tok == "SHADER_BINDING_TABLE") { + r = ParseSBT(pipeline.get()); } else { r = Result("unknown token in pipeline block: " + tok); } @@ -1075,8 +1102,8 @@ Result Parser::ParsePipelineBind(Pipeline* pipeline) { if (!token->IsIdentifier()) { return Result( - "missing BUFFER, BUFFER_ARRAY, SAMPLER, or SAMPLER_ARRAY in BIND " - "command"); + "missing BUFFER, BUFFER_ARRAY, SAMPLER, SAMPLER_ARRAY, or " + "ACCELERATION_STRUCTURE in BIND command"); } auto object_type = token->AsString(); @@ -1416,6 +1443,38 @@ Result Parser::ParsePipelineBind(Pipeline* pipeline) { } else { return Result("missing DESCRIPTOR_SET or KERNEL for BIND command"); } + } else if (object_type == "ACCELERATION_STRUCTURE") { + token = tokenizer_->NextToken(); + if (!token->IsIdentifier()) + return Result( + "missing top level acceleration structure name in BIND command"); + + TLAS* tlas = script_->GetTLAS(token->AsString()); + if (!tlas) + return Result("unknown top level acceleration structure: " + + token->AsString()); + + token = tokenizer_->NextToken(); + if (token->AsString() == "DESCRIPTOR_SET") { + token = tokenizer_->NextToken(); + if (!token->IsInteger()) + return Result("invalid value for DESCRIPTOR_SET in BIND command"); + uint32_t descriptor_set = token->AsUint32(); + + token = tokenizer_->NextToken(); + if (!token->IsIdentifier() || token->AsString() != "BINDING") + return Result("missing BINDING for BIND command"); + + token = tokenizer_->NextToken(); + if (!token->IsInteger()) + return Result("invalid value for BINDING in BIND command"); + + uint32_t binding = token->AsUint32(); + + pipeline->AddTLAS(tlas, descriptor_set, binding); + } else { + return Result("missing DESCRIPTOR_SET or BINDING in BIND command"); + } } else { return Result("missing BUFFER or SAMPLER in BIND command"); } @@ -1955,6 +2014,81 @@ Result Parser::ParsePipelineBlend(Pipeline* pipeline) { return ValidateEndOfStatement("BLEND command"); } +Result Parser::ParsePipelineShaderGroup(Pipeline* pipeline) { + std::unique_ptr<Token> token = tokenizer_->NextToken(); + if (!token->IsIdentifier()) + return Result("Group name expected"); + + auto tok = token->AsString(); + if (pipeline->GetShaderGroup(tok)) + return Result("Group name already exists"); + std::unique_ptr<ShaderGroup> group = MakeUnique<ShaderGroup>(); + group->SetName(tok); + + while (true) { + token = tokenizer_->NextToken(); + if (token->IsEOL() || token->IsEOS()) + break; + if (!token->IsIdentifier()) + return Result("Shader name expected"); + + tok = token->AsString(); + Shader* shader = script_->GetShader(tok); + if (shader == nullptr) + return Result("Shader not found: " + tok); + + Result r = pipeline->AddShader(shader, shader->GetType()); + if (!r.IsSuccess()) + return r; + + switch (shader->GetType()) { + case kShaderTypeRayGeneration: + case kShaderTypeMiss: + case kShaderTypeCall: { + if (group->IsHitGroup()) + return Result("Hit group cannot contain general shaders"); + if (group->GetGeneralShader() != nullptr) + return Result("Two general shaders cannot be in one group"); + group->SetGeneralShader(shader); + break; + } + case kShaderTypeAnyHit: { + if (group->IsGeneralGroup()) + return Result("General group cannot contain any hit shaders"); + if (group->GetAnyHitShader() != nullptr) + return Result("Two any hit shaders cannot be in one group"); + group->SetAnyHitShader(shader); + break; + } + case kShaderTypeClosestHit: { + if (group->IsGeneralGroup()) + return Result("General group cannot contain closest hit shaders"); + if (group->GetClosestHitShader() != nullptr) + return Result("Two closest hit shaders cannot be in one group"); + group->SetClosestHitShader(shader); + break; + } + case kShaderTypeIntersection: { + if (group->IsGeneralGroup()) + return Result("General group cannot contain intersection shaders"); + if (group->GetIntersectionShader() != nullptr) + return Result("Two intersection shaders cannot be in one group"); + group->SetIntersectionShader(shader); + break; + } + default: + return Result("Shader must be of raytracing type"); + } + } + + if (!group->IsGeneralGroup() && !group->IsHitGroup()) + return Result("No shaders in shader group defined"); + + pipeline->AddShaderGroup(std::move(group)); + + return {}; +} + Result Parser::ParseStruct() { auto token = tokenizer_->NextToken(); if (!token->IsIdentifier()) @@ -2572,6 +2706,71 @@ Result Parser::ParseRun() { if (!pipeline) return Result("unknown pipeline for RUN command: " + token->AsString()); + if (pipeline->IsRayTracing()) { + auto cmd = MakeUnique<RayTracingCommand>(pipeline); + cmd->SetLine(line); + + while (true) { + if (tokenizer_->PeekNextToken()->IsInteger()) + break; + + token = tokenizer_->NextToken(); + + if (token->IsEOL() || token->IsEOS()) + return Result("Incomplete RUN command"); + + if (!token->IsIdentifier()) + return Result("Shader binding table type is expected"); + + std::string tok = token->AsString(); + token = tokenizer_->NextToken(); + + if (!token->IsIdentifier()) + return Result("Shader binding table name expected"); + + std::string sbtname = token->AsString(); + if (pipeline->GetSBT(sbtname) == nullptr) + return Result("Shader binding table with this name was not defined"); + + if (tok == "RAYGEN") { + if (!cmd->GetRayGenSBTName().empty()) + return Result("RAYGEN shader binding table can specified only once"); + cmd->SetRayGenSBTName(sbtname); + } else if (tok == "MISS") { + if (!cmd->GetMissSBTName().empty()) + return Result("MISS shader binding table can specified only once"); + cmd->SetMissSBTName(sbtname); + } else if (tok == "HIT") { + if (!cmd->GetHitsSBTName().empty()) + return Result("HIT shader binding table can specified only once"); + cmd->SetHitsSBTName(sbtname); + } else if (tok == "CALL") { + if (!cmd->GetCallSBTName().empty()) + return Result("CALL shader binding table can specified only once"); + cmd->SetCallSBTName(sbtname); + } else { + return Result("Unknown shader binding table type"); + } + } + + for (int i = 0; i < 3; i++) { + token = tokenizer_->NextToken(); + + if (!token->IsInteger()) + return Result("invalid parameter for RUN command: " + + token->ToOriginalString()); + if (i == 0) + cmd->SetX(token->AsUint32()); + else if (i == 1) + cmd->SetY(token->AsUint32()); + else + cmd->SetZ(token->AsUint32()); + } + + command_list_.push_back(std::move(cmd)); + return ValidateEndOfStatement("RUN command"); + } + token = tokenizer_->NextToken(); if (token->IsEOL() || token->IsEOS()) return Result("RUN command requires parameters"); @@ -3421,6 +3620,20 @@ Result Parser::ParseDeviceFeature() { return ValidateEndOfStatement("DEVICE_FEATURE command"); } +Result Parser::ParseDeviceProperty() { + auto token = tokenizer_->NextToken(); + if (token->IsEOS() || token->IsEOL()) + return Result("missing property name for DEVICE_PROPERTY command"); + if (!token->IsIdentifier()) + return Result("invalid property name for DEVICE_PROPERTY command"); + if (!script_->IsKnownProperty(token->AsString())) + return Result("unknown property name for DEVICE_PROPERTY command"); + + script_->AddRequiredProperty(token->AsString()); + + return ValidateEndOfStatement("DEVICE_PROPERTY command"); +} + Result Parser::ParseRepeat() { auto token = tokenizer_->NextToken(); if (token->IsEOL() || token->IsEOL()) @@ -3712,6 +3925,442 @@ Result Parser::ParseSampler() { return script_->AddSampler(std::move(sampler)); } +bool Parser::IsRayTracingShader(ShaderType type) { + return type == kShaderTypeRayGeneration || type == kShaderTypeAnyHit || + type == kShaderTypeClosestHit || type == kShaderTypeMiss || + type == kShaderTypeIntersection || type == kShaderTypeCall; +} + +Result Parser::ParseAS() { + auto token = tokenizer_->NextToken(); + if (!token->IsIdentifier()) + return Result("Acceleration structure requires TOP_LEVEL or BOTTOM_LEVEL"); + + Result r; + auto type = token->AsString(); + if (type == "BOTTOM_LEVEL") + r = ParseBLAS(); + else if (type == "TOP_LEVEL") + r = ParseTLAS(); + else + return Result("Unexpected acceleration structure type"); + + return r; +} + +Result Parser::ParseBLAS() { + auto token = tokenizer_->NextToken(); + if (!token->IsIdentifier()) + return Result("Bottom level acceleration structure requires a name"); + + auto name = token->AsString(); + if (script_->GetBLAS(name) != nullptr) + return Result( + "Bottom level acceleration structure with this name already defined"); + + std::unique_ptr<BLAS> blas = MakeUnique<BLAS>(); + blas->SetName(name); + + token = tokenizer_->NextToken(); + if (!token->IsEOL()) + return Result("New line expected"); + + Result r; + while (true) { + token = tokenizer_->NextToken(); + if (token->IsEOL()) { + continue; + } + if (token->IsEOS()) { + return Result("END command missing"); + } + if (!token->IsIdentifier()) { + return Result("Identifier expected"); + } + + auto geom = token->AsString(); + if (geom == "END") { + break; + } else if (geom == "GEOMETRY") { + token = tokenizer_->NextToken(); + if (!token->IsIdentifier()) { + return Result("Identifier expected"); + } + + auto type = token->AsString(); + if (type == "TRIANGLES") { + r = ParseBLASTriangle(blas.get()); + } else if (type == "AABBS") { + r = ParseBLASAABB(blas.get()); + } else { + return Result("Unexpected geometry type"); + } + } else { + return Result("Unexpected identifier"); + } + + if (!r.IsSuccess()) { + return r; + } + } + + if (blas->GetGeometrySize() > 0) { + auto type = blas->GetGeometries()[0]->GetType(); + auto& geometries = blas->GetGeometries(); + for (auto& g : geometries) + if (g->GetType() != type) + return Result("Only one type of geometry is allowed within a BLAS"); + } + + return script_->AddBLAS(std::move(blas)); +} + +Result Parser::ParseBLASTriangle(BLAS* blas) { + std::unique_ptr<Geometry> geometry = MakeUnique<Geometry>(); + std::vector<float> g; + geometry->SetType(GeometryType::kTriangle); + + while (true) { + auto token = tokenizer_->NextToken(); + + if (token->IsEOS()) + return Result("END expected"); + if (token->IsEOL()) + continue; + + if (token->IsIdentifier()) { + std::string tok = token->AsString(); + if (tok == "END") { + break; + } else { + return Result("END or float value is expected"); + } + } else if (token->IsInteger() || token->IsDouble()) { + g.push_back(token->AsFloat()); + } else { + return Result("Unexpected data type"); + } + } + + if (g.empty()) + return Result("No triangles have been specified."); + + if (g.size() % 3 != 0) + return Result("Each vertex consists of three float coordinates."); + + if ((g.size() / 3) % 3 != 0) + return Result("Each triangle should include three vertices."); + + geometry->SetData(g); + + blas->AddGeometry(&geometry); + + return {}; +} + +Result Parser::ParseBLASAABB(BLAS* blas) { + std::unique_ptr<Geometry> geometry = MakeUnique<Geometry>(); + std::vector<float> g; + geometry->SetType(GeometryType::kAABB); + + while (true) { + auto token = tokenizer_->NextToken(); + + if (token->IsEOS()) + return Result("END expected"); + if (token->IsEOL()) + continue; + + if (token->IsIdentifier()) { + std::string tok = token->AsString(); + if (tok == "END") { + break; + } else { + return Result("END or float value is expected"); + } + } else if (token->IsInteger() || token->IsDouble()) { + g.push_back(token->AsFloat()); + } else { + return Result("Unexpected data type"); + } + } + + if (g.empty()) + return Result("No AABBs have been specified."); + + if ((g.size() % 6) != 0) + return Result( + "Each vertex consists of three float coordinates. Each AABB should " + "include two vertices."); + + geometry->SetData(g); + + blas->AddGeometry(&geometry); + + return {}; +} + +Result Parser::ParseTLAS() { + auto token = tokenizer_->NextToken(); + if (!token->IsIdentifier()) + return Result("invalid TLAS name provided"); + + auto name = token->AsString(); + + token = tokenizer_->NextToken(); + if (!token->IsEOL()) + return Result("New line expected"); + + std::unique_ptr<TLAS> tlas = MakeUnique<TLAS>(); + + tlas->SetName(name); + + while (true) { + token = tokenizer_->NextToken(); + if (token->IsEOL()) + continue; + if (token->IsEOS()) + return Result("END command missing"); + if (!token->IsIdentifier()) + return Result("expected identifier"); + + Result r; + std::string tok = token->AsString(); + if (tok == "END") + break; + if (tok == "BOTTOM_LEVEL_INSTANCE") + r = ParseBLASInstance(tlas.get()); + else + r = Result("unknown token: " + tok); + + if (!r.IsSuccess()) + return r; + } + + Result r = script_->AddTLAS(std::move(tlas)); + if (!r.IsSuccess()) + return r; + + return {}; +} + +// BOTTOM_LEVEL_INSTANCE <blas_name> [MASK 0-255] [OFFSET 0-16777215] [INDEX +// 0-16777215] [FLAGS {flags}] [TRANSFORM {float x 12} END] +Result Parser::ParseBLASInstance(TLAS* tlas) { + std::unique_ptr<Token> token; + std::unique_ptr<BLASInstance> instance = MakeUnique<BLASInstance>(); + + token = tokenizer_->NextToken(); + + if (!token->IsIdentifier()) + return Result("Bottom level acceleration structure name expected"); + + std::string name = token->AsString(); + auto ptr = script_->GetBLAS(name); + + if (!ptr) + return Result( + "Bottom level acceleration structure with given name not found"); + + instance->SetUsedBLAS(name, ptr); + + while (true) { + token = tokenizer_->NextToken(); + if (token->IsEOS()) + return Result("Unexpected end"); + if (token->IsEOL()) + continue; + + if (!token->IsIdentifier()) + return Result("expected identifier"); + + Result r; + std::string tok = token->AsString(); + if (tok == "END") { + break; + } else if (tok == "TRANSFORM") { + r = ParseBLASInstanceTransform(instance.get()); + } else if (tok == "FLAGS") { + r = ParseBLASInstanceFlags(instance.get()); + } else if (tok == "MASK") { + token = tokenizer_->NextToken(); + uint64_t v; + + if (token->IsInteger()) + v = token->AsUint64(); + else if (token->IsHex()) + v = token->AsHex(); + else + return Result("Integer or hex value expected"); + + instance->SetMask(uint32_t(v)); + } else if (tok == "OFFSET") { + token = tokenizer_->NextToken(); + uint64_t v; + + if (token->IsInteger()) + v = token->AsUint64(); + else if (token->IsHex()) + v = token->AsHex(); + else + return Result("Integer or hex value expected"); + + instance->SetOffset(uint32_t(v)); + } else if (tok == "INDEX") { + token = tokenizer_->NextToken(); + uint64_t v; + + if (token->IsInteger()) + v = token->AsUint64(); + else if (token->IsHex()) + v = token->AsHex(); + else + return Result("Integer or hex value expected"); + + instance->SetInstanceIndex(uint32_t(v)); + } else { + r = Result("Unknown token in BOTTOM_LEVEL_INSTANCE block: " + tok); + } + + if (!r.IsSuccess()) + return r; + } + + tlas->AddInstance(std::move(instance)); + + return {}; +} + +Result Parser::ParseBLASInstanceTransform(BLASInstance* instance) { + std::unique_ptr<Token> token; + std::vector<float> transform; + + transform.reserve(12); + + while (true) { + token = tokenizer_->NextToken(); + if (token->IsEOL()) + continue; + if (token->IsEOS()) + return Result("END command missing"); + + if (token->IsIdentifier() && token->AsString() == "END") + break; + else if (token->IsDouble() || token->IsInteger()) + transform.push_back(token->AsFloat()); + else + return Result("Unknown token: " + token->AsString()); + } + + if (transform.size() != 12) + return Result("Transform matrix expected to have 12 numbers"); + + instance->SetTransform(transform); + + return {}; +} + +Result Parser::ParseBLASInstanceFlags(BLASInstance* instance) { + std::unique_ptr<Token> token; + uint32_t flags = 0; + bool first_eol = true; + bool singleline = true; + Result r; + + while (true) { + token = tokenizer_->NextToken(); + if (token->IsEOL()) { + if (first_eol) { + first_eol = false; + singleline = (flags != 0); + } + if (singleline) + break; + else + continue; + } + if (token->IsEOS()) + return Result("END command missing"); + + if (token->IsInteger()) { + flags |= token->AsUint32(); + } else if (token->IsHex()) { + flags |= uint32_t(token->AsHex()); + } else if (token->IsIdentifier()) { + if (token->AsString() == "END") + break; + else if (token->AsString() == "TRIANGLE_FACING_CULL_DISABLE") + flags |= VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + else if (token->AsString() == "TRIANGLE_FLIP_FACING") + flags |= VK_GEOMETRY_INSTANCE_TRIANGLE_FLIP_FACING_BIT_KHR; + else if (token->AsString() == "FORCE_OPAQUE") + flags |= VK_GEOMETRY_INSTANCE_FORCE_OPAQUE_BIT_KHR; + else if (token->AsString() == "FORCE_NO_OPAQUE") + flags |= VK_GEOMETRY_INSTANCE_FORCE_NO_OPAQUE_BIT_KHR; + else if (token->AsString() == "FORCE_OPACITY_MICROMAP_2_STATE") + flags |= VK_GEOMETRY_INSTANCE_FORCE_OPACITY_MICROMAP_2_STATE_EXT; + else if (token->AsString() == "DISABLE_OPACITY_MICROMAPS") + flags |= VK_GEOMETRY_INSTANCE_DISABLE_OPACITY_MICROMAPS_EXT; + else + return Result("Unknown flag: " + token->AsString()); + } else { + r = Result("Identifier expected"); + } + + if (!r.IsSuccess()) + return r; + } + + if (r.IsSuccess()) + instance->SetFlags(flags); + + return {}; +} + +Result Parser::ParseSBT(Pipeline* pipeline) { + auto token = tokenizer_->NextToken(); + if (!token->IsIdentifier()) + return Result("SHADER_BINDINGS_TABLE requires a name"); + + auto name = token->AsString(); + if (pipeline->GetSBT(name) != nullptr) + return Result("SHADER_BINDINGS_TABLE with this name already defined"); + + std::unique_ptr<SBT> sbt = MakeUnique<SBT>(); + sbt->SetName(name); + + token = tokenizer_->NextToken(); + if (!token->IsEOL()) + return Result("New line expected"); + + while (true) { + token = tokenizer_->NextToken(); + if (token->IsEOL()) { + continue; + } + if (token->IsEOS()) { + return Result("END command missing"); + } + if (!token->IsIdentifier()) { + return Result("Identifier expected"); + } + + auto tok = token->AsString(); + if (tok == "END") { + break; + } + + std::unique_ptr<SBTRecord> sbtrecord = MakeUnique<SBTRecord>(); + + sbtrecord->SetUsedShaderGroupName(tok, pipeline->GetShaderGroupIndex(tok)); + sbtrecord->SetCount(1); + + sbt->AddSBTRecord(std::move(sbtrecord)); + } + + return pipeline->AddSBT(std::move(sbt)); +} + Result Parser::ParseTolerances(std::vector<Probe::Tolerance>* tolerances) { auto token = tokenizer_->PeekNextToken(); while (!token->IsEOL() && !token->IsEOS()) { diff --git a/src/amberscript/parser.h b/src/amberscript/parser.h index fb81c82..e9cd19c 100644 --- a/src/amberscript/parser.h +++ b/src/amberscript/parser.h @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -75,6 +76,7 @@ class Parser : public amber::Parser { Result ParsePipelineDepth(Pipeline* pipeline); Result ParsePipelineStencil(Pipeline* pipeline); Result ParsePipelineBlend(Pipeline* pipeline); + Result ParsePipelineShaderGroup(Pipeline* pipeline); Result ParseRun(); Result ParseClear(); Result ParseClearColor(); @@ -84,6 +86,7 @@ class Parser : public amber::Parser { Result ParseCopy(); Result ParseDeviceFeature(); Result ParseDeviceExtension(); + Result ParseDeviceProperty(); Result ParseInstanceExtension(); Result ParseRepeat(); Result ParseSet(); @@ -94,6 +97,16 @@ class Parser : public amber::Parser { std::unique_ptr<Pipeline> pipeline); Result ParseShaderSpecialization(Pipeline* pipeline); Result ParseSampler(); + bool IsRayTracingShader(ShaderType type); + Result ParseAS(); + Result ParseBLAS(); + Result ParseBLASTriangle(BLAS* blas); + Result ParseBLASAABB(BLAS* blas); + Result ParseTLAS(); + Result ParseBLASInstance(TLAS* tlas); + Result ParseBLASInstanceTransform(BLASInstance* instance); + Result ParseBLASInstanceFlags(BLASInstance* instance); + Result ParseSBT(Pipeline* pipeline); Result ParseTolerances(std::vector<Probe::Tolerance>* tolerances); /// Parses a set of values out of the token stream. |name| is the name of the diff --git a/src/amberscript/parser_device_property_test.cc b/src/amberscript/parser_device_property_test.cc new file mode 100644 index 0000000..f42fafe --- /dev/null +++ b/src/amberscript/parser_device_property_test.cc @@ -0,0 +1,120 @@ +// Copyright 2024 The Amber Authors. +// +// 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 parseried. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gtest/gtest.h" +#include "src/amberscript/parser.h" + +namespace amber { +namespace amberscript { + +using AmberScriptParserTest = testing::Test; + +TEST_F(AmberScriptParserTest, DeviceProperty) { + std::string in = R"( +DEVICE_PROPERTY FloatControlsProperties.shaderSignedZeroInfNanPreserveFloat16 +DEVICE_PROPERTY FloatControlsProperties.shaderSignedZeroInfNanPreserveFloat32 +DEVICE_PROPERTY FloatControlsProperties.shaderSignedZeroInfNanPreserveFloat64 +DEVICE_PROPERTY FloatControlsProperties.shaderDenormPreserveFloat16 +DEVICE_PROPERTY FloatControlsProperties.shaderDenormPreserveFloat32 +DEVICE_PROPERTY FloatControlsProperties.shaderDenormPreserveFloat64 +DEVICE_PROPERTY FloatControlsProperties.shaderDenormFlushToZeroFloat16 +DEVICE_PROPERTY FloatControlsProperties.shaderDenormFlushToZeroFloat32 +DEVICE_PROPERTY FloatControlsProperties.shaderDenormFlushToZeroFloat64 +DEVICE_PROPERTY FloatControlsProperties.shaderRoundingModeRTEFloat16 +DEVICE_PROPERTY FloatControlsProperties.shaderRoundingModeRTEFloat32 +DEVICE_PROPERTY FloatControlsProperties.shaderRoundingModeRTEFloat64 +DEVICE_PROPERTY FloatControlsProperties.shaderRoundingModeRTZFloat16 +DEVICE_PROPERTY FloatControlsProperties.shaderRoundingModeRTZFloat32 +DEVICE_PROPERTY FloatControlsProperties.shaderRoundingModeRTZFloat64)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_TRUE(r.IsSuccess()) << r.Error(); + + auto script = parser.GetScript(); + const auto& properties = script->GetRequiredProperties(); + ASSERT_EQ(15U, properties.size()); + EXPECT_EQ("FloatControlsProperties.shaderSignedZeroInfNanPreserveFloat16", + properties[0]); + EXPECT_EQ("FloatControlsProperties.shaderSignedZeroInfNanPreserveFloat32", + properties[1]); + EXPECT_EQ("FloatControlsProperties.shaderSignedZeroInfNanPreserveFloat64", + properties[2]); + EXPECT_EQ("FloatControlsProperties.shaderDenormPreserveFloat16", + properties[3]); + EXPECT_EQ("FloatControlsProperties.shaderDenormPreserveFloat32", + properties[4]); + EXPECT_EQ("FloatControlsProperties.shaderDenormPreserveFloat64", + properties[5]); + EXPECT_EQ("FloatControlsProperties.shaderDenormFlushToZeroFloat16", + properties[6]); + EXPECT_EQ("FloatControlsProperties.shaderDenormFlushToZeroFloat32", + properties[7]); + EXPECT_EQ("FloatControlsProperties.shaderDenormFlushToZeroFloat64", + properties[8]); + EXPECT_EQ("FloatControlsProperties.shaderRoundingModeRTEFloat16", + properties[9]); + EXPECT_EQ("FloatControlsProperties.shaderRoundingModeRTEFloat32", + properties[10]); + EXPECT_EQ("FloatControlsProperties.shaderRoundingModeRTEFloat64", + properties[11]); + EXPECT_EQ("FloatControlsProperties.shaderRoundingModeRTZFloat16", + properties[12]); + EXPECT_EQ("FloatControlsProperties.shaderRoundingModeRTZFloat32", + properties[13]); + EXPECT_EQ("FloatControlsProperties.shaderRoundingModeRTZFloat64", + properties[14]); +} + +TEST_F(AmberScriptParserTest, DevicePropertyMissingProperty) { + std::string in = "DEVICE_PROPERTY"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("1: missing property name for DEVICE_PROPERTY command", r.Error()); +} + +TEST_F(AmberScriptParserTest, DevicePropertyUnknown) { + std::string in = "DEVICE_PROPERTY unknown"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("1: unknown property name for DEVICE_PROPERTY command", r.Error()); +} + +TEST_F(AmberScriptParserTest, DevicePropertyInvalid) { + std::string in = "DEVICE_PROPERTY 12345"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("1: invalid property name for DEVICE_PROPERTY command", r.Error()); +} + +TEST_F(AmberScriptParserTest, DevicePropertyExtraParams) { + std::string in = + "DEVICE_PROPERTY FloatControlsProperties.shaderDenormPreserveFloat16 " + "EXTRA"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("1: extra parameters after DEVICE_PROPERTY command: EXTRA", + r.Error()); +} + +} // namespace amberscript +} // namespace amber diff --git a/src/amberscript/parser_raytracing_test.cc b/src/amberscript/parser_raytracing_test.cc new file mode 100644 index 0000000..ca6b33b --- /dev/null +++ b/src/amberscript/parser_raytracing_test.cc @@ -0,0 +1,1297 @@ +// Copyright 2024 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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 parseried. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "gtest/gtest.h" +#include "src/amberscript/parser.h" + +namespace amber { +namespace amberscript { + +using AmberScriptParserTest = testing::Test; + +TEST_F(AmberScriptParserTest, RayTracingBlasName) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: Bottom level acceleration structure requires a name", + r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasNameDup) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name +END +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ( + "4: Bottom level acceleration structure with this name already defined", + r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasNameNoEOL) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("2: New line expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasNoEND) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: END command missing", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasNoId) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name +1)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: Identifier expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasUnexpId) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + UNEXPECTED)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: Unexpected identifier", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasUnexpGeomId) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY 1)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: Identifier expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasUnexpGeom) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY UNEXPECTED)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: Unexpected geometry type", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasGeomSingleType) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY TRIANGLES + 0 0 0 0 1 0 1 0 0 + END + GEOMETRY AABBS + 0 0 0 1 1 1 + END +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("9: Only one type of geometry is allowed within a BLAS", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasTriangleEmpty) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY TRIANGLES + END +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("4: No triangles have been specified.", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasTriangleThreeVertices) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY TRIANGLES + 0.0 0.0 0.0 0.0 0.0 0.0 + END +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("5: Each triangle should include three vertices.", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasTriangleThreeFloats) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY TRIANGLES + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + END +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("5: Each vertex consists of three float coordinates.", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasTriangleNoEND) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY TRIANGLES +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("4: END expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasTriangleUnexpDataType) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY TRIANGLES "unexpected_string" +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: Unexpected data type", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasAABBEmpty) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + END +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("4: No AABBs have been specified.", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasAABBInvalidData) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 0.0 0.0 0.0 0.0 + END +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ( + "5: Each vertex consists of three float coordinates. Each AABB should " + "include two vertices.", + r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasAABBNoEND) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("4: END expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingBlasAABBUnexpDataType) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS "unexpected_string" +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: Unexpected data type", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasName) { + std::string in = R"( +ACCELERATION_STRUCTURE TOP_LEVEL +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: invalid TLAS name provided", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasNameDup) { + std::string in = R"( +ACCELERATION_STRUCTURE TOP_LEVEL tlas_name +END +ACCELERATION_STRUCTURE TOP_LEVEL tlas_name +END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("5: duplicate TLAS name provided", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasNameNoEOL) { + std::string in = R"( +ACCELERATION_STRUCTURE TOP_LEVEL tlas_name END)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("2: New line expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasNoEND) { + std::string in = R"( +ACCELERATION_STRUCTURE TOP_LEVEL tlas_name +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: END command missing", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasNoId) { + std::string in = R"( +ACCELERATION_STRUCTURE TOP_LEVEL tlas_name +1)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: expected identifier", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasUnexpId) { + std::string in = R"( +ACCELERATION_STRUCTURE TOP_LEVEL tlas_name + UNEXPECTED)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: unknown token: UNEXPECTED", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasBlasInstNoName) { + std::string in = R"( +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: Bottom level acceleration structure name expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasBlasInstNoBlas) { + std::string in = R"( +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas1)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: Bottom level acceleration structure with given name not found", + r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasBlasInstUnexpEnd) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("9: Unexpected end", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasBlasInstExpId) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name 1)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("9: expected identifier", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasBlasInstInvalidToken) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name TOKEN)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("9: Unknown token in BOTTOM_LEVEL_INSTANCE block: TOKEN", + r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasBlasInstMask) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name MASK no_mask)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("9: Integer or hex value expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasBlasInstOffset) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name OFFSET no_offset)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("9: Integer or hex value expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasBlasInstIndex) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name INDEX no_index)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("9: Integer or hex value expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasBlasInstFlagsEmpty) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name FLAGS)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("9: END command missing", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasBlasInstFlagsUnkFlag) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name FLAGS 16 0x0F NO_SUCH_FLAG)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("9: Unknown flag: NO_SUCH_FLAG", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasBlasInstFlagsIdExp) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name FLAGS "no_id")"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("9: Identifier expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasBlasInstTransformNoEnd) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name + TRANSFORM + 1 0 0 0 0 1 0 0 0 0 1 0 +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("12: END command missing", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasBlasInstTransformUnknownToken) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name TRANSFORM + INVALID_TOKEN +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("10: Unknown token: INVALID_TOKEN", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingTlasBlasInstTransformIncomplete) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name TRANSFORM + 1 2 + END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("11: Transform matrix expected to have 12 numbers", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBind) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name + END +END + +PIPELINE raytracing my_rtpipeline + BIND 0 tlas1 DESCRIPTOR_SET 0 BINDING 0 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ( + "14: missing BUFFER, BUFFER_ARRAY, SAMPLER, SAMPLER_ARRAY, or " + "ACCELERATION_STRUCTURE in BIND command", + r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindNothing) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name + END +END + +PIPELINE raytracing my_rtpipeline + BIND ACCELERATION_STRUCTURE 0 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: missing top level acceleration structure name in BIND command", + r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindNoTlas) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name + END +END + +PIPELINE raytracing my_rtpipeline + BIND ACCELERATION_STRUCTURE no_tlas +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: unknown top level acceleration structure: no_tlas", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindNoSetOrBinding) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name + END +END + +PIPELINE raytracing my_rtpipeline + BIND ACCELERATION_STRUCTURE tlas1 NO_TOKEN +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: missing DESCRIPTOR_SET or BINDING in BIND command", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindBadSet) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name + END +END + +PIPELINE raytracing my_rtpipeline + BIND ACCELERATION_STRUCTURE tlas1 DESCRIPTOR_SET 0.0 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: invalid value for DESCRIPTOR_SET in BIND command", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindBadBindingKeyword) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name + END +END + +PIPELINE raytracing my_rtpipeline + BIND ACCELERATION_STRUCTURE tlas1 DESCRIPTOR_SET 0 NOT_BINDING +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: missing BINDING for BIND command", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindBadBindingValue) { + std::string in = R"( +ACCELERATION_STRUCTURE BOTTOM_LEVEL blas_name + GEOMETRY AABBS + 0.0 0.0 0.0 1.0 1.0 1.0 + END +END + +ACCELERATION_STRUCTURE TOP_LEVEL tlas1 + BOTTOM_LEVEL_INSTANCE blas_name + END +END + +PIPELINE raytracing my_rtpipeline + BIND ACCELERATION_STRUCTURE tlas1 DESCRIPTOR_SET 0 BINDING 0.0 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: invalid value for BINDING in BIND command", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindShaderGroupNoName) { + std::string in = R"( +PIPELINE raytracing my_rtpipeline + SHADER_GROUP 1 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: Group name expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindShaderGroupNoNameDup) { + std::string in = R"( +SHADER ray_generation raygen1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP group raygen1 + SHADER_GROUP group raygen1 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("9: Group name already exists", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindShaderGroupEmpty) { + std::string in = R"( +PIPELINE raytracing my_rtpipeline + SHADER_GROUP group +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("4: No shaders in shader group defined", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindShaderGroupNoShaderName) { + std::string in = R"( +PIPELINE raytracing my_rtpipeline + SHADER_GROUP group 1 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: Shader name expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindShaderGroupNoShader) { + std::string in = R"( +PIPELINE raytracing my_rtpipeline + SHADER_GROUP group no_shader +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: Shader not found: no_shader", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindShaderGroupInvalidShader) { + std::string in = R"( +SHADER vertex vertex1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP group vertex1 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("8: Shader must be of raytracing type", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindShaderGroupTwoGeneral) { + std::string in = R"( +SHADER ray_generation raygen1 GLSL + #version 460 core + void main() {} +END + +SHADER ray_generation raygen2 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP group raygen1 raygen2 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: Two general shaders cannot be in one group", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindShaderGroupAddGenToHit) { + std::string in = R"( +SHADER ray_generation raygen1 GLSL + #version 460 core + void main() {} +END + +SHADER intersection intersection1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP hit_group intersection1 raygen1 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: Hit group cannot contain general shaders", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindShaderGroupAddAHitToGen) { + std::string in = R"( +SHADER ray_generation raygen1 GLSL + #version 460 core + void main() {} +END + +SHADER any_hit ahit1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP gen_group raygen1 ahit1 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: General group cannot contain any hit shaders", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindShaderGroupAddCHitToGen) { + std::string in = R"( +SHADER ray_generation raygen1 GLSL + #version 460 core + void main() {} +END + +SHADER closest_hit chit1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP gen_group raygen1 chit1 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: General group cannot contain closest hit shaders", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindShaderGroupAddSectToGen) { + std::string in = R"( +SHADER ray_generation raygen1 GLSL + #version 460 core + void main() {} +END + +SHADER intersection sect1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP gen_group raygen1 sect1 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: General group cannot contain intersection shaders", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindShaderGroupAHitDouble) { + std::string in = R"( +SHADER any_hit ahit1 GLSL + #version 460 core + void main() {} +END + +SHADER any_hit ahit2 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP gen_group ahit1 ahit2 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: Two any hit shaders cannot be in one group", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindShaderGroupCHitDouble) { + std::string in = R"( +SHADER closest_hit chit1 GLSL + #version 460 core + void main() {} +END + +SHADER closest_hit chit2 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP gen_group chit1 chit2 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: Two closest hit shaders cannot be in one group", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineBindShaderGroupSectDouble) { + std::string in = R"( +SHADER intersection sect1 GLSL + #version 460 core + void main() {} +END + +SHADER intersection sect2 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP gen_group sect1 sect2 +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("13: Two intersection shaders cannot be in one group", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineSBTNoName) { + std::string in = R"( +PIPELINE raytracing my_rtpipeline + SHADER_BINDING_TABLE +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("4: SHADER_BINDINGS_TABLE requires a name", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineSBTDup) { + std::string in = R"( +SHADER ray_generation raygen1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP gen_group raygen1 + SHADER_BINDING_TABLE sbt1 + END + SHADER_BINDING_TABLE sbt1 + END +END +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("11: SHADER_BINDINGS_TABLE with this name already defined", + r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineSBTExtraToken) { + std::string in = R"( +PIPELINE raytracing my_rtpipeline + SHADER_BINDING_TABLE sbt1 extra_token +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("3: New line expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineSBTNoEnd) { + std::string in = R"( +PIPELINE raytracing my_rtpipeline + SHADER_BINDING_TABLE sbt1 +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("4: END command missing", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingPipelineSBTNoId) { + std::string in = R"( +PIPELINE raytracing my_rtpipeline + SHADER_BINDING_TABLE sbt1 + 0 +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("4: Identifier expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingRun) { + std::string in = R"( +SHADER ray_generation raygen1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP g1 raygen1 + SHADER_BINDING_TABLE sbt1 + g1 + END +END + +RUN my_rtpipeline RAYGEN sbt1 1 1 z +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: invalid parameter for RUN command: z", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingRunIncomplete) { + std::string in = R"( +SHADER ray_generation raygen1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP g1 raygen1 + SHADER_BINDING_TABLE sbt1 + g1 + END +END + +RUN my_rtpipeline +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("15: Incomplete RUN command", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingRunExpectsSBTType) { + std::string in = R"( +SHADER ray_generation raygen1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP g1 raygen1 + SHADER_BINDING_TABLE sbt1 + g1 + END +END + +RUN my_rtpipeline 0.0 +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: Shader binding table type is expected", r.Error()); +} + + +TEST_F(AmberScriptParserTest, RayTracingRunExpectsSBTName) { + std::string in = R"( +SHADER ray_generation raygen1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP g1 raygen1 + SHADER_BINDING_TABLE sbt1 + g1 + END +END + +RUN my_rtpipeline RAYGEN 0.0 +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: Shader binding table name expected", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingRunExpectsSBTUndefined) { + std::string in = R"( +PIPELINE raytracing my_rtpipeline +END +RUN my_rtpipeline RAYGEN sbt3 +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("4: Shader binding table with this name was not defined", + r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingRunExpectsSBTUnknownType) { + std::string in = R"( +SHADER ray_generation raygen1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP g1 raygen1 + SHADER_BINDING_TABLE sbt1 + g1 + END +END + +RUN my_rtpipeline RAYGEN2 sbt1 +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: Unknown shader binding table type", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingRunSBTRGenDup) { + std::string in = R"( +SHADER ray_generation raygen1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP g1 raygen1 + SHADER_BINDING_TABLE sbt1 + g1 + END +END + +RUN my_rtpipeline RAYGEN sbt1 RAYGEN sbt1 +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: RAYGEN shader binding table can specified only once", + r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingRunSBTMissDup) { + std::string in = R"( +SHADER miss miss1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP g1 miss1 + SHADER_BINDING_TABLE sbt1 + g1 + END +END + +RUN my_rtpipeline MISS sbt1 MISS sbt1 +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: MISS shader binding table can specified only once", + r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingRunSBTHitDup) { + std::string in = R"( +SHADER any_hit ahit1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP g1 ahit1 + SHADER_BINDING_TABLE sbt1 + g1 + END +END + +RUN my_rtpipeline HIT sbt1 HIT sbt1 +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: HIT shader binding table can specified only once", r.Error()); +} + +TEST_F(AmberScriptParserTest, RayTracingRunSBTCallDup) { + std::string in = R"( +SHADER callable call1 GLSL + #version 460 core + void main() {} +END + +PIPELINE raytracing my_rtpipeline + SHADER_GROUP g1 call1 + SHADER_BINDING_TABLE sbt1 + g1 + END +END + +RUN my_rtpipeline CALL sbt1 CALL sbt1 +)"; + + Parser parser; + Result r = parser.Parse(in); + ASSERT_FALSE(r.IsSuccess()); + EXPECT_EQ("14: CALL shader binding table can specified only once", r.Error()); +} + +} // namespace amberscript +} // namespace amber diff --git a/src/command.cc b/src/command.cc index ea242b7..75e5916 100644 --- a/src/command.cc +++ b/src/command.cc @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -46,6 +47,10 @@ ComputeCommand* Command::AsCompute() { return static_cast<ComputeCommand*>(this); } +RayTracingCommand* Command::AsRayTracing() { + return static_cast<RayTracingCommand*>(this); +} + CopyCommand* Command::AsCopy() { return static_cast<CopyCommand*>(this); } @@ -184,4 +189,14 @@ RepeatCommand::RepeatCommand(uint32_t count) RepeatCommand::~RepeatCommand() = default; +TLASCommand::TLASCommand(Pipeline* pipeline) + : BindableResourceCommand(Type::kTLAS, pipeline) {} + +TLASCommand::~TLASCommand() = default; + +RayTracingCommand::RayTracingCommand(Pipeline* pipeline) + : PipelineCommand(Type::kRayTracing, pipeline) {} + +RayTracingCommand::~RayTracingCommand() = default; + } // namespace amber diff --git a/src/command.h b/src/command.h index 213d7b8..a0bdc76 100644 --- a/src/command.h +++ b/src/command.h @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -23,6 +24,7 @@ #include "amber/shader_info.h" #include "amber/value.h" +#include "src/acceleration_structure.h" #include "src/buffer.h" #include "src/command_data.h" #include "src/pipeline_data.h" @@ -46,7 +48,9 @@ class PatchParameterVerticesCommand; class Pipeline; class ProbeCommand; class ProbeSSBOCommand; +class RayTracingCommand; class RepeatCommand; +class TLASCommand; /// Base class for all commands. class Command { @@ -69,7 +73,9 @@ class Command { kProbeSSBO, kBuffer, kRepeat, - kSampler + kSampler, + kTLAS, + kRayTracing }; virtual ~Command(); @@ -81,6 +87,8 @@ class Command { bool IsDrawArrays() const { return command_type_ == Type::kDrawArrays; } bool IsCompareBuffer() const { return command_type_ == Type::kCompareBuffer; } bool IsCompute() const { return command_type_ == Type::kCompute; } + bool IsRayTracing() const { return command_type_ == Type::kRayTracing; } + bool IsTLAS() const { return command_type_ == Type::kTLAS; } bool IsCopy() const { return command_type_ == Type::kCopy; } bool IsProbe() const { return command_type_ == Type::kProbe; } bool IsProbeSSBO() const { return command_type_ == Type::kProbeSSBO; } @@ -101,6 +109,7 @@ class Command { ClearStencilCommand* AsClearStencil(); CompareBufferCommand* AsCompareBuffer(); ComputeCommand* AsCompute(); + RayTracingCommand* AsRayTracing(); CopyCommand* AsCopy(); DrawArraysCommand* AsDrawArrays(); DrawRectCommand* AsDrawRect(); @@ -711,6 +720,60 @@ class RepeatCommand : public Command { std::vector<std::unique_ptr<Command>> commands_; }; +/// Command for setting TLAS parameters and binding. +class TLASCommand : public BindableResourceCommand { + public: + explicit TLASCommand(Pipeline* pipeline); + ~TLASCommand() override; + + void SetTLAS(TLAS* tlas) { tlas_ = tlas; } + TLAS* GetTLAS() const { return tlas_; } + + std::string ToString() const override { return "TLASCommand"; } + + private: + TLAS* tlas_ = nullptr; +}; + +/// Command to execute a ray tracing command. +class RayTracingCommand : public PipelineCommand { + public: + explicit RayTracingCommand(Pipeline* pipeline); + ~RayTracingCommand() override; + + void SetX(uint32_t x) { x_ = x; } + uint32_t GetX() const { return x_; } + + void SetY(uint32_t y) { y_ = y; } + uint32_t GetY() const { return y_; } + + void SetZ(uint32_t z) { z_ = z; } + uint32_t GetZ() const { return z_; } + + void SetRayGenSBTName(const std::string& name) { rgen_sbt_name_ = name; } + std::string GetRayGenSBTName() const { return rgen_sbt_name_; } + + void SetMissSBTName(const std::string& name) { miss_sbt_name_ = name; } + std::string GetMissSBTName() const { return miss_sbt_name_; } + + void SetHitsSBTName(const std::string& name) { hits_sbt_name_ = name; } + std::string GetHitsSBTName() const { return hits_sbt_name_; } + + void SetCallSBTName(const std::string& name) { call_sbt_name_ = name; } + std::string GetCallSBTName() const { return call_sbt_name_; } + + std::string ToString() const override { return "RayTracingCommand"; } + + private: + uint32_t x_ = 0; + uint32_t y_ = 0; + uint32_t z_ = 0; + std::string rgen_sbt_name_; + std::string miss_sbt_name_; + std::string hits_sbt_name_; + std::string call_sbt_name_; +}; + } // namespace amber #endif // SRC_COMMAND_H_ diff --git a/src/engine.h b/src/engine.h index d444258..71c14c8 100644 --- a/src/engine.h +++ b/src/engine.h @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -71,6 +72,7 @@ class Engine { EngineConfig* config, Delegate* delegate, const std::vector<std::string>& features, + const std::vector<std::string>& properties, const std::vector<std::string>& instance_extensions, const std::vector<std::string>& device_extensions) = 0; @@ -101,6 +103,9 @@ class Engine { /// Execute the compute command virtual Result DoCompute(const ComputeCommand* cmd) = 0; + /// Execute the trace rays command + virtual Result DoTraceRays(const RayTracingCommand* cmd) = 0; + /// Execute the entry point command virtual Result DoEntryPoint(const EntryPointCommand* cmd) = 0; diff --git a/src/executor.cc b/src/executor.cc index 53e3e55..40cb353 100644 --- a/src/executor.cc +++ b/src/executor.cc @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -159,6 +160,8 @@ Result Executor::ExecuteCommand(Engine* engine, Command* cmd) { return engine->DoDrawArrays(cmd->AsDrawArrays()); if (cmd->IsCompute()) return engine->DoCompute(cmd->AsCompute()); + if (cmd->IsRayTracing()) + return engine->DoTraceRays(cmd->AsRayTracing()); if (cmd->IsEntryPoint()) return engine->DoEntryPoint(cmd->AsEntryPoint()); if (cmd->IsPatchParameterVertices()) diff --git a/src/executor_test.cc b/src/executor_test.cc index fa57c74..2f9429e 100644 --- a/src/executor_test.cc +++ b/src/executor_test.cc @@ -37,9 +37,11 @@ class EngineStub : public Engine { Result Initialize(EngineConfig*, Delegate*, const std::vector<std::string>& features, + const std::vector<std::string>& properties, const std::vector<std::string>& instance_exts, const std::vector<std::string>& device_exts) override { features_ = features; + properties_ = properties; instance_extensions_ = instance_exts; device_extensions_ = device_exts; return {}; @@ -167,6 +169,10 @@ class EngineStub : public Engine { return {}; } + Result DoTraceRays(const RayTracingCommand*) override { + return Result("traceray stub not implemented"); + } + private: bool fail_clear_command_ = false; bool fail_clear_color_command_ = false; @@ -193,6 +199,7 @@ class EngineStub : public Engine { bool did_buffer_command_ = false; std::vector<std::string> features_; + std::vector<std::string> properties_; std::vector<std::string> instance_extensions_; std::vector<std::string> device_extensions_; @@ -207,11 +214,12 @@ class VkScriptExecutorTest : public testing::Test { std::unique_ptr<Engine> MakeEngine() { return MakeUnique<EngineStub>(); } std::unique_ptr<Engine> MakeAndInitializeEngine( const std::vector<std::string>& features, + const std::vector<std::string>& properties, const std::vector<std::string>& instance_extensions, const std::vector<std::string>& device_extensions) { std::unique_ptr<Engine> engine = MakeUnique<EngineStub>(); - engine->Initialize(nullptr, nullptr, features, instance_extensions, - device_extensions); + engine->Initialize(nullptr, nullptr, features, properties, + instance_extensions, device_extensions); return engine; } EngineStub* ToStub(Engine* engine) { @@ -233,6 +241,7 @@ logicOp)"; auto script = parser.GetScript(); auto engine = MakeAndInitializeEngine(script->GetRequiredFeatures(), + script->GetRequiredProperties(), script->GetRequiredInstanceExtensions(), script->GetRequiredDeviceExtensions()); @@ -263,6 +272,7 @@ VK_KHR_variable_pointers)"; auto script = parser.GetScript(); auto engine = MakeAndInitializeEngine(script->GetRequiredFeatures(), + script->GetRequiredProperties(), script->GetRequiredInstanceExtensions(), script->GetRequiredDeviceExtensions()); @@ -293,6 +303,7 @@ depthstencil D24_UNORM_S8_UINT)"; auto script = parser.GetScript(); auto engine = MakeAndInitializeEngine(script->GetRequiredFeatures(), + script->GetRequiredProperties(), script->GetRequiredInstanceExtensions(), script->GetRequiredDeviceExtensions()); @@ -320,6 +331,7 @@ fence_timeout 12345)"; auto script = parser.GetScript(); auto engine = MakeAndInitializeEngine(script->GetRequiredFeatures(), + script->GetRequiredProperties(), script->GetRequiredInstanceExtensions(), script->GetRequiredDeviceExtensions()); @@ -355,6 +367,7 @@ fence_timeout 12345)"; auto script = parser.GetScript(); auto engine = MakeAndInitializeEngine(script->GetRequiredFeatures(), + script->GetRequiredProperties(), script->GetRequiredInstanceExtensions(), script->GetRequiredDeviceExtensions()); diff --git a/src/pipeline.cc b/src/pipeline.cc index a9b66ca..4be1de2 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -61,7 +62,8 @@ Pipeline::ShaderInfo::ShaderInfo(const ShaderInfo&) = default; Pipeline::ShaderInfo::~ShaderInfo() = default; -Pipeline::Pipeline(PipelineType type) : pipeline_type_(type) {} +Pipeline::Pipeline(PipelineType type) : pipeline_type_(type) { +} Pipeline::~Pipeline() = default; @@ -99,13 +101,15 @@ Result Pipeline::AddShader(Shader* shader, ShaderType shader_type) { return Result("can not add a compute shader to a graphics pipeline"); } - for (auto& info : shaders_) { - const auto* is = info.GetShader(); - if (is == shader) - return Result("can not add duplicate shader to pipeline"); - if (is->GetType() == shader_type) { - info.SetShader(shader); - return {}; + if (pipeline_type_ != PipelineType::kRayTracing) { + for (auto& info : shaders_) { + const auto* is = info.GetShader(); + if (is == shader) + return Result("can not add duplicate shader to pipeline"); + if (is->GetType() == shader_type) { + info.SetShader(shader); + return {}; + } } } @@ -292,12 +296,21 @@ Result Pipeline::Validate() const { } } - if (pipeline_type_ == PipelineType::kGraphics) + if (pipeline_type_ == PipelineType::kRayTracing) + return ValidateRayTracing(); + else if (pipeline_type_ == PipelineType::kGraphics) return ValidateGraphics(); return ValidateCompute(); } +Result Pipeline::ValidateRayTracing() const { + if (shader_groups_.empty() && shaders_.empty() && tlases_.empty()) + return Result("Shader groups are missing"); + + return {}; +} + Result Pipeline::ValidateGraphics() const { if (color_attachments_.empty()) return Result("PIPELINE missing color attachment"); @@ -655,6 +668,15 @@ void Pipeline::AddSampler(uint32_t mask, info.binding = binding; } +void Pipeline::AddTLAS(TLAS* tlas, uint32_t descriptor_set, uint32_t binding) { + tlases_.push_back(TLASInfo(tlas)); + + auto& info = tlases_.back(); + + info.descriptor_set = descriptor_set; + info.binding = binding; +} + void Pipeline::ClearSamplers(uint32_t descriptor_set, uint32_t binding) { samplers_.erase( std::remove_if(samplers_.begin(), samplers_.end(), diff --git a/src/pipeline.h b/src/pipeline.h index 12792f0..24b29a1 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -23,6 +24,7 @@ #include <vector> #include "amber/result.h" +#include "src/acceleration_structure.h" #include "src/buffer.h" #include "src/command_data.h" #include "src/pipeline_data.h" @@ -31,7 +33,7 @@ namespace amber { -enum class PipelineType { kCompute = 0, kGraphics }; +enum class PipelineType { kCompute = 0, kGraphics, kRayTracing }; /// Stores all information related to a pipeline. class Pipeline { @@ -216,6 +218,15 @@ class Pipeline { uint32_t mask = 0; }; + /// Information on a top level acceleration structure at the pipeline. + struct TLASInfo { + TLASInfo() = default; + explicit TLASInfo(TLAS* as) : tlas(as) {} + + TLAS* tlas = nullptr; + uint32_t descriptor_set = 0; + uint32_t binding = 0; + }; static const char* kGeneratedColorBuffer; static const char* kGeneratedDepthBuffer; static const char* kGeneratedPushConstantBuffer; @@ -227,6 +238,9 @@ class Pipeline { bool IsGraphics() const { return pipeline_type_ == PipelineType::kGraphics; } bool IsCompute() const { return pipeline_type_ == PipelineType::kCompute; } + bool IsRayTracing() const { + return pipeline_type_ == PipelineType::kRayTracing; + } PipelineType GetType() const { return pipeline_type_; } @@ -261,6 +275,18 @@ class Pipeline { return nullptr; } + /// Returns a success result if |shader| found and the shader index is + /// returned in |out|. Returns failure otherwise. + Result GetShaderIndex(Shader* shader, uint32_t* out) const { + for (size_t index = 0; index < shaders_.size(); index++) { + if (shaders_[index].GetShader() == shader) { + *out = static_cast<uint32_t>(index); + return {}; + } + } + return Result("Referred shader not found in group"); + } + /// Sets the |type| of |shader| in the pipeline. Result SetShaderType(const Shader* shader, ShaderType type); /// Sets the entry point |name| for |shader| in this pipeline. @@ -377,6 +403,68 @@ class Pipeline { /// Returns information on all samplers in this pipeline. const std::vector<SamplerInfo>& GetSamplers() const { return samplers_; } + /// Adds |tlas| to the pipeline at the given |descriptor_set| and + /// |binding|. + void AddTLAS(TLAS* tlas, uint32_t descriptor_set, uint32_t binding); + + /// Returns information on all bound TLAS in the pipeline. + std::vector<TLASInfo>& GetTLASes() { return tlases_; } + + /// Adds |sbt| to the list of known shader binding tables. + /// The |sbt| must have a unique name within pipeline. + Result AddSBT(std::unique_ptr<SBT> sbt) { + if (name_to_sbt_.count(sbt->GetName()) > 0) + return Result("duplicate SBT name provided"); + + sbts_.push_back(std::move(sbt)); + name_to_sbt_[sbts_.back()->GetName()] = sbts_.back().get(); + + return {}; + } + + /// Retrieves the SBT with |name|, |nullptr| if not found. + SBT* GetSBT(const std::string& name) const { + auto it = name_to_sbt_.find(name); + return it == name_to_sbt_.end() ? nullptr : it->second; + } + + /// Retrieves a list of all SBTs. + const std::vector<std::unique_ptr<SBT>>& GetSBTs() const { return sbts_; } + + /// Adds |group| to the list of known shader groups. + /// The |group| must have a unique name within pipeline. + Result AddShaderGroup(std::unique_ptr<ShaderGroup> group) { + if (name_to_shader_group_.count(group->GetName()) > 0) + return Result("shader group name already exists"); + + shader_groups_.push_back(std::move(group)); + name_to_shader_group_[shader_groups_.back()->GetName()] = + shader_groups_.back().get(); + + return {}; + } + + /// Retrieves the Shader Group with |name|, |nullptr| if not found. + ShaderGroup* GetShaderGroup(const std::string& name) const { + auto it = name_to_shader_group_.find(name); + return it == name_to_shader_group_.end() ? nullptr : it->second; + } + uint32_t GetShaderGroupIndex(const std::string& name) const { + ShaderGroup* shader_group = GetShaderGroup(name); + + for (size_t i = 0; i < shader_groups_.size(); i++) { + if (shader_groups_[i].get() == shader_group) { + return static_cast<uint32_t>(i); + } + } + return static_cast<uint32_t>(-1); + } + + /// Retrieves a list of all Shader Groups. + const std::vector<std::unique_ptr<ShaderGroup>>& GetShaderGroups() const { + return shader_groups_; + } + /// Updates the descriptor set and binding info for the OpenCL-C kernel bound /// to the pipeline. No effect for other shader formats. Result UpdateOpenCLBufferBindings(); @@ -435,10 +523,12 @@ class Pipeline { Result ValidateGraphics() const; Result ValidateCompute() const; + Result ValidateRayTracing() const; PipelineType pipeline_type_ = PipelineType::kCompute; std::string name_; std::vector<ShaderInfo> shaders_; + std::vector<TLASInfo> tlases_; std::vector<BufferInfo> color_attachments_; std::vector<BufferInfo> resolve_targets_; std::vector<BufferInfo> vertex_buffers_; @@ -459,6 +549,11 @@ class Pipeline { std::map<std::pair<uint32_t, uint32_t>, Buffer*> opencl_pod_buffer_map_; std::vector<std::unique_ptr<Sampler>> opencl_literal_samplers_; std::unique_ptr<Buffer> opencl_push_constants_; + + std::map<std::string, ShaderGroup*> name_to_shader_group_; + std::vector<std::unique_ptr<ShaderGroup>> shader_groups_; + std::map<std::string, SBT*> name_to_sbt_; + std::vector<std::unique_ptr<SBT>> sbts_; }; } // namespace amber diff --git a/src/recipe.cc b/src/recipe.cc index 7e22bd4..7d46f05 100644 --- a/src/recipe.cc +++ b/src/recipe.cc @@ -35,6 +35,10 @@ std::vector<std::string> Recipe::GetRequiredFeatures() const { return impl_ ? impl_->GetRequiredFeatures() : std::vector<std::string>(); } +std::vector<std::string> Recipe::GetRequiredProperties() const { + return impl_ ? impl_->GetRequiredProperties() : std::vector<std::string>(); +} + std::vector<std::string> Recipe::GetRequiredDeviceExtensions() const { return impl_ ? impl_->GetRequiredDeviceExtensions() : std::vector<std::string>(); diff --git a/src/script.cc b/src/script.cc index 091949e..a5d8bed 100644 --- a/src/script.cc +++ b/src/script.cc @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -131,7 +132,32 @@ bool Script::IsKnownFeature(const std::string& name) const { name == "SubgroupSupportedStages.compute" || name == "IndexTypeUint8Features.indexTypeUint8" || name == - "ShaderSubgroupExtendedTypesFeatures.shaderSubgroupExtendedTypes"; + "ShaderSubgroupExtendedTypesFeatures" + ".shaderSubgroupExtendedTypes" || + name == "RayTracingPipelineFeaturesKHR.rayTracingPipeline" || + name == "AccelerationStructureFeaturesKHR.accelerationStructure" || + name == "BufferDeviceAddressFeatures.bufferDeviceAddress"; +} + +bool Script::IsKnownProperty(const std::string& name) const { + return name == + "FloatControlsProperties.shaderSignedZeroInfNanPreserveFloat16" || + name == + "FloatControlsProperties.shaderSignedZeroInfNanPreserveFloat32" || + name == + "FloatControlsProperties.shaderSignedZeroInfNanPreserveFloat64" || + name == "FloatControlsProperties.shaderDenormPreserveFloat16" || + name == "FloatControlsProperties.shaderDenormPreserveFloat32" || + name == "FloatControlsProperties.shaderDenormPreserveFloat64" || + name == "FloatControlsProperties.shaderDenormFlushToZeroFloat16" || + name == "FloatControlsProperties.shaderDenormFlushToZeroFloat32" || + name == "FloatControlsProperties.shaderDenormFlushToZeroFloat64" || + name == "FloatControlsProperties.shaderRoundingModeRTEFloat16" || + name == "FloatControlsProperties.shaderRoundingModeRTEFloat32" || + name == "FloatControlsProperties.shaderRoundingModeRTEFloat64" || + name == "FloatControlsProperties.shaderRoundingModeRTZFloat16" || + name == "FloatControlsProperties.shaderRoundingModeRTZFloat32" || + name == "FloatControlsProperties.shaderRoundingModeRTZFloat64"; } type::Type* Script::ParseType(const std::string& str) { diff --git a/src/script.h b/src/script.h index b4c6e1a..edd8f94 100644 --- a/src/script.h +++ b/src/script.h @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -25,6 +26,7 @@ #include "amber/recipe.h" #include "amber/result.h" +#include "src/acceleration_structure.h" #include "src/buffer.h" #include "src/command.h" #include "src/engine.h" @@ -43,6 +45,7 @@ class Script : public RecipeImpl { ~Script() override; bool IsKnownFeature(const std::string& name) const; + bool IsKnownProperty(const std::string& name) const; /// Retrieves information on the shaders in the given script. std::vector<ShaderInfo> GetShaderInfo() const override; @@ -52,6 +55,10 @@ class Script : public RecipeImpl { return engine_info_.required_features; } + std::vector<std::string> GetRequiredProperties() const override { + return engine_info_.required_properties; + } + /// Returns required device extensions in the given recipe. std::vector<std::string> GetRequiredDeviceExtensions() const override { return engine_info_.required_device_extensions; @@ -155,6 +162,52 @@ class Script : public RecipeImpl { return it == name_to_sampler_.end() ? nullptr : it->second; } + /// Adds |blas| to the list of known bottom level acceleration structures. + /// The |blas| must have a unique name over all BLASes in the script. + Result AddBLAS(std::unique_ptr<BLAS> blas) { + if (name_to_blas_.count(blas->GetName()) > 0) + return Result("duplicate BLAS name provided"); + + blases_.push_back(std::move(blas)); + name_to_blas_[blases_.back()->GetName()] = blases_.back().get(); + + return {}; + } + + /// Retrieves the BLAS with |name|, |nullptr| if not found. + BLAS* GetBLAS(const std::string& name) const { + auto it = name_to_blas_.find(name); + return it == name_to_blas_.end() ? nullptr : it->second; + } + + /// Retrieves a list of all BLASes. + const std::vector<std::unique_ptr<BLAS>>& GetBLASes() const { + return blases_; + } + + /// Adds |tlas| to the list of known top level acceleration structures. + /// The |tlas| must have a unique name over all TLASes in the script. + Result AddTLAS(std::unique_ptr<TLAS> tlas) { + if (name_to_tlas_.count(tlas->GetName()) > 0) + return Result("duplicate TLAS name provided"); + + tlases_.push_back(std::move(tlas)); + name_to_tlas_[tlases_.back()->GetName()] = tlases_.back().get(); + + return {}; + } + + /// Retrieves the TLAS with |name|, |nullptr| if not found. + TLAS* GetTLAS(const std::string& name) const { + auto it = name_to_tlas_.find(name); + return it == name_to_tlas_.end() ? nullptr : it->second; + } + + /// Retrieves a list of all TLASes. + const std::vector<std::unique_ptr<TLAS>>& GetTLASes() const { + return tlases_; + } + /// Retrieves a list of all samplers. const std::vector<std::unique_ptr<Sampler>>& GetSamplers() const { return samplers_; @@ -166,6 +219,12 @@ class Script : public RecipeImpl { engine_info_.required_features.push_back(feature); } + /// Adds |prop| to the list of properties that must be supported by the + /// engine. + void AddRequiredProperty(const std::string& prop) { + engine_info_.required_properties.push_back(prop); + } + /// Checks if |feature| is in required features bool IsRequiredFeature(const std::string& feature) const { return std::find(engine_info_.required_features.begin(), @@ -173,6 +232,13 @@ class Script : public RecipeImpl { feature) != engine_info_.required_features.end(); } + /// Checks if |prop| is in required features + bool IsRequiredProperty(const std::string& prop) const { + return std::find(engine_info_.required_properties.begin(), + engine_info_.required_properties.end(), + prop) != engine_info_.required_properties.end(); + } + /// Adds |ext| to the list of device extensions that must be supported. void AddRequiredDeviceExtension(const std::string& ext) { engine_info_.required_device_extensions.push_back(ext); @@ -257,6 +323,7 @@ class Script : public RecipeImpl { private: struct { std::vector<std::string> required_features; + std::vector<std::string> required_properties; std::vector<std::string> required_device_extensions; std::vector<std::string> required_instance_extensions; } engine_info_; @@ -267,12 +334,16 @@ class Script : public RecipeImpl { std::map<std::string, Buffer*> name_to_buffer_; std::map<std::string, Sampler*> name_to_sampler_; std::map<std::string, Pipeline*> name_to_pipeline_; + std::map<std::string, BLAS*> name_to_blas_; + std::map<std::string, TLAS*> name_to_tlas_; std::map<std::string, std::unique_ptr<type::Type>> name_to_type_; std::vector<std::unique_ptr<Shader>> shaders_; std::vector<std::unique_ptr<Command>> commands_; std::vector<std::unique_ptr<Buffer>> buffers_; std::vector<std::unique_ptr<Sampler>> samplers_; std::vector<std::unique_ptr<Pipeline>> pipelines_; + std::vector<std::unique_ptr<BLAS>> blases_; + std::vector<std::unique_ptr<TLAS>> tlases_; std::vector<std::unique_ptr<type::Type>> types_; std::vector<std::unique_ptr<Format>> formats_; std::unique_ptr<VirtualFileStore> virtual_files_; diff --git a/src/shader_compiler.cc b/src/shader_compiler.cc index 285dd97..bec44e4 100644 --- a/src/shader_compiler.cc +++ b/src/shader_compiler.cc @@ -241,6 +241,18 @@ Result ShaderCompiler::CompileGlsl(const Shader* shader, kind = shaderc_tess_control_shader; else if (shader->GetType() == kShaderTypeTessellationEvaluation) kind = shaderc_tess_evaluation_shader; + else if (shader->GetType() == kShaderTypeRayGeneration) + kind = shaderc_raygen_shader; + else if (shader->GetType() == kShaderTypeAnyHit) + kind = shaderc_anyhit_shader; + else if (shader->GetType() == kShaderTypeClosestHit) + kind = shaderc_closesthit_shader; + else if (shader->GetType() == kShaderTypeMiss) + kind = shaderc_miss_shader; + else if (shader->GetType() == kShaderTypeIntersection) + kind = shaderc_intersection_shader; + else if (shader->GetType() == kShaderTypeCall) + kind = shaderc_callable_shader; else return Result("Unknown shader type"); diff --git a/src/vulkan/CMakeLists.txt b/src/vulkan/CMakeLists.txt index 71efeff..f816b3a 100644 --- a/src/vulkan/CMakeLists.txt +++ b/src/vulkan/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright 2018 The Amber Authors. +# Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -13,6 +14,7 @@ # limitations under the License. set(VULKAN_ENGINE_SOURCES + blas.cc buffer_descriptor.cc buffer_backed_descriptor.cc command_buffer.cc @@ -27,9 +29,13 @@ set(VULKAN_ENGINE_SOURCES index_buffer.cc pipeline.cc push_constant.cc + raytracing_pipeline.cc resource.cc sampler.cc sampler_descriptor.cc + sbt.cc + tlas.cc + tlas_descriptor.cc transfer_buffer.cc transfer_image.cc vertex_buffer.cc diff --git a/src/vulkan/blas.cc b/src/vulkan/blas.cc new file mode 100644 index 0000000..f80ec51 --- /dev/null +++ b/src/vulkan/blas.cc @@ -0,0 +1,268 @@ +// Copyright 2024 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. + +#include "src/vulkan/blas.h" + +#include <cstring> + +#include "src/vulkan/command_buffer.h" + +namespace amber { +namespace vulkan { + +inline VkDeviceSize align(VkDeviceSize v, VkDeviceSize a) { + return (v + a - 1) & ~(a - 1); +} + +BLAS::BLAS(Device* device) : device_(device) {} + +BLAS::~BLAS() { + if (blas_ != VK_NULL_HANDLE) { + device_->GetPtrs()->vkDestroyAccelerationStructureKHR( + device_->GetVkDevice(), blas_, nullptr); + } +} + +Result BLAS::CreateBLAS(amber::BLAS* blas) { + if (blas_ != VK_NULL_HANDLE) + return Result("Cannot recreate acceleration structure"); + + std::vector<std::unique_ptr<Geometry>>& geometries = blas->GetGeometries(); + std::vector<VkDeviceSize> vertexBufferOffsets; + VkDeviceSize vertexBufferSize = 0; + + VkDeviceOrHostAddressConstKHR const_null_placeholder = {}; + VkDeviceOrHostAddressKHR null_placeholder = {}; + + accelerationStructureGeometriesKHR_.resize(geometries.size()); + accelerationStructureBuildRangeInfoKHR_.resize(geometries.size()); + maxPrimitiveCounts_.resize(geometries.size()); + vertexBufferOffsets.resize(geometries.size()); + + for (size_t geometryNdx = 0; geometryNdx < geometries.size(); ++geometryNdx) { + const std::unique_ptr<Geometry>& geometryData = geometries[geometryNdx]; + VkDeviceOrHostAddressConstKHR vertexData = {}; + VkAccelerationStructureGeometryDataKHR geometry; + VkGeometryTypeKHR geometryType = VK_GEOMETRY_TYPE_MAX_ENUM_KHR; + + if (geometryData->IsTriangle()) { + VkAccelerationStructureGeometryTrianglesDataKHR + accelerationStructureGeometryTrianglesDataKHR = { + // NOLINTNEXTLINE(whitespace/line_length) + VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR, + nullptr, + VK_FORMAT_R32G32B32_SFLOAT, + vertexData, + 3 * sizeof(float), + static_cast<uint32_t>(geometryData->getVertexCount()), + VK_INDEX_TYPE_NONE_KHR, + const_null_placeholder, + const_null_placeholder, + }; + + geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR; + geometry.triangles = accelerationStructureGeometryTrianglesDataKHR; + } else if (geometryData->IsAABB()) { + const VkAccelerationStructureGeometryAabbsDataKHR + accelerationStructureGeometryAabbsDataKHR = { + VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR, + nullptr, vertexData, sizeof(VkAabbPositionsKHR)}; + + geometryType = VK_GEOMETRY_TYPE_AABBS_KHR; + geometry.aabbs = accelerationStructureGeometryAabbsDataKHR; + } else { + assert(false && "unknown geometry type"); + } + + const VkAccelerationStructureGeometryKHR accelerationStructureGeometry = { + VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR, + nullptr, + geometryType, + geometry, + 0u, + }; + const VkAccelerationStructureBuildRangeInfoKHR + accelerationStructureBuildRangeInfosKHR = { + static_cast<uint32_t>(geometryData->getPrimitiveCount()), 0, 0, 0}; + + accelerationStructureGeometriesKHR_[geometryNdx] = + accelerationStructureGeometry; + accelerationStructureBuildRangeInfoKHR_[geometryNdx] = + accelerationStructureBuildRangeInfosKHR; + maxPrimitiveCounts_[geometryNdx] = + accelerationStructureBuildRangeInfosKHR.primitiveCount; + vertexBufferOffsets[geometryNdx] = vertexBufferSize; + size_t s1 = sizeof(geometryData->GetData()[0]); + vertexBufferSize += align(geometryData->GetData().size() * s1, 8); + } + + const VkAccelerationStructureGeometryKHR* + accelerationStructureGeometriesKHRPointer = + accelerationStructureGeometriesKHR_.data(); + accelerationStructureBuildGeometryInfoKHR_ = { + VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR, + nullptr, + VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR, + 0u, + VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR, + VK_NULL_HANDLE, + VK_NULL_HANDLE, + static_cast<uint32_t>(accelerationStructureGeometriesKHR_.size()), + accelerationStructureGeometriesKHRPointer, + nullptr, + null_placeholder, + }; + VkAccelerationStructureBuildSizesInfoKHR sizeInfo = { + VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR, nullptr, 0, + 0, 0}; + + device_->GetPtrs()->vkGetAccelerationStructureBuildSizesKHR( + device_->GetVkDevice(), VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, + &accelerationStructureBuildGeometryInfoKHR_, maxPrimitiveCounts_.data(), + &sizeInfo); + + const uint32_t accelerationStructureSize = + static_cast<uint32_t>(sizeInfo.accelerationStructureSize); + + buffer_ = + MakeUnique<TransferBuffer>(device_, accelerationStructureSize, nullptr); + buffer_->AddUsageFlags( + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + buffer_->AddAllocateFlags(VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT); + buffer_->Initialize(); + + const VkAccelerationStructureCreateInfoKHR accelerationStructureCreateInfoKHR{ + VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR, + nullptr, + 0, + buffer_->GetVkBuffer(), + 0, + accelerationStructureSize, + VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR, + 0}; + + if (device_->GetPtrs()->vkCreateAccelerationStructureKHR( + device_->GetVkDevice(), &accelerationStructureCreateInfoKHR, nullptr, + &blas_) != VK_SUCCESS) + return Result("Vulkan::Calling vkCreateAccelerationStructureKHR failed"); + + accelerationStructureBuildGeometryInfoKHR_.dstAccelerationStructure = blas_; + + if (sizeInfo.buildScratchSize > 0) { + scratch_buffer_ = MakeUnique<TransferBuffer>( + device_, static_cast<uint32_t>(sizeInfo.buildScratchSize), nullptr); + scratch_buffer_->AddUsageFlags(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + scratch_buffer_->AddAllocateFlags(VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT); + scratch_buffer_->Initialize(); + + accelerationStructureBuildGeometryInfoKHR_.scratchData.deviceAddress = + scratch_buffer_->getBufferDeviceAddress(); + } + + if (vertexBufferSize > 0) { + vertex_buffer_ = MakeUnique<TransferBuffer>( + device_, static_cast<uint32_t>(vertexBufferSize), nullptr); + vertex_buffer_->AddUsageFlags( + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + vertex_buffer_->AddAllocateFlags(VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT); + vertex_buffer_->SetMemoryPropertiesFlags( + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | + VK_MEMORY_PROPERTY_HOST_CACHED_BIT); + vertex_buffer_->Initialize(); + + void* memory_ptr = vertex_buffer_->HostAccessibleMemoryPtr(); + + for (size_t geometryNdx = 0; geometryNdx < geometries.size(); + ++geometryNdx) { + VkDeviceOrHostAddressConstKHR p; + + p.deviceAddress = vertex_buffer_.get()->getBufferDeviceAddress() + + vertexBufferOffsets[geometryNdx]; + + const auto& data = geometries[geometryNdx]->GetData(); + std::memcpy(reinterpret_cast<char*>(memory_ptr) + + vertexBufferOffsets[geometryNdx], + data.data(), data.size() * sizeof(*data.data())); + + if (geometries[geometryNdx]->IsTriangle()) { + accelerationStructureGeometriesKHR_[geometryNdx] + .geometry.triangles.vertexData = p; + } else if (geometries[geometryNdx]->IsAABB()) { + accelerationStructureGeometriesKHR_[geometryNdx].geometry.aabbs.data = + p; + } else { + assert(false && "unknown geometry type"); + } + } + } + + return {}; +} + +Result BLAS::BuildBLAS(CommandBuffer* command_buffer) { + if (blas_ == VK_NULL_HANDLE) + return Result("Acceleration structure should be created first"); + if (built_) + return {}; + + VkCommandBuffer cmdBuffer = command_buffer->GetVkCommandBuffer(); + + vertex_buffer_->CopyToDevice(command_buffer); + + VkAccelerationStructureBuildRangeInfoKHR* + accelerationStructureBuildRangeInfoKHRPtr = + accelerationStructureBuildRangeInfoKHR_.data(); + + device_->GetPtrs()->vkCmdBuildAccelerationStructuresKHR( + cmdBuffer, 1, &accelerationStructureBuildGeometryInfoKHR_, + &accelerationStructureBuildRangeInfoKHRPtr); + + const VkAccessFlags accessMasks = + VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR | + VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR; + const VkMemoryBarrier memBarrier{ + VK_STRUCTURE_TYPE_MEMORY_BARRIER, + nullptr, + accessMasks, + accessMasks, + }; + + device_->GetPtrs()->vkCmdPipelineBarrier( + cmdBuffer, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &memBarrier, 0, nullptr, 0, + nullptr); + + built_ = true; + + return {}; +} + +VkDeviceAddress BLAS::getVkBLASDeviceAddress() { + VkAccelerationStructureDeviceAddressInfoKHR info = { + VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR, nullptr, + blas_}; + + assert(blas_ != VK_NULL_HANDLE); + + return device_->GetPtrs()->vkGetAccelerationStructureDeviceAddressKHR( + device_->GetVkDevice(), &info); +} + +} // namespace vulkan +} // namespace amber diff --git a/src/vulkan/blas.h b/src/vulkan/blas.h new file mode 100644 index 0000000..7a22097 --- /dev/null +++ b/src/vulkan/blas.h @@ -0,0 +1,58 @@ +// Copyright 2024 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. + +#ifndef SRC_VULKAN_BLAS_H_ +#define SRC_VULKAN_BLAS_H_ + +#include <vector> +#include <memory> + +#include "src/acceleration_structure.h" +#include "src/vulkan/device.h" +#include "src/vulkan/transfer_buffer.h" + +namespace amber { +namespace vulkan { + +class BLAS { + public: + explicit BLAS(Device* device); + ~BLAS(); + + Result CreateBLAS(amber::BLAS* blas); + Result BuildBLAS(CommandBuffer* command_buffer); + VkAccelerationStructureKHR GetVkBLAS() { return blas_; } + VkDeviceAddress getVkBLASDeviceAddress(); + + private: + Device* device_ = nullptr; + VkAccelerationStructureKHR blas_ = VK_NULL_HANDLE; + bool built_ = false; + std::unique_ptr<TransferBuffer> buffer_; + std::unique_ptr<TransferBuffer> scratch_buffer_; + std::unique_ptr<TransferBuffer> vertex_buffer_; + VkAccelerationStructureBuildGeometryInfoKHR + accelerationStructureBuildGeometryInfoKHR_; + std::vector<VkAccelerationStructureGeometryKHR> + accelerationStructureGeometriesKHR_; + std::vector<VkAccelerationStructureBuildRangeInfoKHR> + accelerationStructureBuildRangeInfoKHR_; + std::vector<uint32_t> maxPrimitiveCounts_; +}; + +} // namespace vulkan +} // namespace amber + +#endif // SRC_VULKAN_BLAS_H_ diff --git a/src/vulkan/command_buffer.cc b/src/vulkan/command_buffer.cc index b4d28a6..d5546a8 100644 --- a/src/vulkan/command_buffer.cc +++ b/src/vulkan/command_buffer.cc @@ -74,7 +74,7 @@ Result CommandBuffer::BeginRecording() { } Result CommandBuffer::SubmitAndReset(uint32_t timeout_ms, - bool pipeline_runtime_layer_enabled) { + bool pipeline_runtime_layer_enabled) { if (device_->GetPtrs()->vkEndCommandBuffer(command_) != VK_SUCCESS) return Result("Vulkan::Calling vkEndCommandBuffer Fail"); @@ -87,6 +87,7 @@ Result CommandBuffer::SubmitAndReset(uint32_t timeout_ms, submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &command_; + if (device_->GetPtrs()->vkQueueSubmit(device_->GetVkQueue(), 1, &submit_info, fence_) != VK_SUCCESS) { return Result("Vulkan::Calling vkQueueSubmit Fail"); @@ -94,9 +95,12 @@ Result CommandBuffer::SubmitAndReset(uint32_t timeout_ms, guarded_ = false; + const uint64_t timeout_ns = + timeout_ms == static_cast<uint32_t>(~0u) // honor 32bit infinity + ? ~0ull + : static_cast<uint64_t>(timeout_ms) * 1000ULL * 1000ULL; VkResult r = device_->GetPtrs()->vkWaitForFences( - device_->GetVkDevice(), 1, &fence_, VK_TRUE, - static_cast<uint64_t>(timeout_ms) * 1000ULL * 1000ULL /* nanosecond */); + device_->GetVkDevice(), 1, &fence_, VK_TRUE, timeout_ns); if (r == VK_TIMEOUT) return Result("Vulkan::Calling vkWaitForFences Timeout"); if (r != VK_SUCCESS) { @@ -118,12 +122,12 @@ Result CommandBuffer::SubmitAndReset(uint32_t timeout_ms, return Result("Vulkan::Calling vkWaitForFences Fail (" + result_str + ")"); } - /* - google/vulkan-performance-layers requires a call to vkDeviceWaitIdle or - vkQueueWaitIdle in order to report the information. Since we want to be - able to use that layer in conjunction with Amber we need to somehow - communicate that the Amber script has completed. - */ + /* +google/vulkan-performance-layers requires a call to vkDeviceWaitIdle or +vkQueueWaitIdle in order to report the information. Since we want to be +able to use that layer in conjunction with Amber we need to somehow +communicate that the Amber script has completed. +*/ if (pipeline_runtime_layer_enabled) device_->GetPtrs()->vkQueueWaitIdle(device_->GetVkQueue()); @@ -152,7 +156,7 @@ CommandBufferGuard::~CommandBufferGuard() { } Result CommandBufferGuard::Submit(uint32_t timeout_ms, - bool pipeline_runtime_layer_enabled) { + bool pipeline_runtime_layer_enabled) { assert(buffer_->guarded_); return buffer_->SubmitAndReset(timeout_ms, pipeline_runtime_layer_enabled); } diff --git a/src/vulkan/command_buffer.h b/src/vulkan/command_buffer.h index 349cce4..67bfa11 100644 --- a/src/vulkan/command_buffer.h +++ b/src/vulkan/command_buffer.h @@ -84,8 +84,7 @@ class CommandBufferGuard { Result GetResult() { return result_; } /// Submits and resets the internal command buffer. - Result Submit(uint32_t timeout_ms, - bool pipeline_runtime_layer_enabled); + Result Submit(uint32_t timeout_ms, bool pipeline_runtime_layer_enabled); private: Result result_; diff --git a/src/vulkan/descriptor.cc b/src/vulkan/descriptor.cc index 169c71c..fd605be 100644 --- a/src/vulkan/descriptor.cc +++ b/src/vulkan/descriptor.cc @@ -1,4 +1,5 @@ // Copyright 2019 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -54,6 +55,8 @@ VkDescriptorType Descriptor::GetVkDescriptorType() const { return VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; case DescriptorType::kStorageTexelBuffer: return VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER; + case DescriptorType::kTLAS: + return VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR; default: assert(type_ == DescriptorType::kSampledImage); return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; diff --git a/src/vulkan/descriptor.h b/src/vulkan/descriptor.h index 88f6813..cc3c7c1 100644 --- a/src/vulkan/descriptor.h +++ b/src/vulkan/descriptor.h @@ -1,4 +1,5 @@ // Copyright 2019 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -34,6 +35,7 @@ class BufferDescriptor; class ImageDescriptor; class BufferBackedDescriptor; class SamplerDescriptor; +class TLASDescriptor; enum class DescriptorType : uint8_t { kStorageBuffer = 0, @@ -45,7 +47,8 @@ enum class DescriptorType : uint8_t { kCombinedImageSampler, kUniformTexelBuffer, kStorageTexelBuffer, - kSampler + kSampler, + kTLAS }; class Descriptor { @@ -66,6 +69,7 @@ class Descriptor { virtual ImageDescriptor* AsImageDescriptor() { return nullptr; } virtual BufferBackedDescriptor* AsBufferBackedDescriptor() { return nullptr; } virtual SamplerDescriptor* AsSamplerDescriptor() { return nullptr; } + virtual TLASDescriptor* AsTLASDescriptor() { return nullptr; } uint32_t GetDescriptorSet() const { return descriptor_set_; } uint32_t GetBinding() const { return binding_; } VkDescriptorType GetVkDescriptorType() const; diff --git a/src/vulkan/device.cc b/src/vulkan/device.cc index 43a1d8b..0aebd7c 100644 --- a/src/vulkan/device.cc +++ b/src/vulkan/device.cc @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -86,8 +87,14 @@ const char kSubgroupSupportedStagesCompute[] = const char kShaderSubgroupExtendedTypes[] = "ShaderSubgroupExtendedTypesFeatures.shaderSubgroupExtendedTypes"; -const char kIndexTypeUint8[] = - "IndexTypeUint8Features.indexTypeUint8"; +const char kIndexTypeUint8[] = "IndexTypeUint8Features.indexTypeUint8"; + +const char kAccelerationStructure[] = + "AccelerationStructureFeaturesKHR.accelerationStructure"; +const char kBufferDeviceAddress[] = + "BufferDeviceAddressFeatures.bufferDeviceAddress"; +const char kRayTracingPipeline[] = + "RayTracingPipelineFeaturesKHR.rayTracingPipeline"; struct BaseOutStructure { VkStructureType sType; @@ -447,9 +454,11 @@ Result Device::Initialize( PFN_vkGetInstanceProcAddr getInstanceProcAddr, Delegate* delegate, const std::vector<std::string>& required_features, + const std::vector<std::string>& required_properties, const std::vector<std::string>& required_device_extensions, const VkPhysicalDeviceFeatures& available_features, const VkPhysicalDeviceFeatures2KHR& available_features2, + const VkPhysicalDeviceProperties2KHR& available_properties2, const std::vector<std::string>& available_extensions) { Result r = LoadVulkanPointers(getInstanceProcAddr, delegate); if (!r.IsSuccess()) @@ -479,6 +488,12 @@ Result Device::Initialize( VkPhysicalDeviceShaderSubgroupExtendedTypesFeatures* shader_subgroup_extended_types_ptrs = nullptr; VkPhysicalDeviceIndexTypeUint8FeaturesEXT* index_type_uint8_ptrs = nullptr; + VkPhysicalDeviceAccelerationStructureFeaturesKHR* + acceleration_structure_ptrs = nullptr; + VkPhysicalDeviceBufferDeviceAddressFeatures* bda_ptrs = nullptr; + VkPhysicalDeviceRayTracingPipelineFeaturesKHR* ray_tracing_pipeline_ptrs = + nullptr; + void* ptr = available_features2.pNext; while (ptr != nullptr) { BaseOutStructure* s = static_cast<BaseOutStructure*>(ptr); @@ -513,6 +528,19 @@ Result Device::Initialize( index_type_uint8_ptrs = static_cast<VkPhysicalDeviceIndexTypeUint8FeaturesEXT*>(ptr); break; + // NOLINTNEXTLINE(whitespace/line_length) + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR: + acceleration_structure_ptrs = + static_cast<VkPhysicalDeviceAccelerationStructureFeaturesKHR*>(ptr); + break; + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES: + bda_ptrs = + static_cast<VkPhysicalDeviceBufferDeviceAddressFeatures*>(ptr); + break; + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR: + ray_tracing_pipeline_ptrs = + static_cast<VkPhysicalDeviceRayTracingPipelineFeaturesKHR*>(ptr); + break; case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES: vulkan11_ptrs = static_cast<VkPhysicalDeviceVulkan11Features*>(ptr); break; @@ -520,8 +548,8 @@ Result Device::Initialize( vulkan12_ptrs = static_cast<VkPhysicalDeviceVulkan12Features*>(ptr); break; case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES: - vulkan13_ptrs = static_cast<VkPhysicalDeviceVulkan13Features*>(ptr); - break; + vulkan13_ptrs = static_cast<VkPhysicalDeviceVulkan13Features*>(ptr); + break; default: break; } @@ -581,7 +609,21 @@ Result Device::Initialize( return amber::Result( "Index type uint8_t requested but feature not returned"); } - + if (feature == kAccelerationStructure && + acceleration_structure_ptrs == nullptr) { + return amber::Result( + "Acceleration structure requested but feature not returned"); + } + if (feature == kBufferDeviceAddress && bda_ptrs == nullptr && + vulkan12_ptrs == nullptr) { + return amber::Result( + "Buffer device address requested but feature not returned"); + } + if (feature == kRayTracingPipeline && + ray_tracing_pipeline_ptrs == nullptr) { + return amber::Result( + "Ray tracing pipeline requested but feature not returned"); + } // Next check the fields of the feature structures. @@ -668,6 +710,10 @@ Result Device::Initialize( vulkan12_ptrs->shaderSubgroupExtendedTypes != VK_TRUE) { return amber::Result("Missing subgroup extended types"); } + if (feature == kBufferDeviceAddress && + vulkan12_ptrs->bufferDeviceAddress != VK_TRUE) { + return amber::Result("Missing buffer device address"); + } } else { // Vulkan 1.2 structure was not found. Use separate structures per each // feature. @@ -695,18 +741,22 @@ Result Device::Initialize( VK_TRUE) { return amber::Result("Missing subgroup extended types"); } + if (feature == kBufferDeviceAddress && + bda_ptrs->bufferDeviceAddress != VK_TRUE) { + return amber::Result("Missing buffer device address"); + } } // If Vulkan 1.3 structure exists the features are set there. if (vulkan13_ptrs) { - if (feature == kSubgroupSizeControl && - vulkan13_ptrs->subgroupSizeControl != VK_TRUE) { - return amber::Result("Missing subgroup size control feature"); - } - if (feature == kComputeFullSubgroups && - vulkan13_ptrs->computeFullSubgroups != VK_TRUE) { - return amber::Result("Missing compute full subgroups feature"); - } + if (feature == kSubgroupSizeControl && + vulkan13_ptrs->subgroupSizeControl != VK_TRUE) { + return amber::Result("Missing subgroup size control feature"); + } + if (feature == kComputeFullSubgroups && + vulkan13_ptrs->computeFullSubgroups != VK_TRUE) { + return amber::Result("Missing compute full subgroups feature"); + } } else { if (feature == kSubgroupSizeControl && subgroup_size_control_features->subgroupSizeControl != VK_TRUE) { @@ -726,6 +776,87 @@ Result Device::Initialize( "required extensions"); } + const bool needs_shader_group_handle_size = + std::find(required_features.begin(), required_features.end(), + kAccelerationStructure) != required_features.end(); + + if (needs_shader_group_handle_size) { + VkPhysicalDeviceRayTracingPipelinePropertiesKHR rt_pipeline_properties = {}; + rt_pipeline_properties.sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR; + + VkPhysicalDeviceProperties2KHR properties2 = {}; + properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + properties2.pNext = &rt_pipeline_properties; + + ptrs_.vkGetPhysicalDeviceProperties2(physical_device_, &properties2); + + shader_group_handle_size_ = rt_pipeline_properties.shaderGroupHandleSize; + } + + VkPhysicalDeviceVulkan12Properties* pv12 = nullptr; + VkPhysicalDeviceFloatControlsProperties* pfc = nullptr; + + ptr = available_properties2.pNext; + while (ptr != nullptr) { + BaseOutStructure* s = static_cast<BaseOutStructure*>(ptr); + switch (s->sType) { + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES: + pv12 = static_cast<VkPhysicalDeviceVulkan12Properties*>(ptr); + break; + case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT_CONTROLS_PROPERTIES_KHR: + pfc = static_cast<VkPhysicalDeviceFloatControlsPropertiesKHR*>(ptr); + break; + default: + break; + } + ptr = s->pNext; + } + +#define CHK_P(R, P, NAME, S1, S2) \ + do { \ + if (R == -1 && P == #NAME) \ + R = ((S1 && S1->NAME) || (S2 && S2->NAME)) ? 1 : 0; \ + } while (false) + + for (const std::string& prop : required_properties) { + const size_t dot_pos = prop.find('.'); + const size_t dot_found = dot_pos != std::string::npos; + const std::string prefix = dot_found ? prop.substr(0, dot_pos) : ""; + const std::string name = dot_found ? prop.substr(dot_pos + 1) : prop; + int supported = -1; + + if (supported == -1 && prefix == "FloatControlsProperties") { + if (pfc == nullptr && pv12 == nullptr) + return Result( + "Vulkan: Device::Initialize given physical device does not support " + "required float control properties"); + + CHK_P(supported, name, shaderSignedZeroInfNanPreserveFloat16, pfc, pv12); + CHK_P(supported, name, shaderSignedZeroInfNanPreserveFloat32, pfc, pv12); + CHK_P(supported, name, shaderSignedZeroInfNanPreserveFloat64, pfc, pv12); + CHK_P(supported, name, shaderDenormPreserveFloat16, pfc, pv12); + CHK_P(supported, name, shaderDenormPreserveFloat32, pfc, pv12); + CHK_P(supported, name, shaderDenormPreserveFloat64, pfc, pv12); + CHK_P(supported, name, shaderDenormFlushToZeroFloat16, pfc, pv12); + CHK_P(supported, name, shaderDenormFlushToZeroFloat32, pfc, pv12); + CHK_P(supported, name, shaderDenormFlushToZeroFloat64, pfc, pv12); + CHK_P(supported, name, shaderRoundingModeRTEFloat16, pfc, pv12); + CHK_P(supported, name, shaderRoundingModeRTEFloat32, pfc, pv12); + CHK_P(supported, name, shaderRoundingModeRTEFloat64, pfc, pv12); + CHK_P(supported, name, shaderRoundingModeRTZFloat16, pfc, pv12); + CHK_P(supported, name, shaderRoundingModeRTZFloat32, pfc, pv12); + CHK_P(supported, name, shaderRoundingModeRTZFloat64, pfc, pv12); + } + + if (supported == 0) + return Result("Vulkan: Device::Initialize missing " + prop + " property"); + + if (supported == -1) + return Result( + "Vulkan: Device::Initialize property not handled " + prop); + } + ptrs_.vkGetPhysicalDeviceMemoryProperties(physical_device_, &physical_memory_properties_); @@ -1396,5 +1527,9 @@ uint32_t Device::GetMaxSubgroupSize() const { return subgroup_size_control_properties_.maxSubgroupSize; } +uint32_t Device::GetRayTracingShaderGroupHandleSize() const { + return shader_group_handle_size_; +} + } // namespace vulkan } // namespace amber diff --git a/src/vulkan/device.h b/src/vulkan/device.h index ff76c0f..8cda4b7 100644 --- a/src/vulkan/device.h +++ b/src/vulkan/device.h @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -47,9 +48,11 @@ class Device { Result Initialize(PFN_vkGetInstanceProcAddr getInstanceProcAddr, Delegate* delegate, const std::vector<std::string>& required_features, + const std::vector<std::string>& required_properties, const std::vector<std::string>& required_device_extensions, const VkPhysicalDeviceFeatures& available_features, const VkPhysicalDeviceFeatures2KHR& available_features2, + const VkPhysicalDeviceProperties2KHR& available_properties2, const std::vector<std::string>& available_extensions); /// Returns true if |format| and the |buffer|s buffer type combination is @@ -88,6 +91,8 @@ class Device { /// Returns the maximum required subgroup size or 0 if subgroup size control /// is not supported. uint32_t GetMaxSubgroupSize() const; + /// Returns ray tracing shader group handle size. + uint32_t GetRayTracingShaderGroupHandleSize() const; private: Result LoadVulkanPointers(PFN_vkGetInstanceProcAddr, Delegate* delegate); @@ -102,6 +107,7 @@ class Device { VkDevice device_ = VK_NULL_HANDLE; VkQueue queue_ = VK_NULL_HANDLE; uint32_t queue_family_index_ = 0; + uint32_t shader_group_handle_size_ = 0; VulkanPtrs ptrs_; }; diff --git a/src/vulkan/engine_vulkan.cc b/src/vulkan/engine_vulkan.cc index b0842ec..a61d84e 100644 --- a/src/vulkan/engine_vulkan.cc +++ b/src/vulkan/engine_vulkan.cc @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -23,6 +24,7 @@ #include "src/type_parser.h" #include "src/vulkan/compute_pipeline.h" #include "src/vulkan/graphics_pipeline.h" +#include "src/vulkan/raytracing_pipeline.h" namespace amber { namespace vulkan { @@ -48,6 +50,24 @@ Result ToVkShaderStage(ShaderType type, VkShaderStageFlagBits* ret) { case kShaderTypeTessellationEvaluation: *ret = VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT; break; + case kShaderTypeRayGeneration: + *ret = VK_SHADER_STAGE_RAYGEN_BIT_KHR; + break; + case kShaderTypeAnyHit: + *ret = VK_SHADER_STAGE_ANY_HIT_BIT_KHR; + break; + case kShaderTypeClosestHit: + *ret = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; + break; + case kShaderTypeMiss: + *ret = VK_SHADER_STAGE_MISS_BIT_KHR; + break; + case kShaderTypeIntersection: + *ret = VK_SHADER_STAGE_INTERSECTION_BIT_KHR; + break; + case kShaderTypeCall: + *ret = VK_SHADER_STAGE_CALLABLE_BIT_KHR; + break; case kShaderTypeCompute: *ret = VK_SHADER_STAGE_COMPUTE_BIT; break; @@ -85,6 +105,9 @@ EngineVulkan::~EngineVulkan() { device_->GetPtrs()->vkDestroyShaderModule(vk_device, shader.second, nullptr); } + pipeline_map_.clear(); + tlases_.clear(); + blases_.clear(); } } @@ -92,6 +115,7 @@ Result EngineVulkan::Initialize( EngineConfig* config, Delegate* delegate, const std::vector<std::string>& features, + const std::vector<std::string>& properties, const std::vector<std::string>& instance_extensions, const std::vector<std::string>& device_extensions) { if (device_) @@ -118,8 +142,9 @@ Result EngineVulkan::Initialize( vk_config->queue); Result r = device_->Initialize( - vk_config->vkGetInstanceProcAddr, delegate, features, device_extensions, - vk_config->available_features, vk_config->available_features2, + vk_config->vkGetInstanceProcAddr, delegate, features, properties, + device_extensions, vk_config->available_features, + vk_config->available_features2, vk_config->available_properties2, vk_config->available_device_extensions); if (!r.IsSuccess()) return r; @@ -139,8 +164,8 @@ Result EngineVulkan::CreatePipeline(amber::Pipeline* pipeline) { pipeline_map_[pipeline] = PipelineInfo(); auto& info = pipeline_map_[pipeline]; - for (const auto& shader_info : pipeline->GetShaders()) { - Result r = SetShader(pipeline, shader_info); + for (size_t i = 0; i < pipeline->GetShaders().size(); i++) { + Result r = SetShader(pipeline, pipeline->GetShaders()[i], i); if (!r.IsSuccess()) return r; } @@ -168,13 +193,23 @@ Result EngineVulkan::CreatePipeline(amber::Pipeline* pipeline) { const auto& engine_data = GetEngineData(); std::unique_ptr<Pipeline> vk_pipeline; - if (pipeline->GetType() == PipelineType::kCompute) { + if (pipeline->GetType() == PipelineType::kRayTracing) { + std::vector<VkRayTracingShaderGroupCreateInfoKHR> shader_group_create_info; + + r = GetVkShaderGroupInfo(pipeline, &shader_group_create_info); + if (!r.IsSuccess()) + return r; + + vk_pipeline = MakeUnique<RayTracingPipeline>( + device_.get(), &blases_, &tlases_, engine_data.fence_timeout_ms, + engine_data.pipeline_runtime_layer_enabled, stage_create_info); + r = vk_pipeline->AsRayTracingPipeline()->Initialize( + pool_.get(), shader_group_create_info); + } else if (pipeline->GetType() == PipelineType::kCompute) { vk_pipeline = MakeUnique<ComputePipeline>( device_.get(), engine_data.fence_timeout_ms, engine_data.pipeline_runtime_layer_enabled, stage_create_info); r = vk_pipeline->AsCompute()->Initialize(pool_.get()); - if (!r.IsSuccess()) - return r; } else { vk_pipeline = MakeUnique<GraphicsPipeline>( device_.get(), pipeline->GetColorAttachments(), @@ -188,10 +223,11 @@ Result EngineVulkan::CreatePipeline(amber::Pipeline* pipeline) { r = vk_pipeline->AsGraphics()->Initialize(pipeline->GetFramebufferWidth(), pipeline->GetFramebufferHeight(), pool_.get()); - if (!r.IsSuccess()) - return r; } + if (!r.IsSuccess()) + return r; + info.vk_pipeline = std::move(vk_pipeline); // Set the entry point names for the pipeline. @@ -286,21 +322,38 @@ Result EngineVulkan::CreatePipeline(amber::Pipeline* pipeline) { return r; } + if (info.vk_pipeline->IsRayTracing()) { + for (const auto& tlas_info : pipeline->GetTLASes()) { + auto cmd = MakeUnique<TLASCommand>(pipeline); + cmd->SetDescriptorSet(tlas_info.descriptor_set); + cmd->SetBinding(tlas_info.binding); + cmd->SetTLAS(tlas_info.tlas); + + r = info.vk_pipeline->AddTLASDescriptor(cmd.get()); + if (!r.IsSuccess()) + return r; + } + } + return {}; } Result EngineVulkan::SetShader(amber::Pipeline* pipeline, - const amber::Pipeline::ShaderInfo& shader) { + const amber::Pipeline::ShaderInfo& shader, + size_t index) { + const bool rt = pipeline->IsRayTracing(); const auto type = shader.GetShaderType(); const auto& data = shader.GetData(); const auto shader_name = shader.GetShader()->GetName(); auto& info = pipeline_map_[pipeline]; - auto it = info.shader_info.find(type); - if (it != info.shader_info.end()) - return Result("Vulkan::Setting Duplicated Shader Types Fail"); + if (!rt) { + auto it = info.shader_info.find(type); + if (it != info.shader_info.end()) + return Result("Vulkan::Setting Duplicated Shader Types Fail"); + } - VkShaderModule shader_module; + VkShaderModule shader_module = VK_NULL_HANDLE; if (shaders_.find(shader_name) != shaders_.end()) { shader_module = shaders_[shader_name]; } else { @@ -318,7 +371,18 @@ Result EngineVulkan::SetShader(amber::Pipeline* pipeline, shaders_[shader_name] = shader_module; } - info.shader_info[type].shader = shader_module; + if (!rt) { + info.shader_info[type].shader = shader_module; + } else { + assert(index <= info.shader_info_rt.size()); + if (info.shader_info_rt.size() == index) { + info.shader_info_rt.push_back(PipelineInfo::ShaderInfo()); + } + info.shader_info_rt[index].shader = shader_module; + info.shader_info_rt[index].type = type; + + return {}; + } for (auto& shader_info : pipeline->GetShaders()) { if (shader_info.GetShaderType() != type) @@ -348,6 +412,7 @@ Result EngineVulkan::SetShader(amber::Pipeline* pipeline, "device."); } } + info.shader_info[type].required_subgroup_size = required_subgroup_size_uint; info.shader_info[type].create_flags = 0; @@ -388,48 +453,138 @@ Result EngineVulkan::SetShader(amber::Pipeline* pipeline, } Result EngineVulkan::GetVkShaderStageInfo( + ShaderType shader_type, + const PipelineInfo::ShaderInfo& shader_info, + VkPipelineShaderStageCreateInfo* stage_info) { + VkShaderStageFlagBits stage = VK_SHADER_STAGE_FLAG_BITS_MAX_ENUM; + Result r = ToVkShaderStage(shader_type, &stage); + if (!r.IsSuccess()) + return r; + + *stage_info = VkPipelineShaderStageCreateInfo(); + stage_info->sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stage_info->flags = shader_info.create_flags; + stage_info->stage = stage; + stage_info->module = shader_info.shader; + + stage_info->pName = nullptr; + if (shader_info.specialization_entries && + !shader_info.specialization_entries->empty()) { + stage_info->pSpecializationInfo = shader_info.specialization_info.get(); + } + + return {}; +} + +Result EngineVulkan::GetVkShaderStageInfo( amber::Pipeline* pipeline, std::vector<VkPipelineShaderStageCreateInfo>* out) { auto& info = pipeline_map_[pipeline]; - std::vector<VkPipelineShaderStageCreateInfo> stage_info( - info.shader_info.size()); - uint32_t stage_count = 0; - for (auto& it : info.shader_info) { - VkShaderStageFlagBits stage = VK_SHADER_STAGE_FLAG_BITS_MAX_ENUM; - Result r = ToVkShaderStage(it.first, &stage); - if (!r.IsSuccess()) - return r; - - stage_info[stage_count] = VkPipelineShaderStageCreateInfo(); - stage_info[stage_count].sType = - VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - stage_info[stage_count].flags = it.second.create_flags; - stage_info[stage_count].stage = stage; - stage_info[stage_count].module = it.second.shader; - stage_info[stage_count].pName = nullptr; - if (it.second.specialization_entries && - !it.second.specialization_entries->empty()) { - stage_info[stage_count].pSpecializationInfo = - it.second.specialization_info.get(); + const size_t size = pipeline->IsRayTracing() ? info.shader_info_rt.size() + : info.shader_info.size(); + std::vector<VkPipelineShaderStageCreateInfo> stage_info(size); + if (pipeline->IsRayTracing()) { + for (size_t i = 0; i < info.shader_info_rt.size(); i++) { + Result r = GetVkShaderStageInfo(info.shader_info_rt[i].type, + info.shader_info_rt[i], &stage_info[i]); + if (!r.IsSuccess()) + return r; } - - if (stage == VK_SHADER_STAGE_COMPUTE_BIT && - it.second.required_subgroup_size > 0) { - VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT* pSubgroupSize = - new VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT(); - pSubgroupSize->sType = - VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT; // NOLINT(whitespace/line_length) - pSubgroupSize->pNext = nullptr; - pSubgroupSize->requiredSubgroupSize = it.second.required_subgroup_size; - stage_info[stage_count].pNext = pSubgroupSize; + } else { + uint32_t stage_count = 0; + for (auto& it : info.shader_info) { + Result r = + GetVkShaderStageInfo(it.first, it.second, &stage_info[stage_count]); + if (!r.IsSuccess()) + return r; + + if (it.first == kShaderTypeCompute && + it.second.required_subgroup_size > 0) { + VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT* pSubgroupSize = + new VkPipelineShaderStageRequiredSubgroupSizeCreateInfoEXT(); + pSubgroupSize->sType = + VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO_EXT; // NOLINT(whitespace/line_length) + pSubgroupSize->pNext = nullptr; + pSubgroupSize->requiredSubgroupSize = it.second.required_subgroup_size; + stage_info[stage_count].pNext = pSubgroupSize; + } + ++stage_count; } - ++stage_count; } *out = stage_info; return {}; } +Result EngineVulkan::GetVkShaderGroupInfo( + amber::Pipeline* pipeline, + std::vector<VkRayTracingShaderGroupCreateInfoKHR>* out) { + auto shaders = pipeline->GetShaders(); + + out->clear(); + out->reserve(pipeline->GetShaderGroups().size()); + + for (auto& g : pipeline->GetShaderGroups()) { + Result r; + ShaderGroup* sg = g.get(); + + if (sg == nullptr) + return Result("Invalid shader group"); + + VkRayTracingShaderGroupCreateInfoKHR group_info = { + VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR, + nullptr, + VK_RAY_TRACING_SHADER_GROUP_TYPE_MAX_ENUM_KHR, + VK_SHADER_UNUSED_KHR, + VK_SHADER_UNUSED_KHR, + VK_SHADER_UNUSED_KHR, + VK_SHADER_UNUSED_KHR, + nullptr + }; + + if (sg->IsGeneralGroup()) { + group_info.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; + r = pipeline->GetShaderIndex(sg->GetGeneralShader(), + &group_info.generalShader); + if (!r.IsSuccess()) + return r; + } else if (sg->IsHitGroup()) { + group_info.type = + sg->GetIntersectionShader() == nullptr + ? VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR + : VK_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT_GROUP_KHR; + + if (sg->GetClosestHitShader()) { + r = pipeline->GetShaderIndex(sg->GetClosestHitShader(), + &group_info.closestHitShader); + if (!r.IsSuccess()) + return r; + } + + if (sg->GetAnyHitShader()) { + r = pipeline->GetShaderIndex(sg->GetAnyHitShader(), + &group_info.anyHitShader); + if (!r.IsSuccess()) + return r; + } + + if (sg->GetIntersectionShader()) { + r = pipeline->GetShaderIndex(sg->GetIntersectionShader(), + &group_info.intersectionShader); + if (!r.IsSuccess()) + return r; + } + } else { + return Result("Uninitialized shader group"); + } + + out->push_back(group_info); + } + + return {}; +} + Result EngineVulkan::DoClearColor(const ClearColorCommand* command) { auto& info = pipeline_map_[command->GetPipeline()]; if (!info.vk_pipeline->IsGraphics()) @@ -626,13 +781,30 @@ Result EngineVulkan::DoDrawArrays(const DrawArraysCommand* command) { Result EngineVulkan::DoCompute(const ComputeCommand* command) { auto& info = pipeline_map_[command->GetPipeline()]; - if (info.vk_pipeline->IsGraphics()) - return Result("Vulkan: Compute called for graphics pipeline."); + if (!info.vk_pipeline->IsCompute()) + return Result("Vulkan: Compute called for non-compute pipeline."); return info.vk_pipeline->AsCompute()->Compute( command->GetX(), command->GetY(), command->GetZ()); } +Result EngineVulkan::DoTraceRays(const RayTracingCommand* command) { + auto& info = pipeline_map_[command->GetPipeline()]; + if (!info.vk_pipeline->IsRayTracing()) + return Result("Vulkan: RayTracing called for non-RayTracing pipeline."); + + amber::Pipeline* pipeline = command->GetPipeline(); + + amber::SBT* rSBT = pipeline->GetSBT(command->GetRayGenSBTName()); + amber::SBT* mSBT = pipeline->GetSBT(command->GetMissSBTName()); + amber::SBT* hSBT = pipeline->GetSBT(command->GetHitsSBTName()); + amber::SBT* cSBT = pipeline->GetSBT(command->GetCallSBTName()); + + return info.vk_pipeline->AsRayTracingPipeline()->TraceRays( + rSBT, mSBT, hSBT, cSBT, command->GetX(), command->GetY(), + command->GetZ()); +} + Result EngineVulkan::DoEntryPoint(const EntryPointCommand* command) { auto& info = pipeline_map_[command->GetPipeline()]; if (!info.vk_pipeline) diff --git a/src/vulkan/engine_vulkan.h b/src/vulkan/engine_vulkan.h index 76668fb..6ac6e6e 100644 --- a/src/vulkan/engine_vulkan.h +++ b/src/vulkan/engine_vulkan.h @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -23,13 +24,17 @@ #include <vector> #include "amber/vulkan_header.h" +#include "src/acceleration_structure.h" #include "src/cast_hash.h" #include "src/engine.h" #include "src/pipeline.h" +#include "src/vulkan/blas.h" #include "src/vulkan/buffer_descriptor.h" #include "src/vulkan/command_pool.h" #include "src/vulkan/device.h" #include "src/vulkan/pipeline.h" +#include "src/vulkan/tlas.h" +#include "src/vulkan/tlas_descriptor.h" #include "src/vulkan/vertex_buffer.h" namespace amber { @@ -45,6 +50,7 @@ class EngineVulkan : public Engine { Result Initialize(EngineConfig* config, Delegate* delegate, const std::vector<std::string>& features, + const std::vector<std::string>& properties, const std::vector<std::string>& instance_extensions, const std::vector<std::string>& device_extensions) override; Result CreatePipeline(amber::Pipeline* type) override; @@ -57,6 +63,7 @@ class EngineVulkan : public Engine { Result DoDrawGrid(const DrawGridCommand* cmd) override; Result DoDrawArrays(const DrawArraysCommand* cmd) override; Result DoCompute(const ComputeCommand* cmd) override; + Result DoTraceRays(const RayTracingCommand* cmd) override; Result DoEntryPoint(const EntryPointCommand* cmd) override; Result DoPatchParameterVertices( const PatchParameterVerticesCommand* cmd) override; @@ -67,6 +74,7 @@ class EngineVulkan : public Engine { std::unique_ptr<Pipeline> vk_pipeline; std::unique_ptr<VertexBuffer> vertex_buffer; struct ShaderInfo { + ShaderType type; VkShaderModule shader; std::unique_ptr<std::vector<VkSpecializationMapEntry>> specialization_entries; @@ -77,14 +85,24 @@ class EngineVulkan : public Engine { }; std::unordered_map<ShaderType, ShaderInfo, CastHash<ShaderType>> shader_info; + std::vector<PipelineInfo::ShaderInfo> shader_info_rt; }; + Result GetVkShaderStageInfo(ShaderType shader_type, + const PipelineInfo::ShaderInfo& shader_info, + VkPipelineShaderStageCreateInfo* stage_ci); + Result GetVkShaderStageInfo( amber::Pipeline* pipeline, std::vector<VkPipelineShaderStageCreateInfo>* out); Result SetShader(amber::Pipeline* pipeline, - const amber::Pipeline::ShaderInfo& shader); + const amber::Pipeline::ShaderInfo& shader, + size_t index); + + Result GetVkShaderGroupInfo( + amber::Pipeline* pipeline, + std::vector<VkRayTracingShaderGroupCreateInfoKHR>* out); std::unique_ptr<Device> device_; std::unique_ptr<CommandPool> pool_; @@ -92,6 +110,10 @@ class EngineVulkan : public Engine { std::map<amber::Pipeline*, PipelineInfo> pipeline_map_; std::map<std::string, VkShaderModule> shaders_; + + BlasesMap blases_; + + TlasesMap tlases_; }; } // namespace vulkan diff --git a/src/vulkan/graphics_pipeline.cc b/src/vulkan/graphics_pipeline.cc index 485ebb6..9db5eb7 100644 --- a/src/vulkan/graphics_pipeline.cc +++ b/src/vulkan/graphics_pipeline.cc @@ -871,8 +871,8 @@ Result GraphicsPipeline::Clear() { frame_->TransferImagesToHost(command_.get()); - Result r = cmd_buf_guard.Submit(GetFenceTimeout(), - GetPipelineRuntimeLayerEnabled()); + Result r = + cmd_buf_guard.Submit(GetFenceTimeout(), GetPipelineRuntimeLayerEnabled()); if (!r.IsSuccess()) return r; diff --git a/src/vulkan/pipeline.cc b/src/vulkan/pipeline.cc index d03b2de..6fbb254 100644 --- a/src/vulkan/pipeline.cc +++ b/src/vulkan/pipeline.cc @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -26,7 +27,9 @@ #include "src/vulkan/device.h" #include "src/vulkan/graphics_pipeline.h" #include "src/vulkan/image_descriptor.h" +#include "src/vulkan/raytracing_pipeline.h" #include "src/vulkan/sampler_descriptor.h" +#include "src/vulkan/tlas_descriptor.h" namespace amber { namespace vulkan { @@ -40,7 +43,7 @@ Pipeline::Pipeline( PipelineType type, Device* device, uint32_t fence_timeout_ms, - bool pipeline_runtime_layer_enabled, + bool pipeline_runtime_layer_enabled, const std::vector<VkPipelineShaderStageCreateInfo>& shader_stage_info) : device_(device), pipeline_type_(type), @@ -77,6 +80,10 @@ ComputePipeline* Pipeline::AsCompute() { return static_cast<ComputePipeline*>(this); } +RayTracingPipeline* Pipeline::AsRayTracingPipeline() { + return static_cast<RayTracingPipeline*>(this); +} + Result Pipeline::Initialize(CommandPool* pool) { push_constant_ = MakeUnique<PushConstant>(device_); @@ -277,9 +284,9 @@ Result Pipeline::GetDescriptorSlot(uint32_t desc_set, Result Pipeline::AddDescriptorBuffer(Buffer* amber_buffer) { // Don't add the buffer if it's already added. - const auto& buffer = std::find_if( - descriptor_buffers_.begin(), descriptor_buffers_.end(), - [&](const Buffer* buf) { return buf == amber_buffer; }); + const auto& buffer = + std::find_if(descriptor_buffers_.begin(), descriptor_buffers_.end(), + [&](const Buffer* buf) { return buf == amber_buffer; }); if (buffer != descriptor_buffers_.end()) { return {}; } @@ -411,6 +418,35 @@ Result Pipeline::AddSamplerDescriptor(const SamplerCommand* cmd) { return {}; } +Result Pipeline::AddTLASDescriptor(const TLASCommand* cmd) { + if (cmd == nullptr) + return Result("Pipeline::AddTLASDescriptor TLASCommand is nullptr"); + + Descriptor* desc; + Result r = + GetDescriptorSlot(cmd->GetDescriptorSet(), cmd->GetBinding(), &desc); + if (!r.IsSuccess()) + return r; + + auto& descriptors = descriptor_set_info_[cmd->GetDescriptorSet()].descriptors; + + if (desc == nullptr) { + auto tlas_desc = MakeUnique<TLASDescriptor>( + cmd->GetTLAS(), DescriptorType::kTLAS, device_, GetBlases(), + GetTlases(), cmd->GetDescriptorSet(), cmd->GetBinding()); + descriptors.push_back(std::move(tlas_desc)); + } else { + if (desc->GetDescriptorType() != DescriptorType::kTLAS) { + return Result( + "Descriptors bound to the same binding needs to have matching " + "descriptor types"); + } + desc->AsTLASDescriptor()->AddAmberTLAS(cmd->GetTLAS()); + } + + return {}; +} + Result Pipeline::SendDescriptorDataToDeviceIfNeeded() { { CommandBufferGuard guard(GetCommandBuffer()); @@ -434,7 +470,7 @@ Result Pipeline::SendDescriptorDataToDeviceIfNeeded() { } Result r = descriptor_transfer_resources_[buffer]->Initialize(); if (!r.IsSuccess()) - return r; + return r; } // Note that if a buffer for a descriptor is host accessible and @@ -443,8 +479,8 @@ Result Pipeline::SendDescriptorDataToDeviceIfNeeded() { // done after resizing backed buffer i.e., copying data to the new // buffer from the old one. Thus, we must submit commands here to // guarantee this. - Result r = guard.Submit(GetFenceTimeout(), - GetPipelineRuntimeLayerEnabled()); + Result r = + guard.Submit(GetFenceTimeout(), GetPipelineRuntimeLayerEnabled()); if (!r.IsSuccess()) return r; } @@ -508,8 +544,10 @@ void Pipeline::BindVkDescriptorSets(const VkPipelineLayout& pipeline_layout) { device_->GetPtrs()->vkCmdBindDescriptorSets( command_->GetVkCommandBuffer(), - IsGraphics() ? VK_PIPELINE_BIND_POINT_GRAPHICS - : VK_PIPELINE_BIND_POINT_COMPUTE, + IsGraphics() ? VK_PIPELINE_BIND_POINT_GRAPHICS + : IsCompute() ? VK_PIPELINE_BIND_POINT_COMPUTE + : IsRayTracing() ? VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR + : VK_PIPELINE_BIND_POINT_MAX_ENUM, pipeline_layout, static_cast<uint32_t>(i), 1, &descriptor_set_info_[i].vk_desc_set, static_cast<uint32_t>(dynamic_offsets.size()), dynamic_offsets.data()); @@ -517,6 +555,9 @@ void Pipeline::BindVkDescriptorSets(const VkPipelineLayout& pipeline_layout) { } Result Pipeline::ReadbackDescriptorsToHostDataQueue() { + if (descriptor_buffers_.empty()) + return Result{}; + // Record required commands to copy the data to a host visible buffer. { CommandBufferGuard guard(GetCommandBuffer()); @@ -551,8 +592,8 @@ Result Pipeline::ReadbackDescriptorsToHostDataQueue() { } } - Result r = guard.Submit(GetFenceTimeout(), - GetPipelineRuntimeLayerEnabled()); + Result r = + guard.Submit(GetFenceTimeout(), GetPipelineRuntimeLayerEnabled()); if (!r.IsSuccess()) return r; } diff --git a/src/vulkan/pipeline.h b/src/vulkan/pipeline.h index 58cefcb..99bac36 100644 --- a/src/vulkan/pipeline.h +++ b/src/vulkan/pipeline.h @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -38,6 +39,7 @@ namespace vulkan { class ComputePipeline; class Device; class GraphicsPipeline; +class RayTracingPipeline; /// Base class for a pipeline in Vulkan. class Pipeline { @@ -46,12 +48,17 @@ class Pipeline { bool IsGraphics() const { return pipeline_type_ == PipelineType::kGraphics; } bool IsCompute() const { return pipeline_type_ == PipelineType::kCompute; } + bool IsRayTracing() const { + return pipeline_type_ == PipelineType::kRayTracing; + } GraphicsPipeline* AsGraphics(); ComputePipeline* AsCompute(); + RayTracingPipeline* AsRayTracingPipeline(); Result AddBufferDescriptor(const BufferCommand*); Result AddSamplerDescriptor(const SamplerCommand*); + Result AddTLASDescriptor(const TLASCommand*); /// Add |buffer| data to the push constants at |offset|. Result AddPushConstantBuffer(const Buffer* buf, uint32_t offset); @@ -72,6 +79,8 @@ class Pipeline { CommandBuffer* GetCommandBuffer() const { return command_.get(); } Device* GetDevice() const { return device_; } + virtual BlasesMap* GetBlases() { return nullptr; } + virtual TlasesMap* GetTlases() { return nullptr; } protected: Pipeline( diff --git a/src/vulkan/raytracing_pipeline.cc b/src/vulkan/raytracing_pipeline.cc new file mode 100644 index 0000000..7e9b6a2 --- /dev/null +++ b/src/vulkan/raytracing_pipeline.cc @@ -0,0 +1,240 @@ +// Copyright 2024 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. + +#include <utility> + +#include "src/vulkan/raytracing_pipeline.h" + +#include "src/vulkan/blas.h" +#include "src/vulkan/command_pool.h" +#include "src/vulkan/device.h" +#include "src/vulkan/sbt.h" +#include "src/vulkan/tlas.h" + +namespace amber { +namespace vulkan { + +inline VkStridedDeviceAddressRegionKHR makeStridedDeviceAddressRegionKHR( + VkDeviceAddress deviceAddress, + VkDeviceSize stride, + VkDeviceSize size) { + VkStridedDeviceAddressRegionKHR res; + res.deviceAddress = deviceAddress; + res.stride = stride; + res.size = size; + return res; +} + +inline VkDeviceAddress getBufferDeviceAddress(Device* device, VkBuffer buffer) { + const VkBufferDeviceAddressInfo bufferDeviceAddressInfo = { + VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_KHR, + nullptr, + buffer, + }; + + return device->GetPtrs()->vkGetBufferDeviceAddress(device->GetVkDevice(), + &bufferDeviceAddressInfo); +} + +RayTracingPipeline::RayTracingPipeline( + Device* device, + BlasesMap* blases, + TlasesMap* tlases, + uint32_t fence_timeout_ms, + bool pipeline_runtime_layer_enabled, + const std::vector<VkPipelineShaderStageCreateInfo>& shader_stage_info) + : Pipeline(PipelineType::kRayTracing, + device, + fence_timeout_ms, + pipeline_runtime_layer_enabled, + shader_stage_info), + shader_group_create_info_(), + blases_(blases), + tlases_(tlases) {} + +RayTracingPipeline::~RayTracingPipeline() = default; + +Result RayTracingPipeline::Initialize( + CommandPool* pool, + std::vector<VkRayTracingShaderGroupCreateInfoKHR>& + shader_group_create_info) { + shader_group_create_info_.swap(shader_group_create_info); + + return Pipeline::Initialize(pool); +} + +Result RayTracingPipeline::CreateVkRayTracingPipeline( + const VkPipelineLayout& pipeline_layout, + VkPipeline* pipeline) { + std::vector<VkPipelineShaderStageCreateInfo> shader_stage_info = + GetVkShaderStageInfo(); + + for (auto& info : shader_stage_info) + info.pName = GetEntryPointName(info.stage); + + const uint32_t maxRecursionDepth = + 1u; // make it a parameter in the AmberScript + + VkRayTracingPipelineCreateInfoKHR pipelineCreateInfo{ + VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR, + nullptr, + static_cast<VkPipelineCreateFlags>(0), + static_cast<uint32_t>(shader_stage_info.size()), + shader_stage_info.data(), + static_cast<uint32_t>(shader_group_create_info_.size()), + shader_group_create_info_.data(), + maxRecursionDepth, + nullptr, + nullptr, + nullptr, + pipeline_layout, + VK_NULL_HANDLE, + 0, + }; + + VkResult r = device_->GetPtrs()->vkCreateRayTracingPipelinesKHR( + device_->GetVkDevice(), VK_NULL_HANDLE, VK_NULL_HANDLE, 1u, + &pipelineCreateInfo, nullptr, pipeline); + if (r != VK_SUCCESS) + return Result("Vulkan::Calling vkCreateRayTracingPipelinesKHR Fail"); + + return {}; +} + +Result RayTracingPipeline::getVulkanSBTRegion( + VkPipeline pipeline, + amber::SBT* aSBT, + VkStridedDeviceAddressRegionKHR* region) { + const uint32_t handle_size = device_->GetRayTracingShaderGroupHandleSize(); + if (aSBT != nullptr) { + SBT* vSBT = nullptr; + auto x = sbtses_.find(aSBT); + + if (x == sbtses_.end()) { + auto p = MakeUnique<amber::vulkan::SBT>(device_); + sbts_.push_back(std::move(p)); + auto sbt_vulkan = sbtses_.emplace(aSBT, sbts_.back().get()); + + vSBT = sbt_vulkan.first->second; + + Result r = vSBT->Create(aSBT, pipeline); + if (!r.IsSuccess()) + return r; + } else { + vSBT = x->second; + } + + *region = makeStridedDeviceAddressRegionKHR( + getBufferDeviceAddress(device_, vSBT->getBuffer()->GetVkBuffer()), + handle_size, handle_size * aSBT->GetSBTSize()); + } else { + *region = makeStridedDeviceAddressRegionKHR(0, 0, 0); + } + + return {}; +} + +Result RayTracingPipeline::TraceRays(amber::SBT* rSBT, + amber::SBT* mSBT, + amber::SBT* hSBT, + amber::SBT* cSBT, + uint32_t x, + uint32_t y, + uint32_t z) { + Result r = SendDescriptorDataToDeviceIfNeeded(); + if (!r.IsSuccess()) + return r; + + VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; + r = CreateVkPipelineLayout(&pipeline_layout); + if (!r.IsSuccess()) + return r; + + VkPipeline pipeline = VK_NULL_HANDLE; + r = CreateVkRayTracingPipeline(pipeline_layout, &pipeline); + if (!r.IsSuccess()) + return r; + + // Note that a command updating a descriptor set and a command using + // it must be submitted separately, because using a descriptor set + // while updating it is not safe. + UpdateDescriptorSetsIfNeeded(); + + { + CommandBufferGuard guard(GetCommandBuffer()); + if (!guard.IsRecording()) + return guard.GetResult(); + + for (auto& i : *blases_) { + i.second->BuildBLAS(GetCommandBuffer()); + } + for (auto& i : *tlases_) { + i.second->BuildTLAS(GetCommandBuffer()->GetVkCommandBuffer()); + } + + BindVkDescriptorSets(pipeline_layout); + + r = RecordPushConstant(pipeline_layout); + if (!r.IsSuccess()) + return r; + + device_->GetPtrs()->vkCmdBindPipeline( + command_->GetVkCommandBuffer(), VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, + pipeline); + + VkStridedDeviceAddressRegionKHR rSBTRegion = {}; + VkStridedDeviceAddressRegionKHR mSBTRegion = {}; + VkStridedDeviceAddressRegionKHR hSBTRegion = {}; + VkStridedDeviceAddressRegionKHR cSBTRegion = {}; + + r = getVulkanSBTRegion(pipeline, rSBT, &rSBTRegion); + if (!r.IsSuccess()) + return r; + + r = getVulkanSBTRegion(pipeline, mSBT, &mSBTRegion); + if (!r.IsSuccess()) + return r; + + r = getVulkanSBTRegion(pipeline, hSBT, &hSBTRegion); + if (!r.IsSuccess()) + return r; + + r = getVulkanSBTRegion(pipeline, cSBT, &cSBTRegion); + if (!r.IsSuccess()) + return r; + + device_->GetPtrs()->vkCmdTraceRaysKHR(command_->GetVkCommandBuffer(), + &rSBTRegion, &mSBTRegion, &hSBTRegion, + &cSBTRegion, x, y, z); + + r = guard.Submit(GetFenceTimeout(), GetPipelineRuntimeLayerEnabled()); + if (!r.IsSuccess()) + return r; + } + + r = ReadbackDescriptorsToHostDataQueue(); + if (!r.IsSuccess()) + return r; + + device_->GetPtrs()->vkDestroyPipeline(device_->GetVkDevice(), pipeline, + nullptr); + device_->GetPtrs()->vkDestroyPipelineLayout(device_->GetVkDevice(), + pipeline_layout, nullptr); + + return {}; +} + +} // namespace vulkan +} // namespace amber diff --git a/src/vulkan/raytracing_pipeline.h b/src/vulkan/raytracing_pipeline.h new file mode 100644 index 0000000..66f4a4c --- /dev/null +++ b/src/vulkan/raytracing_pipeline.h @@ -0,0 +1,76 @@ +// Copyright 2024 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. + +#ifndef SRC_VULKAN_RAYTRACING_PIPELINE_H_ +#define SRC_VULKAN_RAYTRACING_PIPELINE_H_ + +#include <memory> +#include <vector> + +#include "amber/result.h" +#include "amber/vulkan_header.h" +#include "src/vulkan/pipeline.h" + +namespace amber { +namespace vulkan { + +/// Pipepline to handle compute commands. +class RayTracingPipeline : public Pipeline { + public: + RayTracingPipeline( + Device* device, + BlasesMap* blases, + TlasesMap* tlases, + uint32_t fence_timeout_ms, + bool pipeline_runtime_layer_enabled, + const std::vector<VkPipelineShaderStageCreateInfo>& shader_stage_info); + ~RayTracingPipeline() override; + + Result AddTLASDescriptor(const TLASCommand* cmd); + + Result Initialize(CommandPool* pool, + std::vector<VkRayTracingShaderGroupCreateInfoKHR>& + shader_group_create_info); + + Result getVulkanSBTRegion(VkPipeline pipeline, + amber::SBT* aSBT, + VkStridedDeviceAddressRegionKHR* region); + + Result TraceRays(amber::SBT* rSBT, + amber::SBT* mSBT, + amber::SBT* hSBT, + amber::SBT* cSBT, + uint32_t x, + uint32_t y, + uint32_t z); + + BlasesMap* GetBlases() override { return blases_; } + TlasesMap* GetTlases() override { return tlases_; } + + private: + Result CreateVkRayTracingPipeline(const VkPipelineLayout& pipeline_layout, + VkPipeline* pipeline); + + std::vector<VkRayTracingShaderGroupCreateInfoKHR> shader_group_create_info_; + BlasesMap* blases_; + TlasesMap* tlases_; + SbtsMap sbtses_; + std::vector<std::unique_ptr<amber::vulkan::SBT>> sbts_; +}; + +} // namespace vulkan +} // namespace amber + +#endif // SRC_VULKAN_RAYTRACING_PIPELINE_H_ diff --git a/src/vulkan/resource.cc b/src/vulkan/resource.cc index a52df3d..15537a1 100644 --- a/src/vulkan/resource.cc +++ b/src/vulkan/resource.cc @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -141,9 +142,21 @@ Result Resource::AllocateMemory(VkDeviceMemory* memory, VkDeviceSize size, uint32_t memory_type_index) { VkMemoryAllocateInfo alloc_info = VkMemoryAllocateInfo(); + VkMemoryAllocateFlagsInfo allocFlagsInfo = VkMemoryAllocateFlagsInfo(); + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; alloc_info.allocationSize = size; alloc_info.memoryTypeIndex = memory_type_index; + + if (memory_allocate_flags_ != 0) { + allocFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO; + allocFlagsInfo.pNext = nullptr; + allocFlagsInfo.flags = memory_allocate_flags_; + allocFlagsInfo.deviceMask = 0u; + + alloc_info.pNext = &allocFlagsInfo; + } + if (device_->GetPtrs()->vkAllocateMemory(device_->GetVkDevice(), &alloc_info, nullptr, memory) != VK_SUCCESS) { return Result("Vulkan::Calling vkAllocateMemory Fail"); diff --git a/src/vulkan/resource.h b/src/vulkan/resource.h index d3cc0de..8ae4fec 100644 --- a/src/vulkan/resource.h +++ b/src/vulkan/resource.h @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -15,6 +16,7 @@ #ifndef SRC_VULKAN_RESOURCE_H_ #define SRC_VULKAN_RESOURCE_H_ +#include <map> #include <memory> #include <vector> @@ -23,12 +25,24 @@ #include "amber/vulkan_header.h" namespace amber { + +class BLAS; +class TLAS; +class SBT; + namespace vulkan { class CommandBuffer; class Device; class TransferBuffer; class TransferImage; +class BLAS; +class TLAS; +class SBT; + +typedef std::map<amber::BLAS*, std::unique_ptr<amber::vulkan::BLAS>> BlasesMap; +typedef std::map<amber::TLAS*, std::unique_ptr<amber::vulkan::TLAS>> TlasesMap; +typedef std::map<amber::SBT*, amber::vulkan::SBT*> SbtsMap; // Class for Vulkan resources. Its children are Vulkan Buffer and Vulkan Image. class Resource { @@ -52,6 +66,15 @@ class Resource { virtual Result Initialize() = 0; virtual TransferBuffer* AsTransferBuffer() { return nullptr; } virtual TransferImage* AsTransferImage() { return nullptr; } + virtual void AddAllocateFlags(VkMemoryAllocateFlags memory_allocate_flags) { + memory_allocate_flags_ |= memory_allocate_flags; + } + VkMemoryPropertyFlags GetMemoryPropertiesFlags() { + return memory_properties_flags_; + } + void SetMemoryPropertiesFlags(VkMemoryPropertyFlags flags) { + memory_properties_flags_ = flags; + } protected: Resource(Device* device, uint32_t size); @@ -90,6 +113,10 @@ class Resource { uint32_t size_in_bytes_ = 0; void* memory_ptr_ = nullptr; bool is_read_only_ = false; + VkMemoryAllocateFlags memory_allocate_flags_ = 0u; + VkMemoryPropertyFlags memory_properties_flags_ = + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | + VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; }; } // namespace vulkan diff --git a/src/vulkan/sbt.cc b/src/vulkan/sbt.cc new file mode 100644 index 0000000..caead26 --- /dev/null +++ b/src/vulkan/sbt.cc @@ -0,0 +1,73 @@ +// Copyright 2024 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. + +#include <cstring> + +#include "src/vulkan/sbt.h" +#include "src/vulkan/pipeline.h" + +namespace amber { +namespace vulkan { + +SBT::SBT(Device* device) : device_(device) {} + +Result SBT::Create(amber::SBT* sbt, VkPipeline pipeline) { + uint32_t handles_count = 0; + for (auto& x : sbt->GetSBTRecords()) + handles_count += x->GetCount(); + + if (handles_count == 0) + return Result("SBT must contain at least one record"); + + const uint32_t handle_size = device_->GetRayTracingShaderGroupHandleSize(); + const uint32_t buffer_size = handle_size * handles_count; + std::vector<uint8_t> handles(buffer_size); + + buffer_ = MakeUnique<TransferBuffer>(device_, buffer_size, nullptr); + buffer_->AddUsageFlags(VK_BUFFER_USAGE_TRANSFER_DST_BIT | + VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + buffer_->AddAllocateFlags(VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT); + Result r = buffer_->Initialize(); + if (!r.IsSuccess()) + return r; + + size_t start = 0; + for (auto& x : sbt->GetSBTRecords()) { + const uint32_t index = x->GetUsedShaderGroupPipelineIndex(); + const uint32_t count = x->GetCount(); + if (index != static_cast<uint32_t>(-1)) { + VkResult vr = device_->GetPtrs()->vkGetRayTracingShaderGroupHandlesKHR( + device_->GetVkDevice(), pipeline, index, count, count * handle_size, + &handles[start * handle_size]); + + if (vr != VK_SUCCESS) + return Result("vkGetRayTracingShaderGroupHandlesKHR has failed"); + } + + start += x->GetCount(); + } + + memcpy(buffer_->HostAccessibleMemoryPtr(), handles.data(), handles.size()); + + // Skip flush as memory allocated for buffer is coherent + + return r; +} + +SBT::~SBT() = default; + +} // namespace vulkan +} // namespace amber diff --git a/src/vulkan/sbt.h b/src/vulkan/sbt.h new file mode 100644 index 0000000..7ff1242 --- /dev/null +++ b/src/vulkan/sbt.h @@ -0,0 +1,45 @@ +// Copyright 2024 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. + +#ifndef SRC_VULKAN_SBT_H_ +#define SRC_VULKAN_SBT_H_ + +#include <memory> +#include <vector> + +#include "src/acceleration_structure.h" +#include "src/vulkan/device.h" +#include "src/vulkan/transfer_buffer.h" + +namespace amber { +namespace vulkan { + +class SBT { + public: + explicit SBT(Device* device); + ~SBT(); + + Result Create(amber::SBT* sbt, VkPipeline pipeline); + TransferBuffer* getBuffer() { return buffer_.get(); } + + private: + Device* device_ = nullptr; + std::unique_ptr<TransferBuffer> buffer_; +}; + +} // namespace vulkan +} // namespace amber + +#endif // SRC_VULKAN_SBT_H_ diff --git a/src/vulkan/tlas.cc b/src/vulkan/tlas.cc new file mode 100644 index 0000000..c0fe422 --- /dev/null +++ b/src/vulkan/tlas.cc @@ -0,0 +1,245 @@ +// Copyright 2024 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. + +#include "src/vulkan/tlas.h" +#include "src/vulkan/blas.h" + +namespace amber { +namespace vulkan { + +static VkTransformMatrixKHR makeVkMatrix(const float* m) { + const VkTransformMatrixKHR identityMatrix3x4 = {{{1.0f, 0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}}}; + VkTransformMatrixKHR v; + + if (m == nullptr) + return identityMatrix3x4; + + for (size_t i = 0; i < 12; i++) { + const size_t r = i / 4; + const size_t c = i % 4; + v.matrix[r][c] = m[i]; + } + + return v; +} + +TLAS::TLAS(Device* device) : device_(device) {} + +Result TLAS::CreateTLAS(amber::TLAS* tlas, + BlasesMap* blases) { + if (tlas_ != VK_NULL_HANDLE) + return {}; + + assert(tlas != nullptr); + + VkDeviceOrHostAddressConstKHR const_default_ptr; + VkDeviceOrHostAddressKHR default_ptr; + + const_default_ptr.hostAddress = nullptr; + default_ptr.hostAddress = nullptr; + + instances_count_ = static_cast<uint32_t>(tlas->GetInstances().size()); + + const uint32_t ib_size = + uint32_t(instances_count_ * sizeof(VkAccelerationStructureInstanceKHR)); + + instance_buffer_ = MakeUnique<TransferBuffer>(device_, ib_size, nullptr); + instance_buffer_->AddUsageFlags( + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + instance_buffer_->AddAllocateFlags(VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT); + instance_buffer_->Initialize(); + + VkAccelerationStructureInstanceKHR* instances_ptr = + reinterpret_cast<VkAccelerationStructureInstanceKHR*> + (instance_buffer_->HostAccessibleMemoryPtr()); + + for (auto& instance : tlas->GetInstances()) { + auto blas = instance->GetUsedBLAS(); + + assert(blas != nullptr); + + auto blas_vulkan_it = blases->find(blas); + amber::vulkan::BLAS* blas_vulkan_ptr = nullptr; + + if (blas_vulkan_it == blases->end()) { + auto blas_vulkan = + blases->emplace(blas, new amber::vulkan::BLAS(device_)); + blas_vulkan_ptr = blas_vulkan.first->second.get(); + + Result r = blas_vulkan_ptr->CreateBLAS(blas); + + if (!r.IsSuccess()) + return r; + } else { + blas_vulkan_ptr = blas_vulkan_it->second.get(); + } + + VkDeviceAddress accelerationStructureAddress = + blas_vulkan_ptr->getVkBLASDeviceAddress(); + + *instances_ptr = VkAccelerationStructureInstanceKHR{ + makeVkMatrix(instance->GetTransform()), + instance->GetInstanceIndex(), + instance->GetMask(), + instance->GetOffset(), + instance->GetFlags(), + static_cast<uint64_t>(accelerationStructureAddress)}; + + instances_ptr++; + } + + VkAccelerationStructureGeometryInstancesDataKHR + accelerationStructureGeometryInstancesDataKHR = { + VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR, + nullptr, + VK_FALSE, + const_default_ptr, + }; + VkAccelerationStructureGeometryDataKHR geometry = {}; + geometry.instances = accelerationStructureGeometryInstancesDataKHR; + + accelerationStructureGeometryKHR_ = { + VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR, + nullptr, + VK_GEOMETRY_TYPE_INSTANCES_KHR, + geometry, + 0, + }; + + accelerationStructureBuildGeometryInfoKHR_ = { + VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR, + nullptr, + VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, + 0, + VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR, + VK_NULL_HANDLE, + VK_NULL_HANDLE, + 1, + &accelerationStructureGeometryKHR_, + nullptr, + default_ptr, + }; + + VkAccelerationStructureBuildSizesInfoKHR sizeInfo = { + VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR, + nullptr, + 0, + 0, + 0, + }; + + device_->GetPtrs()->vkGetAccelerationStructureBuildSizesKHR( + device_->GetVkDevice(), VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, + &accelerationStructureBuildGeometryInfoKHR_, &instances_count_, + &sizeInfo); + + const uint32_t as_size = + static_cast<uint32_t>(sizeInfo.accelerationStructureSize); + + buffer_ = MakeUnique<TransferBuffer>(device_, as_size, nullptr); + buffer_->AddUsageFlags( + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + buffer_->AddAllocateFlags(VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT); + buffer_->Initialize(); + + const VkAccelerationStructureCreateInfoKHR + accelerationStructureCreateInfoKHR = { + VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR, + nullptr, + 0, + buffer_->GetVkBuffer(), + 0, + as_size, + VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, + 0, + }; + + if (device_->GetPtrs()->vkCreateAccelerationStructureKHR( + device_->GetVkDevice(), &accelerationStructureCreateInfoKHR, nullptr, + &tlas_) != VK_SUCCESS) { + return Result( + "Vulkan::Calling vkCreateAccelerationStructureKHR " + "failed"); + } + + accelerationStructureBuildGeometryInfoKHR_.dstAccelerationStructure = tlas_; + + if (sizeInfo.buildScratchSize > 0) { + scratch_buffer_ = MakeUnique<TransferBuffer>( + device_, static_cast<uint32_t>(sizeInfo.buildScratchSize), nullptr); + scratch_buffer_->AddUsageFlags(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + scratch_buffer_->AddAllocateFlags(VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT); + scratch_buffer_->Initialize(); + + accelerationStructureBuildGeometryInfoKHR_.scratchData.deviceAddress = + scratch_buffer_->getBufferDeviceAddress(); + } + + accelerationStructureGeometryKHR_.geometry.instances.data.deviceAddress = + instance_buffer_->getBufferDeviceAddress(); + + return {}; +} + +Result TLAS::BuildTLAS(VkCommandBuffer cmdBuffer) { + if (tlas_ == VK_NULL_HANDLE) + return Result("Acceleration structure should be created first"); + if (built_) + return {}; + + VkAccelerationStructureBuildRangeInfoKHR + accelerationStructureBuildRangeInfoKHR = {instances_count_, 0, 0, 0}; + VkAccelerationStructureBuildRangeInfoKHR* + accelerationStructureBuildRangeInfoKHRPtr = + &accelerationStructureBuildRangeInfoKHR; + + device_->GetPtrs()->vkCmdBuildAccelerationStructuresKHR( + cmdBuffer, 1, &accelerationStructureBuildGeometryInfoKHR_, + &accelerationStructureBuildRangeInfoKHRPtr); + + const VkAccessFlags accessMasks = + VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR | + VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR; + const VkMemoryBarrier memBarrier{ + VK_STRUCTURE_TYPE_MEMORY_BARRIER, + nullptr, + accessMasks, + accessMasks, + }; + + device_->GetPtrs()->vkCmdPipelineBarrier( + cmdBuffer, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &memBarrier, 0, nullptr, 0, + nullptr); + + built_ = true; + + return {}; +} + +TLAS::~TLAS() { + if (tlas_ != VK_NULL_HANDLE) { + device_->GetPtrs()->vkDestroyAccelerationStructureKHR( + device_->GetVkDevice(), tlas_, nullptr); + } +} + +} // namespace vulkan +} // namespace amber diff --git a/src/vulkan/tlas.h b/src/vulkan/tlas.h new file mode 100644 index 0000000..9046d65 --- /dev/null +++ b/src/vulkan/tlas.h @@ -0,0 +1,53 @@ +// Copyright 2024 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. + +#ifndef SRC_VULKAN_TLAS_H_ +#define SRC_VULKAN_TLAS_H_ + +#include <memory> + +#include "src/acceleration_structure.h" +#include "src/vulkan/device.h" +#include "src/vulkan/transfer_buffer.h" + +namespace amber { +namespace vulkan { + +class TLAS { + public: + explicit TLAS(Device* device); + ~TLAS(); + + Result CreateTLAS(amber::TLAS* tlas, BlasesMap* blases); + Result BuildTLAS(VkCommandBuffer cmdBuffer); + VkAccelerationStructureKHR GetVkTLAS() { return tlas_; } + + private: + Device* device_ = nullptr; + VkAccelerationStructureKHR tlas_ = VK_NULL_HANDLE; + bool built_ = false; + std::unique_ptr<TransferBuffer> buffer_; + std::unique_ptr<TransferBuffer> scratch_buffer_; + std::unique_ptr<TransferBuffer> instance_buffer_; + uint32_t instances_count_ = 0; + VkAccelerationStructureGeometryKHR accelerationStructureGeometryKHR_; + VkAccelerationStructureBuildGeometryInfoKHR + accelerationStructureBuildGeometryInfoKHR_; +}; + +} // namespace vulkan +} // namespace amber + +#endif // SRC_VULKAN_TLAS_H_ diff --git a/src/vulkan/tlas_descriptor.cc b/src/vulkan/tlas_descriptor.cc new file mode 100644 index 0000000..a1c7adf --- /dev/null +++ b/src/vulkan/tlas_descriptor.cc @@ -0,0 +1,86 @@ +// Copyright 2024 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. + +#include "src/vulkan/tlas_descriptor.h" + +#include "src/vulkan/device.h" +#include "src/vulkan/resource.h" + +namespace amber { +namespace vulkan { + +TLASDescriptor::TLASDescriptor(amber::TLAS* tlas, + DescriptorType type, + Device* device, + BlasesMap* blases, + TlasesMap* tlases, + uint32_t desc_set, + uint32_t binding) + : Descriptor(type, device, desc_set, binding), + blases_(blases), + tlases_(tlases) { + assert(blases != nullptr); + assert(tlases != nullptr); + AddAmberTLAS(tlas); +} + +TLASDescriptor::~TLASDescriptor() = default; + +Result TLASDescriptor::CreateResourceIfNeeded() { + for (amber::TLAS* amber_tlas : amber_tlases_) { + if (tlases_->find(amber_tlas) == tlases_->end()) { + auto& vulkan_tlas = ((*tlases_)[amber_tlas] = MakeUnique<TLAS>(device_)); + Result r = vulkan_tlas->CreateTLAS(amber_tlas, blases_); + if (!r.IsSuccess()) + return r; + } + } + + return {}; +} + +void TLASDescriptor::UpdateDescriptorSetIfNeeded( + VkDescriptorSet descriptor_set) { + std::vector<VkAccelerationStructureKHR> as; + + for (auto& amber_tlas : amber_tlases_) { + auto vulkan_tlas = tlases_->find(amber_tlas); + assert(vulkan_tlas != tlases_->end()); + as.push_back(vulkan_tlas->second->GetVkTLAS()); + } + + VkWriteDescriptorSetAccelerationStructureKHR writeDescriptorTlas; + writeDescriptorTlas.sType = + VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR; + writeDescriptorTlas.pNext = nullptr; + writeDescriptorTlas.accelerationStructureCount = + static_cast<uint32_t>(as.size()); + writeDescriptorTlas.pAccelerationStructures = as.data(); + + VkWriteDescriptorSet write = VkWriteDescriptorSet(); + write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write.pNext = &writeDescriptorTlas; + write.dstSet = descriptor_set; + write.dstBinding = binding_; + write.dstArrayElement = 0; + write.descriptorCount = static_cast<uint32_t>(as.size()); + write.descriptorType = GetVkDescriptorType(); + + device_->GetPtrs()->vkUpdateDescriptorSets(device_->GetVkDevice(), 1, &write, + 0, nullptr); +} + +} // namespace vulkan +} // namespace amber diff --git a/src/vulkan/tlas_descriptor.h b/src/vulkan/tlas_descriptor.h new file mode 100644 index 0000000..d043ce2 --- /dev/null +++ b/src/vulkan/tlas_descriptor.h @@ -0,0 +1,59 @@ +// Copyright 2024 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. + +#ifndef SRC_VULKAN_TLAS_DESCRIPTOR_H_ +#define SRC_VULKAN_TLAS_DESCRIPTOR_H_ + +#include <memory> +#include <vector> + +#include "src/vulkan/descriptor.h" +#include "src/vulkan/tlas.h" +#include "src/vulkan/transfer_image.h" + +namespace amber { +namespace vulkan { + +class TLASDescriptor : public Descriptor { + public: + TLASDescriptor(amber::TLAS* tlas, + DescriptorType type, + Device* device, + BlasesMap* blases, + TlasesMap* tlases, + uint32_t desc_set, + uint32_t binding); + ~TLASDescriptor() override; + + void UpdateDescriptorSetIfNeeded(VkDescriptorSet descriptor_set) override; + + Result CreateResourceIfNeeded() override; + + void AddAmberTLAS(amber::TLAS* tlas) { amber_tlases_.push_back(tlas); } + uint32_t GetDescriptorCount() override { + return static_cast<uint32_t>(amber_tlases_.size()); + } + TLASDescriptor* AsTLASDescriptor() override { return this; } + + private: + std::vector<amber::TLAS*> amber_tlases_; + BlasesMap* blases_; + TlasesMap* tlases_; +}; + +} // namespace vulkan +} // namespace amber + +#endif // SRC_VULKAN_TLAS_DESCRIPTOR_H_ diff --git a/src/vulkan/transfer_buffer.cc b/src/vulkan/transfer_buffer.cc index 512fb7b..174c42b 100644 --- a/src/vulkan/transfer_buffer.cc +++ b/src/vulkan/transfer_buffer.cc @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -56,10 +57,8 @@ Result TransferBuffer::Initialize() { return r; uint32_t memory_type_index = 0; - r = AllocateAndBindMemoryToVkBuffer(buffer_, &memory_, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | - VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - true, &memory_type_index); + r = AllocateAndBindMemoryToVkBuffer( + buffer_, &memory_, GetMemoryPropertiesFlags(), true, &memory_type_index); if (!r.IsSuccess()) return r; @@ -90,6 +89,17 @@ Result TransferBuffer::Initialize() { return MapMemory(memory_); } +VkDeviceAddress TransferBuffer::getBufferDeviceAddress() { + const VkBufferDeviceAddressInfo bufferDeviceAddressInfo = { + VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO_KHR, + nullptr, + GetVkBuffer(), + }; + + return device_->GetPtrs()->vkGetBufferDeviceAddress(device_->GetVkDevice(), + &bufferDeviceAddressInfo); +} + void TransferBuffer::CopyToDevice(CommandBuffer* command_buffer) { // This is redundant because this buffer is always host visible // and coherent and vkQueueSubmit will make writes from host diff --git a/src/vulkan/transfer_buffer.h b/src/vulkan/transfer_buffer.h index 7d96bec..8734363 100644 --- a/src/vulkan/transfer_buffer.h +++ b/src/vulkan/transfer_buffer.h @@ -1,4 +1,5 @@ // Copyright 2018 The Amber Authors. +// Copyright (C) 2024 Advanced Micro Devices, Inc. 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. @@ -48,6 +49,7 @@ class TransferBuffer : public Resource { const VkBufferView* GetVkBufferView() const { return &view_; } VkBuffer GetVkBuffer() const { return buffer_; } + VkDeviceAddress getBufferDeviceAddress(); /// Records a command on |command_buffer| to copy the buffer contents from the /// host to the device. diff --git a/src/vulkan/vk-funcs-1-0.inc b/src/vulkan/vk-funcs-1-0.inc index b5a7ac0..50a821b 100644 --- a/src/vulkan/vk-funcs-1-0.inc +++ b/src/vulkan/vk-funcs-1-0.inc @@ -21,6 +21,7 @@ AMBER_VK_FUNC(vkCmdPipelineBarrier) AMBER_VK_FUNC(vkCmdPushConstants) AMBER_VK_FUNC(vkCreateBuffer) AMBER_VK_FUNC(vkCreateBufferView) +AMBER_VK_FUNC(vkGetBufferDeviceAddress) AMBER_VK_FUNC(vkCreateCommandPool) AMBER_VK_FUNC(vkCreateComputePipelines) AMBER_VK_FUNC(vkCreateDescriptorPool) diff --git a/src/vulkan/vk-funcs-1-1.inc b/src/vulkan/vk-funcs-1-1.inc index 7fca3c5..db3b2c5 100644 --- a/src/vulkan/vk-funcs-1-1.inc +++ b/src/vulkan/vk-funcs-1-1.inc @@ -1 +1,10 @@ AMBER_VK_FUNC(vkGetPhysicalDeviceProperties2) +AMBER_VK_FUNC(vkCreateRayTracingPipelinesKHR) +AMBER_VK_FUNC(vkCreateAccelerationStructureKHR) +AMBER_VK_FUNC(vkDestroyAccelerationStructureKHR) +AMBER_VK_FUNC(vkGetAccelerationStructureBuildSizesKHR) +AMBER_VK_FUNC(vkBuildAccelerationStructuresKHR) +AMBER_VK_FUNC(vkCmdBuildAccelerationStructuresKHR) +AMBER_VK_FUNC(vkGetAccelerationStructureDeviceAddressKHR) +AMBER_VK_FUNC(vkCmdTraceRaysKHR) +AMBER_VK_FUNC(vkGetRayTracingShaderGroupHandlesKHR)
\ No newline at end of file |