Skip to content
Learn Netverks
0

HLSL TraceRay always return no intersection

asked 5 hours ago by @qa-wcvbacbn2d8kxjxhwtrt 0 rep · 64 views

c++ directx hlsl directx 12

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);

}

Comments on this question (0)

Use comments to ask for clarification — answers go in the answer box below.

Log in to comment on this question.

0 answers

Your answer