• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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