blob: c7fc9417bc373f6586dda4ad70d8889922bb371c [file] [log] [blame]
// Copyright 2026 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "dawn/native/d3d12/ResourceTableD3D12.h"
#include <utility>
#include <vector>
#include "dawn/common/Enumerator.h"
#include "dawn/common/MatchVariant.h"
#include "dawn/common/Range.h"
#include "dawn/native/DynamicUploader.h"
#include "dawn/native/Queue.h"
#include "dawn/native/ResourceTableDefaultResources.h"
#include "dawn/native/d3d/D3DError.h"
#include "dawn/native/d3d12/DeviceD3D12.h"
#include "dawn/native/d3d12/PipelineLayoutD3D12.h"
#include "dawn/native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h"
#include "dawn/native/d3d12/StagingDescriptorAllocatorD3D12.h"
namespace dawn::native::d3d12 {
// static
std::vector<D3D12_DESCRIPTOR_RANGE1> ResourceTable::GetCbvUavSrvDescriptorRanges(
const PipelineLayout& layout) {
// For SM 6.5-, we need to create one descriptor table with multiple overlapping ranges,
// each in its own register space, per resource type. This is because HLSL does not allow
// overlapping register ranges, and we need separate unbounded array types for each resource
// type: HLSL example:
// Texture2D<float4> TextureTable1[] : register(t0, space1);
// Texture2D<uint4> TextureTable2[] : register(t0, space2);
// ...
// float4 color = TextureTable1[MyTextureIndex + 1].Load(int3(0,0,0));
// uint4 data = TextureTable2[MyTextureIndex + 2].Load(int3(0,0,0));
//
// For SM 6.6+, we don't need any descriptor tables, but only a single shader-visible
// descriptor heap to hold the resources, and shaders access the heap via built-in
// ResourceDescriptorHeap. HLSL example:
// Texture2D<float4> myTex = ResourceDescriptorHeap[MyTextureIndex + 1];
// Texture2D<uint4> myTex = ResourceDescriptorHeap[MyTextureIndex + 2];
//
// TODO(crbug.com/480110521): Add support for the SM >= 6.6 path
std::vector<D3D12_DESCRIPTOR_RANGE1> ranges;
const uint32_t baseRegisterSpace = layout.GetBaseResourceTableRegisterSpace();
const uint32_t defaultResourceCount =
static_cast<uint32_t>(ResourceTableDefaultResources::GetCount());
// The metadata storage buffer is bound to (kBaseResourceTableRegisterSpace, 0)
ranges.push_back(D3D12_DESCRIPTOR_RANGE1{
.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
.NumDescriptors = 1,
.BaseShaderRegister = 0,
.RegisterSpace = baseRegisterSpace,
.Flags = D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE,
.OffsetInDescriptorsFromTableStart = 0, // Only one in this space
});
// Create multiple overlapping ranges, one per resource type,
// bound to (1 + baseRegisterSpace + i, 0)
for (uint32_t i : Range(defaultResourceCount)) {
ranges.push_back(D3D12_DESCRIPTOR_RANGE1{
.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV,
.NumDescriptors = kMaxResourceTableSize + defaultResourceCount,
// HLSL doesn't allow overlapping register ranges, so each one is in its own space
// (group), and starts at register (binding) 0 with no other range bound after it.
.BaseShaderRegister = 0,
.RegisterSpace = 1 + baseRegisterSpace + i,
// Volatile required for bindless
.Flags = D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE,
// Force the same offset in the descriptor table to overlaps these ranges
.OffsetInDescriptorsFromTableStart = 1,
});
}
return ranges;
}
// static
ResultOrError<Ref<ResourceTable>> ResourceTable::Create(Device* device,
const ResourceTableDescriptor* descriptor) {
Ref<ResourceTable> table = AcquireRef(new ResourceTable(device, descriptor));
DAWN_TRY(table->Initialize());
return table;
}
ResourceTable::~ResourceTable() = default;
MaybeError ResourceTable::Initialize() {
DAWN_TRY(ResourceTableBase::InitializeBase());
Device* device = ToBackend(GetDevice());
ID3D12Device* d3d12Device = device->GetD3D12Device();
// Allocate the CPU descriptor heap
const uint32_t descriptorCount = 1u + static_cast<uint32_t>(GetSizeWithDefaultResources());
DAWN_TRY(AllocateCPUHeap(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV, descriptorCount));
// Only write the metadata buffer to the heap initially, all the other bindings will be written
// as needed when they are inserted in the ResourceTable.
Buffer* metadataBuffer = ToBackend(GetMetadataBuffer());
ID3D12Resource* resource = metadataBuffer->GetD3D12Resource();
DAWN_ASSERT(resource != nullptr);
// Like StorageBuffer, Tint outputs HLSL shaders for readonly storage buffer with
// ByteAddressBuffer. So we must use D3D12_BUFFER_SRV_FLAG_RAW when making the SRV
// descriptor. And it has similar requirement for format, element size, etc.
D3D12_SHADER_RESOURCE_VIEW_DESC desc;
desc.Format = DXGI_FORMAT_R32_TYPELESS;
desc.ViewDimension = D3D12_SRV_DIMENSION_BUFFER;
desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
desc.Buffer.FirstElement = 0;
desc.Buffer.NumElements = metadataBuffer->GetSize() / 4;
desc.Buffer.StructureByteStride = 0;
desc.Buffer.Flags = D3D12_BUFFER_SRV_FLAG_RAW;
uint32_t offsetInDescriptorCount = 0; // Metadata buffer is the first element in the table
d3d12Device->CreateShaderResourceView(
resource, &desc,
mCPUViewAllocation.OffsetFrom(mViewSizeIncrement, offsetInDescriptorCount));
return {};
}
MaybeError ResourceTable::AllocateCPUHeap(D3D12_DESCRIPTOR_HEAP_TYPE heapType,
uint32_t descriptorCount) {
DAWN_ASSERT(!mCPUHeap);
Device* device = ToBackend(GetDevice());
ID3D12Device* d3d12Device = device->GetD3D12Device();
D3D12_DESCRIPTOR_HEAP_DESC heapDescriptor;
heapDescriptor.Type = heapType;
heapDescriptor.NumDescriptors = descriptorCount;
heapDescriptor.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
heapDescriptor.NodeMask = 0;
ComPtr<ID3D12DescriptorHeap> heap;
DAWN_TRY(CheckHRESULT(d3d12Device->CreateDescriptorHeap(&heapDescriptor, IID_PPV_ARGS(&heap)),
"ID3D12Device::CreateDescriptorHeap"));
const D3D12_CPU_DESCRIPTOR_HANDLE baseCPUDescriptor = {
heap->GetCPUDescriptorHandleForHeapStart().ptr};
mCPUHeap = std::move(heap);
mCPUViewAllocation = CPUDescriptorHeapAllocation{baseCPUDescriptor, 0};
mViewSizeIncrement = d3d12Device->GetDescriptorHandleIncrementSize(heapType);
return {};
}
void ResourceTable::FreeCPUHeap() {
mCPUHeap.Reset();
mCPUViewAllocation.Invalidate();
mViewSizeIncrement = 0;
}
// Apply updates to resources or to the metadata buffers that are pending.
MaybeError ResourceTable::ApplyPendingUpdates(CommandRecordingContext* recordingContext) {
Updates updates = AcquireDirtySlotUpdates();
if (!updates.metadataUpdates.empty()) {
DAWN_TRY(UpdateMetadataBuffer(recordingContext, updates.metadataUpdates));
}
if (!updates.resourceUpdates.empty()) {
DAWN_TRY(UpdateResourceBindings(updates.resourceUpdates));
}
return {};
}
MaybeError ResourceTable::UpdateMetadataBuffer(CommandRecordingContext* recordingContext,
const std::vector<MetadataUpdate>& updates) {
Device* device = ToBackend(GetDevice());
// Allocate enough space for all the data to modify and schedule the copies.
return device->GetDynamicUploader()->WithUploadReservation(
sizeof(uint32_t) * updates.size(), kCopyBufferToBufferOffsetAlignment,
[&](UploadReservation reservation) -> MaybeError {
uint32_t* stagedData = static_cast<uint32_t*>(reservation.mappedPointer);
// The metadata buffer will be copied to.
Buffer* metadataBuffer = ToBackend(GetMetadataBuffer());
DAWN_ASSERT(metadataBuffer->IsInitialized());
auto scopedUseMetadataBuffer = metadataBuffer->UseInternal();
metadataBuffer->TrackUsageAndTransitionNow(recordingContext,
wgpu::BufferUsage::CopyDst);
// Record a CopyBufferRegion for each update
// TODO(crbug.com/473354062): reduce number of calls by copying contiguous regions
for (auto [i, update] : Enumerate(updates)) {
stagedData[i] = update.data; // Copy to staged
// Copy staged to metadata buffer
recordingContext->GetCommandList1()->CopyBufferRegion(
metadataBuffer->GetD3D12Resource(), update.offset,
ToBackend(reservation.buffer)->GetD3D12Resource(),
reservation.offsetInBuffer + i * sizeof(uint32_t), sizeof(uint32_t));
}
// Transition the buffer back to be used as storage as that's how it will be used for
// shader-side validation.
metadataBuffer->TrackUsageAndTransitionNow(recordingContext, kReadOnlyStorageBuffer);
return {};
});
}
MaybeError ResourceTable::UpdateResourceBindings(const std::vector<ResourceUpdate>& updates) {
Device* device = ToBackend(GetDevice());
ID3D12Device* d3d12Device = device->GetD3D12Device();
for (const ResourceUpdate& update : updates) {
// TODO(https://issues.chromium.org/473444515): Support buffer, texel buffers and storage
// textures.
MatchVariant(
update.resource,
[&](TextureViewBase* textureView) {
auto* view = ToBackend(textureView);
ID3D12Resource* resource = ToBackend(view->GetTexture())->GetD3D12Resource();
if (resource == nullptr) {
// Skip resource if it was destroyed
return;
}
// Add 1 to skip metadata descriptor
uint32_t offsetInDescriptorCount = 1 + static_cast<uint32_t>(update.slot);
d3d12Device->CreateShaderResourceView(
resource, &view->GetSRVDescriptor(),
mCPUViewAllocation.OffsetFrom(mViewSizeIncrement, offsetInDescriptorCount));
},
[&](SamplerBase* sampler) {
// TODO(https://issues.chromium.org/473354063): Support samplers updates.
// Skip for now to allow most e2e tests to pass when attempting to add default
// samplers.
});
}
return {};
}
bool ResourceTable::PopulateViews(ShaderVisibleDescriptorAllocator* viewAllocator) {
if (viewAllocator->IsAllocationStillValid(mGPUViewAllocation)) {
return true;
}
// Attempt to allocate descriptors for the currently bound shader-visible heaps.
// Return false if allocation fails to indicate that AllocateAndSwitchShaderVisibleHeap should
// be called.
Device* device = ToBackend(GetDevice());
// Copy metadata + resource descriptors
const uint32_t descriptorCount = 1u + static_cast<uint32_t>(GetSizeWithDefaultResources());
D3D12_CPU_DESCRIPTOR_HANDLE baseCPUDescriptor;
if (!viewAllocator->AllocateGPUDescriptors(descriptorCount,
device->GetQueue()->GetPendingCommandSerial(),
&baseCPUDescriptor, &mGPUViewAllocation)) {
return false;
}
// CPU bindgroups are sparsely allocated across CPU heaps. Instead of doing
// simple copies per bindgroup, a single non-simple copy could be issued.
// TODO(dawn:155): Consider doing this optimization.
device->GetD3D12Device()->CopyDescriptorsSimple(descriptorCount, baseCPUDescriptor,
mCPUViewAllocation.GetBaseDescriptor(),
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
return true;
}
D3D12_GPU_DESCRIPTOR_HANDLE ResourceTable::GetBaseViewDescriptor() const {
return mGPUViewAllocation.GetBaseDescriptor();
}
void ResourceTable::DestroyImpl(DestroyReason reason) {
ResourceTableBase::DestroyImpl(reason);
FreeCPUHeap();
}
void ResourceTable::SetLabelImpl() {
// TODO(crbug.com/473354062): SetDebugName
}
} // namespace dawn::native::d3d12