Loading a cubemap with DirectX12

138 views Asked by At

been working on writing a small raytracing app using DirectX12's DXR features. I'm trying to implement IBL, however I'm running into a lot of deadends trying to figure out how to properly load a DDS Cubemap in DX12.

The issue I'm running into is that, while the resource is being properly allocated on the GPU, it's accessible, and I can even sample it! But the entire resource is black, and if I inspect the memory in NSight Graphics, it's clear that the proper data from the texture upload isn't making it there. I've even stepped through the CreateDDSTextureFromFile12 call you'll see below, and verified that at the time of upload, the CPU side data loaded from the texture is correct, and non-zero. I can't quite crack why that data isn't making it to the GPU. I'm not receiving any errors from DirectX12, and I've exhausted the avenues I typically use to debug this sort of problem. Though also worth pointing out that the resource that is on the GPU, validated via NSight Graphics, does have the correct dimensions, array size, format, and mip levels. So SOME of the data is properly making it to the GPU, just not the pixel data.

Here's the steps I'm taking currently:

  1. First, I'm initializing my raytracing pipeline, loading my shader libraries, creating the root signatures for them, and creating the rt state object. (I'll focus mainly on the Miss shader the rest of this post, that is where I'd like to use the cubemap)
  2. During this process, I create the miss shader signature like so:
ComPtr<ID3D12RootSignature> D3D12HelloTriangle::CreateMissSignature() {
  nv_helpers_dx12::RootSignatureGenerator rsc;
  rsc.AddHeapRangesParameter({ {0, 1, 0, D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 3} });
  auto s = GetStaticSamplers();
  return rsc.Generate(m_device.Get(), true, static_cast<UINT>(s.size()), s.data());
}

You'll notice my code is using the sample code provided by NVIDIA for setting up a raytracing project. Throughout this project I've relied pretty heavily on common place sample code and external libraries, so I'm hoping it seems pretty familiar to the community.

  1. Next up, I create all my buffers for the various resources I'm using, one of which is my cubemap:
void D3D12HelloTriangle::CreateCubemapBuffer()
{
  auto tex = std::make_unique<Texture>();
  tex->name = "cubemap";
  tex->filename = L"assets/earth-cubemap.dds"; // This sample is provided by DirectXTex
  ThrowIfFailed(CreateDDSTextureFromFile12(
    m_device.Get(),
    m_commandList.Get(),
    tex->filename.c_str(),
    tex->resource,
    tex->uploadHeap
  ));
  m_cubemap = std::move(tex);
}

I wanted to use DirectXTex here, which provides DDS Texture Loaders as standalone header files. And this implementation does seem heavily inspired, however I got this code from a open-source project online. The reason I didn't go with DirectXTex directly is because for their DirectX12 version of the DDS Loaders, they got rid of the Create* calls from the library and just left the Load* calls, which I couldn't find a ton of information about how to wrap up, and none of the resources I've been using to study DX12 provides any examples on how to do that. The Texture class there is just a small struct that encapsulates a name, filepath, and the ID3D12Resource points for the heap and texture.

  1. Then I create my shader resource heap, with the cubemap in the 4th slot:
void D3D12HelloTriangle::CreateShaderResourceHeap() {
  m_srvUavHeap = nv_helpers_dx12::CreateDescriptorHeap(m_device.Get(), 4, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, true);

  D3D12_CPU_DESCRIPTOR_HANDLE srvHandle = m_srvUavHeap->GetCPUDescriptorHandleForHeapStart();

  D3D12_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
  uavDesc.ViewDimension = D3D12_UAV_DIMENSION_TEXTURE2D;
  m_device->CreateUnorderedAccessView(m_outputResource.Get(), nullptr, &uavDesc, srvHandle);

  srvHandle.ptr += m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

  D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc;
  srvDesc.Format = DXGI_FORMAT_UNKNOWN;
  srvDesc.ViewDimension = D3D12_SRV_DIMENSION_RAYTRACING_ACCELERATION_STRUCTURE;
  srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
  srvDesc.RaytracingAccelerationStructure.Location = m_topLevelASBuffers.pResult->GetGPUVirtualAddress();
  m_device->CreateShaderResourceView(nullptr, &srvDesc, srvHandle);

  srvHandle.ptr += m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

  D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
  cbvDesc.BufferLocation = m_cameraBuffer->GetGPUVirtualAddress();
  cbvDesc.SizeInBytes = m_cameraBufferSize;
  m_device->CreateConstantBufferView(&cbvDesc, srvHandle);

  srvHandle.ptr += m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

  D3D12_SHADER_RESOURCE_VIEW_DESC srvDescCubemap = {};
  srvDescCubemap.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
  srvDescCubemap.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
  srvDescCubemap.TextureCube.MostDetailedMip = 0;
  srvDescCubemap.TextureCube.MipLevels = m_cubemap->resource->GetDesc().MipLevels;
  srvDescCubemap.TextureCube.ResourceMinLODClamp = 0.0f;
  srvDescCubemap.Format = m_cubemap->resource->GetDesc().Format;
  m_device->CreateShaderResourceView(m_cubemap->resource.Get(), &srvDescCubemap, srvHandle);
}
  1. And then the SBT for the App, where I pass the heap pointer to the Miss program creation:
void D3D12HelloTriangle::CreateShaderBindingTable() {
  m_sbtHelper.Reset();

  D3D12_GPU_DESCRIPTOR_HANDLE srvUavHeapHandle = m_srvUavHeap->GetGPUDescriptorHandleForHeapStart();

  auto heapPointer = reinterpret_cast<UINT64*>(srvUavHeapHandle.ptr);

  m_sbtHelper.AddRayGenerationProgram(L"RayGen", { heapPointer });

  m_sbtHelper.AddMissProgram(L"Miss", { heapPointer });

  m_sbtHelper.AddHitGroup(L"HitGroup", { (void*)(m_vertexBuffer->GetGPUVirtualAddress()), heapPointer });

  uint32_t sbtSize = m_sbtHelper.ComputeSBTSize();

  m_sbtStorage = nv_helpers_dx12::CreateBuffer(
    m_device.Get(),
    sbtSize,
    D3D12_RESOURCE_FLAG_NONE,
    D3D12_RESOURCE_STATE_GENERIC_READ,
    nv_helpers_dx12::kUploadHeapProps
  );

  if (!m_sbtStorage) {
    throw std::logic_error("Could not allocate the shader binding table");
  }
  m_sbtHelper.Generate(m_sbtStorage.Get(), m_rtStateObjectProps.Get());
}

And for clarity, here is my Miss.hlsl file:

#include "Common.hlsl"

TextureCube gCubemap : register(t0);
SamplerState gsamBilinearWrap : register(s0);

[shader("miss")]
void Miss(inout HitInfo payload : SV_RayPayload)
{
  uint2 launchIndex = DispatchRaysIndex().xy;
  float2 dims = float2(DispatchRaysDimensions().xy);
   
  float3 unitDir = normalize(WorldRayDirection());
  //float ramp = 0.5f * (unitDir.y + 1.0f);
  //float3 color = lerp(float3(.91f, .94f, .99f), float3(.24f, .49f, .79f), ramp); // blue sky-like fade
  
  float4 color = gCubemap.SampleLevel(gsamBilinearWrap, WorldRayDirection(), 0);
  
  payload.colorAndDistance = float4(color.rgb,1e7);
}

Happy to provide more code from any of these helpers.

0

There are 0 answers