diff --git a/include/API/Device.h b/include/API/Device.h index eaa7e48ca..c5be534ba 100644 --- a/include/API/Device.h +++ b/include/API/Device.h @@ -121,6 +121,12 @@ struct TraditionalRasterPipelineCreateDesc { case Stages::Compute: case Stages::Amplification: case Stages::Mesh: + case Stages::RayGeneration: + case Stages::Miss: + case Stages::ClosestHit: + case Stages::AnyHit: + case Stages::Intersection: + case Stages::Callable: llvm_unreachable("Not a traditional raster pipeline stage."); } } @@ -151,6 +157,12 @@ struct MeshShaderRasterPipelineCreateDesc { case Stages::Domain: case Stages::Geometry: case Stages::Compute: + case Stages::RayGeneration: + case Stages::Miss: + case Stages::ClosestHit: + case Stages::AnyHit: + case Stages::Intersection: + case Stages::Callable: llvm_unreachable("Not a mesh raster pipeline stage."); } } diff --git a/include/Support/Pipeline.h b/include/Support/Pipeline.h index c138457c2..285f1534f 100644 --- a/include/Support/Pipeline.h +++ b/include/Support/Pipeline.h @@ -41,15 +41,53 @@ enum class Stages { // Mesh Shader Raster Amplification, - Mesh + Mesh, + + // Ray Tracing + RayGeneration, + Miss, + ClosestHit, + AnyHit, + Intersection, + Callable }; inline constexpr std::array AllStages = { - Stages::Compute, Stages::Vertex, Stages::Hull, Stages::Domain, - Stages::Geometry, Stages::Pixel, Stages::Amplification, Stages::Mesh, + Stages::Compute, Stages::Vertex, Stages::Hull, + Stages::Domain, Stages::Geometry, Stages::Pixel, + Stages::Amplification, Stages::Mesh, Stages::RayGeneration, + Stages::Miss, Stages::ClosestHit, Stages::AnyHit, + Stages::Intersection, Stages::Callable, }; inline constexpr size_t NumStages = AllStages.size(); -enum class ShaderPipelineKind { Compute, TraditionalRaster, MeshShaderRaster }; +inline constexpr bool isRayTracingStage(Stages S) { + switch (S) { + case Stages::RayGeneration: + case Stages::Miss: + case Stages::ClosestHit: + case Stages::AnyHit: + case Stages::Intersection: + case Stages::Callable: + return true; + case Stages::Compute: + case Stages::Vertex: + case Stages::Hull: + case Stages::Domain: + case Stages::Geometry: + case Stages::Pixel: + case Stages::Amplification: + case Stages::Mesh: + return false; + } + llvm_unreachable("All stages handled"); +} + +enum class ShaderPipelineKind { + Compute, + TraditionalRaster, + MeshShaderRaster, + RayTracing +}; enum class Rule { BufferExact, BufferFloatULP, BufferFloatEpsilon }; @@ -528,6 +566,40 @@ struct AccelerationStructureDescs { llvm::SmallVector TLAS; }; +enum class HitGroupType { Triangles, Procedural }; + +struct HitGroup { + std::string Name; + HitGroupType Type = HitGroupType::Triangles; + std::string ClosestHit; + std::optional AnyHit; + std::optional Intersection; +}; + +struct RayTracingPipelineConfig { + uint32_t MaxTraceRecursionDepth = 1; + uint32_t MaxPayloadSizeInBytes = 0; + uint32_t MaxAttributeSizeInBytes = 8; + std::optional PipelineFlags; +}; + +struct SBTEntry { + // For RayGen / Miss / Callable entries: the shader's Entry name. + // For HitGroup entries: the HitGroup's Name. + std::string ShaderName; + // Optional per-record local-root data, laid out as the local root signature + // describes. Not used during PR1 bring-up; reserved here so the schema is + // stable when local root signatures land. + llvm::SmallVector LocalRootData; +}; + +struct ShaderBindingTable { + SBTEntry RayGen; + llvm::SmallVector Miss; + llvm::SmallVector HitGroup; + llvm::SmallVector Callable; +}; + struct Pipeline { ShaderPipelineKind Kind; llvm::SmallVector Shaders; @@ -541,6 +613,9 @@ struct Pipeline { llvm::SmallVector Sets; DispatchParametersSet DispatchParameters; AccelerationStructureDescs AccelStructs; + std::optional RTConfig; + llvm::SmallVector HitGroups; + std::optional SBT; uint32_t getVertexCount() const { if (DispatchParameters.VertexCount) @@ -608,6 +683,7 @@ struct Pipeline { bool isRaster() const { return isTraditionalRaster() || isMeshShaderRaster(); } + bool isRayTracing() const { return Kind == ShaderPipelineKind::RayTracing; } }; } // namespace offloadtest @@ -628,6 +704,8 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::AABBGeometry) LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::BLASDesc) LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::InstanceDesc) LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::TLASDesc) +LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::HitGroup) +LLVM_YAML_IS_SEQUENCE_VECTOR(offloadtest::SBTEntry) namespace llvm { namespace yaml { @@ -736,6 +814,22 @@ template <> struct MappingTraits { static void mapping(IO &I, offloadtest::AccelerationStructureDescs &D); }; +template <> struct MappingTraits { + static void mapping(IO &I, offloadtest::HitGroup &G); +}; + +template <> struct MappingTraits { + static void mapping(IO &I, offloadtest::RayTracingPipelineConfig &C); +}; + +template <> struct MappingTraits { + static void mapping(IO &I, offloadtest::SBTEntry &E); +}; + +template <> struct MappingTraits { + static void mapping(IO &I, offloadtest::ShaderBindingTable &S); +}; + template <> struct ScalarEnumerationTraits { static void enumeration(IO &I, offloadtest::Rule &V) { #define ENUM_CASE(Val) I.enumCase(V, #Val, offloadtest::Rule::Val) @@ -887,6 +981,21 @@ template <> struct ScalarEnumerationTraits { ENUM_CASE(Pixel); ENUM_CASE(Amplification); ENUM_CASE(Mesh); + ENUM_CASE(RayGeneration); + ENUM_CASE(Miss); + ENUM_CASE(ClosestHit); + ENUM_CASE(AnyHit); + ENUM_CASE(Intersection); + ENUM_CASE(Callable); +#undef ENUM_CASE + } +}; + +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &I, offloadtest::HitGroupType &V) { +#define ENUM_CASE(Val) I.enumCase(V, #Val, offloadtest::HitGroupType::Val) + ENUM_CASE(Triangles); + ENUM_CASE(Procedural); #undef ENUM_CASE } }; diff --git a/lib/API/DX/Device.cpp b/lib/API/DX/Device.cpp index a1217be10..669e44540 100644 --- a/lib/API/DX/Device.cpp +++ b/lib/API/DX/Device.cpp @@ -3131,6 +3131,9 @@ class DXDevice : public offloadtest::Device { if (auto Err = createGraphicsCommands(P, State)) return Err; llvm::outs() << "Graphics command list created complete.\n"; + } else if (P.isRayTracing()) { + return llvm::createStringError( + "RayTracing pipeline not yet supported on DirectX"); } else { return llvm::createStringError("Pipeline was neither Compute nor Raster"); } diff --git a/lib/API/MTL/MTLDevice.cpp b/lib/API/MTL/MTLDevice.cpp index 4a1e64377..d6a5aa6b2 100644 --- a/lib/API/MTL/MTLDevice.cpp +++ b/lib/API/MTL/MTLDevice.cpp @@ -148,6 +148,13 @@ static IRShaderStage getShaderStage(Stages Stage) { return IRShaderStageAmplification; case Stages::Mesh: return IRShaderStageMesh; + case Stages::RayGeneration: + case Stages::Miss: + case Stages::ClosestHit: + case Stages::AnyHit: + case Stages::Intersection: + case Stages::Callable: + llvm_unreachable("RayTracing shaders take a different path on Metal."); } llvm_unreachable("All cases handled"); } @@ -2408,6 +2415,9 @@ class MTLDevice : public offloadtest::Device { if (auto Err = createGraphicsCommands(P, IS)) return Err; + } else if (P.isRayTracing()) { + return llvm::createStringError( + "RayTracing pipeline not yet supported on Metal"); } auto SubmitResult = GraphicsQueue.submit(std::move(IS.CB)); diff --git a/lib/API/VK/Device.cpp b/lib/API/VK/Device.cpp index 4abe71382..a4128c1bc 100644 --- a/lib/API/VK/Device.cpp +++ b/lib/API/VK/Device.cpp @@ -223,6 +223,18 @@ static VkShaderStageFlagBits getShaderStageFlag(Stages Stage) { return VK_SHADER_STAGE_TASK_BIT_EXT; case Stages::Mesh: return VK_SHADER_STAGE_MESH_BIT_EXT; + case Stages::RayGeneration: + return VK_SHADER_STAGE_RAYGEN_BIT_KHR; + case Stages::Miss: + return VK_SHADER_STAGE_MISS_BIT_KHR; + case Stages::ClosestHit: + return VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; + case Stages::AnyHit: + return VK_SHADER_STAGE_ANY_HIT_BIT_KHR; + case Stages::Intersection: + return VK_SHADER_STAGE_INTERSECTION_BIT_KHR; + case Stages::Callable: + return VK_SHADER_STAGE_CALLABLE_BIT_KHR; } llvm_unreachable("All cases handled"); } @@ -4450,6 +4462,9 @@ class VulkanDevice : public offloadtest::Device { if (auto Err = createFrameBuffer(State)) return Err; llvm::outs() << "Frame buffer created.\n"; + } else if (P.isRayTracing()) { + return llvm::createStringError( + "RayTracing pipeline not yet supported on Vulkan"); } else { return llvm::createStringError( "Pipeline was neither Compute nor Traditional Raster"); diff --git a/lib/Support/Pipeline.cpp b/lib/Support/Pipeline.cpp index 5bd9b0355..bd58c8431 100644 --- a/lib/Support/Pipeline.cpp +++ b/lib/Support/Pipeline.cpp @@ -66,10 +66,14 @@ void MappingTraits::mapping(IO &I, I.mapOptional("Bindings", P.Bindings); I.mapOptional("PushConstants", P.PushConstants); I.mapOptional("AccelerationStructures", P.AccelStructs); + I.mapOptional("RayTracingPipelineConfig", P.RTConfig); + I.mapOptional("HitGroups", P.HitGroups); + I.mapOptional("ShaderBindingTable", P.SBT); // Runs here (not right after Shaders) because the tessellation topology - // check reads Bindings.Topology and Bindings.PatchControlPoints. Must - // still run before validateDispatchParameters, which reads P.Kind. + // check reads Bindings.Topology and Bindings.PatchControlPoints, and the + // RT-pipeline check reads HitGroups / RTConfig / SBT. Must still run + // before validateDispatchParameters, which reads P.Kind. if (auto Err = P.validatePipelineKind()) I.setError(llvm::toString(std::move(Err))); @@ -684,23 +688,93 @@ void MappingTraits::mapping( I.mapOptional("TLAS", D.TLAS); } +void MappingTraits::mapping(IO &I, + offloadtest::HitGroup &G) { + I.mapRequired("Name", G.Name); + I.mapOptional("Type", G.Type, offloadtest::HitGroupType::Triangles); + I.mapRequired("ClosestHit", G.ClosestHit); + I.mapOptional("AnyHit", G.AnyHit); + I.mapOptional("Intersection", G.Intersection); +} + +void MappingTraits::mapping( + IO &I, offloadtest::RayTracingPipelineConfig &C) { + I.mapOptional("MaxTraceRecursionDepth", C.MaxTraceRecursionDepth, 1u); + I.mapOptional("MaxPayloadSizeInBytes", C.MaxPayloadSizeInBytes, 0u); + I.mapOptional("MaxAttributeSizeInBytes", C.MaxAttributeSizeInBytes, 8u); + I.mapOptional("PipelineFlags", C.PipelineFlags); +} + +void MappingTraits::mapping(IO &I, + offloadtest::SBTEntry &E) { + I.mapRequired("ShaderName", E.ShaderName); + llvm::SmallVector Bytes; + if (I.outputting()) + for (const uint8_t B : E.LocalRootData) + Bytes.push_back(static_cast(B)); + I.mapOptional("LocalRootData", Bytes); + if (!I.outputting()) { + E.LocalRootData.clear(); + E.LocalRootData.reserve(Bytes.size()); + for (auto B : Bytes) + E.LocalRootData.push_back(static_cast(B)); + } +} + +void MappingTraits::mapping( + IO &I, offloadtest::ShaderBindingTable &S) { + I.mapRequired("RayGen", S.RayGen); + I.mapOptional("Miss", S.Miss); + I.mapOptional("HitGroup", S.HitGroup); + I.mapOptional("Callable", S.Callable); +} + } // namespace yaml } // namespace llvm llvm::Error offloadtest::Pipeline::validatePipelineKind() { bool HasShaderType[NumStages] = {}; + bool HasAnyRayTracingStage = false; for (const auto &Shader : Shaders) { - // This works except for ray tracing shaders. We will have to make an - // exception for miss, closest hit, any hit and intersection shaders once we - // support those. - if (HasShaderType[llvm::to_underlying(Shader.Stage)]) + const auto Idx = llvm::to_underlying(Shader.Stage); + // RayTracing pipelines may host multiple shaders of the same stage (e.g. + // several miss shaders, multiple hit groups). Every other pipeline kind + // forbids duplicates. + if (HasShaderType[Idx] && !isRayTracingStage(Shader.Stage)) return llvm::createStringError( "Pipeline has multiple shaders of the same type."); + HasShaderType[Idx] = true; + if (isRayTracingStage(Shader.Stage)) + HasAnyRayTracingStage = true; + } - HasShaderType[llvm::to_underlying(Shader.Stage)] = true; + const bool HasComputeStage = + HasShaderType[llvm::to_underlying(Stages::Compute)]; + const bool HasVertexStage = + HasShaderType[llvm::to_underlying(Stages::Vertex)]; + const bool HasMeshStage = HasShaderType[llvm::to_underlying(Stages::Mesh)]; + const bool HasAmplificationStage = + HasShaderType[llvm::to_underlying(Stages::Amplification)]; + + if (HasAnyRayTracingStage) { + if (HasComputeStage || HasVertexStage || HasMeshStage || + HasAmplificationStage) + return llvm::createStringError( + "RayTracing shaders cannot be combined with Compute, Vertex, " + "Amplification, or Mesh shaders."); + if (!HasShaderType[llvm::to_underlying(Stages::RayGeneration)]) + return llvm::createStringError( + "RayTracing pipeline requires at least one RayGeneration shader."); + Kind = ShaderPipelineKind::RayTracing; + return llvm::Error::success(); } - if (HasShaderType[llvm::to_underlying(Stages::Compute)]) { + if (!HitGroups.empty() || RTConfig || SBT) + return llvm::createStringError( + "HitGroups / RayTracingPipelineConfig / ShaderBindingTable are only " + "valid on a RayTracing pipeline."); + + if (HasComputeStage) { if (Shaders.size() > 1) return llvm::createStringError( "Compute Pipeline is only allowed to have Compute Shader."); @@ -708,9 +782,8 @@ llvm::Error offloadtest::Pipeline::validatePipelineKind() { return llvm::Error::success(); } - if (HasShaderType[llvm::to_underlying(Stages::Vertex)]) { - if (HasShaderType[llvm::to_underlying(Stages::Amplification)] || - HasShaderType[llvm::to_underlying(Stages::Mesh)]) + if (HasVertexStage) { + if (HasAmplificationStage || HasMeshStage) return llvm::createStringError("Vertex and Mesh/Amplification Shaders " "cannot be used in the same pipeline."); @@ -738,7 +811,7 @@ llvm::Error offloadtest::Pipeline::validatePipelineKind() { return llvm::Error::success(); } - if (HasShaderType[llvm::to_underlying(Stages::Mesh)]) { + if (HasMeshStage) { Kind = ShaderPipelineKind::MeshShaderRaster; return llvm::Error::success(); } @@ -746,7 +819,7 @@ llvm::Error offloadtest::Pipeline::validatePipelineKind() { // As we add more pipeline types this error message should be updated with // more required shader types. return llvm::createStringError( - "The pipeline misses a Compute, Vertex or Mesh Shader."); + "The pipeline misses a Compute, Vertex, Mesh, or RayGeneration Shader."); } llvm::Error offloadtest::Pipeline::validateDispatchParameters() { @@ -765,6 +838,14 @@ llvm::Error offloadtest::Pipeline::validateDispatchParameters() { "DispatchParameters.DispatchGroupCount set on a TraditionalRaster " "pipeline. Only allowed on a Compute pipeline."); break; + case ShaderPipelineKind::RayTracing: + // DispatchGroupCount is reinterpreted as { Width, Height, Depth } for + // DispatchRays. VertexCount is not meaningful for an RT dispatch. + if (DispatchParameters.VertexCount) + return llvm::createStringError( + "DispatchParameters.VertexCount set on a RayTracing pipeline. Only " + "allowed on a TraditionalRaster pipeline."); + break; } return llvm::Error::success(); diff --git a/test/Feature/RT/raygen-roundtrip.test b/test/Feature/RT/raygen-roundtrip.test new file mode 100644 index 000000000..c74b64365 --- /dev/null +++ b/test/Feature/RT/raygen-roundtrip.test @@ -0,0 +1,113 @@ +#--- source.hlsl + +struct Payload { + uint Value; +}; + +[[vk::binding(0, 0)]] RaytracingAccelerationStructure Scene : register(t0); +[[vk::binding(1, 0)]] RWStructuredBuffer Output : register(u0); + +[shader("raygeneration")] +void RayGen() { + Payload P; + P.Value = 0; + RayDesc Ray; + Ray.Origin = float3(0, 0, 1); + Ray.Direction = float3(0, 0, -1); + Ray.TMin = 0.0; + Ray.TMax = 100.0; + TraceRay(Scene, RAY_FLAG_NONE, 0xFF, 0, 1, 0, Ray, P); + Output[DispatchRaysIndex().x] = P.Value; +} + +[shader("miss")] +void MissMain(inout Payload P) { + P.Value = 0xDEAD; +} + +[shader("closesthit")] +void ClosestHitMain(inout Payload P, in BuiltInTriangleIntersectionAttributes Attr) { + P.Value = 0xBEEF; +} +//--- pipeline.yaml +--- +Shaders: + - Stage: RayGeneration + Entry: RayGen + - Stage: Miss + Entry: MissMain + - Stage: ClosestHit + Entry: ClosestHitMain +Buffers: + - Name: Vertices + Format: Float32 + Stride: 12 + Data: [ 0.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 0.0 ] + - Name: Output + Format: UInt32 + Stride: 4 + FillSize: 4 + - Name: Expected + Format: UInt32 + Stride: 4 + Data: [ 0xBEEF ] +AccelerationStructures: + BLAS: + - Name: TriangleBLAS + Triangles: + - VertexBuffer: Vertices + VertexFormat: RGB32Float + VertexStride: 12 + VertexCount: 3 + TLAS: + - Name: Scene + Instances: + - BLAS: TriangleBLAS +RayTracingPipelineConfig: + MaxTraceRecursionDepth: 1 + MaxPayloadSizeInBytes: 4 + MaxAttributeSizeInBytes: 8 +HitGroups: + - Name: TriangleHitGroup + Type: Triangles + ClosestHit: ClosestHitMain +ShaderBindingTable: + RayGen: + ShaderName: RayGen + Miss: + - ShaderName: MissMain + HitGroup: + - ShaderName: TriangleHitGroup +DescriptorSets: + - Resources: + - Name: Scene + Kind: AccelerationStructure + DirectXBinding: + Register: 0 + Space: 0 + VulkanBinding: + Binding: 0 + - Name: Output + Kind: RWStructuredBuffer + DirectXBinding: + Register: 0 + Space: 0 + VulkanBinding: + Binding: 1 +DispatchParameters: + DispatchGroupCount: [ 1, 1, 1 ] +Results: + - Result: RaygenRoundtrip + Rule: BufferExact + Actual: Output + Expected: Expected +... +#--- end + +# REQUIRES: raytracing-pipeline +# Unimplemented https://github.com/llvm/offload-test-suite/issues/1268 +# XFAIL: * + +# RUN: split-file %s %t +# RUN: %dxc_target_lib -T lib_6_5 -Fo %t.o %t/source.hlsl +# RUN: %offloader %t/pipeline.yaml %t.o diff --git a/test/lit.cfg.py b/test/lit.cfg.py index 7f1b68303..d23d351ae 100644 --- a/test/lit.cfg.py +++ b/test/lit.cfg.py @@ -182,6 +182,12 @@ def setDeviceFeatures(config, device, compiler): setWaveSizeFeaturesDirectX(config, device) if device["Features"].get("RaytracingTier", "NotSupported") != "NotSupported": config.available_features.add("acceleration-structure") + # PSO-based raytracing (state objects, DispatchRays, SBT) needs + # the same Tier1.0+ surface as inline RT on D3D12. The DX + # backend implementation lands in a follow-up PR; until then + # tests that REQUIRE this feature will still see the per- + # backend not-yet-supported error and XFAIL where applicable. + config.available_features.add("raytracing-pipeline") if device["API"] == "Metal": config.available_features.add("Int16") @@ -217,6 +223,13 @@ def setDeviceFeatures(config, device, compiler): if "VK_KHR_acceleration_structure" in config.available_features: config.available_features.add("acceleration-structure") + # Same plumbing-vs-impl split as DX: the lit detection tracks the + # device capability, the backend implementation lands later. The + # framework's RT bring-up returns a not-yet-supported error from + # the Vulkan backend until PR 2 wires up VK_KHR_ray_tracing_pipeline. + if "VK_KHR_ray_tracing_pipeline" in config.available_features: + config.available_features.add("raytracing-pipeline") + offloader_args = [] if config.offloadtest_test_warp: @@ -261,17 +274,37 @@ def setDeviceFeatures(config, device, compiler): "%dxc_target", FindTool("clang-dxc"), extra_args=ExtraCompilerArgs ) ) + tools.append( + ToolSubst( + "%dxc_target_lib", FindTool("clang-dxc"), extra_args=ExtraCompilerArgs + ) + ) else: tools.append( ToolSubst( "%dxc_target", FindTool("clang-dxc"), extra_args=ExtraCompilerArgs ) ) + tools.append( + ToolSubst( + "%dxc_target_lib", FindTool("clang-dxc"), extra_args=ExtraCompilerArgs + ) + ) HLSLCompiler = "Clang" else: tools.append( ToolSubst("%dxc_target", config.offloadtest_dxc, extra_args=ExtraCompilerArgs) ) + # %dxc_target_lib is the DXIL-library variant: tests pass `-T lib_6_5` + # (or similar) to emit a library with multiple entry points, which is + # the input shape for RT pipeline state object creation. The compiler + # binary is the same; the separate substitution names the intent so + # the test reader can tell at a glance. + tools.append( + ToolSubst( + "%dxc_target_lib", config.offloadtest_dxc, extra_args=ExtraCompilerArgs + ) + ) HLSLCompiler = "DXC" config.available_features.add(HLSLCompiler)