1 /*
2 * Copyright © Microsoft Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24 #include "d3d12_batch.h"
25 #include "d3d12_bufmgr.h"
26 #include "d3d12_residency.h"
27 #include "d3d12_resource.h"
28 #include "d3d12_screen.h"
29
30 #include "util/os_time.h"
31
32 #include <dxguids/dxguids.h>
33
34 static constexpr unsigned residency_batch_size = 128;
35
36 static void
evict_aged_allocations(struct d3d12_screen * screen,uint64_t completed_fence,int64_t time,int64_t grace_period)37 evict_aged_allocations(struct d3d12_screen *screen, uint64_t completed_fence, int64_t time, int64_t grace_period)
38 {
39 ID3D12Pageable *to_evict[residency_batch_size];
40 unsigned num_pending_evictions = 0;
41
42 list_for_each_entry_safe(struct d3d12_bo, bo, &screen->residency_list, residency_list_entry) {
43 /* This residency list should all be base bos, not suballocated ones */
44 assert(bo->res);
45
46 if (bo->last_used_fence > completed_fence ||
47 time - bo->last_used_timestamp <= grace_period) {
48 /* List is LRU-sorted, this bo is still in use, so we're done */
49 break;
50 }
51
52 assert(bo->residency_status == d3d12_resident);
53
54 to_evict[num_pending_evictions++] = bo->res;
55 bo->residency_status = d3d12_evicted;
56 list_del(&bo->residency_list_entry);
57
58 if (num_pending_evictions == residency_batch_size) {
59 screen->dev->Evict(num_pending_evictions, to_evict);
60 num_pending_evictions = 0;
61 }
62 }
63
64 if (num_pending_evictions)
65 screen->dev->Evict(num_pending_evictions, to_evict);
66 }
67
68 static void
evict_to_fence_or_budget(struct d3d12_screen * screen,uint64_t target_fence,uint64_t current_usage,uint64_t target_budget)69 evict_to_fence_or_budget(struct d3d12_screen *screen, uint64_t target_fence, uint64_t current_usage, uint64_t target_budget)
70 {
71 screen->fence->SetEventOnCompletion(target_fence, nullptr);
72
73 ID3D12Pageable *to_evict[residency_batch_size];
74 unsigned num_pending_evictions = 0;
75
76 list_for_each_entry_safe(struct d3d12_bo, bo, &screen->residency_list, residency_list_entry) {
77 /* This residency list should all be base bos, not suballocated ones */
78 assert(bo->res);
79
80 if (bo->last_used_fence > target_fence || current_usage < target_budget) {
81 break;
82 }
83
84 assert(bo->residency_status == d3d12_resident);
85
86 to_evict[num_pending_evictions++] = bo->res;
87 bo->residency_status = d3d12_evicted;
88 list_del(&bo->residency_list_entry);
89
90 current_usage -= bo->estimated_size;
91
92 if (num_pending_evictions == residency_batch_size) {
93 screen->dev->Evict(num_pending_evictions, to_evict);
94 num_pending_evictions = 0;
95 }
96 }
97
98 if (num_pending_evictions)
99 screen->dev->Evict(num_pending_evictions, to_evict);
100 }
101
102 static constexpr int64_t eviction_grace_period_seconds_min = 1;
103 static constexpr int64_t eviction_grace_period_seconds_max = 60;
104 static constexpr int64_t microseconds_per_second = 1000000;
105 static constexpr int64_t eviction_grace_period_microseconds_min =
106 eviction_grace_period_seconds_min * microseconds_per_second;
107 static constexpr int64_t eviction_grace_period_microseconds_max =
108 eviction_grace_period_seconds_max * microseconds_per_second;
109 static constexpr double trim_percentage_usage_threshold = 0.7;
110
111 static int64_t
get_eviction_grace_period(struct d3d12_memory_info * mem_info)112 get_eviction_grace_period(struct d3d12_memory_info *mem_info)
113 {
114 double pressure = double(mem_info->usage) / double(mem_info->budget);
115 pressure = MIN2(pressure, 1.0);
116
117 if (pressure > trim_percentage_usage_threshold) {
118 /* Normalize pressure for the range [0, threshold] */
119 pressure = (pressure - trim_percentage_usage_threshold) / (1.0 - trim_percentage_usage_threshold);
120 /* Linearly interpolate between min and max period based on pressure */
121 return (int64_t)((eviction_grace_period_microseconds_max - eviction_grace_period_microseconds_min) *
122 (1.0 - pressure)) + eviction_grace_period_microseconds_min;
123 }
124
125 /* Unlimited grace period, essentially don't trim at all */
126 return INT64_MAX;
127 }
128
129 static void
gather_base_bos(struct d3d12_screen * screen,set * base_bo_set,struct d3d12_bo * bo,uint64_t & size_to_make_resident,uint64_t pending_fence_value,int64_t current_time)130 gather_base_bos(struct d3d12_screen *screen, set *base_bo_set, struct d3d12_bo *bo, uint64_t &size_to_make_resident, uint64_t pending_fence_value, int64_t current_time)
131 {
132 uint64_t offset;
133 struct d3d12_bo *base_bo = d3d12_bo_get_base(bo, &offset);
134
135 if (base_bo->residency_status == d3d12_evicted) {
136 bool added = false;
137 _mesa_set_search_or_add(base_bo_set, base_bo, &added);
138 assert(!added);
139
140 base_bo->residency_status = d3d12_resident;
141 size_to_make_resident += base_bo->estimated_size;
142 list_addtail(&base_bo->residency_list_entry, &screen->residency_list);
143 } else if (base_bo->last_used_fence != pending_fence_value &&
144 base_bo->residency_status == d3d12_resident) {
145 /* First time seeing this already-resident base bo in this batch */
146 list_del(&base_bo->residency_list_entry);
147 list_addtail(&base_bo->residency_list_entry, &screen->residency_list);
148 }
149
150 base_bo->last_used_fence = pending_fence_value;
151 base_bo->last_used_timestamp = current_time;
152 }
153
154 void
d3d12_process_batch_residency(struct d3d12_screen * screen,struct d3d12_batch * batch)155 d3d12_process_batch_residency(struct d3d12_screen *screen, struct d3d12_batch *batch)
156 {
157 d3d12_memory_info mem_info;
158 screen->get_memory_info(screen, &mem_info);
159
160 uint64_t completed_fence_value = screen->fence->GetCompletedValue();
161 uint64_t pending_fence_value = screen->fence_value + 1;
162 int64_t current_time = os_time_get();
163 int64_t grace_period = get_eviction_grace_period(&mem_info);
164
165 /* Gather base bos for the batch */
166 uint64_t size_to_make_resident = 0;
167 set *base_bo_set = _mesa_pointer_set_create(nullptr);
168
169 util_dynarray_foreach(&batch->local_bos, d3d12_bo*, bo)
170 gather_base_bos(screen, base_bo_set, *bo, size_to_make_resident, pending_fence_value, current_time);
171 hash_table_foreach(batch->bos, entry)
172 gather_base_bos(screen, base_bo_set, (struct d3d12_bo *)entry->key, size_to_make_resident, pending_fence_value, current_time);
173
174 /* Now that bos referenced by this batch are moved to the end of the LRU, trim it */
175 evict_aged_allocations(screen, completed_fence_value, current_time, grace_period);
176
177 /* If there's nothing needing to be made newly resident, we're done once we've trimmed */
178 if (base_bo_set->entries == 0) {
179 _mesa_set_destroy(base_bo_set, nullptr);
180 return;
181 }
182
183 uint64_t residency_fence_value_snapshot = screen->residency_fence_value;
184
185 struct set_entry *entry = _mesa_set_next_entry(base_bo_set, nullptr);
186 uint64_t batch_memory_size = 0;
187 unsigned batch_count = 0;
188 ID3D12Pageable *to_make_resident[residency_batch_size];
189 while (true) {
190 /* Refresh memory stats */
191 screen->get_memory_info(screen, &mem_info);
192
193 int64_t available_memory = (int64_t)mem_info.budget - (int64_t)mem_info.usage;
194
195 assert(!list_is_empty(&screen->residency_list));
196 struct d3d12_bo *oldest_resident_bo =
197 list_first_entry(&screen->residency_list, struct d3d12_bo, residency_list_entry);
198 bool anything_to_wait_for = oldest_resident_bo->last_used_fence < pending_fence_value;
199
200 /* We've got some room, or we can't free up any more room, make some resources resident */
201 HRESULT hr = S_OK;
202 if ((available_memory || !anything_to_wait_for) && batch_count < residency_batch_size) {
203 for (; entry; entry = _mesa_set_next_entry(base_bo_set, entry)) {
204 struct d3d12_bo *bo = (struct d3d12_bo *)entry->key;
205 if (anything_to_wait_for &&
206 (int64_t)(batch_memory_size + bo->estimated_size) > available_memory)
207 break;
208
209 batch_memory_size += bo->estimated_size;
210 to_make_resident[batch_count++] = bo->res;
211 if (batch_count == residency_batch_size)
212 break;
213 }
214
215 if (batch_count) {
216 hr = screen->dev->EnqueueMakeResident(D3D12_RESIDENCY_FLAG_NONE, batch_count, to_make_resident,
217 screen->residency_fence, screen->residency_fence_value + 1);
218 if (SUCCEEDED(hr))
219 ++screen->residency_fence_value;
220 }
221
222 if (SUCCEEDED(hr) && batch_count == residency_batch_size) {
223 batch_count = 0;
224 size_to_make_resident -= batch_memory_size;
225 continue;
226 }
227 }
228
229 /* We need to free up some space, either we broke early from the resource loop,
230 * or the MakeResident call itself failed.
231 */
232 if (FAILED(hr) || entry) {
233 if (!anything_to_wait_for) {
234 assert(false);
235 break;
236 }
237
238 evict_to_fence_or_budget(screen, oldest_resident_bo->last_used_fence, mem_info.usage + size_to_make_resident, mem_info.budget);
239 continue;
240 }
241
242 /* Made it to the end without explicitly needing to loop, so we're done */
243 break;
244 }
245 _mesa_set_destroy(base_bo_set, nullptr);
246
247 /* The GPU needs to wait for these resources to be made resident */
248 if (residency_fence_value_snapshot != screen->residency_fence_value)
249 screen->cmdqueue->Wait(screen->residency_fence, screen->residency_fence_value);
250 }
251
252 bool
d3d12_init_residency(struct d3d12_screen * screen)253 d3d12_init_residency(struct d3d12_screen *screen)
254 {
255 list_inithead(&screen->residency_list);
256 if (FAILED(screen->dev->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&screen->residency_fence))))
257 return false;
258
259 return true;
260 }
261
262 void
d3d12_deinit_residency(struct d3d12_screen * screen)263 d3d12_deinit_residency(struct d3d12_screen *screen)
264 {
265 if (screen->residency_fence) {
266 screen->residency_fence->Release();
267 screen->residency_fence = nullptr;
268 }
269 }
270
271 void
d3d12_promote_to_permanent_residency(struct d3d12_screen * screen,struct d3d12_resource * resource)272 d3d12_promote_to_permanent_residency(struct d3d12_screen *screen, struct d3d12_resource* resource)
273 {
274 mtx_lock(&screen->submit_mutex);
275 uint64_t offset;
276 struct d3d12_bo *base_bo = d3d12_bo_get_base(resource->bo, &offset);
277
278 /* Promote non-permanent resident resources to permanent residency*/
279 if(base_bo->residency_status != d3d12_permanently_resident) {
280
281 /* Mark as permanently resident*/
282 base_bo->residency_status = d3d12_permanently_resident;
283
284 /* If it wasn't made resident before, make it*/
285 bool was_made_resident = (base_bo->residency_status == d3d12_resident);
286 if(!was_made_resident) {
287 ID3D12Pageable *pageable = base_bo->res;
288 ASSERTED HRESULT hr = screen->dev->MakeResident(1, &pageable);
289 assert(SUCCEEDED(hr));
290 }
291 }
292 mtx_unlock(&screen->submit_mutex);
293 }
294