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 34static constexpr unsigned residency_batch_size = 128; 35 36static void 37evict_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 if (bo->residency_status == d3d12_permanently_resident) 53 continue; 54 55 to_evict[num_pending_evictions++] = bo->res; 56 bo->residency_status = d3d12_evicted; 57 list_del(&bo->residency_list_entry); 58 59 if (num_pending_evictions == residency_batch_size) { 60 screen->dev->Evict(num_pending_evictions, to_evict); 61 num_pending_evictions = 0; 62 } 63 } 64 65 if (num_pending_evictions) 66 screen->dev->Evict(num_pending_evictions, to_evict); 67} 68 69static void 70evict_to_fence_or_budget(struct d3d12_screen *screen, uint64_t target_fence, uint64_t current_usage, uint64_t target_budget) 71{ 72 screen->fence->SetEventOnCompletion(target_fence, nullptr); 73 74 ID3D12Pageable *to_evict[residency_batch_size]; 75 unsigned num_pending_evictions = 0; 76 77 list_for_each_entry_safe(struct d3d12_bo, bo, &screen->residency_list, residency_list_entry) { 78 /* This residency list should all be base bos, not suballocated ones */ 79 assert(bo->res); 80 81 if (bo->last_used_fence > target_fence || current_usage < target_budget) { 82 break; 83 } 84 85 if (bo->residency_status == d3d12_permanently_resident) 86 continue; 87 88 to_evict[num_pending_evictions++] = bo->res; 89 bo->residency_status = d3d12_evicted; 90 list_del(&bo->residency_list_entry); 91 92 current_usage -= bo->estimated_size; 93 94 if (num_pending_evictions == residency_batch_size) { 95 screen->dev->Evict(num_pending_evictions, to_evict); 96 num_pending_evictions = 0; 97 } 98 } 99 100 if (num_pending_evictions) 101 screen->dev->Evict(num_pending_evictions, to_evict); 102} 103 104static constexpr int64_t eviction_grace_period_seconds_min = 1; 105static constexpr int64_t eviction_grace_period_seconds_max = 60; 106static constexpr int64_t microseconds_per_second = 1000000; 107static constexpr int64_t eviction_grace_period_microseconds_min = 108 eviction_grace_period_seconds_min * microseconds_per_second; 109static constexpr int64_t eviction_grace_period_microseconds_max = 110 eviction_grace_period_seconds_max * microseconds_per_second; 111static constexpr double trim_percentage_usage_threshold = 0.7; 112 113static int64_t 114get_eviction_grace_period(struct d3d12_memory_info *mem_info) 115{ 116 double pressure = double(mem_info->usage) / double(mem_info->budget); 117 pressure = MIN2(pressure, 1.0); 118 119 if (pressure > trim_percentage_usage_threshold) { 120 /* Normalize pressure for the range [0, threshold] */ 121 pressure = (pressure - trim_percentage_usage_threshold) / (1.0 - trim_percentage_usage_threshold); 122 /* Linearly interpolate between min and max period based on pressure */ 123 return (int64_t)((eviction_grace_period_microseconds_max - eviction_grace_period_microseconds_min) * 124 (1.0 - pressure)) + eviction_grace_period_microseconds_min; 125 } 126 127 /* Unlimited grace period, essentially don't trim at all */ 128 return INT64_MAX; 129} 130 131void 132d3d12_process_batch_residency(struct d3d12_screen *screen, struct d3d12_batch *batch) 133{ 134 d3d12_memory_info mem_info; 135 screen->get_memory_info(screen, &mem_info); 136 137 uint64_t completed_fence_value = screen->fence->GetCompletedValue(); 138 uint64_t pending_fence_value = screen->fence_value + 1; 139 int64_t current_time = os_time_get(); 140 int64_t grace_period = get_eviction_grace_period(&mem_info); 141 142 /* Gather base bos for the batch */ 143 uint64_t size_to_make_resident = 0; 144 set *base_bo_set = _mesa_pointer_set_create(nullptr); 145 hash_table_foreach(batch->bos, entry) { 146 struct d3d12_bo *bo = (struct d3d12_bo *)entry->key; 147 uint64_t offset; 148 struct d3d12_bo *base_bo = d3d12_bo_get_base(bo, &offset); 149 150 if (base_bo->residency_status == d3d12_evicted) { 151 bool added = false; 152 _mesa_set_search_or_add(base_bo_set, base_bo, &added); 153 assert(!added); 154 155 base_bo->residency_status = d3d12_resident; 156 size_to_make_resident += base_bo->estimated_size; 157 list_addtail(&base_bo->residency_list_entry, &screen->residency_list); 158 } else if (base_bo->last_used_fence != pending_fence_value) { 159 /* First time seeing this already-resident base bo in this batch */ 160 list_del(&base_bo->residency_list_entry); 161 list_addtail(&base_bo->residency_list_entry, &screen->residency_list); 162 } 163 164 base_bo->last_used_fence = pending_fence_value; 165 base_bo->last_used_timestamp = current_time; 166 } 167 168 /* Now that bos referenced by this batch are moved to the end of the LRU, trim it */ 169 evict_aged_allocations(screen, completed_fence_value, current_time, grace_period); 170 171 /* If there's nothing needing to be made newly resident, we're done once we've trimmed */ 172 if (base_bo_set->entries == 0) { 173 _mesa_set_destroy(base_bo_set, nullptr); 174 return; 175 } 176 177 uint64_t residency_fence_value_snapshot = screen->residency_fence_value; 178 179 struct set_entry *entry = _mesa_set_next_entry(base_bo_set, nullptr); 180 uint64_t batch_memory_size = 0; 181 unsigned batch_count = 0; 182 ID3D12Pageable *to_make_resident[residency_batch_size]; 183 while (true) { 184 /* Refresh memory stats */ 185 screen->get_memory_info(screen, &mem_info); 186 187 int64_t available_memory = (int64_t)mem_info.budget - (int64_t)mem_info.usage; 188 189 assert(!list_is_empty(&screen->residency_list)); 190 struct d3d12_bo *oldest_resident_bo = 191 list_first_entry(&screen->residency_list, struct d3d12_bo, residency_list_entry); 192 bool anything_to_wait_for = oldest_resident_bo->last_used_fence < pending_fence_value; 193 194 /* We've got some room, or we can't free up any more room, make some resources resident */ 195 HRESULT hr = S_OK; 196 if ((available_memory || !anything_to_wait_for) && batch_count < residency_batch_size) { 197 for (; entry; entry = _mesa_set_next_entry(base_bo_set, entry)) { 198 struct d3d12_bo *bo = (struct d3d12_bo *)entry->key; 199 if (anything_to_wait_for && 200 (int64_t)(batch_memory_size + bo->estimated_size) > available_memory) 201 break; 202 203 batch_memory_size += bo->estimated_size; 204 to_make_resident[batch_count++] = bo->res; 205 if (batch_count == residency_batch_size) 206 break; 207 } 208 209 if (batch_count) { 210 hr = screen->dev->EnqueueMakeResident(D3D12_RESIDENCY_FLAG_NONE, batch_count, to_make_resident, 211 screen->residency_fence, screen->residency_fence_value + 1); 212 if (SUCCEEDED(hr)) 213 ++screen->residency_fence_value; 214 } 215 216 if (SUCCEEDED(hr) && batch_count == residency_batch_size) { 217 batch_count = 0; 218 size_to_make_resident -= batch_memory_size; 219 continue; 220 } 221 } 222 223 /* We need to free up some space, either we broke early from the resource loop, 224 * or the MakeResident call itself failed. 225 */ 226 if (FAILED(hr) || entry) { 227 if (!anything_to_wait_for) { 228 assert(false); 229 break; 230 } 231 232 evict_to_fence_or_budget(screen, oldest_resident_bo->last_used_fence, mem_info.usage + size_to_make_resident, mem_info.budget); 233 continue; 234 } 235 236 /* Made it to the end without explicitly needing to loop, so we're done */ 237 break; 238 } 239 _mesa_set_destroy(base_bo_set, nullptr); 240 241 /* The GPU needs to wait for these resources to be made resident */ 242 if (residency_fence_value_snapshot != screen->residency_fence_value) 243 screen->cmdqueue->Wait(screen->residency_fence, screen->residency_fence_value); 244} 245 246bool 247d3d12_init_residency(struct d3d12_screen *screen) 248{ 249 list_inithead(&screen->residency_list); 250 if (FAILED(screen->dev->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&screen->residency_fence)))) 251 return false; 252 253 return true; 254} 255 256void 257d3d12_deinit_residency(struct d3d12_screen *screen) 258{ 259 if (screen->residency_fence) { 260 screen->residency_fence->Release(); 261 screen->residency_fence = nullptr; 262 } 263} 264 265void 266d3d12_promote_to_permanent_residency(struct d3d12_screen *screen, struct d3d12_resource* resource) 267{ 268 mtx_lock(&screen->submit_mutex); 269 uint64_t offset; 270 struct d3d12_bo *base_bo = d3d12_bo_get_base(resource->bo, &offset); 271 272 /* Promote non-permanent resident resources to permanent residency*/ 273 if(base_bo->residency_status != d3d12_permanently_resident) { 274 275 /* Mark as permanently resident*/ 276 base_bo->residency_status = d3d12_permanently_resident; 277 278 /* If it wasn't made resident before, make it*/ 279 bool was_made_resident = (base_bo->residency_status == d3d12_resident); 280 if(!was_made_resident) { 281 list_addtail(&base_bo->residency_list_entry, &screen->residency_list); 282 ID3D12Pageable *pageable = base_bo->res; 283 HRESULT hr = screen->dev->MakeResident(1, &pageable); 284 assert(SUCCEEDED(hr)); 285 } 286 } 287 mtx_unlock(&screen->submit_mutex); 288}