• 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/ResidencyManagerD3D12.h"
16 
17 #include "dawn_native/d3d12/AdapterD3D12.h"
18 #include "dawn_native/d3d12/D3D12Error.h"
19 #include "dawn_native/d3d12/DeviceD3D12.h"
20 #include "dawn_native/d3d12/Forward.h"
21 #include "dawn_native/d3d12/HeapD3D12.h"
22 
23 namespace dawn_native { namespace d3d12 {
24 
ResidencyManager(Device * device)25     ResidencyManager::ResidencyManager(Device* device)
26         : mDevice(device),
27           mResidencyManagementEnabled(
28               device->IsToggleEnabled(Toggle::UseD3D12ResidencyManagement)) {
29         UpdateVideoMemoryInfo();
30     }
31 
32     // Increments number of locks on a heap to ensure the heap remains resident.
LockAllocation(Pageable * pageable)33     MaybeError ResidencyManager::LockAllocation(Pageable* pageable) {
34         if (!mResidencyManagementEnabled) {
35             return {};
36         }
37 
38         // If the heap isn't already resident, make it resident.
39         if (!pageable->IsInResidencyLRUCache() && !pageable->IsResidencyLocked()) {
40             ID3D12Pageable* d3d12Pageable = pageable->GetD3D12Pageable();
41             uint64_t size = pageable->GetSize();
42 
43             DAWN_TRY(MakeAllocationsResident(GetMemorySegmentInfo(pageable->GetMemorySegment()),
44                                              size, 1, &d3d12Pageable));
45         }
46 
47         // Since we can't evict the heap, it's unnecessary to track the heap in the LRU Cache.
48         if (pageable->IsInResidencyLRUCache()) {
49             pageable->RemoveFromList();
50         }
51 
52         pageable->IncrementResidencyLock();
53 
54         return {};
55     }
56 
57     // Decrements number of locks on a heap. When the number of locks becomes zero, the heap is
58     // inserted into the LRU cache and becomes eligible for eviction.
UnlockAllocation(Pageable * pageable)59     void ResidencyManager::UnlockAllocation(Pageable* pageable) {
60         if (!mResidencyManagementEnabled) {
61             return;
62         }
63 
64         ASSERT(pageable->IsResidencyLocked());
65         ASSERT(!pageable->IsInResidencyLRUCache());
66         pageable->DecrementResidencyLock();
67 
68         // If another lock still exists on the heap, nothing further should be done.
69         if (pageable->IsResidencyLocked()) {
70             return;
71         }
72 
73         // When all locks have been removed, the resource remains resident and becomes tracked in
74         // the corresponding LRU.
75         TrackResidentAllocation(pageable);
76     }
77 
78     // Returns the appropriate MemorySegmentInfo for a given MemorySegment.
GetMemorySegmentInfo(MemorySegment memorySegment)79     ResidencyManager::MemorySegmentInfo* ResidencyManager::GetMemorySegmentInfo(
80         MemorySegment memorySegment) {
81         switch (memorySegment) {
82             case MemorySegment::Local:
83                 return &mVideoMemoryInfo.local;
84             case MemorySegment::NonLocal:
85                 ASSERT(!mDevice->GetDeviceInfo().isUMA);
86                 return &mVideoMemoryInfo.nonLocal;
87             default:
88                 UNREACHABLE();
89         }
90     }
91 
92     // Allows an application component external to Dawn to cap Dawn's residency budgets to prevent
93     // competition for device memory. Returns the amount of memory reserved, which may be less
94     // that the requested reservation when under pressure.
SetExternalMemoryReservation(MemorySegment segment,uint64_t requestedReservationSize)95     uint64_t ResidencyManager::SetExternalMemoryReservation(MemorySegment segment,
96                                                             uint64_t requestedReservationSize) {
97         MemorySegmentInfo* segmentInfo = GetMemorySegmentInfo(segment);
98 
99         segmentInfo->externalRequest = requestedReservationSize;
100 
101         UpdateMemorySegmentInfo(segmentInfo);
102 
103         return segmentInfo->externalReservation;
104     }
105 
UpdateVideoMemoryInfo()106     void ResidencyManager::UpdateVideoMemoryInfo() {
107         UpdateMemorySegmentInfo(&mVideoMemoryInfo.local);
108         if (!mDevice->GetDeviceInfo().isUMA) {
109             UpdateMemorySegmentInfo(&mVideoMemoryInfo.nonLocal);
110         }
111     }
112 
UpdateMemorySegmentInfo(MemorySegmentInfo * segmentInfo)113     void ResidencyManager::UpdateMemorySegmentInfo(MemorySegmentInfo* segmentInfo) {
114         DXGI_QUERY_VIDEO_MEMORY_INFO queryVideoMemoryInfo;
115 
116         ToBackend(mDevice->GetAdapter())
117             ->GetHardwareAdapter()
118             ->QueryVideoMemoryInfo(0, segmentInfo->dxgiSegment, &queryVideoMemoryInfo);
119 
120         // The video memory budget provided by QueryVideoMemoryInfo is defined by the operating
121         // system, and may be lower than expected in certain scenarios. Under memory pressure, we
122         // cap the external reservation to half the available budget, which prevents the external
123         // component from consuming a disproportionate share of memory and ensures that Dawn can
124         // continue to make forward progress. Note the choice to halve memory is arbitrarily chosen
125         // and subject to future experimentation.
126         segmentInfo->externalReservation =
127             std::min(queryVideoMemoryInfo.Budget / 2, segmentInfo->externalRequest);
128 
129         segmentInfo->usage = queryVideoMemoryInfo.CurrentUsage - segmentInfo->externalReservation;
130 
131         // If we're restricting the budget for testing, leave the budget as is.
132         if (mRestrictBudgetForTesting) {
133             return;
134         }
135 
136         // We cap Dawn's budget to 95% of the provided budget. Leaving some budget unused
137         // decreases fluctuations in the operating-system-defined budget, which improves stability
138         // for both Dawn and other applications on the system. Note the value of 95% is arbitrarily
139         // chosen and subject to future experimentation.
140         static constexpr float kBudgetCap = 0.95;
141         segmentInfo->budget =
142             (queryVideoMemoryInfo.Budget - segmentInfo->externalReservation) * kBudgetCap;
143     }
144 
145     // Removes a heap from the LRU and returns the least recently used heap when possible. Returns
146     // nullptr when nothing further can be evicted.
RemoveSingleEntryFromLRU(MemorySegmentInfo * memorySegment)147     ResultOrError<Pageable*> ResidencyManager::RemoveSingleEntryFromLRU(
148         MemorySegmentInfo* memorySegment) {
149         // If the LRU is empty, return nullptr to allow execution to continue. Note that fully
150         // emptying the LRU is undesirable, because it can mean either 1) the LRU is not accurately
151         // accounting for Dawn's GPU allocations, or 2) a component external to Dawn is using all of
152         // the process budget and starving Dawn, which will cause thrash.
153         if (memorySegment->lruCache.empty()) {
154             return nullptr;
155         }
156 
157         Pageable* pageable = memorySegment->lruCache.head()->value();
158 
159         ExecutionSerial lastSubmissionSerial = pageable->GetLastSubmission();
160 
161         // If the next candidate for eviction was inserted into the LRU during the current serial,
162         // it is because more memory is being used in a single command list than is available.
163         // In this scenario, we cannot make any more resources resident and thrashing must occur.
164         if (lastSubmissionSerial == mDevice->GetPendingCommandSerial()) {
165             return nullptr;
166         }
167 
168         // We must ensure that any previous use of a resource has completed before the resource can
169         // be evicted.
170         if (lastSubmissionSerial > mDevice->GetCompletedCommandSerial()) {
171             DAWN_TRY(mDevice->WaitForSerial(lastSubmissionSerial));
172         }
173 
174         pageable->RemoveFromList();
175         return pageable;
176     }
177 
EnsureCanAllocate(uint64_t allocationSize,MemorySegment memorySegment)178     MaybeError ResidencyManager::EnsureCanAllocate(uint64_t allocationSize,
179                                                    MemorySegment memorySegment) {
180         if (!mResidencyManagementEnabled) {
181             return {};
182         }
183 
184         uint64_t bytesEvicted;
185         DAWN_TRY_ASSIGN(bytesEvicted,
186                         EnsureCanMakeResident(allocationSize, GetMemorySegmentInfo(memorySegment)));
187         DAWN_UNUSED(bytesEvicted);
188 
189         return {};
190     }
191 
192     // Any time we need to make something resident, we must check that we have enough free memory to
193     // make the new object resident while also staying within budget. If there isn't enough
194     // memory, we should evict until there is. Returns the number of bytes evicted.
EnsureCanMakeResident(uint64_t sizeToMakeResident,MemorySegmentInfo * memorySegment)195     ResultOrError<uint64_t> ResidencyManager::EnsureCanMakeResident(
196         uint64_t sizeToMakeResident,
197         MemorySegmentInfo* memorySegment) {
198         ASSERT(mResidencyManagementEnabled);
199 
200         UpdateMemorySegmentInfo(memorySegment);
201 
202         uint64_t memoryUsageAfterMakeResident = sizeToMakeResident + memorySegment->usage;
203 
204         // Return when we can call MakeResident and remain under budget.
205         if (memoryUsageAfterMakeResident < memorySegment->budget) {
206             return 0;
207         }
208 
209         std::vector<ID3D12Pageable*> resourcesToEvict;
210         uint64_t sizeNeededToBeUnderBudget = memoryUsageAfterMakeResident - memorySegment->budget;
211         uint64_t sizeEvicted = 0;
212         while (sizeEvicted < sizeNeededToBeUnderBudget) {
213             Pageable* pageable;
214             DAWN_TRY_ASSIGN(pageable, RemoveSingleEntryFromLRU(memorySegment));
215 
216             // If no heap was returned, then nothing more can be evicted.
217             if (pageable == nullptr) {
218                 break;
219             }
220 
221             sizeEvicted += pageable->GetSize();
222             resourcesToEvict.push_back(pageable->GetD3D12Pageable());
223         }
224 
225         if (resourcesToEvict.size() > 0) {
226             DAWN_TRY(CheckHRESULT(
227                 mDevice->GetD3D12Device()->Evict(resourcesToEvict.size(), resourcesToEvict.data()),
228                 "Evicting resident heaps to free memory"));
229         }
230 
231         return sizeEvicted;
232     }
233 
234     // Given a list of heaps that are pending usage, this function will estimate memory needed,
235     // evict resources until enough space is available, then make resident any heaps scheduled for
236     // usage.
EnsureHeapsAreResident(Heap ** heaps,size_t heapCount)237     MaybeError ResidencyManager::EnsureHeapsAreResident(Heap** heaps, size_t heapCount) {
238         if (!mResidencyManagementEnabled) {
239             return {};
240         }
241 
242         std::vector<ID3D12Pageable*> localHeapsToMakeResident;
243         std::vector<ID3D12Pageable*> nonLocalHeapsToMakeResident;
244         uint64_t localSizeToMakeResident = 0;
245         uint64_t nonLocalSizeToMakeResident = 0;
246 
247         ExecutionSerial pendingCommandSerial = mDevice->GetPendingCommandSerial();
248         for (size_t i = 0; i < heapCount; i++) {
249             Heap* heap = heaps[i];
250 
251             // Heaps that are locked resident are not tracked in the LRU cache.
252             if (heap->IsResidencyLocked()) {
253                 continue;
254             }
255 
256             if (heap->IsInResidencyLRUCache()) {
257                 // If the heap is already in the LRU, we must remove it and append again below to
258                 // update its position in the LRU.
259                 heap->RemoveFromList();
260             } else {
261                 if (heap->GetMemorySegment() == MemorySegment::Local) {
262                     localSizeToMakeResident += heap->GetSize();
263                     localHeapsToMakeResident.push_back(heap->GetD3D12Pageable());
264                 } else {
265                     nonLocalSizeToMakeResident += heap->GetSize();
266                     nonLocalHeapsToMakeResident.push_back(heap->GetD3D12Pageable());
267                 }
268             }
269 
270             // If we submit a command list to the GPU, we must ensure that heaps referenced by that
271             // command list stay resident at least until that command list has finished execution.
272             // Setting this serial unnecessarily can leave the LRU in a state where nothing is
273             // eligible for eviction, even though some evictions may be possible.
274             heap->SetLastSubmission(pendingCommandSerial);
275 
276             // Insert the heap into the appropriate LRU.
277             TrackResidentAllocation(heap);
278         }
279 
280         if (localSizeToMakeResident > 0) {
281             return MakeAllocationsResident(&mVideoMemoryInfo.local, localSizeToMakeResident,
282                                            localHeapsToMakeResident.size(),
283                                            localHeapsToMakeResident.data());
284         }
285 
286         if (nonLocalSizeToMakeResident > 0) {
287             ASSERT(!mDevice->GetDeviceInfo().isUMA);
288             return MakeAllocationsResident(&mVideoMemoryInfo.nonLocal, nonLocalSizeToMakeResident,
289                                            nonLocalHeapsToMakeResident.size(),
290                                            nonLocalHeapsToMakeResident.data());
291         }
292 
293         return {};
294     }
295 
MakeAllocationsResident(MemorySegmentInfo * segment,uint64_t sizeToMakeResident,uint64_t numberOfObjectsToMakeResident,ID3D12Pageable ** allocations)296     MaybeError ResidencyManager::MakeAllocationsResident(MemorySegmentInfo* segment,
297                                                          uint64_t sizeToMakeResident,
298                                                          uint64_t numberOfObjectsToMakeResident,
299                                                          ID3D12Pageable** allocations) {
300         uint64_t bytesEvicted;
301         DAWN_TRY_ASSIGN(bytesEvicted, EnsureCanMakeResident(sizeToMakeResident, segment));
302         DAWN_UNUSED(bytesEvicted);
303 
304         // Note that MakeResident is a synchronous function and can add a significant
305         // overhead to command recording. In the future, it may be possible to decrease this
306         // overhead by using MakeResident on a secondary thread, or by instead making use of
307         // the EnqueueMakeResident function (which is not available on all Windows 10
308         // platforms).
309         HRESULT hr =
310             mDevice->GetD3D12Device()->MakeResident(numberOfObjectsToMakeResident, allocations);
311 
312         // A MakeResident call can fail if there's not enough available memory. This
313         // could occur when there's significant fragmentation or if the allocation size
314         // estimates are incorrect. We may be able to continue execution by evicting some
315         // more memory and calling MakeResident again.
316         while (FAILED(hr)) {
317             constexpr uint32_t kAdditonalSizeToEvict = 50000000;  // 50MB
318 
319             uint64_t sizeEvicted = 0;
320 
321             DAWN_TRY_ASSIGN(sizeEvicted, EnsureCanMakeResident(kAdditonalSizeToEvict, segment));
322 
323             // If nothing can be evicted after MakeResident has failed, we cannot continue
324             // execution and must throw a fatal error.
325             if (sizeEvicted == 0) {
326                 return DAWN_OUT_OF_MEMORY_ERROR(
327                     "MakeResident has failed due to excessive video memory usage.");
328             }
329 
330             hr =
331                 mDevice->GetD3D12Device()->MakeResident(numberOfObjectsToMakeResident, allocations);
332         }
333 
334         return {};
335     }
336 
337     // Inserts a heap at the bottom of the LRU. The passed heap must be resident or scheduled to
338     // become resident within the current serial. Failing to call this function when an allocation
339     // is implicitly made resident will cause the residency manager to view the allocation as
340     // non-resident and call MakeResident - which will make D3D12's internal residency refcount on
341     // the allocation out of sync with Dawn.
TrackResidentAllocation(Pageable * pageable)342     void ResidencyManager::TrackResidentAllocation(Pageable* pageable) {
343         if (!mResidencyManagementEnabled) {
344             return;
345         }
346 
347         ASSERT(pageable->IsInList() == false);
348         GetMemorySegmentInfo(pageable->GetMemorySegment())->lruCache.Append(pageable);
349     }
350 
351     // Places an artifical cap on Dawn's budget so we can test in a predictable manner. If used,
352     // this function must be called before any resources have been created.
RestrictBudgetForTesting(uint64_t artificialBudgetCap)353     void ResidencyManager::RestrictBudgetForTesting(uint64_t artificialBudgetCap) {
354         ASSERT(mVideoMemoryInfo.nonLocal.lruCache.empty());
355         ASSERT(!mRestrictBudgetForTesting);
356 
357         mRestrictBudgetForTesting = true;
358         UpdateVideoMemoryInfo();
359 
360         // Dawn has a non-zero memory usage even before any resources have been created, and this
361         // value can vary depending on the environment Dawn is running in. By adding this in
362         // addition to the artificial budget cap, we can create a predictable and reproducible
363         // budget for testing.
364         mVideoMemoryInfo.local.budget = mVideoMemoryInfo.local.usage + artificialBudgetCap;
365         if (!mDevice->GetDeviceInfo().isUMA) {
366             mVideoMemoryInfo.nonLocal.budget =
367                 mVideoMemoryInfo.nonLocal.usage + artificialBudgetCap;
368         }
369     }
370 
371 }}  // namespace dawn_native::d3d12
372