Cogs.Core
TexAtlasCache.cpp
1#include "Foundation/Logging/Logger.h"
2#include "TexAtlasSystem.h"
3
4#include <cassert>
5
6namespace {
7 using namespace Cogs::Core;
8 using namespace Cogs::Core::TexAtlas;
9
11
12 TreeIx getTreePosition(const glm::uvec3& tileId)
13 {
14 uint64_t a = tileId.x & 0x1FFFFFFFu; // 29 bits
15 uint64_t b = tileId.y & 0x1FFFFFFFu; // 29 bits
16 uint64_t c = tileId.z & 0x0000003Fu; // 6 bits
17 return static_cast<TreeIx>((c << (29 + 29)) | (b << 29) | a);
18 }
19
20 SlotIx tryEvictACacheSlot(Cache& cache)
21 {
22 if (cache.lruPointer) {
23 SlotIx slotIx = cache.lru[--cache.lruPointer];
24 Cache::Slot& slot = cache.slots[slotIx];
25
26 // Note: Since we evict while updating the tree, we must avoid
27 // evicting a tree item that has been visible, is still visible,
28 // but haven't been visited yet due to changes further down in
29 // the tree.
30 if (1 < cache.currentFrame - slot.lastTouched) {
31
32 assert(slot.treePos != NoTreeIx);
33 cache.tree.erase(slot.treePos);
34 // We should cancel if there is an active fetch
35 cache.setStateNone(slot);
36 slot.treePos = NoTreeIx;
37
38 LOG_TRACE(logger, "Evicted slotIx=%zu", size_t(slotIx));
39 return slotIx;
40 }
41 else {
42 // The rest of the slots will be younger, so we can just clear
43 // the LRU pointer to make next check slightly faster.
44 cache.lruPointer = 0;
45 }
46 }
47 return NoSlotIx;
48 }
49
50 SlotIx tryAllocNewCacheSlot(Cache& cache)
51 {
52 size_t o = cache.slots.size();
53 if (cache.maxItemCount <= o) {
54 return NoSlotIx;
55 }
56 SlotIx slotIx = static_cast<SlotIx>(o);
57
58 Cache::Slot& slot = cache.slots.emplace_back();
59 (void)slot;
60
61 cache.lru.emplace_back(slotIx);
62 return slotIx;
63 }
64
65}
66
67void Cogs::Core::TexAtlas::Cache::update(uint32_t currentFrame_, uint32_t currentTime_, uint32_t minRetryDelay_, uint32_t maxItemCount_)
68{
69 currentFrame = currentFrame_;
70 currentTime = currentTime_;
71 maxItemCount = maxItemCount_;
72 minRetryDelay = std::min(0xFFFFu, minRetryDelay_);
73
74
75 // If maxItemCount has been reduced, just remove them
76 if (maxItemCount < slots.size()) {
77 for (size_t i = maxItemCount; i < slots.size(); i++) {
78 tree.erase(slots[i].treePos);
79 }
80 slots.resize(maxItemCount);
81 }
82
83 // Try to remove all slots more than 100 frames old
84 while (!slots.empty() && (100 < (currentFrame - slots.back().lastTouched))) {
85 tree.erase(slots.back().treePos);
86 slots.pop_back();
87 }
88
89 // If we removed slots, scan through lru and remove them
90 if (slots.size() != lru.size()) {
91
92 size_t m = slots.size();
93 size_t n = lru.size();
94 for (size_t i = 0; i < n; ) {
95 if (m <= lru[i]) {
96 std::swap(lru[i], lru.back());
97 lru.pop_back();
98 n--;
99 }
100 else {
101 i++;
102 }
103 }
104
105 assert(slots.size() == lru.size());
106 }
107
108 // Sort LRU such that the oldest slots are in the end
109 assert(slots.size() == lru.size());
110 std::sort(lru.begin(), lru.end(),
111 [this](const SlotIx& a, const SlotIx& b) -> bool
112 {
113 uint32_t age_a = currentFrame - slots[a].lastTouched;
114 uint32_t age_b = currentFrame - slots[b].lastTouched;
115 return age_a < age_b;
116 });
117
118
119
120 // Decrement towards zero when choosing slots to evict between sort rounds.
121 lruPointer = static_cast<uint32_t>(slots.size());
122
123 // Sanity check to make sure slots in LRU are unique
124 for (size_t i = 1; i < lruPointer; i++) {
125 assert(lru[i - 1] != lru[i]);
126 }
127}
128
129Cogs::Core::TexAtlas::SlotIx Cogs::Core::TexAtlas::Cache::getOrAllocCacheSlot(Context* context, Fetcher& fetcher, const glm::uvec3& tileId)
130{
131 TreeIx treeIx = getTreePosition(tileId);
132
133 if (auto it = tree.find(treeIx); it != tree.end()) {
134 SlotIx slotIx = it->second;
135 Cache::Slot& slot = slots[slotIx];
136 slot.lastTouched = currentFrame;
137
138
139 // Check if we should retry fetching it.
140 if (slot.state == Cache::Slot::State::Failed && minRetryDelay && fetcher.canFetch()) {
141 uint32_t elapsed = currentTime - slot.stateChangeTime;
142 uint32_t delay = minRetryDelay << slot.retries;
143 if (delay <= elapsed) {
144 if (slot.retries < 15) {
145 slot.retries++;
146 }
147 LOG_DEBUG(logger, "Retrying failed tile [%u %u %u] (elapsed=%u delay=%u slot=%u, retry=%u)",
148 tileId.x, tileId.y, tileId.z, elapsed, delay, slotIx, slot.retries);
149 fetcher.issueFetch(context, tileId, treeIx, slotIx);
150 setStateLoading(slot);
151 }
152 }
153
154 // If tile was cancelled we just refetch it
155 else if (slot.state == Cache::Slot::State::Cancelled && fetcher.canFetch()) {
156 LOG_DEBUG(logger, "Retrying cancelled tile [%u %u %u] (slot=%u)",
157 tileId.x, tileId.y, tileId.z, slotIx);
158 fetcher.issueFetch(context, tileId, treeIx, slotIx);
159 setStateLoading(slot);
160 }
161
162 // Only return slot when data is ready.
163 return slot.state == Cache::Slot::State::Loaded ? slotIx : NoSlotIx;
164 }
165
166 // No point in allocating slots if we cannot fetch
167 if (fetcher.canFetch()) {
168
169 // First we try to recycle an old slot
170 SlotIx slotIx = tryEvictACacheSlot(*this);
171
172 // If no slots can be recycled, ws try to allocate a new slot
173 if (slotIx == NoSlotIx) {
174 slotIx = tryAllocNewCacheSlot(*this);
175 }
176
177 // If we managed to get hold of a slot to use, isse a fetch to populate slot
178 if (slotIx != NoSlotIx) {
179 Cache::Slot& slot = slots[slotIx];
180 slot.treePos = treeIx;
181 slot.lastTouched = currentFrame;
182
183 assert(tree.find(treeIx) == tree.end());
184 tree[treeIx] = slotIx;
185
186 fetcher.issueFetch(context, tileId, treeIx, slotIx);
187 setStateLoading(slot);
188 //LOG_DEBUG(logger, "using slot %u: %u %u %u -> %u", slotIx, tileId.z, tileId.x, tileId.y, treeIx);
189 }
190 }
191
192 return NoSlotIx;
193}
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
Log implementation class.
Definition: LogManager.h:140
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
COGSFOUNDATION_API Time currentTime()
High resolution clock time (NTP / UTC time). Returns an implementation defined absolute timestamp,...
std::vector< SlotIx > lru
Slots sorted s.t. the least recently used items is at end.
uint32_t maxItemCount
Number of tiles in cache.
uint32_t minRetryDelay
Initial delay for retrying a fetch. For subsequent retries this delay is doubled each time.
uint32_t lruPointer
Points to one past the least recently and recycable item, decrements and zero means nothing to recycl...
void issueFetch(Context *context, const glm::uvec3 &tileId, TreeIx treeIx, SlotIx slotIx)