46 return init_status_.load(std::memory_order_acquire);
51 std::lock_guard<std::mutex> lk(mutex_);
52 default_config_ = cfg;
54 if(gpu_stats ==
nullptr)
69 set_init_status(status);
72 seed_startup_budget_locked(gpu_stats);
78 std::lock_guard<std::mutex> lk(mutex_);
79 for(
auto* r : resident_)
81 r->evict_slot_ = UINT32_MAX;
83 for(
auto* r : evicted_)
85 r->evict_slot_ = UINT32_MAX;
91 queued_allocation_bytes_.store(0, std::memory_order_relaxed);
92 external_queued_bytes_.store(0, std::memory_order_relaxed);
93 pending_release_bytes_.store(0, std::memory_order_relaxed);
95 budget_used_bytes_ = 0;
105 std::lock_guard<std::mutex> lk(mutex_);
114 r->last_use_frame_ =
frame;
115 r->evict_frame_ =
frame;
116 add_to(resident_, r);
117 const std::uint64_t sz = r->gpu_size();
118 resident_bytes_ += sz;
119 queued_allocation_bytes_.fetch_add(sz, std::memory_order_relaxed);
128 std::lock_guard<std::mutex> lk(mutex_);
134 r->last_use_frame_ =
frame;
135 r->evict_frame_ =
frame;
137 evicted_bytes_ += r->gpu_size();
142 if(r ==
nullptr || r->evict_slot_ == UINT32_MAX)
146 std::lock_guard<std::mutex> lk(mutex_);
149 const std::uint64_t sz = r->gpu_size();
150 resident_bytes_ -= sz;
151 note_pending_release_locked(sz);
152 remove_from(resident_, r);
156 evicted_bytes_ -= r->gpu_size();
157 remove_from(evicted_, r);
159 r->evict_slot_ = UINT32_MAX;
168 std::unique_lock<std::mutex> lk(mutex_);
174 restore_locked(lk, r);
179 std::unique_lock<std::mutex> lk(mutex_);
187 const std::vector<ievictable*> pending = evicted_;
188 for(
auto* r : pending)
192 restore_locked(lk, r);
200 std::lock_guard<std::mutex> lk(mutex_);
207 req.strat = cfg.strat;
208 req.min_age_frames = cfg.min_age_frames;
209 req.max_idle_frames = cfg.max_idle_frames;
210 req.max_evictions = cfg.max_evictions;
214 req.target_resident = 0;
215 req.free_limit = UINT64_MAX;
219 const std::uint64_t target = (cfg.target_bytes != 0) ? cfg.target_bytes : cfg.budget_bytes;
224 req.target_resident = target;
225 req.free_limit = UINT64_MAX;
227 return do_sweep(req);
232 std::uint32_t min_age_frames,
235 std::lock_guard<std::mutex> lk(mutex_);
242 req.min_age_frames = min_age_frames;
243 req.max_idle_frames = 0;
244 req.max_evictions = max_evictions;
245 req.target_resident = 0;
246 req.free_limit = free_bytes;
247 return do_sweep(req);
252 std::lock_guard<std::mutex> lk(mutex_);
259 req.min_age_frames = 0;
260 req.max_idle_frames = 0;
261 req.max_evictions = 0;
262 req.target_resident = 0;
263 req.free_limit = UINT64_MAX;
264 return do_sweep(req);
271 std::lock_guard<std::mutex> lk(mutex_);
272 cfg = default_config_;
279 std::lock_guard<std::mutex> lk(mutex_);
285 budget_used_bytes_ = used_bytes;
286 publish_budget_locked();
294 const auto seq = published_budget_seq_.load(std::memory_order_acquire);
299 const auto snapshot = published_budget_;
300 if(published_budget_seq_.load(std::memory_order_acquire) ==
seq)
309 std::lock_guard<std::mutex> lk(mutex_);
310 return default_config_;
315 std::lock_guard<std::mutex> lk(mutex_);
327 queued_allocation_bytes_.fetch_add(
bytes, std::memory_order_relaxed);
328 external_queued_bytes_.fetch_add(
bytes, std::memory_order_relaxed);
333 return external_queued_bytes_.load(std::memory_order_relaxed);
338 return pending_release_bytes_.load(std::memory_order_relaxed);
343 return queued_allocation_bytes_.load(std::memory_order_relaxed);
352 queued_allocation_bytes_.store(0, std::memory_order_relaxed);
353 external_queued_bytes_.store(0, std::memory_order_relaxed);
354 pending_release_bytes_.store(0, std::memory_order_relaxed);
362 last_restore_ms_ = 0.0;
363 last_pass_scanned_ = 0;
364 last_pass_evicted_ = 0;
365 last_pass_freed_bytes_ = 0;
373 init_status_.store(s, std::memory_order_release);
376 static auto startup_safety_margin(std::uint64_t hard_limit_bytes) -> std::uint64_t
378 constexpr std::uint64_t k_floor = std::uint64_t(64) * 1024 * 1024;
379 return std::max(k_floor, hard_limit_bytes / 50);
382 void seed_startup_budget_locked(
const gfx::stats* gpu_stats)
384 if(gpu_stats ==
nullptr || gpu_stats->gpuMemoryMax <= 0)
388 const std::uint64_t gpu_max =
static_cast<std::uint64_t
>(gpu_stats->gpuMemoryMax);
389 eviction::budget_state
b;
390 b.hard_limit_bytes = gpu_max;
393 b.soft_budget_bytes =
static_cast<std::uint64_t
>(
static_cast<double>(gpu_max) * 0.85);
394 b.target_bytes =
static_cast<std::uint64_t
>(
static_cast<double>(gpu_max) * 0.75);
395 b.safety_margin_bytes = startup_safety_margin(gpu_max);
397 budget_used_bytes_ =
static_cast<std::uint64_t
>(std::max<std::int64_t>(0, gpu_stats->gpuMemoryUsed));
398 publish_budget_locked();
401 void publish_budget_locked()
403 published_budget_seq_.fetch_add(1, std::memory_order_release);
404 published_budget_ = budget_;
405 published_budget_seq_.fetch_add(1, std::memory_order_release);
408 void note_pending_release_locked(std::uint64_t
bytes)
412 pending_release_bytes_.fetch_add(
bytes, std::memory_order_relaxed);
416 static void add_to(std::vector<ievictable*>& bucket, ievictable* r)
418 r->evict_slot_ =
static_cast<std::uint32_t
>(bucket.size());
422 static void remove_from(std::vector<ievictable*>& bucket, ievictable* r)
424 const std::uint32_t idx =
r->evict_slot_;
425 ievictable* last = bucket.back();
427 last->evict_slot_ = idx;
436 std::uint32_t min_age_frames = 0;
437 std::uint32_t max_idle_frames = 0;
438 std::uint32_t max_evictions = 0;
439 std::uint64_t target_resident = 0;
440 std::uint64_t free_limit = UINT64_MAX;
443 void restore_locked(std::unique_lock<std::mutex>& lk, ievictable* r)
445 const auto t0 = clock::now();
446 const std::uint64_t sz =
r->gpu_size();
450 const bool ok =
r->on_restore();
452 last_restore_ms_ = std::max(last_restore_ms_, to_ms(clock::now() - t0));
462 const std::uint32_t slot =
r->evict_slot_;
463 if(slot >= evicted_.size() || evicted_[slot] != r)
467 remove_from(evicted_, r);
468 add_to(resident_, r);
469 evicted_bytes_ -= sz;
470 resident_bytes_ += sz;
471 queued_allocation_bytes_.fetch_add(sz, std::memory_order_relaxed);
473 total_bytes_restored_ += sz;
481 auto do_sweep(
const sweep_request& req) -> eviction::stats
483 const auto t0 = clock::now();
487 collect_candidates(req,
frame);
488 order_candidates(req.strat);
490 std::uint64_t pass_evicted = 0;
491 std::uint64_t freed = 0;
492 for(
auto* r : candidates_)
494 if(req.max_evictions != 0 && pass_evicted >= req.max_evictions)
498 if(resident_bytes_ <= req.target_resident || freed >= req.free_limit)
502 const std::uint64_t sz =
r->gpu_size();
504 note_pending_release_locked(sz);
505 remove_from(resident_, r);
507 resident_bytes_ -= sz;
508 evicted_bytes_ += sz;
512 total_bytes_evicted_ += sz;
519 last_pass_scanned_ = std::max<std::uint64_t>(last_pass_scanned_, candidates_.size());
520 last_pass_evicted_ = std::max<std::uint64_t>(last_pass_evicted_, pass_evicted);
521 last_pass_freed_bytes_ = std::max<std::uint64_t>(last_pass_freed_bytes_, freed);
522 last_pass_ms_ = std::max(last_pass_ms_, to_ms(clock::now() - t0));
526 void collect_candidates(
const sweep_request& req, std::uint64_t
frame)
528 for(
auto* r : resident_)
534 const std::uint64_t idle =
frame -
r->get_last_use_frame();
535 if(req.min_age_frames != 0 && idle < req.min_age_frames)
540 (req.max_idle_frames == 0 || idle <= req.max_idle_frames))
544 candidates_.push_back(r);
554 std::sort(candidates_.begin(),
556 [](
const ievictable*
a,
const ievictable*
b) ->
bool
558 return a->get_last_use_frame() < b->get_last_use_frame();
562 std::sort(candidates_.begin(),
564 [](
const ievictable*
a,
const ievictable*
b) ->
bool
566 return a->get_use_count() < b->get_use_count();
570 std::sort(candidates_.begin(),
572 [](
const ievictable*
a,
const ievictable*
b) ->
bool
574 return a->gpu_size() > b->gpu_size();
580 auto snapshot() const -> eviction::
stats
583 s.resident_count = resident_.size();
584 s.resident_bytes = resident_bytes_;
585 s.evicted_count = evicted_.size();
586 s.evicted_bytes = evicted_bytes_;
587 s.registered_count = resident_.size() + evicted_.size();
588 s.total_evictions = total_evictions_;
589 s.total_restores = total_restores_;
590 s.total_bytes_evicted = total_bytes_evicted_;
591 s.total_bytes_restored = total_bytes_restored_;
592 s.failed_restores = failed_restores_;
593 s.thrash_events = thrash_events_;
599 s.budget_used_bytes = budget_used_bytes_;
600 s.last_pass_scanned = last_pass_scanned_;
601 s.last_pass_evicted = last_pass_evicted_;
602 s.last_pass_freed_bytes = last_pass_freed_bytes_;
603 s.pending_release_bytes = pending_release_bytes_.load(std::memory_order_relaxed);
604 s.last_pass_ms = last_pass_ms_;
605 s.last_restore_ms = last_restore_ms_;
609 mutable std::mutex mutex_;
611 eviction::config default_config_{};
613 std::vector<ievictable*> resident_;
614 std::vector<ievictable*> evicted_;
615 std::vector<ievictable*> candidates_;
617 std::uint64_t resident_bytes_ = 0;
618 std::uint64_t evicted_bytes_ = 0;
622 std::atomic<std::uint64_t> queued_allocation_bytes_{0};
623 std::atomic<std::uint64_t> external_queued_bytes_{0};
624 std::atomic<std::uint64_t> pending_release_bytes_{0};
625 std::uint64_t total_evictions_ = 0;
626 std::uint64_t total_restores_ = 0;
627 std::uint64_t total_bytes_evicted_ = 0;
628 std::uint64_t total_bytes_restored_ = 0;
629 std::uint64_t failed_restores_ = 0;
630 std::uint64_t thrash_events_ = 0;
631 eviction::budget_state budget_{};
632 std::atomic<std::uint32_t> published_budget_seq_{0};
633 eviction::budget_state published_budget_{};
634 std::uint64_t budget_used_bytes_ = 0;
635 std::uint64_t last_pass_scanned_ = 0;
636 std::uint64_t last_pass_evicted_ = 0;
637 std::uint64_t last_pass_freed_bytes_ = 0;
638 double last_pass_ms_ = 0.0;
639 double last_restore_ms_ = 0.0;
674auto evict_bytes(std::uint64_t free_bytes,
strategy strat, std::uint32_t min_age_frames, std::uint32_t max_evictions)
687auto live_gpu_used() -> std::uint64_t
690 if(gpu_stats ==
nullptr)
694 return static_cast<std::uint64_t
>(std::max<std::int64_t>(0, gpu_stats->gpuMemoryUsed));
697auto credit_pending_release(std::uint64_t gross) -> std::uint64_t
700 return gross > pending ? gross - pending : 0;
703auto project_occupancy(std::uint64_t used, std::uint64_t queued, std::uint64_t request, std::uint64_t margin)
706 return credit_pending_release(used + queued + request + margin);
709auto projected_allocation_bytes(std::uint64_t
bytes) -> std::uint64_t
712 if(budget.hard_limit_bytes == 0)
716 return project_occupancy(live_gpu_used(),
719 budget.safety_margin_bytes);
754 const std::uint64_t projected = projected_allocation_bytes(
bytes);
766 auto after_evict = [&]() -> std::uint64_t
768 return projected_allocation_bytes(
bytes);
771 std::uint64_t occupancy = after_evict();
774 if(
bytes > 0 && freed > 0)
777 occupancy = after_evict();
787 occupancy = after_evict();
826auto reserved() -> debug_reserve&
828 static debug_reserve s_reserved;
832constexpr std::uint16_t k_reserve_dim = 4096;
833constexpr std::uint64_t k_reserve_chunk = std::uint64_t(k_reserve_dim) * k_reserve_dim * 4;
837auto alloc_reserve_chunk(std::uint16_t dim) -> std::uint64_t
841 if(!bgfx::isValid(
handle))
845 reserved().chunks.push_back(
handle);
846 const std::uint64_t sz = std::uint64_t(dim) * dim * 4;
847 reserved().bytes += sz;
854 std::uint64_t remaining =
bytes;
855 while(remaining >= k_reserve_chunk)
857 const std::uint64_t got = alloc_reserve_chunk(k_reserve_dim);
860 return reserved().bytes;
867 const double texels =
static_cast<double>(remaining) / 4.0;
868 auto side =
static_cast<std::uint32_t
>(std::ceil(std::sqrt(texels)));
869 side = std::clamp<std::uint32_t>(side, 1, k_reserve_dim);
870 alloc_reserve_chunk(
static_cast<std::uint16_t
>(side));
872 return reserved().bytes;
878 if(gpu_stats ==
nullptr || gpu_stats->gpuMemoryMax <= 0)
889 const auto budget =
static_cast<std::uint64_t
>(gpu_stats->gpuMemoryMax);
890 const auto used =
static_cast<std::uint64_t
>(std::max<std::int64_t>(0, gpu_stats->gpuMemoryUsed));
891 if(target_free_bytes >= budget)
895 const std::uint64_t desired_used = budget - target_free_bytes;
896 if(desired_used <= used)
912 reserved().chunks.clear();
913 reserved().bytes = 0;
920 return reserved().bytes;
928 return reg.get_stats();
935 const auto pre = reg.get_stats();
936 if(pre.evicted_bytes != 0)
940 return reg.restore_all();