diff --git a/tools/clang/unittests/HLSLExec/DXRUtil.h b/tools/clang/unittests/HLSLExec/DXRUtil.h new file mode 100644 index 0000000000..14bbf5bf1b --- /dev/null +++ b/tools/clang/unittests/HLSLExec/DXRUtil.h @@ -0,0 +1,221 @@ +//===------------ DXRUtil.h - DXR Utility Functions ------------*- C++ -*-===// +/////////////////////////////////////////////////////////////////////////////// +// // +// DXRUtil.h // +// Copyright (C) Nvidia Corporation. All rights reserved. // +// This file is distributed under the University of Illinois Open Source // +// License. See LICENSE.TXT for details. // +// // +// This file contains the utility functions for DXR execution tests. // +// // +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +//= DXR Utility +//============================================================================ +#define SHADER_ID_SIZE_IN_BYTES 32 + +#ifndef ROUND_UP +#define ROUND_UP(v, PowerOf2Alignment) \ + (((v) + (PowerOf2Alignment)-1) & ~((PowerOf2Alignment)-1)) +#endif +struct SceneConsts { + DirectX::XMFLOAT4 Eye; + DirectX::XMFLOAT4 U; + DirectX::XMFLOAT4 V; + DirectX::XMFLOAT4 W; + float SceneScale; + unsigned WindowSize[2]; + int RayFlags; +}; + +struct Instance { + D3D12_RAYTRACING_GEOMETRY_TYPE Type; + DirectX::XMFLOAT4X4 Matrix; + UINT GeometryCount; + UINT BottomASIdx; + UINT InstanceID; + UINT Mask; + UINT Flags; +}; + +class ShaderTable { +public: + ShaderTable(ID3D12Device *Device, int RaygenCount, int MissCount, + int HitGroupCount, int RayTypeCount, int RootTableDwords) + : RayTypeCount(RayTypeCount), RaygenCount(RaygenCount), + MissCount(MissCount * RayTypeCount), + HitGroupCount(HitGroupCount * RayTypeCount), + RootTableSizeInBytes(RootTableDwords * 4), + ShaderRecordSizeInBytes( + ROUND_UP(RootTableSizeInBytes + SHADER_ID_SIZE_IN_BYTES, + D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT)), + MissStartIdx(RaygenCount), HitGroupStartIdx(MissStartIdx + MissCount) { + + const int TotalSizeInBytes = + (RaygenCount + MissCount + HitGroupCount) * ShaderRecordSizeInBytes; + + D3D12_RESOURCE_DESC Desc = CD3DX12_RESOURCE_DESC::Buffer( + TotalSizeInBytes, D3D12_RESOURCE_FLAG_NONE, + std::max(D3D12_RAYTRACING_SHADER_RECORD_BYTE_ALIGNMENT, + D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT)); + CD3DX12_HEAP_PROPERTIES Heap = + CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT); + VERIFY_SUCCEEDED(Device->CreateCommittedResource( + &Heap, D3D12_HEAP_FLAG_NONE, &Desc, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, nullptr, + IID_PPV_ARGS(&SBTResource))); + SBTResource->SetName(L"SBT Resource Heap"); + CD3DX12_HEAP_PROPERTIES Upload = + CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD); + VERIFY_SUCCEEDED(Device->CreateCommittedResource( + &Upload, D3D12_HEAP_FLAG_NONE, &Desc, D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, IID_PPV_ARGS(&SBTUploadResource))); + SBTUploadResource->SetName(L"SBT Upload Heap"); + + VERIFY_SUCCEEDED(SBTUploadResource->Map(0, nullptr, (void **)&HostPtr)); + } + + void Upload(ID3D12GraphicsCommandList *CmdList) { + CD3DX12_RESOURCE_BARRIER Barrier = CD3DX12_RESOURCE_BARRIER::Transition( + SBTResource, D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, + D3D12_RESOURCE_STATE_COPY_DEST); + CmdList->ResourceBarrier(1, &Barrier); + CmdList->CopyResource(SBTResource, SBTUploadResource); + CD3DX12_RESOURCE_BARRIER Barrier2 = CD3DX12_RESOURCE_BARRIER::Transition( + SBTResource, D3D12_RESOURCE_STATE_COPY_DEST, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE); + CmdList->ResourceBarrier(1, &Barrier2); + } + + int GetShaderRecordSizeInBytes() { return ShaderRecordSizeInBytes; } + + int GetRaygenShaderRecordIdx(int Idx) { return Idx; } + int GetMissShaderRecordIdx(int Idx, int RayType) { + return MissStartIdx + Idx * RayTypeCount + RayType; + } + int GetHitGroupShaderRecordIdx(int Idx, int RayType) { + return HitGroupStartIdx + Idx * RayTypeCount + RayType; + } + + void *GetRaygenShaderIdPtr(int Idx) { + return HostPtr + GetRaygenShaderRecordIdx(Idx) * ShaderRecordSizeInBytes; + } + void *GetMissShaderIdPtr(int Idx, int RayType) { + return HostPtr + + GetMissShaderRecordIdx(Idx, RayType) * ShaderRecordSizeInBytes; + } + void *GetHitGroupShaderIdPtr(int Idx, int RayType) { + return HostPtr + + GetHitGroupShaderRecordIdx(Idx, RayType) * ShaderRecordSizeInBytes; + } + + void *GetRaygenRootTablePtr(int Idx) { + return (char *)GetRaygenShaderIdPtr(Idx) + SHADER_ID_SIZE_IN_BYTES; + } + void *GetMissRootTablePtr(int Idx, int RayType) { + return (char *)GetMissShaderIdPtr(Idx, RayType) + SHADER_ID_SIZE_IN_BYTES; + } + void *GetHitGroupRootTablePtr(int Idx, int RayType) { + return (char *)GetHitGroupShaderIdPtr(Idx, RayType) + + SHADER_ID_SIZE_IN_BYTES; + } + + int GetRaygenRangeInBytes() { return RaygenCount * ShaderRecordSizeInBytes; } + int GetMissRangeInBytes() { return MissCount * ShaderRecordSizeInBytes; } + int GetHitGroupRangeInBytes() { + return HitGroupCount * ShaderRecordSizeInBytes; + } + + D3D12_GPU_VIRTUAL_ADDRESS GetRaygenStartGpuVA() { + return SBTResource->GetGPUVirtualAddress() + + GetRaygenShaderRecordIdx(0) * ShaderRecordSizeInBytes; + } + D3D12_GPU_VIRTUAL_ADDRESS GetMissStartGpuVA() { + return SBTResource->GetGPUVirtualAddress() + + GetMissShaderRecordIdx(0, 0) * ShaderRecordSizeInBytes; + } + D3D12_GPU_VIRTUAL_ADDRESS GetHitGroupStartGpuVA() { + return SBTResource->GetGPUVirtualAddress() + + GetHitGroupShaderRecordIdx(0, 0) * ShaderRecordSizeInBytes; + } + +private: + CComPtr SBTResource; + CComPtr SBTUploadResource; + char *HostPtr = nullptr; + int RayTypeCount = 0; + int RaygenCount = 0; + int MissCount = 0; + int HitGroupCount = 0; + int RootTableSizeInBytes = 0; + int ShaderRecordSizeInBytes = 0; + int MissStartIdx = 0; + int HitGroupStartIdx = 0; +}; + +//----------------------------------------------------------------------------- +void AllocateBuffer( + ID3D12Device *Device, UINT64 BufferSize, ID3D12Resource **Resource, + bool AllowUAV = false, + D3D12_RESOURCE_STATES InitialResourceState = D3D12_RESOURCE_STATE_COMMON, + const wchar_t *ResourceName = nullptr) { + auto UploadHeapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT); + auto BufferDesc = CD3DX12_RESOURCE_DESC::Buffer( + BufferSize, AllowUAV ? D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS + : D3D12_RESOURCE_FLAG_NONE); + VERIFY_SUCCEEDED(Device->CreateCommittedResource( + &UploadHeapProperties, D3D12_HEAP_FLAG_NONE, &BufferDesc, + InitialResourceState, nullptr, IID_PPV_ARGS(Resource))); + if (ResourceName) { + (*Resource)->SetName(ResourceName); + } +} + +//----------------------------------------------------------------------------- +void ReallocScratchResource(ID3D12Device *Device, ID3D12Resource **Resource, + UINT64 NBytes) { + if (!(*Resource) || (*Resource)->GetDesc().Width < NBytes) { + AllocateBuffer(Device, NBytes, Resource, true, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS, L"scratchResource"); + } +} + +//----------------------------------------------------------------------------- +void AllocateUploadBuffer(ID3D12Device *Device, const void *Data, + UINT64 DataSize, ID3D12Resource **Resource, + const wchar_t *ResourceName = nullptr) { + auto UploadHeapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD); + auto BufferDesc = CD3DX12_RESOURCE_DESC::Buffer(DataSize); + VERIFY_SUCCEEDED(Device->CreateCommittedResource( + &UploadHeapProperties, D3D12_HEAP_FLAG_NONE, &BufferDesc, + D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(Resource))); + if (ResourceName) { + (*Resource)->SetName(ResourceName); + } + void *MappedData; + VERIFY_SUCCEEDED((*Resource)->Map(0, nullptr, &MappedData)); + memcpy(MappedData, Data, DataSize); + (*Resource)->Unmap(0, nullptr); +} + +//----------------------------------------------------------------------------- +void AllocateBufferFromUpload(ID3D12Device *Device, + ID3D12GraphicsCommandList *CommandList, + ID3D12Resource *UploadSource, + ID3D12Resource **Resource, + D3D12_RESOURCE_STATES TargetResourceState, + const wchar_t *ResourceName = nullptr) { + const bool AllowUAV = + TargetResourceState == D3D12_RESOURCE_STATE_UNORDERED_ACCESS; + AllocateBuffer(Device, UploadSource->GetDesc().Width, Resource, AllowUAV, + D3D12_RESOURCE_STATE_COPY_DEST, ResourceName); + CommandList->CopyResource(*Resource, UploadSource); + CD3DX12_RESOURCE_BARRIER Barrier = CD3DX12_RESOURCE_BARRIER::Transition( + *Resource, D3D12_RESOURCE_STATE_COPY_DEST, TargetResourceState); + CommandList->ResourceBarrier(1, (const D3D12_RESOURCE_BARRIER *)&Barrier); +} + +//= DXR Utility +//============================================================================ \ No newline at end of file diff --git a/tools/clang/unittests/HLSLExec/ExecutionTest.cpp b/tools/clang/unittests/HLSLExec/ExecutionTest.cpp index 55d569dd8d..0632fd91ca 100644 --- a/tools/clang/unittests/HLSLExec/ExecutionTest.cpp +++ b/tools/clang/unittests/HLSLExec/ExecutionTest.cpp @@ -64,6 +64,7 @@ #include #include #include "LongVectors.h" +#include "DXRUtil.h" #include "CoopVecAPI.h" #include "CoopVec.h" // clang-format on @@ -620,6 +621,25 @@ class ExecutionTest { TEST_METHOD(LongVector_Clamp_uint64); TEST_METHOD(LongVector_Initialize_uint64); + // Shader Execution Reordering tests + TEST_METHOD(SERBasicTest); + TEST_METHOD(SERScalarGetterTest); + TEST_METHOD(SERVectorGetterTest); + TEST_METHOD(SERMatrixGetterTest); + TEST_METHOD(SERRayQueryTest); + TEST_METHOD(SERIntersectionTest); + TEST_METHOD(SERGetAttributesTest); + TEST_METHOD(SERTraceHitMissNopTest); + TEST_METHOD(SERIsMissTest); + TEST_METHOD(SERShaderTableIndexTest); + TEST_METHOD(SERLoadLocalRootTableConstantTest); + TEST_METHOD(SERInvokeNoSBTTest); + TEST_METHOD(SERMaybeReorderThreadTest) + TEST_METHOD(SERDynamicHitObjectArrayTest); + TEST_METHOD(SERWaveIncoherentHitTest); + TEST_METHOD(SERReorderCoherentTest); + + // CoopVec tests TEST_METHOD(CoopVec_Mul); TEST_METHOD(CoopVec_OuterProduct); @@ -2106,6 +2126,14 @@ class ExecutionTest { CComPtr &pRootSignature, LPCWSTR pTargetProfile, LPCWSTR *pOptions, int numOptions); + bool CreateDXRDevice(ID3D12Device **ppDevice, D3D_SHADER_MODEL testModel, + bool skipUnsupported); + CComPtr RunDXRTest(ID3D12Device *Device0, LPCSTR ShaderSrc, + LPCWSTR TargetProfile, LPCWSTR *Options, + int NumOptions, std::vector &TestData, + int WindowWidth, int WindowHeight, + bool UseMesh, bool UseProceduralGeometry, + int PayloadCount, int AttributeCount); void SetDescriptorHeap(ID3D12GraphicsCommandList *pCommandList, ID3D12DescriptorHeap *pHeap) { @@ -2267,6 +2295,704 @@ void ExecutionTest::RunRWByteBufferComputeTest(ID3D12Device *pDevice, WaitForSignal(pCommandQueue, FO); } +bool ExecutionTest::CreateDXRDevice(ID3D12Device **ppDevice, + D3D_SHADER_MODEL testModel, + bool skipUnsupported) { + bool SupportsSM = CreateDevice(ppDevice, testModel, skipUnsupported); + if (!SupportsSM) + return false; + + if (DoesDeviceSupportRayTracing(*ppDevice)) + return true; + + if (skipUnsupported) { + WEX::Logging::Log::Comment( + L"DXR test skipped: device does not support DXR."); + WEX::Logging::Log::Result(WEX::Logging::TestResults::Skipped); + } + return false; +} + +CComPtr ExecutionTest::RunDXRTest( + ID3D12Device *Device0, LPCSTR ShaderSrc, LPCWSTR TargetProfile, + LPCWSTR *Options, int NumOptions, std::vector &TestData, + int WindowWidth, int WindowHeight, bool UseMesh, bool UseProceduralGeometry, + int PayloadCount, int AttributeCount) { + CComPtr Device; + VERIFY_SUCCEEDED(Device0->QueryInterface(IID_PPV_ARGS(&Device))); + + FenceObj FO; + InitFenceObj(Device, &FO); + + // Setup Resources + CComPtr TestBuffer; + CComPtr TestBufferRead; + CComPtr SceneConstantBuffer; + + // Descriptor heap + CComPtr DescriptorHeap; + { + // + // UAV descriptor heap layout: + // 0 - test buffer UAV + // 1 - vertex buffer SRV + // 2 - index buffer SRV + // + D3D12_DESCRIPTOR_HEAP_DESC DescriptorHeapDesc = {}; + DescriptorHeapDesc.NumDescriptors = 3; + DescriptorHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV; + DescriptorHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; + Device->CreateDescriptorHeap(&DescriptorHeapDesc, + IID_PPV_ARGS(&DescriptorHeap)); + DescriptorHeap->SetName(L"Descriptor Heap"); + } + int DescriptorSize = Device->GetDescriptorHandleIncrementSize( + D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV); + + // Testbuffer + { + auto ResDesc = CD3DX12_RESOURCE_DESC::Buffer( + TestData.size() * sizeof(int), + D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS); + auto DefaultHeapProperties = + CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT); + VERIFY_SUCCEEDED(Device->CreateCommittedResource( + &DefaultHeapProperties, D3D12_HEAP_FLAG_NONE, &ResDesc, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS, nullptr, + IID_PPV_ARGS(&TestBuffer))); + TestBuffer->SetName(L"Test Buffer"); + + const int DescriptorIndex = 0; + D3D12_CPU_DESCRIPTOR_HANDLE CPUDescriptorHandle = + CD3DX12_CPU_DESCRIPTOR_HANDLE( + DescriptorHeap->GetCPUDescriptorHandleForHeapStart(), + DescriptorIndex, DescriptorSize); + D3D12_UNORDERED_ACCESS_VIEW_DESC UAVDesc = {}; + UAVDesc.Format = DXGI_FORMAT_UNKNOWN; + UAVDesc.ViewDimension = D3D12_UAV_DIMENSION_BUFFER; + UAVDesc.Buffer.FirstElement = 0; + UAVDesc.Buffer.NumElements = (UINT)TestData.size(); + UAVDesc.Buffer.StructureByteStride = sizeof(int); + UAVDesc.Buffer.CounterOffsetInBytes = 0; + UAVDesc.Buffer.Flags = D3D12_BUFFER_UAV_FLAG_NONE; + Device->CreateUnorderedAccessView(TestBuffer, nullptr, &UAVDesc, + CPUDescriptorHandle); + } + + // Testbuffer Readback + { + CD3DX12_HEAP_PROPERTIES ReadHeap(D3D12_HEAP_TYPE_READBACK); + CD3DX12_RESOURCE_DESC ReadDesc( + CD3DX12_RESOURCE_DESC::Buffer(TestData.size() * sizeof(int))); + Device->CreateCommittedResource(&ReadHeap, D3D12_HEAP_FLAG_NONE, &ReadDesc, + D3D12_RESOURCE_STATE_COPY_DEST, nullptr, + IID_PPV_ARGS(&TestBufferRead)); + } + + // Create CBV resource (sceneConstantBuffer), index 1 + { + const int DescriptorIndex = 1; + const UINT ConstantBufferSize = + (sizeof(SceneConsts) + + (D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT - 1)) & + ~(D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT - + 1); // must be a multiple 256 bytes + D3D12_CPU_DESCRIPTOR_HANDLE CPUDescriptorHandle = + CD3DX12_CPU_DESCRIPTOR_HANDLE( + DescriptorHeap->GetCPUDescriptorHandleForHeapStart(), + DescriptorIndex, DescriptorSize); + auto ResDesc = CD3DX12_RESOURCE_DESC::Buffer(ConstantBufferSize); + auto UploadHeapProperties = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD); + Device->CreateCommittedResource(&UploadHeapProperties, D3D12_HEAP_FLAG_NONE, + &ResDesc, D3D12_RESOURCE_STATE_GENERIC_READ, + nullptr, + IID_PPV_ARGS(&SceneConstantBuffer)); + + UINT8 *SceneConstantBufferWO; + CD3DX12_RANGE ReadRange( + 0, 0); // We do not intend to read from this resource on the CPU. + SceneConstantBuffer->Map(0, &ReadRange, + reinterpret_cast(&SceneConstantBufferWO)); + + // Setup Scene Constants + SceneConsts SceneConsts = { + {25.f, -25.f, 700.f, 0.f}, + {536.f, 0.f, 0.f, 0.f}, + {0.f, 301.f, 0.f, 0.f}, + {0.f, 0., -699.f, 0.f}, + 100.f, + {(unsigned int)WindowWidth, (unsigned int)WindowHeight}, + 0x00}; + + memcpy(SceneConstantBufferWO, &SceneConsts, sizeof(SceneConsts)); + SceneConstantBuffer->Unmap(0, nullptr); + + D3D12_CONSTANT_BUFFER_VIEW_DESC Desc = {}; + Desc.SizeInBytes = ConstantBufferSize; + Desc.BufferLocation = SceneConstantBuffer->GetGPUVirtualAddress(); + Device->CreateConstantBufferView(&Desc, CPUDescriptorHandle); + } + + // Local (SBT) root signature + CComPtr LocalRootSignature; + { + CD3DX12_DESCRIPTOR_RANGE BufferRanges[1]; + CD3DX12_ROOT_PARAMETER RootParameters[2]; + BufferRanges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 2, 1, 0, + 2); // vertexBuffer(t1), indexBuffer(t2) + RootParameters[0].InitAsDescriptorTable( + _countof(BufferRanges), BufferRanges, D3D12_SHADER_VISIBILITY_ALL); + RootParameters[1].InitAsConstants(4, 1, 0, D3D12_SHADER_VISIBILITY_ALL); + + CD3DX12_ROOT_SIGNATURE_DESC RootSignatureDesc; + RootSignatureDesc.Init(_countof(RootParameters), RootParameters, 0, nullptr, + D3D12_ROOT_SIGNATURE_FLAG_LOCAL_ROOT_SIGNATURE); + CComPtr Signature; + CComPtr Error; + VERIFY_SUCCEEDED(D3D12SerializeRootSignature( + &RootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &Signature, &Error)); + VERIFY_SUCCEEDED(Device->CreateRootSignature( + 0, Signature->GetBufferPointer(), Signature->GetBufferSize(), + IID_PPV_ARGS(&LocalRootSignature))); + LocalRootSignature->SetName(L"Local Root Signature"); + } + + // Global root signature + CComPtr GlobalRootSignature; + { + CD3DX12_DESCRIPTOR_RANGE BufferRanges[1]; + CD3DX12_ROOT_PARAMETER RootParameters[3]; + BufferRanges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 1, + 0); // testBuffer(u0) + RootParameters[0].InitAsShaderResourceView( + 0, 0, D3D12_SHADER_VISIBILITY_ALL); // accelStruct(t0) + RootParameters[1].InitAsConstantBufferView(0); // sceneConstants(b0) + RootParameters[2].InitAsDescriptorTable( + _countof(BufferRanges), BufferRanges, D3D12_SHADER_VISIBILITY_ALL); + + CD3DX12_ROOT_SIGNATURE_DESC RootSignatureDesc; + RootSignatureDesc.Init(_countof(RootParameters), RootParameters, 0, nullptr, + D3D12_ROOT_SIGNATURE_FLAG_NONE); + CComPtr Signature; + CComPtr Error; + VERIFY_SUCCEEDED(D3D12SerializeRootSignature( + &RootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &Signature, &Error)); + VERIFY_SUCCEEDED(Device->CreateRootSignature( + 0, Signature->GetBufferPointer(), Signature->GetBufferSize(), + IID_PPV_ARGS(&GlobalRootSignature))); + GlobalRootSignature->SetName(L"Global Root Signature"); + } + + // Create command queue. + CComPtr CommandQueue; + CreateCommandQueue(Device, L"RunDXRTest Command Queue", &CommandQueue, + D3D12_COMMAND_LIST_TYPE_DIRECT); + + // Compile raygen shader. + CComPtr ShaderLib; + CompileFromText(ShaderSrc, L"raygen", TargetProfile, &ShaderLib, Options, + NumOptions); + + // Describe and create the RT pipeline state object (RTPSO). + CD3DX12_STATE_OBJECT_DESC StateObjectDesc( + D3D12_STATE_OBJECT_TYPE_RAYTRACING_PIPELINE); + auto Lib = StateObjectDesc.CreateSubobject(); + CD3DX12_SHADER_BYTECODE ByteCode(ShaderLib); + Lib->SetDXILLibrary(&ByteCode); + Lib->DefineExport(L"raygen"); + Lib->DefineExport(L"closesthit"); + Lib->DefineExport(L"anyhit"); + Lib->DefineExport(L"miss"); + if (UseProceduralGeometry) + Lib->DefineExport(L"intersection"); + if (UseMesh && UseProceduralGeometry) { + Lib->DefineExport(L"ahAABB"); + Lib->DefineExport(L"chAABB"); + } + + const int MaxRecursion = 1; + StateObjectDesc.CreateSubobject() + ->Config(PayloadCount * sizeof(float), AttributeCount * sizeof(float)); + StateObjectDesc + .CreateSubobject() + ->Config(MaxRecursion); + + // Set Global Root Signature subobject. + auto GlobalRootSigSubObj = + StateObjectDesc + .CreateSubobject(); + GlobalRootSigSubObj->SetRootSignature(GlobalRootSignature); + // Set Local Root Signature subobject. + StateObjectDesc.CreateSubobject() + ->SetRootSignature(LocalRootSignature); + + auto Exports = StateObjectDesc.CreateSubobject< + CD3DX12_SUBOBJECT_TO_EXPORTS_ASSOCIATION_SUBOBJECT>(); + Exports->SetSubobjectToAssociate(*GlobalRootSigSubObj); + Exports->AddExport(L"raygen"); + Exports->AddExport(L"closesthit"); + Exports->AddExport(L"anyhit"); + Exports->AddExport(L"miss"); + if (UseProceduralGeometry) + Exports->AddExport(L"intersection"); + if (UseMesh && UseProceduralGeometry) { + Exports->AddExport(L"ahAABB"); + Exports->AddExport(L"chAABB"); + } + + auto HitGroup = + StateObjectDesc.CreateSubobject(); + HitGroup->SetClosestHitShaderImport(L"closesthit"); + HitGroup->SetAnyHitShaderImport(L"anyhit"); + if (!UseMesh && UseProceduralGeometry) { + HitGroup->SetIntersectionShaderImport(L"intersection"); + HitGroup->SetHitGroupType(D3D12_HIT_GROUP_TYPE_PROCEDURAL_PRIMITIVE); + } else { + HitGroup->SetHitGroupType(D3D12_HIT_GROUP_TYPE_TRIANGLES); + } + HitGroup->SetHitGroupExport(L"HitGroup"); + + if (UseMesh && UseProceduralGeometry) { + auto HitGroupAABB = + StateObjectDesc.CreateSubobject(); + HitGroupAABB->SetAnyHitShaderImport(L"ahAABB"); + HitGroupAABB->SetClosestHitShaderImport(L"chAABB"); + HitGroupAABB->SetIntersectionShaderImport(L"intersection"); + HitGroupAABB->SetHitGroupType(D3D12_HIT_GROUP_TYPE_PROCEDURAL_PRIMITIVE); + HitGroupAABB->SetHitGroupExport(L"HitGroupAABB"); + } + + CComPtr StateObject; + CComPtr StateObjectProperties; + VERIFY_SUCCEEDED( + Device->CreateStateObject(StateObjectDesc, IID_PPV_ARGS(&StateObject))); + VERIFY_SUCCEEDED(StateObject->QueryInterface(&StateObjectProperties)); + + // Create SBT + ShaderTable ShaderTable( + Device, + 1, // raygen count + 1, // miss count + UseMesh && UseProceduralGeometry ? 2 : 1, // hit group count + 1, // ray type count + 4 // dwords per root table + ); + + int LocalRootConsts[4] = {12, 34, 56, 78}; + memcpy(ShaderTable.GetRaygenShaderIdPtr(0), + StateObjectProperties->GetShaderIdentifier(L"raygen"), + SHADER_ID_SIZE_IN_BYTES); + memcpy(ShaderTable.GetRaygenRootTablePtr(0), LocalRootConsts, + sizeof(LocalRootConsts)); + memcpy(ShaderTable.GetMissShaderIdPtr(0, 0), + StateObjectProperties->GetShaderIdentifier(L"miss"), + SHADER_ID_SIZE_IN_BYTES); + memcpy(ShaderTable.GetMissRootTablePtr(0, 0), LocalRootConsts, + sizeof(LocalRootConsts)); + memcpy(ShaderTable.GetHitGroupShaderIdPtr(0, 0), + StateObjectProperties->GetShaderIdentifier(L"HitGroup"), + SHADER_ID_SIZE_IN_BYTES); + memcpy(ShaderTable.GetHitGroupRootTablePtr(0, 0), LocalRootConsts, + sizeof(LocalRootConsts)); + if (UseMesh && UseProceduralGeometry) + memcpy(ShaderTable.GetHitGroupShaderIdPtr(0, 1), + StateObjectProperties->GetShaderIdentifier(L"HitGroupAABB"), + SHADER_ID_SIZE_IN_BYTES); + + // Create a command allocator and list. + CComPtr CommandAllocator; + CComPtr CommandList; + VERIFY_SUCCEEDED(Device->CreateCommandAllocator( + D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&CommandAllocator))); + VERIFY_SUCCEEDED(Device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, + CommandAllocator, nullptr, + IID_PPV_ARGS(&CommandList))); + CommandList->SetName(L"ExecutionTest::RunDXRTest Command List"); + + CommandList->Close(); + ExecuteCommandList(CommandQueue, CommandList); + WaitForSignal(CommandQueue, FO); + + VERIFY_SUCCEEDED(CommandAllocator->Reset()); + VERIFY_SUCCEEDED(CommandList->Reset(CommandAllocator, nullptr)); + + // Create scene geometry. + CComPtr TLASResource; + CComPtr BLASMeshResource; + CComPtr BLASProceduralGeometryResource; + CComPtr ScratchResource; + + if (UseMesh) { + CComPtr VertexBuffer; + CComPtr VertexBufferUpload; + CComPtr IndexBuffer; + CComPtr IndexBufferUpload; + + // Define a Quad + const float Verts[] = { + -50.5f, 50.5f, 0.5f, // top left + 50.5f, -50.5f, 0.5f, // bottom right + -50.5f, -50.5f, 0.5f, // bottom left + 50.5f, 50.5f, 0.5f // top right + }; + const int Indices[] = { + 0, 1, 2, // first triangle + 0, 3, 1 // second triangle + }; + + const UINT64 VertexDataSize = sizeof(Verts); + const UINT64 IndexDataSize = sizeof(Indices); + + AllocateUploadBuffer(Device, Verts, VertexDataSize, &VertexBufferUpload, + L"VertexBufferUpload"); + AllocateUploadBuffer(Device, Indices, IndexDataSize, &IndexBufferUpload, + L"IndexBufferUpload"); + + AllocateBufferFromUpload( + Device, CommandList, VertexBufferUpload, &VertexBuffer, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, L"VertexBuffer"); + AllocateBufferFromUpload( + Device, CommandList, IndexBufferUpload, &IndexBuffer, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, L"IndexBuffer"); + + { + const int DescriptorIndex = 1; + D3D12_CPU_DESCRIPTOR_HANDLE CpuDescriptorHandle = + CD3DX12_CPU_DESCRIPTOR_HANDLE( + DescriptorHeap->GetCPUDescriptorHandleForHeapStart(), + DescriptorIndex, DescriptorSize); + D3D12_SHADER_RESOURCE_VIEW_DESC SrvDesc = {}; + SrvDesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; + SrvDesc.Shader4ComponentMapping = + D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + SrvDesc.Buffer.NumElements = + UINT(VertexDataSize / sizeof(DirectX::XMFLOAT3)); + SrvDesc.Format = DXGI_FORMAT_UNKNOWN; + SrvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE; + SrvDesc.Buffer.StructureByteStride = sizeof(DirectX::XMFLOAT3); + Device->CreateShaderResourceView(VertexBuffer, &SrvDesc, + CpuDescriptorHandle); + } + { + const int DescriptorIndex = 2; + D3D12_CPU_DESCRIPTOR_HANDLE CpuDescriptorHandle = + CD3DX12_CPU_DESCRIPTOR_HANDLE( + DescriptorHeap->GetCPUDescriptorHandleForHeapStart(), + DescriptorIndex, DescriptorSize); + D3D12_SHADER_RESOURCE_VIEW_DESC SrvDesc = {}; + SrvDesc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER; + SrvDesc.Shader4ComponentMapping = + D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING; + SrvDesc.Buffer.NumElements = UINT(IndexDataSize / sizeof(int)); + SrvDesc.Format = DXGI_FORMAT_UNKNOWN; + SrvDesc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_NONE; + SrvDesc.Buffer.StructureByteStride = sizeof(int); + Device->CreateShaderResourceView(IndexBuffer, &SrvDesc, + CpuDescriptorHandle); + } + + CommandList->Close(); + ExecuteCommandList(CommandQueue, CommandList); + WaitForSignal(CommandQueue, FO); + + VERIFY_SUCCEEDED(CommandAllocator->Reset()); + VERIFY_SUCCEEDED(CommandList->Reset(CommandAllocator, nullptr)); + + // Build triangle BLAS. + D3D12_RAYTRACING_GEOMETRY_DESC GeometryDesc = {}; + GeometryDesc.Type = D3D12_RAYTRACING_GEOMETRY_TYPE_TRIANGLES; + GeometryDesc.Triangles.IndexBuffer = IndexBuffer->GetGPUVirtualAddress(); + GeometryDesc.Triangles.IndexCount = + static_cast(IndexBuffer->GetDesc().Width) / sizeof(int); + GeometryDesc.Triangles.IndexFormat = DXGI_FORMAT_R32_UINT; + GeometryDesc.Triangles.Transform3x4 = 0; + GeometryDesc.Triangles.VertexFormat = DXGI_FORMAT_R32G32B32_FLOAT; + GeometryDesc.Triangles.VertexCount = + static_cast(VertexBuffer->GetDesc().Width) / + sizeof(DirectX::XMFLOAT3); + GeometryDesc.Triangles.VertexBuffer.StartAddress = + VertexBuffer->GetGPUVirtualAddress(); + GeometryDesc.Triangles.VertexBuffer.StrideInBytes = + sizeof(DirectX::XMFLOAT3); + GeometryDesc.Flags = D3D12_RAYTRACING_GEOMETRY_FLAG_NONE; // Non-opaque to + // trigger anyhit. + + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAGS BuildFlags = + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_TRACE; + + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS AccelInputs = {}; + AccelInputs.Type = + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL; + AccelInputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY; + AccelInputs.pGeometryDescs = &GeometryDesc; + AccelInputs.NumDescs = 1; + AccelInputs.Flags = BuildFlags; + + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO PrebuildInfo = {}; + Device->GetRaytracingAccelerationStructurePrebuildInfo(&AccelInputs, + &PrebuildInfo); + + ScratchResource.Release(); + ReallocScratchResource(Device, &ScratchResource, + PrebuildInfo.ScratchDataSizeInBytes); + AllocateBuffer( + Device, PrebuildInfo.ResultDataMaxSizeInBytes, &BLASMeshResource, true, + D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE, L"blasMesh"); + + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC BuildDesc = {}; + BuildDesc.Inputs = AccelInputs; + BuildDesc.ScratchAccelerationStructureData = + ScratchResource->GetGPUVirtualAddress(); + BuildDesc.DestAccelerationStructureData = + BLASMeshResource->GetGPUVirtualAddress(); + + CommandList->BuildRaytracingAccelerationStructure(&BuildDesc, 0, nullptr); + CD3DX12_RESOURCE_BARRIER Barrier = + CD3DX12_RESOURCE_BARRIER::UAV(BLASMeshResource); + CommandList->ResourceBarrier(1, (const D3D12_RESOURCE_BARRIER *)&Barrier); + + CommandList->Close(); + ExecuteCommandList(CommandQueue, CommandList); + WaitForSignal(CommandQueue, FO); + + VERIFY_SUCCEEDED(CommandAllocator->Reset()); + VERIFY_SUCCEEDED(CommandList->Reset(CommandAllocator, nullptr)); + } + + if (UseProceduralGeometry) { + // Define procedural geometry AABB for a plane + CComPtr AabbBuffer; + CComPtr AabbBufferUpload; + + // Define the AABB for the plane, matching the size of the quad defined by + // verts[] + const float BoxSize = 500.f; + const D3D12_RAYTRACING_AABB Aabb = { + -BoxSize, -BoxSize, -BoxSize, // Min corner (x, y, z) + BoxSize, BoxSize, BoxSize // Max corner (x, y, z) + }; + const UINT64 AabbDataSize = sizeof(Aabb); + + // Create an upload buffer for the AABB + AllocateUploadBuffer(Device, &Aabb, AabbDataSize, &AabbBufferUpload, + L"AabbBufferUpload"); + + // Create a GPU buffer for the AABB + AllocateBufferFromUpload(Device, CommandList, AabbBufferUpload, &AabbBuffer, + D3D12_RESOURCE_STATE_NON_PIXEL_SHADER_RESOURCE, + L"AabbBuffer"); + + // Describe the procedural geometry + D3D12_RAYTRACING_GEOMETRY_DESC ProcGeometryDesc = {}; + ProcGeometryDesc.Type = + D3D12_RAYTRACING_GEOMETRY_TYPE_PROCEDURAL_PRIMITIVE_AABBS; + ProcGeometryDesc.AABBs.AABBs.StartAddress = + AabbBuffer->GetGPUVirtualAddress(); + ProcGeometryDesc.AABBs.AABBs.StrideInBytes = sizeof(D3D12_RAYTRACING_AABB); + ProcGeometryDesc.AABBs.AABBCount = 1; + + // Build the BLAS for the procedural geometry + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS BLASInputs = {}; + BLASInputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL; + BLASInputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY; + BLASInputs.NumDescs = 1; + BLASInputs.pGeometryDescs = &ProcGeometryDesc; + BLASInputs.Flags = + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_TRACE; + + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO PrebuildInfo = {}; + Device->GetRaytracingAccelerationStructurePrebuildInfo(&BLASInputs, + &PrebuildInfo); + + // Allocate scratch and result buffers for the BLAS + ScratchResource.Release(); + ReallocScratchResource(Device, &ScratchResource, + PrebuildInfo.ScratchDataSizeInBytes); + AllocateBuffer(Device, PrebuildInfo.ResultDataMaxSizeInBytes, + &BLASProceduralGeometryResource, true, + D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE, + L"BlasProceduralGeometry"); + + // Build the BLAS + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC BLASDesc = {}; + BLASDesc.Inputs = BLASInputs; + BLASDesc.ScratchAccelerationStructureData = + ScratchResource->GetGPUVirtualAddress(); + BLASDesc.DestAccelerationStructureData = + BLASProceduralGeometryResource->GetGPUVirtualAddress(); + + CommandList->BuildRaytracingAccelerationStructure(&BLASDesc, 0, nullptr); + + // Add a UAV barrier to ensure the BLAS is built before using it + CD3DX12_RESOURCE_BARRIER Barrier = + CD3DX12_RESOURCE_BARRIER::UAV(BLASProceduralGeometryResource); + CommandList->ResourceBarrier(1, &Barrier); + + CommandList->Close(); + ExecuteCommandList(CommandQueue, CommandList); + WaitForSignal(CommandQueue, FO); + + VERIFY_SUCCEEDED(CommandAllocator->Reset()); + VERIFY_SUCCEEDED(CommandList->Reset(CommandAllocator, nullptr)); + } + + // Build TLAS. + CComPtr InstanceDescs; + { + D3D12_RAYTRACING_INSTANCE_DESC CPUInstanceDescs[2] = {}; + const int MeshIdx = 0; + const int ProcGeoIdx = UseMesh && UseProceduralGeometry ? 1 : 0; + const int NumInstanceDescs = ProcGeoIdx + 1; + + for (int i = 0; i < NumInstanceDescs; ++i) { + D3D12_RAYTRACING_INSTANCE_DESC &InstanceDesc = CPUInstanceDescs[i]; + InstanceDesc.Transform[0][0] = InstanceDesc.Transform[1][1] = + InstanceDesc.Transform[2][2] = 1; + InstanceDesc.InstanceID = i; + InstanceDesc.InstanceContributionToHitGroupIndex = i; + InstanceDesc.InstanceMask = 1; + InstanceDesc.Flags = D3D12_RAYTRACING_INSTANCE_FLAG_NONE; + } + + if (UseMesh) + CPUInstanceDescs[MeshIdx].AccelerationStructure = + BLASMeshResource->GetGPUVirtualAddress(); + if (UseProceduralGeometry) + CPUInstanceDescs[ProcGeoIdx].AccelerationStructure = + BLASProceduralGeometryResource->GetGPUVirtualAddress(); + + AllocateUploadBuffer(Device, &CPUInstanceDescs, + NumInstanceDescs * + sizeof(D3D12_RAYTRACING_INSTANCE_DESC), + &InstanceDescs, L"InstanceDescs"); + + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAGS BuildFlags = + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_BUILD_FLAG_PREFER_FAST_BUILD; + + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_INPUTS AccelInputs = {}; + AccelInputs.Type = D3D12_RAYTRACING_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL; + AccelInputs.DescsLayout = D3D12_ELEMENTS_LAYOUT_ARRAY; + AccelInputs.NumDescs = NumInstanceDescs; + AccelInputs.Flags = BuildFlags; + AccelInputs.InstanceDescs = InstanceDescs->GetGPUVirtualAddress(); + + D3D12_RAYTRACING_ACCELERATION_STRUCTURE_PREBUILD_INFO PrebuildInfo = {}; + Device->GetRaytracingAccelerationStructurePrebuildInfo(&AccelInputs, + &PrebuildInfo); + + ScratchResource.Release(); + ReallocScratchResource(Device, &ScratchResource, + PrebuildInfo.ScratchDataSizeInBytes); + AllocateBuffer(Device, PrebuildInfo.ResultDataMaxSizeInBytes, &TLASResource, + true, D3D12_RESOURCE_STATE_RAYTRACING_ACCELERATION_STRUCTURE, + L"TLAS"); + + D3D12_BUILD_RAYTRACING_ACCELERATION_STRUCTURE_DESC BuildDesc = {}; + BuildDesc.Inputs = AccelInputs; + BuildDesc.ScratchAccelerationStructureData = + ScratchResource->GetGPUVirtualAddress(); + BuildDesc.DestAccelerationStructureData = + TLASResource->GetGPUVirtualAddress(); + + CommandList->BuildRaytracingAccelerationStructure(&BuildDesc, 0, 0); + + CD3DX12_RESOURCE_BARRIER Barrier = + CD3DX12_RESOURCE_BARRIER::UAV(TLASResource); + CommandList->ResourceBarrier(1, (const D3D12_RESOURCE_BARRIER *)&Barrier); + } + + // Set the local root constants. + CommandList->SetComputeRootSignature(LocalRootSignature); + CommandList->SetComputeRoot32BitConstant(1, 12, 0); + CommandList->SetComputeRoot32BitConstant(1, 34, 1); + CommandList->SetComputeRoot32BitConstant(1, 56, 2); + CommandList->SetComputeRoot32BitConstant(1, 78, 3); + + ShaderTable.Upload(CommandList); + + ID3D12DescriptorHeap *const Heaps[1] = {DescriptorHeap}; + CommandList->SetDescriptorHeaps(1, Heaps); + CommandList->SetComputeRootSignature(GlobalRootSignature); + CommandList->SetComputeRootShaderResourceView( + 0, TLASResource->GetGPUVirtualAddress()); + CommandList->SetComputeRootConstantBufferView( + 1, SceneConstantBuffer->GetGPUVirtualAddress()); + CommandList->SetComputeRootDescriptorTable( + 2, DescriptorHeap->GetGPUDescriptorHandleForHeapStart()); + + D3D12_DISPATCH_RAYS_DESC DispatchDesc = {}; + DispatchDesc.RayGenerationShaderRecord.StartAddress = + ShaderTable.GetRaygenStartGpuVA(); + DispatchDesc.RayGenerationShaderRecord.SizeInBytes = + ShaderTable.GetRaygenRangeInBytes(); + DispatchDesc.MissShaderTable.StartAddress = ShaderTable.GetMissStartGpuVA(); + DispatchDesc.MissShaderTable.SizeInBytes = ShaderTable.GetMissRangeInBytes(); + DispatchDesc.MissShaderTable.StrideInBytes = + ShaderTable.GetShaderRecordSizeInBytes(); + DispatchDesc.HitGroupTable.StartAddress = ShaderTable.GetHitGroupStartGpuVA(); + DispatchDesc.HitGroupTable.SizeInBytes = + ShaderTable.GetHitGroupRangeInBytes(); + DispatchDesc.HitGroupTable.StrideInBytes = + ShaderTable.GetShaderRecordSizeInBytes(); + DispatchDesc.Width = WindowWidth; + DispatchDesc.Height = WindowHeight; + DispatchDesc.Depth = 1; + CommandList->SetPipelineState1(StateObject); + CommandList->DispatchRays(&DispatchDesc); + + CommandList->Close(); + ExecuteCommandList(CommandQueue, CommandList); + WaitForSignal(CommandQueue, FO); + + VERIFY_SUCCEEDED(CommandAllocator->Reset()); + VERIFY_SUCCEEDED(CommandList->Reset(CommandAllocator, nullptr)); + + // Copy the testBuffer contents to CPU + D3D12_RESOURCE_BARRIER Barriers[1]; + Barriers[0] = CD3DX12_RESOURCE_BARRIER::Transition( + TestBuffer, D3D12_RESOURCE_STATE_UNORDERED_ACCESS, + D3D12_RESOURCE_STATE_COPY_SOURCE); + CommandList->ResourceBarrier(1, Barriers); + CommandList->CopyResource(TestBufferRead, TestBuffer); + Barriers[0] = CD3DX12_RESOURCE_BARRIER::Transition( + TestBuffer, D3D12_RESOURCE_STATE_COPY_SOURCE, + D3D12_RESOURCE_STATE_UNORDERED_ACCESS); + CommandList->ResourceBarrier(1, Barriers); + + CommandList->Close(); + ExecuteCommandList(CommandQueue, CommandList); + WaitForSignal(CommandQueue, FO); + + // Copy the shader test data into 'testData'. + MappedData Data(TestBufferRead, (UINT32)TestData.size() * sizeof(int)); + const int *DataPtr = (int *)Data.data(); + + for (int i = 0; i < TestData.size(); i++) + TestData[i] = *DataPtr++; + + // Cleanup resources + TestBuffer.Release(); + TestBufferRead.Release(); + SceneConstantBuffer.Release(); + DescriptorHeap.Release(); + CommandQueue.Release(); + CommandAllocator.Release(); + CommandList.Release(); + StateObject.Release(); + StateObjectProperties.Release(); + TLASResource.Release(); + BLASMeshResource.Release(); + BLASProceduralGeometryResource.Release(); + InstanceDescs.Release(); + ScratchResource.Release(); + + return TestBufferRead; +} + +// SER TESTS +#include "ExecutionTest_SER.h" +// + void ExecutionTest::RunLifetimeIntrinsicComputeTest( ID3D12Device *pDevice, LPCSTR pShader, CComPtr &pUavHeap, diff --git a/tools/clang/unittests/HLSLExec/ExecutionTest_SER.h b/tools/clang/unittests/HLSLExec/ExecutionTest_SER.h new file mode 100644 index 0000000000..8bffa410d5 --- /dev/null +++ b/tools/clang/unittests/HLSLExec/ExecutionTest_SER.h @@ -0,0 +1,2561 @@ +//===--------- ExecutionTest_SER.h - SER Execution Tests -------*- C++ -*-===// +/////////////////////////////////////////////////////////////////////////////// +// // +// ExecutionTest_SER.h // +// Copyright (C) Nvidia Corporation. All rights reserved. // +// This file is distributed under the University of Illinois Open Source // +// License. See LICENSE.TXT for details. // +// // +// This file contains the execution tests for SER. // +// // +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +TEST_F(ExecutionTest, SERScalarGetterTest) { + // SER: Test basic function of HitObject getters. + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 WindowSize; + int rayFlags; +}; + +struct[raypayload] PerRayData +{ + VALTYPE value : read(anyhit,closesthit,miss,caller) : write(anyhit,miss,closesthit); +}; + +struct Attrs +{ + float2 barycentrics : BARYCENTRICS; +}; + +RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); + +RayDesc ComputeRay() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x*sceneConstants.U.xyz + d.y*sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + int id = 2 * (launchIndex.x + launchIndex.y * launchDim.x); + + RayDesc ray = ComputeRay(); + + // Fetch reference value + PerRayData refPayload; + TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, refPayload); + testBuffer[id] = refPayload.value; + + PerRayData serPayload; + dx::HitObject hitObject = dx::HitObject::TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, serPayload); + dx::MaybeReorderThread(hitObject); + VALTYPE serVal = hitObject.SER_GET_SCALAR(); + testBuffer[id + 1] = serVal; +} + +float getFloatZero() { return 0.0f; } +int getIntZero() { return 0; } + +[shader("miss")] +void miss(inout PerRayData payload) +{ + payload.value = MISS_GET_SCALAR(); +} + +[shader("anyhit")] +void anyhit(inout PerRayData payload, in Attrs attrs) +{ + // UNUSED +} + +[shader("closesthit")] +void closesthit(inout PerRayData payload, in Attrs attrs) +{ + payload.value = HIT_GET_SCALAR(); +} +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, true)) + return; + + // Initialize test data. + const int WindowSize = 64; + + // RayTMin + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetRayTMin()"); + std::vector TestData(WindowSize * WindowSize * 2, 0); + LPCWSTR Args[] = {L"-HV 2021", + L"-Vd", + L"-DVALTYPE=float", + L"-DHIT_GET_SCALAR=RayTMin", + L"-DMISS_GET_SCALAR=RayTMin", + L"-DSER_GET_SCALAR=GetRayTMin"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + for (int Id = 0; Id < TestData.size(); Id += 2) { + float *ResArray = (float *)(TestData.data() + Id); + float RefVal = ResArray[0]; + float SerVal = ResArray[1]; + const bool PassRayTMin = CompareFloatEpsilon(SerVal, RefVal, 0.0008f); + if (!PassRayTMin) { + VERIFY_IS_TRUE(PassRayTMin); + return; + } + } + WEX::Logging::Log::Comment(L"HitObject::GetRayTMin() PASSED"); + } + + // RayTCurrent + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetRayTCurrent()"); + std::vector TestData(WindowSize * WindowSize * 2, 0); + LPCWSTR Args[] = {L"-HV 2021", + L"-Vd", + L"-DVALTYPE=float", + L"-DHIT_GET_SCALAR=RayTCurrent", + L"-DMISS_GET_SCALAR=RayTCurrent", + L"-DSER_GET_SCALAR=GetRayTCurrent"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + for (int Id = 0; Id < TestData.size(); Id += 2) { + float *ResArray = (float *)(TestData.data() + Id); + float RefVal = ResArray[0]; + float SerVal = ResArray[1]; + const bool PassRayTCurrent = CompareFloatEpsilon(SerVal, RefVal, 0.0008f); + if (!PassRayTCurrent) { + VERIFY_IS_TRUE(PassRayTCurrent); + return; + } + } + WEX::Logging::Log::Comment(L"HitObject::GetRayTCurrent() PASSED"); + } + + // RayFlags + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetRayFlags()"); + std::vector TestData(WindowSize * WindowSize * 2, 0); + LPCWSTR Args[] = {L"-HV 2021", + L"-Vd", + L"-DVALTYPE=uint", + L"-DHIT_GET_SCALAR=RayFlags", + L"-DMISS_GET_SCALAR=RayFlags", + L"-DSER_GET_SCALAR=GetRayFlags"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + for (int Id = 0; Id < TestData.size(); Id += 2) { + const int RefVal = TestData[Id]; + const int SerVal = TestData[Id + 1]; + if (RefVal != SerVal) { + VERIFY_ARE_EQUAL(RefVal, SerVal); + return; + } + } + WEX::Logging::Log::Comment(L"HitObject::GetRayFlags() PASSED"); + } + + // HitKind + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetHitKind()"); + std::vector TestData(WindowSize * WindowSize * 2, 0); + LPCWSTR Args[] = {L"-HV 2021", + L"-Vd", + L"-DVALTYPE=uint", + L"-DHIT_GET_SCALAR=HitKind", + L"-DMISS_GET_SCALAR=getIntZero", + L"-DSER_GET_SCALAR=GetHitKind"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + for (int Id = 0; Id < TestData.size(); Id += 2) { + const int RefVal = TestData[Id]; + const int SerVal = TestData[Id + 1]; + if (RefVal != SerVal) { + VERIFY_ARE_EQUAL(RefVal, SerVal); + return; + } + } + WEX::Logging::Log::Comment(L"HitObject::GetHitKind() PASSED"); + } + + // GeometryIndex + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetGeometryIndex()"); + std::vector TestData(WindowSize * WindowSize * 2, 0); + LPCWSTR Args[] = {L"-HV 2021", + L"-Vd", + L"-DVALTYPE=uint", + L"-DHIT_GET_SCALAR=GeometryIndex", + L"-DMISS_GET_SCALAR=getIntZero", + L"-DSER_GET_SCALAR=GetGeometryIndex"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + for (int Id = 0; Id < TestData.size(); Id += 2) { + const int RefVal = TestData[Id]; + const int SerVal = TestData[Id + 1]; + if (RefVal != SerVal) { + VERIFY_ARE_EQUAL(RefVal, SerVal); + return; + } + } + WEX::Logging::Log::Comment(L"HitObject::GetGeometryIndex() PASSED"); + } + + // InstanceIndex + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetInstanceIndex()"); + std::vector TestData(WindowSize * WindowSize * 2, 0); + LPCWSTR Args[] = {L"-HV 2021", + L"-Vd", + L"-DVALTYPE=uint", + L"-DHIT_GET_SCALAR=InstanceIndex", + L"-DMISS_GET_SCALAR=getIntZero", + L"-DSER_GET_SCALAR=GetInstanceIndex"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + for (int Id = 0; Id < TestData.size(); Id += 2) { + const int RefVal = TestData[Id]; + const int SerVal = TestData[Id + 1]; + if (RefVal != SerVal) { + VERIFY_ARE_EQUAL(RefVal, SerVal); + return; + } + } + WEX::Logging::Log::Comment(L"HitObject::GetInstanceIndex() PASSED"); + } + + // InstanceID + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetInstanceID()"); + std::vector TestData(WindowSize * WindowSize * 2, 0); + LPCWSTR Args[] = {L"-HV 2021", + L"-Vd", + L"-DVALTYPE=uint", + L"-DHIT_GET_SCALAR=InstanceID", + L"-DMISS_GET_SCALAR=getIntZero", + L"-DSER_GET_SCALAR=GetInstanceID"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + for (int Id = 0; Id < TestData.size(); Id += 2) { + const int RefVal = TestData[Id]; + const int SerVal = TestData[Id + 1]; + if (RefVal != SerVal) { + VERIFY_ARE_EQUAL(RefVal, SerVal); + return; + } + } + WEX::Logging::Log::Comment(L"HitObject::GetInstanceID() PASSED"); + } + + // PrimitiveIndex + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetPrimitiveIndex()"); + std::vector TestData(WindowSize * WindowSize * 2, 0); + LPCWSTR Args[] = {L"-HV 2021", + L"-Vd", + L"-DVALTYPE=uint", + L"-DHIT_GET_SCALAR=PrimitiveIndex", + L"-DMISS_GET_SCALAR=getIntZero", + L"-DSER_GET_SCALAR=GetPrimitiveIndex"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + for (int Id = 0; Id < TestData.size(); Id += 2) { + const int RefVal = TestData[Id]; + const int SerVal = TestData[Id + 1]; + if (RefVal != SerVal) { + VERIFY_ARE_EQUAL(RefVal, SerVal); + return; + } + } + WEX::Logging::Log::Comment(L"HitObject::GetPrimitiveIndex() PASSED"); + } +} + +TEST_F(ExecutionTest, SERVectorGetterTest) { + // SER: Test basic function of HitObject getters. + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 WindowSize; + int rayFlags; +}; + +struct[raypayload] PerRayData +{ + float3 value : read(caller) : write(miss,closesthit); +}; + +struct Attrs +{ + float2 barycentrics : BARYCENTRICS; +}; + +RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); + +RayDesc ComputeRay() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x*sceneConstants.U.xyz + d.y*sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + int id = 6 * (launchIndex.x + launchIndex.y * launchDim.x); + + RayDesc ray = ComputeRay(); + + // Fetch reference value + PerRayData refPayload; + TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, refPayload); + testBuffer[id] = refPayload.value.x; + testBuffer[id + 2] = refPayload.value.y; + testBuffer[id + 4] = refPayload.value.z; + + PerRayData serPayload; + dx::HitObject hitObject = dx::HitObject::TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, serPayload); + dx::MaybeReorderThread(hitObject); + float3 serVal = hitObject.SER_GET_VECTOR(); + testBuffer[id + 1] = serVal.x; + testBuffer[id + 3] = serVal.y; + testBuffer[id + 5] = serVal.z; +} + +float3 getVecZero() { return 0.0f; } + +[shader("miss")] +void miss(inout PerRayData payload) +{ + payload.value = MISS_GET_VECTOR(); +} + +[shader("anyhit")] +void anyhit(inout PerRayData payload, in Attrs attrs) +{ + // UNUSED +} + +[shader("closesthit")] +void closesthit(inout PerRayData payload, in Attrs attrs) +{ + payload.value = HIT_GET_VECTOR(); +} +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, true)) + return; + + // Initialize test data. + const int WindowSize = 64; + + // WorldRayOrigin + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetWorldRayOrigin()"); + std::vector TestData(WindowSize * WindowSize * 6, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd", L"-DHIT_GET_VECTOR=WorldRayOrigin", + L"-DMISS_GET_VECTOR=WorldRayOrigin", + L"-DSER_GET_VECTOR=GetWorldRayOrigin"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 3 /*payloadCount*/, + 2 /*attributeCount*/); + for (int Id = 0; Id < TestData.size(); Id += 6) { + float *ResArray = (float *)(TestData.data() + Id); + float RefX = ResArray[0]; + float SerX = ResArray[1]; + float RefY = ResArray[2]; + float SerY = ResArray[3]; + float RefZ = ResArray[4]; + float SerZ = ResArray[5]; + const bool PassX = CompareFloatEpsilon(SerX, RefX, 0.0008f); + const bool PassY = CompareFloatEpsilon(SerY, RefY, 0.0008f); + const bool PassZ = CompareFloatEpsilon(SerZ, RefZ, 0.0008f); + if (!PassX || !PassY || !PassZ) { + VERIFY_ARE_EQUAL(SerX, RefX); + VERIFY_ARE_EQUAL(SerY, RefY); + VERIFY_ARE_EQUAL(SerZ, RefZ); + break; + } + } + WEX::Logging::Log::Comment(L"HitObject::GetWorldRayOrigin() PASSED"); + } + + // WorldRayDirection + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetWorldRayDirection()"); + std::vector TestData(WindowSize * WindowSize * 6, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd", + L"-DHIT_GET_VECTOR=WorldRayDirection", + L"-DMISS_GET_VECTOR=WorldRayDirection", + L"-DSER_GET_VECTOR=GetWorldRayDirection"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 3 /*payloadCount*/, + 2 /*attributeCount*/); + for (int Id = 0; Id < TestData.size(); Id += 6) { + float *ResArray = (float *)(TestData.data() + Id); + float RefX = ResArray[0]; + float SerX = ResArray[1]; + float RefY = ResArray[2]; + float SerY = ResArray[3]; + float RefZ = ResArray[4]; + float SerZ = ResArray[5]; + const bool PassX = CompareFloatEpsilon(SerX, RefX, 0.0008f); + const bool PassY = CompareFloatEpsilon(SerY, RefY, 0.0008f); + const bool PassZ = CompareFloatEpsilon(SerZ, RefZ, 0.0008f); + if (!PassX || !PassY || !PassZ) { + VERIFY_ARE_EQUAL(SerX, RefX); + VERIFY_ARE_EQUAL(SerY, RefY); + VERIFY_ARE_EQUAL(SerZ, RefZ); + break; + } + } + WEX::Logging::Log::Comment(L"HitObject::GetWorldRayDirection() PASSED"); + } + + // ObjectRayOrigin + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetObjectRayOrigin()"); + std::vector TestData(WindowSize * WindowSize * 6, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd", L"-DHIT_GET_VECTOR=ObjectRayOrigin", + L"-DMISS_GET_VECTOR=WorldRayOrigin", + L"-DSER_GET_VECTOR=GetObjectRayOrigin"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 3 /*payloadCount*/, + 2 /*attributeCount*/); + for (int Id = 0; Id < TestData.size(); Id += 6) { + float *ResArray = (float *)(TestData.data() + Id); + float RefX = ResArray[0]; + float SerX = ResArray[1]; + float RefY = ResArray[2]; + float SerY = ResArray[3]; + float RefZ = ResArray[4]; + float SerZ = ResArray[5]; + const bool PassX = CompareFloatEpsilon(SerX, RefX, 0.0008f); + const bool PassY = CompareFloatEpsilon(SerY, RefY, 0.0008f); + const bool PassZ = CompareFloatEpsilon(SerZ, RefZ, 0.0008f); + if (!PassX || !PassY || !PassZ) { + VERIFY_ARE_EQUAL(SerX, RefX); + VERIFY_ARE_EQUAL(SerY, RefY); + VERIFY_ARE_EQUAL(SerZ, RefZ); + break; + } + } + WEX::Logging::Log::Comment(L"HitObject::GetObjectRayOrigin() PASSED"); + } + + // ObjectRayDirection + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetObjectRayDirection()"); + std::vector TestData(WindowSize * WindowSize * 6, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd", + L"-DHIT_GET_VECTOR=ObjectRayDirection", + L"-DMISS_GET_VECTOR=WorldRayDirection", + L"-DSER_GET_VECTOR=GetObjectRayDirection"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 3 /*payloadCount*/, + 2 /*attributeCount*/); + for (int Id = 0; Id < TestData.size(); Id += 6) { + float *ResArray = (float *)(TestData.data() + Id); + float RefX = ResArray[0]; + float SerX = ResArray[1]; + float RefY = ResArray[2]; + float SerY = ResArray[3]; + float RefZ = ResArray[4]; + float SerZ = ResArray[5]; + const bool PassX = CompareFloatEpsilon(SerX, RefX, 0.0008f); + const bool PassY = CompareFloatEpsilon(SerY, RefY, 0.0008f); + const bool PassZ = CompareFloatEpsilon(SerZ, RefZ, 0.0008f); + if (!PassX || !PassY || !PassZ) { + VERIFY_ARE_EQUAL(SerX, RefX); + VERIFY_ARE_EQUAL(SerY, RefY); + VERIFY_ARE_EQUAL(SerZ, RefZ); + break; + } + } + WEX::Logging::Log::Comment(L"HitObject::GetObjectRayDirection() PASSED"); + } +} + +TEST_F(ExecutionTest, SERMatrixGetterTest) { + // SER: Test basic function of HitObject getters. + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 WindowSize; + int rayFlags; +}; + +struct[raypayload] PerRayData +{ + float elems[ROWS*COLS] : read(caller) : write(miss,closesthit); +}; + +struct Attrs +{ + float2 barycentrics : BARYCENTRICS; +}; + +RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); + +RayDesc ComputeRay() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x*sceneConstants.U.xyz + d.y*sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + int id = 2 * ROWS * COLS * (launchIndex.x + launchIndex.y * launchDim.x); + + RayDesc ray = ComputeRay(); + + // Fetch reference value + PerRayData refPayload; + TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, refPayload); + for (int r = 0; r < ROWS; r++) { + for (int c = 0; c < COLS; c++) { + testBuffer[id + 2 * (r * COLS + c)] = refPayload.elems[r*COLS + c]; + } + } + + PerRayData serPayload; + dx::HitObject hitObject = dx::HitObject::TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, serPayload); + dx::MaybeReorderThread(hitObject); + matrix serVal = hitObject.SER_GET_MATRIX(); + for (int r = 0; r < ROWS; r++) { + for (int c = 0; c < COLS; c++) { + testBuffer[1 + id + 2 * (r * COLS + c)] = serVal[r][c]; + } + } +} + +matrix getMatIdentity() { + matrix mat = 0; + mat[0][0] = 1.f; + mat[1][1] = 1.f; + mat[2][2] = 1.f; + return mat; +} + +[shader("miss")] +void miss(inout PerRayData payload) +{ + matrix mat = MISS_GET_MATRIX(); + for (int r = 0; r < ROWS; r++) { + for (int c = 0; c < COLS; c++) { + payload.elems[r*COLS + c] = mat[r][c]; + } + } +} + +[shader("anyhit")] +void anyhit(inout PerRayData payload, in Attrs attrs) +{ + // UNUSED +} + +[shader("closesthit")] +void closesthit(inout PerRayData payload, in Attrs attrs) +{ + matrix mat = HIT_GET_MATRIX(); + for (int r = 0; r < ROWS; r++) { + for (int c = 0; c < COLS; c++) { + payload.elems[r*COLS + c] = mat[r][c]; + } + } +} +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, true)) + return; + + const int WindowSize = 64; + + // WorldToObject3x4 + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetWorldToObject3x4()"); + std::vector TestData(WindowSize * WindowSize * 24, 0); + LPCWSTR Args[] = {L"-HV 2021", + L"-Vd", + L"-DHIT_GET_MATRIX=WorldToObject3x4", + L"-DMISS_GET_MATRIX=getMatIdentity", + L"-DSER_GET_MATRIX=GetWorldToObject3x4", + L"-DROWS=3", + L"-DCOLS=4"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 12 /*payloadCount*/, + 2 /*attributeCount*/); + const int ROWS = 3; + const int COLS = 4; + for (int Id = 0; Id < TestData.size(); Id += 24) { + float *ResArray = (float *)(TestData.data() + Id); + for (int RowIdx = 0; RowIdx < ROWS; RowIdx++) { + for (int ColIdx = 0; ColIdx < COLS; ColIdx++) { + int RefIdx = 2 * (RowIdx * COLS + ColIdx); + float Ref = ResArray[RefIdx]; + float Ser = ResArray[1 + RefIdx]; + if (!CompareFloatEpsilon(Ser, Ref, 0.0008f)) { + VERIFY_ARE_EQUAL(Ser, Ref); + } + } + } + } + WEX::Logging::Log::Comment(L"HitObject::GetWorldToObject3x4() PASSED"); + } + + // WorldToObject4x3 + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetWorldToObject4x3()"); + const int ROWS = 4; + const int COLS = 3; + std::vector TestData(WindowSize * WindowSize * 2 * ROWS * COLS, 0); + LPCWSTR Args[] = {L"-HV 2021", + L"-Vd", + L"-DHIT_GET_MATRIX=WorldToObject4x3", + L"-DMISS_GET_MATRIX=getMatIdentity", + L"-DSER_GET_MATRIX=GetWorldToObject4x3", + L"-DROWS=4", + L"-DCOLS=3"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 12 /*payloadCount*/, + 2 /*attributeCount*/); + for (int Id = 0; Id < TestData.size(); Id += 2 * ROWS * COLS) { + float *ResArray = (float *)(TestData.data() + Id); + for (int RowIdx = 0; RowIdx < ROWS; RowIdx++) { + for (int ColIdx = 0; ColIdx < COLS; ColIdx++) { + int RefIdx = 2 * (RowIdx * COLS + ColIdx); + float Ref = ResArray[RefIdx]; + float Ser = ResArray[1 + RefIdx]; + if (!CompareFloatEpsilon(Ser, Ref, 0.0008f)) { + VERIFY_ARE_EQUAL(Ser, Ref); + } + } + } + } + WEX::Logging::Log::Comment(L"HitObject::GetWorldToObject4x3() PASSED"); + } + + // ObjectToWorld3x4 + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetObjectToWorld3x4()"); + std::vector TestData(WindowSize * WindowSize * 24, 0); + LPCWSTR Args[] = {L"-HV 2021", + L"-Vd", + L"-DHIT_GET_MATRIX=ObjectToWorld3x4", + L"-DMISS_GET_MATRIX=getMatIdentity", + L"-DSER_GET_MATRIX=GetObjectToWorld3x4", + L"-DROWS=3", + L"-DCOLS=4"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 12 /*payloadCount*/, + 2 /*attributeCount*/); + const int ROWS = 3; + const int COLS = 4; + for (int Id = 0; Id < TestData.size(); Id += 24) { + float *ResArray = (float *)(TestData.data() + Id); + for (int RowIdx = 0; RowIdx < ROWS; RowIdx++) { + for (int ColIdx = 0; ColIdx < COLS; ColIdx++) { + int RefIdx = 2 * (RowIdx * COLS + ColIdx); + float Ref = ResArray[RefIdx]; + float Ser = ResArray[1 + RefIdx]; + if (!CompareFloatEpsilon(Ser, Ref, 0.0008f)) { + VERIFY_ARE_EQUAL(Ser, Ref); + } + } + } + } + WEX::Logging::Log::Comment(L"HitObject::GetObjectToWorld3x4() PASSED"); + } + + // ObjectToWorld4x3 + { + WEX::Logging::Log::Comment(L"Testing HitObject::GetObjectToWorld4x3()"); + std::vector TestData(WindowSize * WindowSize * 24, 0); + LPCWSTR Args[] = {L"-HV 2021", + L"-Vd", + L"-DHIT_GET_MATRIX=ObjectToWorld4x3", + L"-DMISS_GET_MATRIX=getMatIdentity", + L"-DSER_GET_MATRIX=GetObjectToWorld4x3", + L"-DROWS=4", + L"-DCOLS=3"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 12 /*payloadCount*/, + 2 /*attributeCount*/); + const int ROWS = 4; + const int COLS = 3; + for (int Id = 0; Id < TestData.size(); Id += 24) { + float *ResArray = (float *)(TestData.data() + Id); + for (int RowIdx = 0; RowIdx < ROWS; RowIdx++) { + for (int ColIdx = 0; ColIdx < COLS; ColIdx++) { + int RefIdx = 2 * (RowIdx * COLS + ColIdx); + float Ref = ResArray[RefIdx]; + float Ser = ResArray[1 + RefIdx]; + if (!CompareFloatEpsilon(Ser, Ref, 0.0008f)) { + VERIFY_ARE_EQUAL(Ser, Ref); + break; + } + } + } + } + WEX::Logging::Log::Comment(L"HitObject::GetObjectToWorld4x3() PASSED"); + } +} + +TEST_F(ExecutionTest, SERBasicTest) { + // SER: Test basic functionality. + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 WindowSize; + int rayFlags; +}; + +struct[raypayload] PerRayData +{ + uint visited : read(anyhit,closesthit,miss,caller) : write(anyhit,miss,closesthit,caller); +}; + +struct Attrs +{ + float2 barycentrics : BARYCENTRICS; +}; + +RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); + +RayDesc ComputeRay() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x*sceneConstants.U.xyz + d.y*sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + RayDesc ray = ComputeRay(); + + PerRayData payload; + payload.visited = 0; + + // SER Test + dx::HitObject hitObject = dx::HitObject::TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, payload); + dx::MaybeReorderThread(hitObject); + dx::HitObject::Invoke(hitObject, payload); + + int id = launchIndex.x + launchIndex.y * launchDim.x; + testBuffer[id] = payload.visited; +} + +[shader("miss")] +void miss(inout PerRayData payload) +{ + payload.visited |= 2U; +} + +[shader("anyhit")] +void anyhit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 1U; +} + +[shader("closesthit")] +void closesthit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 4U; +} + +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, true)) + return; + + const int WindowSize = 64; + std::vector TestData(WindowSize * WindowSize, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd"}; + + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + std::map Histo; + for (int Val : TestData) + ++Histo[Val]; + VERIFY_ARE_EQUAL(Histo.size(), 2); + VERIFY_ARE_EQUAL(Histo[2], 4030); + VERIFY_ARE_EQUAL(Histo[5], 66); +} + +TEST_F(ExecutionTest, SERShaderTableIndexTest) { + // Test SER with HitObject::SetShaderTableIndex and + // HitObject::GetShaderTableIndex + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 WindowSize; + int rayFlags; +}; + +struct[raypayload] PerRayData +{ + uint visited : read(anyhit,closesthit,miss,caller) : write(anyhit,miss,closesthit,caller); +}; + +struct Attrs +{ + float2 barycentrics : BARYCENTRICS; +}; + +struct CustomAttrs +{ + float dist; +}; + +RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); + +RayDesc ComputeRay() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x*sceneConstants.U.xyz + d.y*sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + RayDesc ray = ComputeRay(); + + PerRayData payload; + payload.visited = 0; + + dx::HitObject hitObject = dx::HitObject::TraceRay(topObject, RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES, 0xFF, 0, 1, 0, ray, payload); + dx::MaybeReorderThread(hitObject); + + // Invoke hit/miss for triangle + dx::HitObject::Invoke( hitObject, payload ); + + if (hitObject.IsHit()) + { + // Transform to an 'aabb' hit. + hitObject.SetShaderTableIndex( 1 ); + } + + // Invoke hit/miss for aabb + dx::HitObject::Invoke( hitObject, payload ); + + if (hitObject.IsHit()) + { + // Poison the test data if GetShaderTableIndex does not match SetShaderTableIndex. + if (hitObject.GetShaderTableIndex() != 1) + payload.visited = 12345; + } + + int id = launchIndex.x + launchIndex.y * launchDim.x; + testBuffer[id] = payload.visited; +} + +[shader("miss")] +void miss(inout PerRayData payload) +{ + if ((payload.visited & 4U) == 0) + payload.visited |= 4U; // First 'miss' invocation + else + payload.visited |= 8U; // Second 'miss' invocation +} + +// Triangles +[shader("anyhit")] +void anyhit(inout PerRayData payload, in Attrs attrs) +{ + AcceptHitAndEndSearch(); +} + +// Triangle closest hit +[shader("closesthit")] +void closesthit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 1U; +} + +// AABB closest hit +[shader("closesthit")] +void chAABB(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 2U; +} + +// Procedural +[shader("intersection")] +void intersection() +{ + // UNUSED +} + +[shader("anyhit")] +void ahAABB(inout PerRayData payload, in CustomAttrs attrs) +{ + // UNUSED +} + +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, true)) + return; + + // Initialize test data. + const int WindowSize = 64; + std::vector TestData(WindowSize * WindowSize, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd"}; + + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*mesh*/, + true /*procedural geometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + std::map Histo; + for (int Val : TestData) + ++Histo[Val]; + + VERIFY_ARE_EQUAL(Histo.size(), 2); + VERIFY_ARE_EQUAL( + Histo[3], + 66); // 'closesthit' invoked at index 0, then 'chAABB' invoked at index 1 + VERIFY_ARE_EQUAL(Histo[12], 4030); // Miss shader invoked twice +} + +TEST_F(ExecutionTest, SERLoadLocalRootTableConstantTest) { + // Test SER with HitObject::LoadLocalRootTableConstant + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 WindowSize; + int rayFlags; +}; + +struct[raypayload] PerRayData +{ + uint res : read(caller) : write(miss,closesthit,caller); +}; + +struct Attrs +{ + float2 barycentrics : BARYCENTRICS; +}; + +struct LocalConstants +{ + int c0; + int c1; + int c2; + int c3; +}; + +RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); +ConstantBuffer localConstants : register(b1); + +RayDesc ComputeRay() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x*sceneConstants.U.xyz + d.y*sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + RayDesc ray = ComputeRay(); + + PerRayData payload; + payload.res = 0; + + // SER Test +#if 1 + dx::HitObject hitObject = dx::HitObject::TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, payload); + dx::MaybeReorderThread(hitObject); + int c0 = hitObject.LoadLocalRootTableConstant(0); + int c1 = hitObject.LoadLocalRootTableConstant(4); + int c2 = hitObject.LoadLocalRootTableConstant(8); + int c3 = hitObject.LoadLocalRootTableConstant(12); + int res = c0 | c1 | c2 | c3; +#else + TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, payload); + int res = payload.res; +#endif + + int id = launchIndex.x + launchIndex.y * launchDim.x; + testBuffer[id] = res; +} + +[shader("miss")] +void miss(inout PerRayData payload) +{ + payload.res = localConstants.c0 | localConstants.c1 | localConstants.c2 | localConstants.c3; +} + +[shader("anyhit")] +void anyhit(inout PerRayData payload, in Attrs attrs) +{ + // UNUSED +} + +[shader("closesthit")] +void closesthit(inout PerRayData payload, in Attrs attrs) +{ + payload.res = localConstants.c0 | localConstants.c1 | localConstants.c2 | localConstants.c3; +} + +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, true)) + return; + + // Initialize test data. + const int WindowSize = 64; + std::vector TestData(WindowSize * WindowSize, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd"}; + + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + std::map Histo; + for (int Val : TestData) + ++Histo[Val]; + VERIFY_ARE_EQUAL(Histo.size(), 1); + VERIFY_ARE_EQUAL(Histo[126], 4096); +} + +TEST_F(ExecutionTest, SERRayQueryTest) { + // Test SER RayQuery + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 WindowSize; + int rayFlags; +}; + +struct[raypayload] PerRayData +{ + uint visited : read(anyhit,closesthit,miss,caller) : write(anyhit,miss,closesthit,caller); +}; + +struct Attrs +{ + float2 barycentrics : BARYCENTRICS; +}; + +RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); + +RayDesc ComputeRay() +{ + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x*sceneConstants.U.xyz + d.y*sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + RayDesc ray = ComputeRay(); + + PerRayData payload; + payload.visited = 0; + + // Template parameter set at runtime before compilation + RayQuery rayQ; + + // Funtion parameter set at runtime before compilation + rayQ.TraceRayInline(topObject, RAY_FLAG_NONE, 0xFF, ray); + + // Storage for procedural primitive hit attributes + Attrs attrs; + attrs.barycentrics = float2(1, 1); + + while (rayQ.Proceed()) + { + switch (rayQ.CandidateType()) + { + + case CANDIDATE_NON_OPAQUE_TRIANGLE: + { + // The system has already determined that the candidate would be the closest + // hit so far in the ray extents + rayQ.CommitNonOpaqueTriangleHit(); + } + } + } + +#if 0 + switch (rayQ.CommittedStatus()) + { + case COMMITTED_TRIANGLE_HIT: + { + if (rayQ.CommittedTriangleFrontFace()) + { + // Hit + payload.visited |= 4U; + } + break; + } + case COMMITTED_PROCEDURAL_PRIMITIVE_HIT: + { + // Unused + break; + } + case COMMITTED_NOTHING: + { + // Miss + payload.visited |= 2U; + break; + } + } +#else + dx::HitObject hit; + if (rayQ.CommittedStatus() == COMMITTED_NOTHING) + { + hit = dx::HitObject::MakeMiss(RAY_FLAG_NONE, 0, ray); + } + else + { + hit = dx::HitObject::FromRayQuery(rayQ); + } + dx::MaybeReorderThread(hit); + dx::HitObject::Invoke(hit, payload); +#endif + + int id = launchIndex.x + launchIndex.y * launchDim.x; + testBuffer[id] = payload.visited; +} + +[shader("miss")] +void miss(inout PerRayData payload) +{ + payload.visited |= 2U; +} + +[shader("anyhit")] +void anyhit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 1U; + AcceptHitAndEndSearch(); +} + +[shader("closesthit")] +void closesthit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 4U; +} + +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, true)) + return; + + // Initialize test data. + const int WindowSize = 64; + std::vector TestData(WindowSize * WindowSize, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd"}; + + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + std::map Histo; + for (int Val : TestData) + ++Histo[Val]; + VERIFY_ARE_EQUAL(Histo.size(), 2); + VERIFY_ARE_EQUAL(Histo[0], 66); + VERIFY_ARE_EQUAL(Histo[2], 4030); +} + +TEST_F(ExecutionTest, SERIntersectionTest) { + // Test SER with Intersection and procedural geometry + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 WindowSize; + int rayFlags; +}; + +struct Attrs +{ + float2 barycentrics : BARYCENTRICS; +}; + +struct[raypayload] PerRayData +{ + uint visited : read(anyhit, closesthit, miss, caller) : write(anyhit, miss, closesthit, caller); +}; + +RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); + +RayDesc ComputeRay() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x * sceneConstants.U.xyz + d.y * sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + RayDesc ray = ComputeRay(); + + PerRayData payload; + payload.visited = 0; + +#if 0 + dx::HitObject hitObject; + TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, payload); +#else + dx::HitObject hitObject = dx::HitObject::TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, payload); + dx::MaybeReorderThread(hitObject); + dx::HitObject::Invoke(hitObject, payload); +#endif + + int id = launchIndex.x + launchIndex.y * launchDim.x; + testBuffer[id] = payload.visited; +} + +[shader("miss")] +void miss(inout PerRayData payload) +{ + payload.visited |= 2U; +} + +[shader("anyhit")] +void anyhit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 1U; + AcceptHitAndEndSearch(); +} + +[shader("closesthit")] +void closesthit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 4U; +} + +[shader("intersection")] +void intersection() +{ + Attrs attrs; + + ReportHit(0.1, 0, attrs); +} + +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, true)) + return; + + // Initialize test data. + const int WindowSize = 64; + std::vector TestData(WindowSize * WindowSize, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd"}; + + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, false /*mesh*/, + true /*procedural geometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + std::map Histo; + for (int Val : TestData) + ++Histo[Val]; + VERIFY_ARE_EQUAL(Histo.size(), 1); + VERIFY_ARE_EQUAL(Histo[5], 4096); // All rays hitting the procedural geometry +} + +TEST_F(ExecutionTest, SERGetAttributesTest) { + // Test SER with HitObject::GetAttributes + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 WindowSize; + int rayFlags; +}; + +struct CustomAttrs +{ + float dist; +}; + +struct[raypayload] PerRayData +{ + uint visited : read(anyhit, closesthit, miss, caller) : write(anyhit, miss, closesthit, caller); +}; + +// reordercoherent // Requires #7250 +RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); + +RayDesc ComputeRay() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x * sceneConstants.U.xyz + d.y * sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + RayDesc ray = ComputeRay(); + + PerRayData payload; + payload.visited = 0; + + dx::HitObject hitObject = dx::HitObject::TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, payload); + dx::MaybeReorderThread(hitObject); + + // Check Attributes for hit detection. + CustomAttrs customAttrs = hitObject.GetAttributes(); + bool isHit = hitObject.IsHit(); + + int testVal = 0; + if (isHit) { + if (int(floor(customAttrs.dist)) % 2 == 0) + testVal = hitObject.GetHitKind(); + } + else + { + // Use 255 to keep outside the HitKind range [0,15] we passthru for hits. + testVal = 255; + } + int id = launchIndex.x + launchIndex.y * launchDim.x; + testBuffer[id] = testVal; +} + +[shader("miss")] +void miss(inout PerRayData payload) +{ + // UNUSED +} + +[shader("anyhit")] +void anyhit(inout PerRayData payload, in CustomAttrs attrs) +{ + AcceptHitAndEndSearch(); +} + +[shader("closesthit")] +void closesthit(inout PerRayData payload, in CustomAttrs attrs) +{ + // UNUSED +} + +[shader("intersection")] +void intersection() +{ + // Intersection with circle on a plane (base, n, radius) + // hitPos is intersection point with plane (base, n) + float3 base = {0.0f,0.0f,0.5f}; + float3 n = normalize(float3(0.0f,0.5f,0.5f)); + float radius = 500.f; + // Plane hit + float t = dot(n, base - ObjectRayOrigin()) / dot(n, ObjectRayDirection()); + if (t > RayTCurrent() || t < RayTMin()) + return; + float3 hitPos = ObjectRayOrigin() + t * ObjectRayDirection(); + float3 relHitPos = hitPos - base; + // Circle hit + float hitDist = length(relHitPos); + if (hitDist > radius) + return; + + CustomAttrs attrs; + attrs.dist = hitDist; + + // Generate wave-incoherent hitKind + uint2 launchIndex = DispatchRaysIndex().xy; + uint hitKind = 1U; + if (launchIndex.x >= 32) + hitKind |= 2U; + if (launchIndex.y >= 32) + hitKind |= 4U; + if ((launchIndex.x + launchIndex.y) % 2 == 0) + hitKind |= 8U; + + ReportHit(t, hitKind, attrs); +} + +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, true)) + return; + + // Initialize test data. + const int WindowSize = 64; + std::vector TestData(WindowSize * WindowSize, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd"}; + + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, false /*mesh*/, + true /*procedural geometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + std::map Histo; + for (int Val : TestData) + ++Histo[Val]; + + VERIFY_ARE_EQUAL(Histo.size(), 10); + VERIFY_ARE_EQUAL(Histo[0], 1587); + VERIFY_ARE_EQUAL(Histo[1], 277); + VERIFY_ARE_EQUAL(Histo[3], 256); + VERIFY_ARE_EQUAL(Histo[5], 167); + VERIFY_ARE_EQUAL(Histo[7], 153); + VERIFY_ARE_EQUAL(Histo[9], 249); + VERIFY_ARE_EQUAL(Histo[11], 260); + VERIFY_ARE_EQUAL(Histo[13], 158); + VERIFY_ARE_EQUAL(Histo[15], 142); + VERIFY_ARE_EQUAL(Histo[255], 847); +} + +TEST_F(ExecutionTest, SERTraceHitMissNopTest) { + // Test SER with conditional HitObject::TraceRay, HitObject::IsHit, + // HitObject::IsMiss, HitObject::IsNop + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 WindowSize; + int rayFlags; +}; + +struct[raypayload] PerRayData +{ + uint visited : read(anyhit,closesthit,miss,caller) : write(anyhit,miss,closesthit,caller); +}; + +struct Attrs +{ + float2 barycentrics : BARYCENTRICS; +}; + +RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); + +RayDesc ComputeRay() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x*sceneConstants.U.xyz + d.y*sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + RayDesc ray = ComputeRay(); + + PerRayData payload; + payload.visited = 0; + + // SER Test + dx::HitObject hitObject; + if (launchIndex.x % 2 == 0) { + hitObject = dx::HitObject::TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, payload); + } + dx::MaybeReorderThread(hitObject); + + // Check hitObject for hit detection. + if (hitObject.IsHit()) + payload.visited |= 4U; + if (hitObject.IsMiss()) + payload.visited |= 2U; + if (hitObject.IsNop()) + payload.visited |= 1U; + + dx::HitObject::Invoke(hitObject, payload); + + int id = launchIndex.x + launchIndex.y * launchDim.x; + testBuffer[id] = payload.visited; +} + +[shader("miss")] +void miss(inout PerRayData payload) +{ + payload.visited |= 16U; +} + +[shader("anyhit")] +void anyhit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 8U; +} + +[shader("closesthit")] +void closesthit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 32U; +} + +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, true)) + return; + + // Initialize test data. + const int WindowSize = 64; + std::vector TestData(WindowSize * WindowSize, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd"}; + + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*mesh*/, + false /*procedural geometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + std::map Histo; + for (int Val : TestData) + ++Histo[Val]; + VERIFY_ARE_EQUAL(Histo.size(), 3); + VERIFY_ARE_EQUAL( + Histo[1], + 2048); // isNop && !isMiss && !isHit && !anyhit && !closesthit && !miss + VERIFY_ARE_EQUAL( + Histo[18], + 2015); // !isNop && isMiss && !isHit && !anyhit && !closesthit && miss + VERIFY_ARE_EQUAL( + Histo[44], + 33); // !isNop && !isMiss && isHit && anyhit && closesthit && !miss +} + +TEST_F(ExecutionTest, SERIsMissTest) { + // Test SER with HitObject::IsMiss + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 WindowSize; + int rayFlags; +}; + +struct[raypayload] PerRayData +{ + uint visited : read(anyhit,closesthit,miss,caller) : write(anyhit,miss,closesthit,caller); +}; + +struct Attrs +{ + float2 barycentrics : BARYCENTRICS; +}; + +RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); + +RayDesc ComputeRay() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x*sceneConstants.U.xyz + d.y*sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + RayDesc ray = ComputeRay(); + + PerRayData payload; + payload.visited = 0; + + // SER Test + dx::HitObject hitObject = dx::HitObject::TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, payload); + dx::MaybeReorderThread(hitObject); + dx::HitObject::Invoke(hitObject, payload); + + // Check hitObject for hit detection. + if (hitObject.IsMiss()) + payload.visited |= 2U; + + int id = launchIndex.x + launchIndex.y * launchDim.x; + testBuffer[id] = payload.visited; +} + +[shader("miss")] +void miss(inout PerRayData payload) +{ + // UNUSED +} + +[shader("anyhit")] +void anyhit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 1U; +} + +[shader("closesthit")] +void closesthit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 4U; +} + +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, true)) + return; + + // Initialize test data. + const int WindowSize = 64; + std::vector TestData(WindowSize * WindowSize, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd"}; + + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*mesh*/, + false /*procedural geometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + std::map Histo; + for (int Val : TestData) + ++Histo[Val]; + VERIFY_ARE_EQUAL(Histo.size(), 2); + VERIFY_ARE_EQUAL(Histo[2], 4030); + VERIFY_ARE_EQUAL(Histo[5], 66); +} + +TEST_F(ExecutionTest, SERInvokeNoSBTTest) { + // Test SER RayQuery with Invoke + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 WindowSize; + int rayFlags; +}; + +struct[raypayload] PerRayData +{ + uint visited : read(anyhit,closesthit,miss,caller) : write(anyhit,miss,closesthit,caller); +}; + +struct Attrs +{ + float2 barycentrics : BARYCENTRICS; +}; + +RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); + +RayDesc ComputeRay() +{ + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x*sceneConstants.U.xyz + d.y*sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + RayDesc ray = ComputeRay(); + + PerRayData payload; + payload.visited = 0; + + // Template parameter set at runtime before compilation + RayQuery rayQ; + + // Funtion parameter set at runtime before compilation + rayQ.TraceRayInline(topObject, RAY_FLAG_NONE, 0xFF, ray); + + // Storage for procedural primitive hit attributes + Attrs attrs; + attrs.barycentrics = float2(1, 1); + + while (rayQ.Proceed()) + { + switch (rayQ.CandidateType()) + { + case CANDIDATE_NON_OPAQUE_TRIANGLE: + { + // The system has already determined that the candidate would be the closest + // hit so far in the ray extents + rayQ.CommitNonOpaqueTriangleHit(); + } + } + } + + dx::HitObject hit = dx::HitObject::FromRayQuery(rayQ); + dx::MaybeReorderThread(hit); + // Set the payload based on the HitObject. + if (hit.IsHit()) + payload.visited |= 8U; + else + payload.visited |= 16U; + // Invoke should not trigger any shader. + dx::HitObject::Invoke(hit, payload); + + int id = launchIndex.x + launchIndex.y * launchDim.x; + testBuffer[id] = payload.visited; +} + +[shader("miss")] +void miss(inout PerRayData payload) +{ + payload.visited |= 2U; +} + +[shader("anyhit")] +void anyhit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 1U; + AcceptHitAndEndSearch(); +} + +[shader("closesthit")] +void closesthit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 4U; +} + +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, false)) + return; + + // Initialize test data. + const int WindowSize = 64; + std::vector TestData(WindowSize * WindowSize, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd"}; + + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + std::map Histo; + for (int Val : TestData) + ++Histo[Val]; + VERIFY_ARE_EQUAL(Histo.size(), 2); + VERIFY_ARE_EQUAL(Histo[8], 66); + VERIFY_ARE_EQUAL(Histo[16], 4030); +} + +TEST_F(ExecutionTest, SERMaybeReorderThreadTest) { + // SER: Test MaybeReorderThread variants. + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 windowSize; + int rayFlags; +}; + +struct[raypayload] PerRayData +{ + uint visited : read(anyhit,closesthit,miss,caller) : write(anyhit,miss,closesthit,caller); +}; + +struct Attrs +{ + float2 barycentrics : BARYCENTRICS; +}; + +RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); + +RayDesc ComputeRay() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x*sceneConstants.U.xyz + d.y*sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + RayDesc ray = ComputeRay(); + + PerRayData payload; + payload.visited = 0; + + dx::HitObject hitObject = dx::HitObject::TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, payload); + + if (launchIndex.x % 3 == 0) { + dx::MaybeReorderThread(hitObject); + } + else if (launchIndex.x % 3 == 1) { + dx::MaybeReorderThread(hitObject, 0xFF, 7); + } + else { + dx::MaybeReorderThread(0xFFF, 5); + } + + dx::HitObject::Invoke(hitObject, payload); + + int id = launchIndex.x + launchIndex.y * launchDim.x; + testBuffer[id] = payload.visited; +} + +[shader("miss")] +void miss(inout PerRayData payload) +{ + payload.visited |= 2U; +} + +[shader("anyhit")] +void anyhit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 1U; +} + +[shader("closesthit")] +void closesthit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 4U; +} + +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, false)) + return; + + // Initialize test data. + const int WindowSize = 64; + std::vector TestData(WindowSize * WindowSize, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd"}; + + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + std::map Histo; + for (int Val : TestData) + ++Histo[Val]; + VERIFY_ARE_EQUAL(Histo.size(), 2); + VERIFY_ARE_EQUAL(Histo[2], 4030); + VERIFY_ARE_EQUAL(Histo[5], 66); +} + +TEST_F(ExecutionTest, SERDynamicHitObjectArrayTest) { + // Test SER with dynamic access to local HitObject array + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 windowSize; + int rayFlags; +}; + +struct Attrs +{ + float2 barycentrics : BARYCENTRICS; +}; + +struct[raypayload] PerRayData +{ + uint dummy : read(caller) : write(miss, closesthit); +}; + +struct LocalConstants +{ + int c0; + int c1; + int c2; + int c3; +}; + +RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); +ConstantBuffer localConstants : register(b1); + +RayDesc ComputeRay() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x * sceneConstants.U.xyz + d.y * sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + RayDesc ray = ComputeRay(); + + int constants[4] = { localConstants.c0, localConstants.c1, localConstants.c2, localConstants.c3 }; + + const int NUM_SAMPLES = 64; + const int NUM_HITOBJECTS = 8; + + // Generate wave-incoerent sample positions + int sampleIndices[NUM_SAMPLES]; + int threadOffset = launchIndex.x; + for (int i = 0; i < NUM_SAMPLES; i++) + { + int baseIndex = i % 4; // Cycle through the 4 constants + sampleIndices[i] = abs(constants[baseIndex] + threadOffset + i * 3) % NUM_HITOBJECTS; + } + + // Define an array of ray flags + uint rayFlagsArray[NUM_HITOBJECTS] = { + RAY_FLAG_NONE, + RAY_FLAG_FORCE_OPAQUE, + RAY_FLAG_FORCE_NON_OPAQUE, + RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH, + RAY_FLAG_SKIP_CLOSEST_HIT_SHADER, + RAY_FLAG_CULL_BACK_FACING_TRIANGLES, + RAY_FLAG_CULL_FRONT_FACING_TRIANGLES, + RAY_FLAG_CULL_OPAQUE + }; + + // Create a local array of HitObjects with TraceRay + dx::HitObject hitObjects[NUM_HITOBJECTS]; + for (uint i = 0; i < NUM_HITOBJECTS; ++i) + { + PerRayData payload; + uint expectedRayFlags = rayFlagsArray[i]; + hitObjects[i] = dx::HitObject::TraceRay( + topObject, // Acceleration structure + expectedRayFlags, // Unique ray flag + 0xFF, // Instance mask + 0, // Ray contribution to hit group index + 1, // Multiplier for geometry contribution + 0, // Miss shader index + ray, // Ray description + payload // Payload + ); + } + + // Evaluate at sample positions. + int testVal = 0; + + for (uint i = 0; i < NUM_SAMPLES; i++) + { + int idx = sampleIndices[i]; + // Verify that the rayFlags match + uint actualRayFlags = hitObjects[idx].GetRayFlags(); + uint expectedRayFlags = rayFlagsArray[idx]; + if (expectedRayFlags != actualRayFlags) + { + testVal = 1; // Mark as failure if flags do not match + } + } + + int id = launchIndex.x + launchIndex.y * launchDim.x; + testBuffer[id] = testVal; +} + +[shader("miss")] +void miss(inout PerRayData payload) +{ + // UNUSED +} + +[shader("anyhit")] +void anyhit(inout PerRayData payload, in Attrs attrs) +{ + AcceptHitAndEndSearch(); +} + +[shader("closesthit")] +void closesthit(inout PerRayData payload, in Attrs attrs) +{ + // UNUSED +} + +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, false)) + return; + + // Initialize test data. + const int WindowSize = 64; + + std::vector TestData(WindowSize * WindowSize, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd"}; + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*mesh*/, + false /*procedural geometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + std::map Histo; + for (int Val : TestData) + ++Histo[Val]; + VERIFY_ARE_EQUAL(Histo.size(), 1); + VERIFY_ARE_EQUAL(Histo[0], 4096); +} + +TEST_F(ExecutionTest, SERWaveIncoherentHitTest) { + // Test SER with wave incoherent conditional assignment of HitObject values + // with and without procedural attributes. + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 windowSize; + int rayFlags; +}; + +struct[raypayload] PerRayData +{ + uint visited : read(anyhit,closesthit,miss,caller) : write(anyhit,miss,closesthit,caller); +}; + +struct Attrs +{ + float2 barycentrics : BARYCENTRICS; +}; + +struct CustomAttrs +{ + float dist; +}; + +RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); + +RayDesc ComputeRay() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x*sceneConstants.U.xyz + d.y*sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +static const uint ProceduralHitKind = 11; + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + RayDesc ray = ComputeRay(); + + PerRayData payload; + payload.visited = 0; + + dx::HitObject hitObject; + + int cat = (launchIndex.x + launchIndex.y) % 4; + + // Use wave incoherence to decide how to create the HitObject + if (cat == 1) + { + // Turn this into an expected miss by moving eye behind triangles + ray.Origin.z -= 1000.0f; + hitObject = dx::HitObject::TraceRay(topObject, RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES, 0xFF, 0, 0, 0, ray, payload); + } + else if (cat == 2) + { + hitObject = dx::HitObject::TraceRay(topObject, RAY_FLAG_SKIP_PROCEDURAL_PRIMITIVES, 0xFF, 0, 0, 0, ray, payload); + } + else if (cat == 3) + { + hitObject = dx::HitObject::TraceRay(topObject, RAY_FLAG_SKIP_TRIANGLES, 0xFF, 0, 0, 0, ray, payload); + } + + dx::MaybeReorderThread(hitObject); + + if (hitObject.IsNop()) + payload.visited |= 1U; + if (hitObject.IsMiss()) + payload.visited |= 2U; + + if (hitObject.GetHitKind() == ProceduralHitKind) + payload.visited |= 8U; + else if (hitObject.IsHit()) + payload.visited |= 4U; + + dx::HitObject::Invoke(hitObject, payload); + + // Store the result in the buffer + int id = launchIndex.x + launchIndex.y * launchDim.x; + testBuffer[id] = payload.visited; +} + +[shader("miss")] +void miss(inout PerRayData payload) +{ + // UNUSED +} + +// Triangles +[shader("anyhit")] +void anyhit(inout PerRayData payload, in Attrs attrs) +{ + AcceptHitAndEndSearch(); +} + +[shader("closesthit")] +void closesthit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 16U; +} + +// Procedural +[shader("closesthit")] +void chAABB(inout PerRayData payload, in CustomAttrs attrs) +{ + payload.visited |= 32U; +} + +[shader("anyhit")] +void ahAABB(inout PerRayData payload, in CustomAttrs attrs) +{ + // UNUSED +} + +[shader("intersection")] +void intersection() +{ + // Intersection with circle on a plane (base, n, radius) + // hitPos is intersection point with plane (base, n) + float3 base = {0.0f,0.0f,0.5f}; + float3 n = normalize(float3(0.0f,0.5f,0.5f)); + float radius = 500.f; + // Plane hit + float t = dot(n, base - ObjectRayOrigin()) / dot(n, ObjectRayDirection()); + if (t > RayTCurrent() || t < RayTMin()) { + return; + } + float3 hitPos = ObjectRayOrigin() + t * ObjectRayDirection(); + float3 relHitPos = hitPos - base; + // Circle hit + float hitDist = length(relHitPos); + if (hitDist > radius) + return; + + CustomAttrs attrs; + attrs.dist = hitDist; + ReportHit(t, ProceduralHitKind, attrs); +} + +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, false)) + return; + + // Initialize test data. + const int WindowSize = 64; + std::vector TestData(WindowSize * WindowSize, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd"}; + + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*mesh*/, + true /*procedural geometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + std::map Histo; + for (int Val : TestData) + ++Histo[Val]; + + VERIFY_ARE_EQUAL(Histo.size(), 4); + VERIFY_ARE_EQUAL(Histo[1], 1024); // nop + VERIFY_ARE_EQUAL(Histo[2], 2243); // miss + VERIFY_ARE_EQUAL(Histo[20], 16); // triangle hit + VERIFY_ARE_EQUAL(Histo[40], 813); // procedural hit +} + +TEST_F(ExecutionTest, SERReorderCoherentTest) { + // SER: Test reordercoherent + static const char *ShaderSrc = R"( +struct SceneConstants +{ + float4 eye; + float4 U; + float4 V; + float4 W; + float sceneScale; + uint2 windowSize; + int rayFlags; +}; + +struct[raypayload] PerRayData +{ + uint visited : read(anyhit,closesthit,miss,caller) : write(anyhit,miss,closesthit,caller); +}; + +struct Attrs +{ + float2 barycentrics : BARYCENTRICS; +}; + +reordercoherent RWStructuredBuffer testBuffer : register(u0); +RaytracingAccelerationStructure topObject : register(t0); +ConstantBuffer sceneConstants : register(b0); + +RayDesc ComputeRay() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + + float2 d = float2(DispatchRaysIndex().xy) / float2(DispatchRaysDimensions().xy) * 2.0f - 1.0f; + RayDesc ray; + ray.Origin = sceneConstants.eye.xyz; + ray.Direction = normalize(d.x*sceneConstants.U.xyz + d.y*sceneConstants.V.xyz + sceneConstants.W.xyz); + ray.TMin = 0; + ray.TMax = 1e18; + + return ray; +} + +[shader("raygeneration")] +void raygen() +{ + uint2 launchIndex = DispatchRaysIndex().xy; + uint2 launchDim = DispatchRaysDimensions().xy; + uint threadId = launchIndex.x + launchIndex.y * launchDim.x; + + RayDesc ray = ComputeRay(); + + PerRayData payload; + payload.visited = 0; + + // Initial test value. + testBuffer[threadId] = threadId; + + dx::HitObject hitObject = dx::HitObject::TraceRay(topObject, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, payload); + + // Conditionally update the test value. + if (hitObject.IsHit()) + { + testBuffer[threadId] += 10; // Add 10 to hits + } + else + { + testBuffer[threadId] += 20; // Add 20 to misses + } + + Barrier(UAV_MEMORY, REORDER_SCOPE); + dx::MaybeReorderThread(hitObject); + + // Conditionally update the test value. + if (threadId % 2 == 0) + { + testBuffer[threadId] += 1000; // Add 1000 to even threads + } + else + { + testBuffer[threadId] += 2000; // Add 2000 to odd threads + } + + // Verify test value. + uint expectedValue = (hitObject.IsHit() ? threadId + 10 : threadId + 20); + expectedValue += (threadId % 2 == 0 ? 1000 : 2000); + if (testBuffer[threadId] != expectedValue) + { + // Mark failure in the buffer if the result does not match + testBuffer[threadId] = 0; + } + else + { + testBuffer[threadId] = 1; + } +} + +[shader("miss")] +void miss(inout PerRayData payload) +{ + payload.visited |= 2U; +} + +[shader("anyhit")] +void anyhit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 1U; +} + +[shader("closesthit")] +void closesthit(inout PerRayData payload, in Attrs attrs) +{ + payload.visited |= 4U; +} + +)"; + + CComPtr Device; + if (!CreateDXRDevice(&Device, D3D_SHADER_MODEL_6_9, false)) + return; + + // Initialize test data. + const int WindowSize = 64; + std::vector TestData(WindowSize * WindowSize, 0); + LPCWSTR Args[] = {L"-HV 2021", L"-Vd"}; + + RunDXRTest(Device, ShaderSrc, L"lib_6_9", Args, _countof(Args), TestData, + WindowSize, WindowSize, true /*useMesh*/, + false /*useProceduralGeometry*/, 1 /*payloadCount*/, + 2 /*attributeCount*/); + std::map Histo; + for (int Val : TestData) + ++Histo[Val]; + VERIFY_ARE_EQUAL(Histo.size(), 1); + VERIFY_ARE_EQUAL(Histo[1], 4096); +} \ No newline at end of file