1 // Copyright 2020 The Dawn Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #include "dawn_native/d3d12/ShaderVisibleDescriptorAllocatorD3D12.h" 16 #include "dawn_native/d3d12/D3D12Error.h" 17 #include "dawn_native/d3d12/DeviceD3D12.h" 18 #include "dawn_native/d3d12/GPUDescriptorHeapAllocationD3D12.h" 19 #include "dawn_native/d3d12/ResidencyManagerD3D12.h" 20 21 namespace dawn_native { namespace d3d12 { 22 23 // Limits the min/max heap size to always be some known value for testing. 24 // Thresholds should be adjusted (lower == faster) to avoid tests taking too long to complete. 25 // We change the value from {1024, 512} to {32, 16} because we use blending 26 // for D3D12DescriptorHeapTests.EncodeManyUBO and R16Float has limited range 27 // and low precision at big integer. 28 static constexpr const uint32_t kShaderVisibleSmallHeapSizes[] = {32, 16}; 29 GetD3D12ShaderVisibleHeapMinSize(D3D12_DESCRIPTOR_HEAP_TYPE heapType,bool useSmallSize)30 uint32_t GetD3D12ShaderVisibleHeapMinSize(D3D12_DESCRIPTOR_HEAP_TYPE heapType, 31 bool useSmallSize) { 32 if (useSmallSize) { 33 return kShaderVisibleSmallHeapSizes[heapType]; 34 } 35 36 // Minimum heap size must be large enough to satisfy the largest descriptor allocation 37 // request and to amortize the cost of sub-allocation. But small enough to avoid wasting 38 // memory should only a tiny fraction ever be used. 39 // TODO(dawn:155): Figure out these values. 40 switch (heapType) { 41 case D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV: 42 return 4096; 43 case D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER: 44 return 256; 45 default: 46 UNREACHABLE(); 47 } 48 } 49 GetD3D12ShaderVisibleHeapMaxSize(D3D12_DESCRIPTOR_HEAP_TYPE heapType,bool useSmallSize)50 uint32_t GetD3D12ShaderVisibleHeapMaxSize(D3D12_DESCRIPTOR_HEAP_TYPE heapType, 51 bool useSmallSize) { 52 if (useSmallSize) { 53 return kShaderVisibleSmallHeapSizes[heapType]; 54 } 55 56 switch (heapType) { 57 case D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV: 58 return D3D12_MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_1; 59 case D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER: 60 return D3D12_MAX_SHADER_VISIBLE_SAMPLER_HEAP_SIZE; 61 default: 62 UNREACHABLE(); 63 } 64 } 65 GetD3D12HeapFlags(D3D12_DESCRIPTOR_HEAP_TYPE heapType)66 D3D12_DESCRIPTOR_HEAP_FLAGS GetD3D12HeapFlags(D3D12_DESCRIPTOR_HEAP_TYPE heapType) { 67 switch (heapType) { 68 case D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV: 69 case D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER: 70 return D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; 71 default: 72 UNREACHABLE(); 73 } 74 } 75 76 // static 77 ResultOrError<std::unique_ptr<ShaderVisibleDescriptorAllocator>> Create(Device * device,D3D12_DESCRIPTOR_HEAP_TYPE heapType)78 ShaderVisibleDescriptorAllocator::Create(Device* device, D3D12_DESCRIPTOR_HEAP_TYPE heapType) { 79 std::unique_ptr<ShaderVisibleDescriptorAllocator> allocator = 80 std::make_unique<ShaderVisibleDescriptorAllocator>(device, heapType); 81 DAWN_TRY(allocator->AllocateAndSwitchShaderVisibleHeap()); 82 return std::move(allocator); 83 } 84 ShaderVisibleDescriptorAllocator(Device * device,D3D12_DESCRIPTOR_HEAP_TYPE heapType)85 ShaderVisibleDescriptorAllocator::ShaderVisibleDescriptorAllocator( 86 Device* device, 87 D3D12_DESCRIPTOR_HEAP_TYPE heapType) 88 : mHeapType(heapType), 89 mDevice(device), 90 mSizeIncrement(device->GetD3D12Device()->GetDescriptorHandleIncrementSize(heapType)), 91 mDescriptorCount(GetD3D12ShaderVisibleHeapMinSize( 92 heapType, 93 mDevice->IsToggleEnabled(Toggle::UseD3D12SmallShaderVisibleHeapForTesting))) { 94 ASSERT(heapType == D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV || 95 heapType == D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER); 96 } 97 AllocateGPUDescriptors(uint32_t descriptorCount,ExecutionSerial pendingSerial,D3D12_CPU_DESCRIPTOR_HANDLE * baseCPUDescriptor,GPUDescriptorHeapAllocation * allocation)98 bool ShaderVisibleDescriptorAllocator::AllocateGPUDescriptors( 99 uint32_t descriptorCount, 100 ExecutionSerial pendingSerial, 101 D3D12_CPU_DESCRIPTOR_HANDLE* baseCPUDescriptor, 102 GPUDescriptorHeapAllocation* allocation) { 103 ASSERT(mHeap != nullptr); 104 const uint64_t startOffset = mAllocator.Allocate(descriptorCount, pendingSerial); 105 if (startOffset == RingBufferAllocator::kInvalidOffset) { 106 return false; 107 } 108 109 ID3D12DescriptorHeap* descriptorHeap = mHeap->GetD3D12DescriptorHeap(); 110 111 const uint64_t heapOffset = mSizeIncrement * startOffset; 112 113 // Check for 32-bit overflow since CPU heap start handle uses size_t. 114 const size_t cpuHeapStartPtr = descriptorHeap->GetCPUDescriptorHandleForHeapStart().ptr; 115 116 ASSERT(heapOffset <= std::numeric_limits<size_t>::max() - cpuHeapStartPtr); 117 118 *baseCPUDescriptor = {cpuHeapStartPtr + static_cast<size_t>(heapOffset)}; 119 120 const D3D12_GPU_DESCRIPTOR_HANDLE baseGPUDescriptor = { 121 descriptorHeap->GetGPUDescriptorHandleForHeapStart().ptr + heapOffset}; 122 123 // Record both the device and heap serials to determine later if the allocations are 124 // still valid. 125 *allocation = GPUDescriptorHeapAllocation{baseGPUDescriptor, pendingSerial, mHeapSerial}; 126 127 return true; 128 } 129 GetShaderVisibleHeap() const130 ID3D12DescriptorHeap* ShaderVisibleDescriptorAllocator::GetShaderVisibleHeap() const { 131 return mHeap->GetD3D12DescriptorHeap(); 132 } 133 Tick(ExecutionSerial completedSerial)134 void ShaderVisibleDescriptorAllocator::Tick(ExecutionSerial completedSerial) { 135 mAllocator.Deallocate(completedSerial); 136 } 137 138 ResultOrError<std::unique_ptr<ShaderVisibleDescriptorHeap>> AllocateHeap(uint32_t descriptorCount) const139 ShaderVisibleDescriptorAllocator::AllocateHeap(uint32_t descriptorCount) const { 140 // The size in bytes of a descriptor heap is best calculated by the increment size 141 // multiplied by the number of descriptors. In practice, this is only an estimate and 142 // the actual size may vary depending on the driver. 143 const uint64_t kSize = mSizeIncrement * descriptorCount; 144 145 DAWN_TRY(mDevice->GetResidencyManager()->EnsureCanAllocate(kSize, MemorySegment::Local)); 146 147 ComPtr<ID3D12DescriptorHeap> d3d12DescriptorHeap; 148 D3D12_DESCRIPTOR_HEAP_DESC heapDescriptor; 149 heapDescriptor.Type = mHeapType; 150 heapDescriptor.NumDescriptors = descriptorCount; 151 heapDescriptor.Flags = GetD3D12HeapFlags(mHeapType); 152 heapDescriptor.NodeMask = 0; 153 DAWN_TRY(CheckOutOfMemoryHRESULT(mDevice->GetD3D12Device()->CreateDescriptorHeap( 154 &heapDescriptor, IID_PPV_ARGS(&d3d12DescriptorHeap)), 155 "ID3D12Device::CreateDescriptorHeap")); 156 157 std::unique_ptr<ShaderVisibleDescriptorHeap> descriptorHeap = 158 std::make_unique<ShaderVisibleDescriptorHeap>(std::move(d3d12DescriptorHeap), kSize); 159 160 // We must track the allocation in the LRU when it is created, otherwise the residency 161 // manager will see the allocation as non-resident in the later call to LockAllocation. 162 mDevice->GetResidencyManager()->TrackResidentAllocation(descriptorHeap.get()); 163 164 return std::move(descriptorHeap); 165 } 166 167 // Creates a GPU descriptor heap that manages descriptors in a FIFO queue. AllocateAndSwitchShaderVisibleHeap()168 MaybeError ShaderVisibleDescriptorAllocator::AllocateAndSwitchShaderVisibleHeap() { 169 std::unique_ptr<ShaderVisibleDescriptorHeap> descriptorHeap; 170 // Dynamically allocate using a two-phase allocation strategy. 171 // The first phase increasingly grows a small heap in binary sizes for light users while the 172 // second phase pool-allocates largest sized heaps for heavy users. 173 if (mHeap != nullptr) { 174 mDevice->GetResidencyManager()->UnlockAllocation(mHeap.get()); 175 176 const uint32_t maxDescriptorCount = GetD3D12ShaderVisibleHeapMaxSize( 177 mHeapType, 178 mDevice->IsToggleEnabled(Toggle::UseD3D12SmallShaderVisibleHeapForTesting)); 179 if (mDescriptorCount < maxDescriptorCount) { 180 // Phase #1. Grow the heaps in powers-of-two. 181 mDevice->ReferenceUntilUnused(mHeap->GetD3D12DescriptorHeap()); 182 mDescriptorCount = std::min(mDescriptorCount * 2, maxDescriptorCount); 183 } else { 184 // Phase #2. Pool-allocate heaps. 185 // Return the switched out heap to the pool and retrieve the oldest heap that is no 186 // longer used by GPU. This maintains a heap buffer to avoid frequently re-creating 187 // heaps for heavy users. 188 // TODO(dawn:256): Consider periodically triming to avoid OOM. 189 mPool.push_back({mDevice->GetPendingCommandSerial(), std::move(mHeap)}); 190 if (mPool.front().heapSerial <= mDevice->GetCompletedCommandSerial()) { 191 descriptorHeap = std::move(mPool.front().heap); 192 mPool.pop_front(); 193 } 194 } 195 } 196 197 if (descriptorHeap == nullptr) { 198 DAWN_TRY_ASSIGN(descriptorHeap, AllocateHeap(mDescriptorCount)); 199 } 200 201 DAWN_TRY(mDevice->GetResidencyManager()->LockAllocation(descriptorHeap.get())); 202 203 // Create a FIFO buffer from the recently created heap. 204 mHeap = std::move(descriptorHeap); 205 mAllocator = RingBufferAllocator(mDescriptorCount); 206 207 // Invalidate all bindgroup allocations on previously bound heaps by incrementing the heap 208 // serial. When a bindgroup attempts to re-populate, it will compare with its recorded 209 // heap serial. 210 mHeapSerial++; 211 212 return {}; 213 } 214 GetShaderVisibleHeapSerialForTesting() const215 HeapVersionID ShaderVisibleDescriptorAllocator::GetShaderVisibleHeapSerialForTesting() const { 216 return mHeapSerial; 217 } 218 GetShaderVisibleHeapSizeForTesting() const219 uint64_t ShaderVisibleDescriptorAllocator::GetShaderVisibleHeapSizeForTesting() const { 220 return mAllocator.GetSize(); 221 } 222 GetShaderVisiblePoolSizeForTesting() const223 uint64_t ShaderVisibleDescriptorAllocator::GetShaderVisiblePoolSizeForTesting() const { 224 return mPool.size(); 225 } 226 IsShaderVisibleHeapLockedResidentForTesting() const227 bool ShaderVisibleDescriptorAllocator::IsShaderVisibleHeapLockedResidentForTesting() const { 228 return mHeap->IsResidencyLocked(); 229 } 230 IsLastShaderVisibleHeapInLRUForTesting() const231 bool ShaderVisibleDescriptorAllocator::IsLastShaderVisibleHeapInLRUForTesting() const { 232 ASSERT(!mPool.empty()); 233 return mPool.back().heap->IsInResidencyLRUCache(); 234 } 235 IsAllocationStillValid(const GPUDescriptorHeapAllocation & allocation) const236 bool ShaderVisibleDescriptorAllocator::IsAllocationStillValid( 237 const GPUDescriptorHeapAllocation& allocation) const { 238 // Consider valid if allocated for the pending submit and the shader visible heaps 239 // have not switched over. 240 return (allocation.GetLastUsageSerial() > mDevice->GetCompletedCommandSerial() && 241 allocation.GetHeapSerial() == mHeapSerial); 242 } 243 ShaderVisibleDescriptorHeap(ComPtr<ID3D12DescriptorHeap> d3d12DescriptorHeap,uint64_t size)244 ShaderVisibleDescriptorHeap::ShaderVisibleDescriptorHeap( 245 ComPtr<ID3D12DescriptorHeap> d3d12DescriptorHeap, 246 uint64_t size) 247 : Pageable(d3d12DescriptorHeap, MemorySegment::Local, size), 248 mD3d12DescriptorHeap(std::move(d3d12DescriptorHeap)) { 249 } 250 GetD3D12DescriptorHeap() const251 ID3D12DescriptorHeap* ShaderVisibleDescriptorHeap::GetD3D12DescriptorHeap() const { 252 return mD3d12DescriptorHeap.Get(); 253 } 254 }} // namespace dawn_native::d3d12 255