HLSL TraceRay always return no intersection
asked 5 hours ago by @qa-wcvbacbn2d8kxjxhwtrt 0 rep · 64 views
I am implementing GPU path tracing using RTXGI and NVRHI.
The problem:
Intersection queries always fails when there are obvious occluders.
Here my shader code adapted from the RTXGI sample shader.
#pragma pack_matrix(column_major)
#include "TinyUniformSampleGenerator.hlsli"
cbuffer PerFrameCameraCB : register(b0, space0)
{
float4x4 ViewToWorld;
float3 CameraWs;
uint StartSampleIndex;
uint NbSamplePerPixel;
float AspectRatio;
float FovyScale;
float FocalDistance;
float LensRadius;
};
RaytracingAccelerationStructure SceneBVH : register(t0, space0);
RWTexture2D<float4> Output : register(u0, space0);
struct [raypayload] PrimaryRayPayload
{
float hitDistance : read(caller) : write(closesthit, miss);
};
struct [raypayload] ShadowRayPayload
{
bool hit : read(caller, anyhit) : write(caller, closesthit, anyhit);
};
struct Attributes
{
float2 uv;
};
[shader("closesthit")]
void ClosestHitPrimary(inout PrimaryRayPayload payload : SV_RayPayload, in Attributes attrib : SV_IntersectionAttributes)
{
uint packedDistance = asuint(RayTCurrent()) & (~0x1u);
packedDistance |= HitKind() == HIT_KIND_TRIANGLE_FRONT_FACE ? 0x1 : 0x0;
payload.hitDistance = asfloat(packedDistance);
}
[shader("anyhit")]
void AnyHitPrimary(inout PrimaryRayPayload payload : SV_RayPayload, in Attributes attrib : SV_IntersectionAttributes)
{
// AcceptHit but continue looking for the closest hit
}
[shader("miss")]
void MissPrimary(inout PrimaryRayPayload payload : SV_RayPayload)
{
payload.hitDistance = -1.0f;
}
[shader("closesthit")]
void ClosestHitShadow(inout ShadowRayPayload payload : SV_RayPayload, in Attributes attrib : SV_IntersectionAttributes)
{
payload.hit = true;
}
[shader("anyhit")]
void AnyHitShadow(inout ShadowRayPayload payload : SV_RayPayload, in Attributes attrib : SV_IntersectionAttributes)
{
payload.hit = true;
}
[shader("miss")]
void MissShadow(inout ShadowRayPayload payload : SV_RayPayload)
{
}
inline float2 uniformSampleUnitCircle(const float r)
{
float theta = 6.2831853f * r;
return float2(cos(theta), sin(theta));
}
RayDesc spawnRay(inout TinyUniformSampleGenerator sg, uint2 pixel, uint2 viewportRes)
{
RayDesc ray;
ray.Origin = CameraWs;
float2 randFloat2 = sampleNext2D(sg);
const float xReal = pixel.x + randFloat2.x;
const float yReal = pixel.y + randFloat2.y;
const float xNorm = xReal / (float) viewportRes.x;
const float yNorm = yReal / (float) viewportRes.y;
const float x = (2.0f * xNorm - 1.0f) * AspectRatio * FovyScale;
const float y = (1.0f - 2.0f * yNorm) * FovyScale;
const float4 targetPointVec4 = mul(float4(x * FocalDistance, y * FocalDistance, -FocalDistance, 1.0f), ViewToWorld);
const float3 targetPoint = targetPointVec4.xyz;
if (LensRadius > 1e-6f)
{
randFloat2 = sampleNext2D(sg);
const float2 lensSampleCameraSpace = uniformSampleUnitCircle(randFloat2.x)
* LensRadius * randFloat2.y;
const float4 lensSampleWorld = mul(ViewToWorld, float4(lensSampleCameraSpace.x, lensSampleCameraSpace.y, 0.0, 1.0f));
ray.Origin = lensSampleWorld.xyz;
}
ray.Direction = normalize(targetPoint - ray.Origin);
ray.TMin = 1e-8f;
ray.TMax = 1e8f;
return ray;
}
[shader("raygeneration")]
void RayGen()
{
const uint2 pixel = DispatchRaysIndex().xy;
const uint2 viewportRes = DispatchRaysDimensions().xy;
if (pixel.x >= viewportRes.x || pixel.y >= viewportRes.y)
return;
for (uint i = 0u; i < NbSamplePerPixel; ++i)
{
TinyUniformSampleGenerator usg;
usg.init(pixel, StartSampleIndex + i);
RayDesc ray = spawnRay(usg, pixel, viewportRes);
PrimaryRayPayload hitData;
TraceRay(SceneBVH, 0, 0xFF, 0, 0, 0, ray, hitData);
if (hitData.hitDistance > 0.f)
{
Output[pixel] = float4(1, 1, 1, 1);
}
else
{
Output[pixel] = float4(1, 0, 0, 1);
}
}
}
This shader will generate this image. I expect white when there is an intersection:
While the rasterization shaders will output this:
Sky in blue means there is no intersection there! And only there!
The spawnRay function comes from my CPU path tracer and it works well
Here my per frame constant buffer
__declspec(align(16)) struct PerFrameCameraCB
{
Math::Mat4 ViewToWorld;
Math::Vec3 CameraWs;
UINT StartSampleIndex;
UINT NbSamplePerPixel;
FLOAT AspectRatio;
FLOAT FovyScale;
FLOAT FocalDistance;
FLOAT LensRadius;
};
Checked in PIX and it looks fine i think:
CPP side it is updated like this:
void PathTracer::updateCameraConstantBuffer()
{
PerFrameCameraCB constants = {};
constants.ViewToWorld = m_renderStartCameraProperties.viewToWorld;
constants.CameraWs = m_renderStartCameraProperties.position;
constants.StartSampleIndex = m_currentSample;
constants.NbSamplePerPixel = s_nbSamplePerFrame;
constants.AspectRatio = m_settings.m_aspectRatio;
constants.FovyScale = m_renderStartCameraProperties.fovyScale;
constants.FocalDistance = m_renderStartCameraProperties.focalDistance;
constants.LensRadius = m_renderStartCameraProperties.lensRadius;
m_commandList->writeBuffer(m_cameraConstantuffer, &constants, sizeof(PerFrameCameraCB));
}
FovyScale is tan(fovy * 0.5f);
Here how the raytracing shader is created according to the RTXGI reference
void PathTracer::initShader()
{
BindingLayouts bindingLayouts = BuildBindingLayouts();
ID3DBlob* shaderBlob = Graphics::Renderer::Realtime::Dx12::Effect::readShader(_STRING("Pathtracing"));
_ASSERT(shaderBlob);
nvrhi::ShaderLibraryHandle shaderLibrary = m_device->createShaderLibrary(shaderBlob->GetBufferPointer(), shaderBlob->GetBufferSize());
_ASSERT(shaderLibrary);
m_permutation.shaderLibrary = shaderLibrary;
nvrhi::rt::PipelineDesc pipelineDesc;
for (int i = 0; i < DescriptorSetIDs::COUNT; ++i)
pipelineDesc.globalBindingLayouts.push_back(bindingLayouts.dummy[i]);
pipelineDesc.globalBindingLayouts[DescriptorSetIDs::Globals] = bindingLayouts.global;
//pipelineDesc.globalBindingLayouts[DescriptorSetIDs::Bindless] = layouts.bindless;
//pipelineDesc.globalBindingLayouts[DescriptorSetIDs::Denoiser] = layouts.denoiser;
pipelineDesc.shaders = {
{ "", shaderLibrary->getShader("RayGen", nvrhi::ShaderType::RayGeneration), nullptr },
{ "", shaderLibrary->getShader("MissPrimary", nvrhi::ShaderType::Miss), nullptr },
{ "", shaderLibrary->getShader("MissShadow", nvrhi::ShaderType::Miss), nullptr },
};
pipelineDesc.hitGroups = {
{
"HitGroup",
shaderLibrary->getShader("ClosestHitPrimary", nvrhi::ShaderType::ClosestHit),
shaderLibrary->getShader("AnyHitPrimary", nvrhi::ShaderType::AnyHit),
nullptr, // intersectionShader
nullptr, // bindingLayout
false // isProceduralPrimitive
},
{
"HitGroupShadow",
shaderLibrary->getShader("ClosestHitShadow", nvrhi::ShaderType::ClosestHit),
shaderLibrary->getShader("AnyHitShadow", nvrhi::ShaderType::AnyHit),
nullptr, // intersectionShader
nullptr, // bindingLayout
false // isProceduralPrimitive
},
};
pipelineDesc.maxPayloadSize = sizeof(float) * 6;
m_permutation.pipeline = m_device->createRayTracingPipeline(pipelineDesc);
m_permutation.shaderTable = m_permutation.pipeline->createShaderTable();
m_permutation.shaderTable->setRayGenerationShader("RayGen");
m_permutation.shaderTable->addHitGroup("HitGroup");
m_permutation.shaderTable->addHitGroup("HitGroupShadow");
m_permutation.shaderTable->addMissShader("MissPrimary");
m_permutation.shaderTable->addMissShader("MissShadow");
}
The ViewToWorld matrix has GLM layout so it is column major as expected by my path tracing shader!
Strange thing? PIX report that my acceleration structure are properly created. So the problem is not there:
https://youtu.be/JdSTel-CMug
Here how BLAS transforms are computed:
void ConvertMat4ToAffineTransform(const Math::Mat4& mat, nvrhi::rt::AffineTransform& dest)
{
// GLM to Affine transform
const Math::Vec3 translation = Math::Vec3(mat[3].x, mat[3].y, mat[3].z);
dest[0] = mat[0].x; dest[1] = mat[0].y; dest[2] = mat[0].z; dest[3] = translation.x;
dest[4] = mat[1].x; dest[5] = mat[1].y; dest[6] = mat[1].z; dest[7] = translation.y;
dest[8] = mat[2].x; dest[9] = mat[2].y; dest[10] = mat[2].z; dest[11] = translation.z;
}
Here how path tracing is launched:
void PathTracer::performPathTracing()
{
ID3D12GraphicsCommandList* d3dCmdList =
m_commandList->getNativeObject(nvrhi::ObjectTypes::D3D12_GraphicsCommandList);
AutoRenderEventTracker autoTracker(d3dCmdList, _STRING("PathTracer::performPathTracing"));
// Transition pathTracerOutput
m_commandList->setTextureState(m_renderingTexture.Get(), nvrhi::TextureSubresourceSet(0, 1, 0, 1), nvrhi::ResourceStates::UnorderedAccess);
m_commandList->commitBarriers();
nvrhi::rt::State state;
for (int i = 0; i < DescriptorSetIDs::COUNT; ++i)
state.bindings.push_back(m_dummyBindingSets[i]); // Unified Binding
state.bindings[DescriptorSetIDs::Globals] = m_globalBindingSet;
state.shaderTable = m_permutation.shaderTable;
m_commandList->setRayTracingState(state);
nvrhi::rt::DispatchRaysArguments args;
args.width = this->getFinalWidth();
args.height = this->getFinalHeight();
m_commandList->dispatchRays(args);
}
What Am I missing?
I tried to transpose ViewToWorld but it doesnt change anything
constants.ViewToWorld = glm::transpose(m_renderStartCameraProperties.viewToWorld);
HELP NEEDED!
Here the complete C++ implementation:
#define MAX_RENDER_PASS_CONSTANT_BUFFER_VERSIONS 16
using namespace Graphics::Renderer::Realtime::Dx12;
PathTracer::PathTracer()
{
nvrhi::d3d12::DeviceDesc deviceDesc;
deviceDesc.pDevice = m_dx12_Native_CommandContext.getContext().device;
deviceDesc.pGraphicsCommandQueue = m_dx12_Native_CommandContext.getContext().commandQueue;
m_device = nvrhi::d3d12::createDevice(deviceDesc);
m_commandList = m_device->createCommandList();
initBindingLayouts();
initShaders();
createConstantBuffer();
}
PathTracer::BindingLayouts PathTracer::BuildBindingLayouts()
{
BindingLayouts layouts;
layouts.global = m_globalBindingLayout;
for (int i = 0; i < DescriptorSetIDs::COUNT; ++i)
layouts.dummy[i] = m_dummyLayouts[i];
return layouts;
}
void PathTracer::initBindingLayouts()
{
nvrhi::BindingLayoutDesc bindingLayoutDesc;
bindingLayoutDesc.visibility = nvrhi::ShaderType::All;
bindingLayoutDesc.registerSpaceIsDescriptorSet = false;
for (int i = 0; i < DescriptorSetIDs::COUNT; ++i)
{
bindingLayoutDesc.registerSpace = i;
m_dummyLayouts[i] = m_device->createBindingLayout(bindingLayoutDesc);
nvrhi::BindingSetDesc dummyBindingDesc;
m_dummyBindingSets[i] = m_device->createBindingSet(dummyBindingDesc, m_dummyLayouts[i]);
}
bindingLayoutDesc.registerSpace = DescriptorSetIDs::Globals;
bindingLayoutDesc.bindings = {
nvrhi::BindingLayoutItem::VolatileConstantBuffer(0),
nvrhi::BindingLayoutItem::RayTracingAccelStruct(0),
nvrhi::BindingLayoutItem::Texture_UAV(0), path tracer output
};
m_globalBindingLayout = m_device->createBindingLayout(bindingLayoutDesc);
}
void PathTracer::initShaders()
{
BindingLayouts bindingLayouts = BuildBindingLayouts();
ID3DBlob* shaderBlob = Graphics::Renderer::Realtime::Dx12::Effect::readShader(_STRING("Pathtracing"));
_ASSERT(shaderBlob);
nvrhi::ShaderLibraryHandle shaderLibrary = m_device->createShaderLibrary(shaderBlob->GetBufferPointer(), shaderBlob->GetBufferSize());
_ASSERT(shaderLibrary);
m_permutation.shaderLibrary = shaderLibrary;
nvrhi::rt::PipelineDesc pipelineDesc;
for (int i = 0; i < DescriptorSetIDs::COUNT; ++i)
pipelineDesc.globalBindingLayouts.push_back(bindingLayouts.dummy[i]);
pipelineDesc.globalBindingLayouts[DescriptorSetIDs::Globals] = bindingLayouts.global;
pipelineDesc.shaders = {
{ "", shaderLibrary->getShader("RayGen", nvrhi::ShaderType::RayGeneration), nullptr },
{ "", shaderLibrary->getShader("MissPrimary", nvrhi::ShaderType::Miss), nullptr },
{ "", shaderLibrary->getShader("MissShadow", nvrhi::ShaderType::Miss), nullptr },
};
pipelineDesc.hitGroups = {
{
"HitGroup",
shaderLibrary->getShader("ClosestHitPrimary", nvrhi::ShaderType::ClosestHit),
shaderLibrary->getShader("AnyHitPrimary", nvrhi::ShaderType::AnyHit),
nullptr, // intersectionShader
nullptr, // bindingLayout
false // isProceduralPrimitive
},
{
"HitGroupShadow",
shaderLibrary->getShader("ClosestHitShadow", nvrhi::ShaderType::ClosestHit),
shaderLibrary->getShader("AnyHitShadow", nvrhi::ShaderType::AnyHit),
nullptr, // intersectionShader
nullptr, // bindingLayout
false // isProceduralPrimitive
},
};
pipelineDesc.maxPayloadSize = sizeof(float) * 6;
m_permutation.pipeline = m_device->createRayTracingPipeline(pipelineDesc);
m_permutation.shaderTable = m_permutation.pipeline->createShaderTable();
m_permutation.shaderTable->setRayGenerationShader("RayGen");
m_permutation.shaderTable->addHitGroup("HitGroup");
m_permutation.shaderTable->addHitGroup("HitGroupShadow");
m_permutation.shaderTable->addMissShader("MissPrimary");
m_permutation.shaderTable->addMissShader("MissShadow");
}
void PathTracer::createConstantBuffer()
{
m_cameraConstantuffer =
m_device->createBuffer(nvrhi::utils::CreateVolatileConstantBufferDesc(sizeof(PerFrameCameraCB), "PerFrameCameraCB", MAX_RENDER_PASS_CONSTANT_BUFFER_VERSIONS));
}
void PathTracer::freeConstantBuffer()
{
m_cameraConstantuffer.Reset();
}
void PathTracer::onStartRender(Math::Uvec2 realtimeViewSize, bool forceCreateIntersector)
{
PathTracingRenderer::onStartRender(realtimeViewSize, forceCreateIntersector);
onTryStopRender();
if (m_settings.m_finalRender)
{
const Math::Uvec2 renderResolution = m_settings.computeRenderResolution(false);
m_settings.m_aspectRatio = (float)renderResolution.x / (float)renderResolution.y;
}
else
{
m_settings.m_nbSamples = 20000;
m_settings.m_aspectRatio = (float)realtimeViewSize.x / (float)realtimeViewSize.y;
}
tryCreateOuputBuffers(realtimeViewSize);
m_stopRender = false;
m_renderStartCameraProperties = m_currentScene->getCamera()->buildCameraRayProps();
m_currentSample = 0u;
m_renderingThread.reset(new std::thread(&PathTracer::renderingThreadProcedure, this, forceCreateIntersector));
}
void PathTracer::updateCameraConstantBuffer()
{
PerFrameCameraCB constants = {};
constants.ViewToWorld = m_renderStartCameraProperties.viewToWorld;
constants.CameraWs = m_renderStartCameraProperties.position;
constants.StartSampleIndex = m_currentSample;
constants.NbSamplePerPixel = s_nbSamplePerFrame;
constants.AspectRatio = m_settings.m_aspectRatio;
constants.FovyScale = m_renderStartCameraProperties.fovyScale;
constants.FocalDistance = m_renderStartCameraProperties.focalDistance;
constants.LensRadius = m_renderStartCameraProperties.lensRadius;
m_commandList->writeBuffer(m_cameraConstantuffer, &constants, sizeof(PerFrameCameraCB));
}
void PathTracer::createBindingSets()
{
nvrhi::BindingSetDesc bindingSetDesc;
bindingSetDesc.bindings = {
nvrhi::BindingSetItem::ConstantBuffer(0, m_cameraConstantuffer),
nvrhi::BindingSetItem::RayTracingAccelStruct(0, m_tlas),
nvrhi::BindingSetItem::Texture_UAV(0, m_renderingTexture)
};
m_globalBindingSet = m_device->createBindingSet(bindingSetDesc, m_globalBindingLayout);
}
void PathTracer::renderingThreadProcedure(bool forceCreateIntersector)
{
if (!m_tlas || forceCreateIntersector)
{
freeAccelerationStructureMemory();
createAccelerationStructures();
}
createBindingSets();
bool isOver = false;
while (!isOver && !m_stopRender)
{
m_commandList->open();
updateCameraConstantBuffer();
performPathTracing();
updatePathTracingCPURenderBuffers_And_ExecuteCommandList();
m_currentSample += s_nbSamplePerFrame;
isOver = (m_currentSample + 1 >= m_settings.m_nbSamples);
}
m_device->waitForIdle();
stopWork();
}
void PathTracer::performPathTracing()
{
ID3D12GraphicsCommandList* d3dCmdList =
m_commandList->getNativeObject(nvrhi::ObjectTypes::D3D12_GraphicsCommandList);
AutoRenderEventTracker autoTracker(d3dCmdList, _STRING("PathTracer::performPathTracing"));
// Transition pathTracerOutput
m_commandList->setTextureState(m_renderingTexture.Get(), nvrhi::TextureSubresourceSet(0, 1, 0, 1), nvrhi::ResourceStates::UnorderedAccess);
m_commandList->commitBarriers();
nvrhi::rt::State state;
for (int i = 0; i < DescriptorSetIDs::COUNT; ++i)
state.bindings.push_back(m_dummyBindingSets[i]); // Unified Binding
state.bindings[DescriptorSetIDs::Globals] = m_globalBindingSet;
state.shaderTable = m_permutation.shaderTable;
m_commandList->setRayTracingState(state);
nvrhi::rt::DispatchRaysArguments args;
args.width = this->getFinalWidth();
args.height = this->getFinalHeight();
m_commandList->dispatchRays(args);
}
void PathTracer::updatePathTracingCPURenderBuffers_And_ExecuteCommandList()
{
nvrhi::TextureSlice slice;
slice.arraySlice = 0;
slice.depth = 1;
slice.height = this->getFinalHeight();
slice.mipLevel = 0;
slice.width = this->getFinalWidth();
m_commandList->copyTexture(m_readBackTexture.Get(), slice, m_renderingTexture, slice);
m_commandList->close();
m_device->executeCommandList(m_commandList);
// Map the staging texture and get the CPU-side memory pointer
size_t rowPitch = 0;
void* data = m_device->mapStagingTexture(m_readBackTexture.Get(), slice, nvrhi::CpuAccessMode::Read, &rowPitch);
_ASSERT(data);
uint8_t* mappedBytes = static_cast<uint8_t*>(data);
size_t destPixelSizeInBytes = 16; // float4 is 16 bytes
_ASSERT(m_transientImage->getFormat() == Graphics::Texture::ImageFormat::RGBA32F);
size_t destRowSizeInBytes = this->getFinalWidth() * destPixelSizeInBytes;
tbb::parallel_for(size_t(0), (size_t)this->getFinalHeight(), [&](size_t y) {
uint8_t* destRow = m_transientImage->getMutableRawData() + (y * destRowSizeInBytes);
uint8_t* srcRow = static_cast<uint8_t*>(mappedBytes) + (y * rowPitch);
std::memcpy(destRow, srcRow, destRowSizeInBytes);
});
m_device->unmapStagingTexture(m_readBackTexture.Get());
std::lock_guard<std::recursive_mutex> lock(m_renderBuffersMutex);
Graphics::Texture::Helper::RGBA32F_to_RGB32F(*m_transientImage, *m_outputImage);
}
void PathTracer::tryCreateOuputBuffers(const Math::Uvec2& realtimeViewSize)
{
const Math::Uvec2 renderResolution = m_settings.computeRenderResolution(false);
const uint32_t newWidth = m_settings.m_finalRender ? renderResolution.x : realtimeViewSize.x;
const uint32_t newHeight = m_settings.m_finalRender ? renderResolution.y : realtimeViewSize.y;
const bool newAllowEntityId = m_settings.m_finalRender && m_settings.m_aov;
const bool allocate = !m_outputImage ||
m_outputImage->getWidth() != newWidth ||
m_outputImage->getHeight() != newHeight ||
newAllowEntityId != (m_entityIdTexture != nullptr);
if (!allocate)
{
return;
}
m_outputImage = std::make_unique<Texture::RGB32FImage>(newWidth, newHeight);
m_transientImage = std::make_unique<Texture::RGBA32FImage>(newWidth, newHeight);
{
auto textureDesc = nvrhi::TextureDesc()
.setDimension(nvrhi::TextureDimension::Texture2D)
.setWidth(newWidth)
.setHeight(newHeight)
.setIsUAV(true)
.setFormat(nvrhi::Format::RGBA32_FLOAT)
.enableAutomaticStateTracking(nvrhi::ResourceStates::UnorderedAccess)
.setInitialState(nvrhi::ResourceStates::UnorderedAccess)
.setKeepInitialState(true)
.setDebugName("Chronos rendering texture");
m_renderingTexture = m_device->createTexture(textureDesc);
}
{
auto textureDesc = nvrhi::TextureDesc()
.setDimension(nvrhi::TextureDimension::Texture2D)
.setWidth(newWidth)
.setHeight(newHeight)
.setFormat(nvrhi::Format::RGBA32_FLOAT)
.enableAutomaticStateTracking(nvrhi::ResourceStates::ShaderResource)
.setDebugName("Chronos staging texture");
m_readBackTexture = m_device->createStagingTexture(textureDesc, nvrhi::CpuAccessMode::Read);
}
{
auto textureDesc = nvrhi::TextureDesc()
.setDimension(nvrhi::TextureDimension::Texture2D)
.setWidth(newWidth)
.setHeight(newHeight)
.setIsUAV(true)
.setFormat(nvrhi::Format::RGBA32_FLOAT)
.enableAutomaticStateTracking(nvrhi::ResourceStates::UnorderedAccess)
.setInitialState(nvrhi::ResourceStates::UnorderedAccess)
.setKeepInitialState(true)
.setDebugName("Chronos final Texture");
m_accumulationTexture = m_device->createTexture(textureDesc);
}
{
auto textureDesc = nvrhi::TextureDesc()
.setDimension(nvrhi::TextureDimension::Texture2D)
.setWidth(newWidth)
.setHeight(newHeight)
.setIsUAV(true)
.setFormat(nvrhi::Format::R32_UINT)
.enableAutomaticStateTracking(nvrhi::ResourceStates::UnorderedAccess)
.setDebugName("Chronos entity id Texture");
m_entityIdTexture = m_device->createTexture(textureDesc);
}
}
bool PathTracer::isWorking() const
{
return m_renderingThread.get() != nullptr;
}
void PathTracer::stopWork()
{
_ASSERT(std::this_thread::get_id() == m_renderingThread->get_id());
std::lock_guard<std::mutex> lock(m_stopMutex);
m_stopRender = true;
}
void PathTracer::onTryStopRender()
{
if (!isWorking())
return;
_ASSERT(m_renderingThread);
_ASSERT(std::this_thread::get_id() != m_renderingThread->get_id());
m_stopMutex.lock();
m_stopRender = true;
if (m_renderingThread->joinable())
{
m_stopMutex.unlock();
m_renderingThread->join();
}
else
{
_TRACE_STD("PathTracer::onTryStopRender - not joinable");
m_stopMutex.unlock();
}
m_renderingThread.reset();
}
uint32_t PathTracer::getRenderCurrentSample() const
{
return m_currentSample;
}
void PathTracer::freeTextureMemory()
{
m_renderingTexture.Reset();
m_accumulationTexture.Reset();
m_entityIdTexture.Reset();
m_readBackTexture.Reset();
m_outputImage.reset();
m_transientImage.reset();
}
void PathTracer::freeAccelerationStructureMemory()
{
m_tlas.Reset();
m_vertexBuffers.clear();
m_indexBuffers.clear();
m_vertexBufferCounts.clear();
m_indexBufferCounts.clear();
m_triangles.clear();
m_blases.clear();
m_blasDescs.clear();
m_instanceDescs.clear();
if (m_instanceTransforms)
delete[] m_instanceTransforms;
}
void ConvertMat4ToAffineTransform(const glm::mat4& mat, nvrhi::rt::AffineTransform& dest) {
const Math::Vec3 translation = Math::Vec3(mat[3].x, mat[3].y, mat[3].z);
dest[0] = mat[0].x; dest[1] = mat[0].y; dest[2] = mat[0].z; dest[3] = translation.x;
dest[4] = mat[1].x; dest[5] = mat[1].y; dest[6] = mat[1].z; dest[7] = translation.y;
dest[8] = mat[2].x; dest[9] = mat[2].y; dest[10] = mat[2].z; dest[11] = translation.z;
}
void PathTracer::createAccelerationStructures()
{
const DX12Model* dx12Model = static_cast<const DX12Model*>(m_currentScene->getModel().get());
// First find the number of meshes.
uint32_t meshCount = 0;
for (auto& meshHandleIt : dx12Model->getMeshHandlesByGroup())
{
meshCount += (uint32_t)meshHandleIt.second.size();
}
// Allocate memory
m_vertexBuffers.reserve(meshCount);
m_indexBuffers.reserve(meshCount);
m_vertexBufferCounts.reserve(meshCount);
m_indexBufferCounts.reserve(meshCount);
m_triangles.reserve(meshCount);
m_blasDescs.reserve(meshCount);
m_instanceDescs.reserve(meshCount);
m_instanceTransforms = new nvrhi::rt::AffineTransform[meshCount];
std::vector<Math::Mat4>transforms;
transforms.reserve(meshCount);
// Build geometries.
uint32_t i = 0;
for (auto& meshHandleIt : dx12Model->getMeshHandlesByGroup())
{
for (auto* meshHandle : meshHandleIt.second)
{
{
nvrhi::BufferDesc vertexBufferDesc;
vertexBufferDesc.setIsVertexBuffer(true);
vertexBufferDesc.setIsAccelStructBuildInput(true);
vertexBufferDesc.setInitialState(nvrhi::ResourceStates::VertexBuffer | nvrhi::ResourceStates::ConstantBuffer | nvrhi::ResourceStates::NonPixelShaderResource);
vertexBufferDesc.setKeepInitialState(true);
vertexBufferDesc.setByteSize(sizeof(FullVertex) * meshHandle->vertexBuffer.count);
vertexBufferDesc.setStructStride(sizeof(FullVertex));
m_vertexBuffers.push_back(
m_device->createHandleForNativeBuffer(
nvrhi::ObjectTypes::D3D12_Resource,
static_cast<nvrhi::Object>(meshHandle->vertexBuffer.buffer),
vertexBufferDesc)
);
m_vertexBufferCounts.push_back(meshHandle->vertexBuffer.count);
}
{
nvrhi::BufferDesc indexBufferDesc;
indexBufferDesc.setIsIndexBuffer(true);
indexBufferDesc.setIsAccelStructBuildInput(true);
indexBufferDesc.setInitialState(nvrhi::ResourceStates::VertexBuffer | nvrhi::ResourceStates::ConstantBuffer | nvrhi::ResourceStates::NonPixelShaderResource);
indexBufferDesc.setKeepInitialState(true);
indexBufferDesc.setByteSize(sizeof(uint32_t) * meshHandle->indexBuffer.count);
indexBufferDesc.setStructStride(sizeof(uint32_t));
m_indexBuffers.push_back(
m_device->createHandleForNativeBuffer(
nvrhi::ObjectTypes::D3D12_Resource,
static_cast<nvrhi::Object>(meshHandle->indexBuffer.buffer),
indexBufferDesc)
);
m_indexBufferCounts.push_back(meshHandle->indexBuffer.count);
}
Graphics::Model::DatabaseMeshGroupPtr groupPtr = Graphics::Model::getMeshGroupPtr_FromEntity(meshHandleIt.first);
const Math::Mat4& glmTransform = groupPtr->getTransform()->getMatrix();
ConvertMat4ToAffineTransform(glmTransform, m_instanceTransforms[i]);
++i;
}
}
// Build native triangles.
for (uint32_t i = 0; i < m_vertexBuffers.size(); ++i)
{
nvrhi::rt::GeometryTriangles triangles = nvrhi::rt::GeometryTriangles()
.setVertexBuffer(m_vertexBuffers[i])
.setVertexFormat(nvrhi::Format::RGB32_FLOAT)
.setVertexCount(m_vertexBufferCounts[i])
.setVertexStride(sizeof(Graphics::FullVertex))
.setIndexBuffer(m_indexBuffers[i])
.setIndexCount(m_indexBufferCounts[i])
.setIndexFormat(nvrhi::Format::R32_UINT);
m_triangles.push_back(triangles);
}
// Create blases.
for (uint32_t i = 0; i < m_vertexBuffers.size(); ++i)
{
auto blasDesc = nvrhi::rt::AccelStructDesc()
.setDebugName("BLAS " + std::to_string(i))
.setIsTopLevel(false)
.addBottomLevelGeometry(nvrhi::rt::GeometryDesc().setTriangles(m_triangles[i]));
m_blases.push_back(m_device->createAccelStruct(blasDesc));
m_blasDescs.push_back(blasDesc);
}
// Create the TLAS.
{
auto tlasDesc = nvrhi::rt::AccelStructDesc()
.setDebugName("TLAS")
.setIsTopLevel(true)
.setTopLevelMaxInstances(m_vertexBuffers.size());
m_tlas = m_device->createAccelStruct(tlasDesc);
}
// Build Acceleration structures.
m_commandList->open();
std::vector<nvrhi::rt::InstanceDesc> instanceDescs(m_vertexBuffers.size());
for (uint32_t i = 0u; i < instanceDescs.size(); ++i)
{
// Build the BLAS using the geometry array populated earlier.
// It's also possible to obtain the descriptor from the BLAS object using getDesc()
// and write the vertex and index buffer references into that descriptor again
// because NVRHI erases those when it creates the AS object.
m_commandList->buildBottomLevelAccelStruct(m_blases[i],
m_blasDescs[i].bottomLevelGeometries.data(), m_blasDescs[i].bottomLevelGeometries.size());
instanceDescs[i] = nvrhi::rt::InstanceDesc()
.setBLAS(m_blases[i])
.setFlags(nvrhi::rt::InstanceFlags::None)
.setTransform(m_instanceTransforms[i])
.setInstanceID(i);
}
m_commandList->buildTopLevelAccelStruct(m_tlas, instanceDescs.data(), instanceDescs.size());
m_commandList->close();
m_device->executeCommandList(m_commandList);
}