Cogs.Core
TexAtlasFetcher.cpp
1#include <cinttypes>
2
3#include "Foundation/Logging/Logger.h"
4#include "Foundation/Platform/IO.h"
5
6#include "Services/Variables.h"
7
8#include "Resources/TextureManager.h"
9
10#include "TexAtlasSystem.h"
11
12namespace {
13 using namespace Cogs::Core;
14 using namespace Cogs::Core::TexAtlas;
15
17
18 const Cogs::StringView urlTemplateLogName = "texAtlas.urlTemplate.log";
19 const Cogs::StringView atlasLayoutLogName = "texAtlas.atlasLayout.log";
20
21 void compileUrlTemplate(Context* context, Fetcher& fetcher)
22 {
23 fetcher.urlBuilder.clear();
24 const std::string_view url(fetcher.url);
25
26 size_t done = 0;
27 size_t next = 0;
28 while (true) {
29 next = fetcher.url.find('{', next);
30 if (next == std::string_view::npos) {
31 if (done < url.length()) {
32 fetcher.urlBuilder.push_back(Fetcher::UrlBuilderOp{ Fetcher::UrlBuilderOp::Kind::InsertChars, url.substr(done) });
33 }
34 break;
35 }
36
37 if (next + 3 <= url.length()) {
38 const std::string_view token = url.substr(next + 1, 2);
39 if (token == "x}") {
40 if (done < next) {
41 fetcher.urlBuilder.push_back(Fetcher::UrlBuilderOp{ Fetcher::UrlBuilderOp::Kind::InsertChars, url.substr(done, next - done) });
42 }
43 fetcher.urlBuilder.push_back(Fetcher::UrlBuilderOp{ Fetcher::UrlBuilderOp::Kind::InsertX, std::string_view() });
44 done = next = next + 3;
45 continue;
46 }
47 else if (token == "y}") {
48 if (done < next) {
49 fetcher.urlBuilder.push_back(Fetcher::UrlBuilderOp{ Fetcher::UrlBuilderOp::Kind::InsertChars, url.substr(done, next - done) });
50 }
51 fetcher.urlBuilder.push_back(Fetcher::UrlBuilderOp{ Fetcher::UrlBuilderOp::Kind::InsertY, std::string_view() });
52 done = next = next + 3;
53 continue;
54 }
55 else if (token == "z}") {
56 if (done < next) {
57 fetcher.urlBuilder.push_back(Fetcher::UrlBuilderOp{ Fetcher::UrlBuilderOp::Kind::InsertChars, url.substr(done, next - done) });
58 }
59 fetcher.urlBuilder.push_back(Fetcher::UrlBuilderOp{ Fetcher::UrlBuilderOp::Kind::InsertZ, std::string_view() });
60 done = next = next + 3;
61 continue;
62 }
63 }
64 if (next + 6 <= url.length()) {
65 const std::string_view token = url.substr(next + 1, 5);
66 if (token == "xmin}") {
67 if (done < next) {
68 fetcher.urlBuilder.push_back(Fetcher::UrlBuilderOp{ Fetcher::UrlBuilderOp::Kind::InsertChars, url.substr(done, next - done) });
69 }
70 fetcher.urlBuilder.push_back(Fetcher::UrlBuilderOp{ Fetcher::UrlBuilderOp::Kind::InsertXMin, std::string_view() });
71 done = next = next + 6;
72 continue;
73 }
74 else if (token == "xmax}") {
75 if (done < next) {
76 fetcher.urlBuilder.push_back(Fetcher::UrlBuilderOp{ Fetcher::UrlBuilderOp::Kind::InsertChars, url.substr(done, next - done) });
77 }
78 fetcher.urlBuilder.push_back(Fetcher::UrlBuilderOp{ Fetcher::UrlBuilderOp::Kind::InsertXMax, std::string_view() });
79 done = next = next + 6;
80 continue;
81 }
82 else if (token == "ymin}") {
83 if (done < next) {
84 fetcher.urlBuilder.push_back(Fetcher::UrlBuilderOp{ Fetcher::UrlBuilderOp::Kind::InsertChars, url.substr(done, next - done) });
85 }
86 fetcher.urlBuilder.push_back(Fetcher::UrlBuilderOp{ Fetcher::UrlBuilderOp::Kind::InsertYMin, std::string_view() });
87 done = next = next + 6;
88 continue;
89 }
90 else if (token == "ymax}") {
91 if (done < next) {
92 fetcher.urlBuilder.push_back(Fetcher::UrlBuilderOp{ Fetcher::UrlBuilderOp::Kind::InsertChars, url.substr(done, next - done) });
93 }
94 fetcher.urlBuilder.push_back(Fetcher::UrlBuilderOp{ Fetcher::UrlBuilderOp::Kind::InsertYMax, std::string_view() });
95 done = next = next + 6;
96 continue;
97 }
98 }
99 next = next + 1; // no match, skip char
100 }
101
102
103 if (context->variables->getOrAdd(urlTemplateLogName, false)) {
104 LOG_DEBUG(logger, "UrlBuilder has %zu steps:", fetcher.urlBuilder.size());
105 for (const Fetcher::UrlBuilderOp& item : fetcher.urlBuilder) {
106 switch (item.kind) {
107 case Fetcher::UrlBuilderOp::Kind::InsertChars:
108 LOG_DEBUG(logger, "-> Insert chars '%.*s'", static_cast<int>(item.chars.size()), item.chars.data());
109 break;
110 case Fetcher::UrlBuilderOp::Kind::InsertX:
111 LOG_DEBUG(logger, "-> Insert X");
112 break;
113 case Fetcher::UrlBuilderOp::Kind::InsertY:
114 LOG_DEBUG(logger, "-> Insert Y");
115 break;
116 case Fetcher::UrlBuilderOp::Kind::InsertZ:
117 LOG_DEBUG(logger, "-> Insert Z");
118 break;
119 case Fetcher::UrlBuilderOp::Kind::InsertXMin:
120 LOG_DEBUG(logger, "-> Insert XMIN");
121 break;
122 case Fetcher::UrlBuilderOp::Kind::InsertXMax:
123 LOG_DEBUG(logger, "-> Insert XMAX");
124 break;
125 case Fetcher::UrlBuilderOp::Kind::InsertYMin:
126 LOG_DEBUG(logger, "-> Insert YMIN");
127 break;
128 case Fetcher::UrlBuilderOp::Kind::InsertYMax:
129 LOG_DEBUG(logger, "-> Insert YMAX");
130 break;
131 default:
132 assert(false);
133 break;
134 }
135 }
136 }
137 }
138
139 std::string getExtentValueForTile(Fetcher& fetcher, const glm::uvec3& tileId, Fetcher::UrlBuilderOp::Kind kind)
140 {
141 double extentValue = -1;
142 double width;
143 double height;
144 switch (kind) {
145 case Fetcher::UrlBuilderOp::Kind::InsertXMin:
146 width = (fetcher.datasetExtentMax.x - fetcher.datasetExtentMin.x) / (1 << tileId.z);
147 extentValue = fetcher.datasetExtentMin.x + width * tileId.x;
148 break;
149 case Fetcher::UrlBuilderOp::Kind::InsertXMax:
150 width = (fetcher.datasetExtentMax.x - fetcher.datasetExtentMin.x) / (1 << tileId.z);
151 extentValue = fetcher.datasetExtentMin.x + width * (tileId.x + 1);
152 break;
153 case Fetcher::UrlBuilderOp::Kind::InsertYMin:
154 height = (fetcher.datasetExtentMax.y - fetcher.datasetExtentMin.y) / (1 << tileId.z);
155 extentValue = fetcher.datasetExtentMax.y - height * (tileId.y + 1);
156 break;
157 case Fetcher::UrlBuilderOp::Kind::InsertYMax:
158 height = (fetcher.datasetExtentMax.y - fetcher.datasetExtentMin.y) / (1 << tileId.z);
159 extentValue = fetcher.datasetExtentMax.y - height * tileId.y;
160 break;
161 default:
162 assert(false);
163 break;
164 }
165
166 return std::to_string(extentValue);
167 }
168
169 void interpretUrlTemplate(Fetcher& fetcher, std::string& out, const glm::uvec3& tileId)
170 {
171 out.clear();
172 for (const Fetcher::UrlBuilderOp& item : fetcher.urlBuilder) {
173 switch (item.kind) {
174 case Fetcher::UrlBuilderOp::Kind::InsertChars:
175 out.append(item.chars);
176 break;
177 case Fetcher::UrlBuilderOp::Kind::InsertX:
178 out.append(std::to_string(tileId.x));
179 break;
180 case Fetcher::UrlBuilderOp::Kind::InsertY:
181 out.append(std::to_string(tileId.y));
182 break;
183 case Fetcher::UrlBuilderOp::Kind::InsertZ:
184 out.append(std::to_string(tileId.z));
185 break;
186 case Fetcher::UrlBuilderOp::Kind::InsertXMin:
187 out.append(getExtentValueForTile(fetcher, tileId, item.kind));
188 break;
189 case Fetcher::UrlBuilderOp::Kind::InsertYMin:
190 out.append(getExtentValueForTile(fetcher, tileId, item.kind));
191 break;
192 case Fetcher::UrlBuilderOp::Kind::InsertXMax:
193 out.append(getExtentValueForTile(fetcher, tileId, item.kind));
194 break;
195 case Fetcher::UrlBuilderOp::Kind::InsertYMax:
196 out.append(getExtentValueForTile(fetcher, tileId, item.kind));
197 break;
198 default:
199 assert(false);
200 break;
201 }
202 }
203 }
204}
205
206void Cogs::Core::TexAtlas::Fetcher::update(Context* context, uint32_t currentFrame_, uint32_t currentTime_, uint32_t timeout_, const std::string_view url_)
207{
208 if (url != url_) {
209 url = url_;
210 compileUrlTemplate(context, *this);
211 }
212
213 currentFrame = currentFrame_;
214 currentTime = currentTime_;
215 timeout = timeout_;
216
217 issuedThisFrame = 0;
218}
219
221{
222 tmp.clear();
223 for (LoadItem& loadItem : loading) {
224 if (auto it = cache.tree.find(loadItem.treeIx); it != cache.tree.end()) {
225 SlotIx slotIx = it->second;
226 if (loadItem.slotIx == slotIx) {
227 assert(slotIx < cache.slots.size());
228 Cache::Slot& slot = cache.slots[slotIx];
229
230 // If slot has been recycled, there is no point in continuing this load.
231 if (slot.treePos != loadItem.treeIx) {
232 context->textureManager->cancelTextureLoad(loadItem.texture);
233 LOG_DEBUG(logger, "Cancelling recycled load item");
234 continue;
235 }
236
237 // If tile has loaded, we move it over to the loaded queue for the renderer to process it.
238 if (loadItem.texture->isResident()) {
239
240 const uint32_t w = loadItem.texture->description.width;
241 const uint32_t h = loadItem.texture->description.height;
242
243 if (w == 0 || h == 0) {
244 cache.setStateFailed(slot);
245 LOG_DEBUG(logger, "Invalid texture size [%u, %u]", w, h);
246 continue;
247 }
248
249 if (tileWidth == 0 || tileHeight == 0) {
250 tileWidth = w;
251 tileHeight = h;
252 if (context->variables->getOrAdd(atlasLayoutLogName, false)) {
253 LOG_DEBUG(logger, "First tile received, setting tileSize=[%u,%u]", tileWidth, tileHeight);
254 }
255 }
256
257 if (tileWidth != w || tileHeight != h) {
258 LOG_DEBUG(logger, "Invalid texture size [%u, %u], expected [%u, %u]", w, h, tileWidth, tileHeight);
259 continue;
260 }
261
262 cache.setStateLoaded(slot);
263 loaded.emplace_back(std::move(loadItem));
264 continue;
265 }
266
267 // If tile has failed, we signal this via the slot state and discard the load item.
268 if (loadItem.texture->hasFailedLoad()) {
269 cache.setStateFailed(slot);
270 LOG_DEBUG(logger, "Failed load item");
271 continue;
272 }
273
274 // If slot hasn't been touched for 10 frames, we just cancel the load and discard the load item.
275 uint32_t framesSinceLastNeeded = currentFrame - slot.lastTouched;
276 if (10 < framesSinceLastNeeded) {
277 context->textureManager->cancelTextureLoad(loadItem.texture);
278 cache.setStateCancelled(slot);
279 continue;
280 }
281
282 // Check timeout
283 if (timeout) {
284 uint32_t timeSinceIssue = currentTime - slot.stateChangeTime;
285 if (timeout <= timeSinceIssue) {
286 context->textureManager->cancelTextureLoad(loadItem.texture);
287 cache.setStateFailed(slot);
288 LOG_DEBUG(logger, "Fetch tiled out, set as failed.");
289 continue;
290 }
291 }
292
293 // Tile is still loading and is still needed, keep in queue.
294 tmp.emplace_back(std::move(loadItem));
295 }
296 }
297 }
298 loading = std::move(tmp);
299}
300
302{
303 return (issuedThisFrame < maxIssuesPerFrame) && (loading.size() < maxQueueSize);
304}
305
306
307namespace {
308
309 uint8_t digitGlyps[11][8][8] = {
310 { // 0
311 { 0, 1, 1, 1, 1, 1, 0, 0 },
312 { 1, 1, 0, 0, 0, 1, 1, 0 },
313 { 1, 1, 0, 0, 0, 1, 1, 0 },
314 { 1, 1, 0, 0, 0, 1, 1, 0 },
315 { 1, 1, 0, 0, 0, 1, 1, 0 },
316 { 1, 1, 0, 0, 0, 1, 1, 0 },
317 { 0, 1, 1, 1, 1, 1, 0, 0 },
318 { 0, 0, 0, 0, 0, 0, 0, 0 }
319 },
320 { // 1
321 { 0, 0, 0, 0, 1, 0, 0, 0 },
322 { 0, 0, 0, 1, 1, 0, 0, 0 },
323 { 0, 0, 1, 1, 1, 0, 0, 0 },
324 { 0, 0, 0, 1, 1, 0, 0, 0 },
325 { 0, 0, 0, 1, 1, 0, 0, 0 },
326 { 0, 0, 0, 1, 1, 0, 0, 0 },
327 { 0, 0, 1, 1, 1, 1, 0, 0 },
328 { 0, 0, 0, 0, 0, 0, 0, 0 }
329 },
330 { // 2
331 { 0, 1, 1, 1, 1, 1, 0, 0 },
332 { 1, 1, 0, 0, 0, 1, 1, 0 },
333 { 0, 0, 0, 0, 0, 1, 1, 0 },
334 { 0, 1, 1, 1, 1, 1, 0, 0 },
335 { 1, 1, 0, 0, 0, 0, 0, 0 },
336 { 1, 1, 0, 0, 0, 1, 1, 0 },
337 { 1, 1, 1, 1, 1, 1, 1, 0 },
338 { 0, 0, 0, 0, 0, 0, 0, 0 }
339 },
340 { // 3
341 { 0, 1, 1, 1, 1, 1, 0, 0 },
342 { 1, 1, 0, 0, 0, 1, 1, 0 },
343 { 0, 0, 0, 0, 0, 1, 1, 0 },
344 { 0, 1, 1, 1, 1, 1, 0, 0 },
345 { 0, 0, 0, 0, 0, 1, 1, 0 },
346 { 1, 1, 0, 0, 0, 1, 1, 0 },
347 { 0, 1, 1, 1, 1, 1, 0, 0 },
348 { 0, 0, 0, 0, 0, 0, 0, 0 }
349 },
350 { // 4
351 { 1, 1, 0, 0, 0, 1, 1, 0 },
352 { 1, 1, 0, 0, 0, 1, 1, 0 },
353 { 1, 1, 0, 0, 0, 1, 1, 0 },
354 { 1, 1, 1, 1, 1, 1, 1, 0 },
355 { 0, 0, 0, 0, 0, 1, 1, 0 },
356 { 0, 0, 0, 0, 0, 1, 1, 0 },
357 { 0, 0, 0, 0, 0, 1, 1, 0 },
358 { 0, 0, 0, 0, 0, 0, 0, 0 }
359 },
360 { // 5
361 { 1, 1, 1, 1, 1, 1, 1, 0 },
362 { 1, 1, 0, 0, 0, 1, 1, 0 },
363 { 1, 1, 0, 0, 0, 0, 0, 0 },
364 { 1, 1, 1, 1, 1, 1, 0, 0 },
365 { 0, 0, 0, 0, 0, 1, 1, 0 },
366 { 1, 1, 0, 0, 0, 1, 1, 0 },
367 { 0, 1, 1, 1, 1, 1, 0, 0 },
368 { 0, 0, 0, 0, 0, 0, 0, 0 }
369 },
370 { // 6
371 { 0, 1, 1, 1, 1, 1, 0, 0 },
372 { 1, 1, 0, 0, 0, 1, 1, 0 },
373 { 1, 1, 0, 0, 0, 0, 0, 0 },
374 { 1, 1, 1, 1, 1, 1, 0, 0 },
375 { 1, 1, 0, 0, 0, 1, 1, 0 },
376 { 1, 1, 0, 0, 0, 1, 1, 0 },
377 { 0, 1, 1, 1, 1, 1, 0, 0 },
378 { 0, 0, 0, 0, 0, 0, 0, 0 }
379 },
380 { // 7
381 { 1, 1, 1, 1, 1, 1, 1, 0 },
382 { 1, 1, 0, 0, 0, 1, 1, 0 },
383 { 0, 0, 0, 0, 0, 1, 1, 0 },
384 { 0, 0, 0, 0, 1, 1, 0, 0 },
385 { 0, 0, 0, 1, 1, 0, 0, 0 },
386 { 0, 0, 0, 1, 1, 0, 0, 0 },
387 { 0, 0, 0, 1, 1, 0, 0, 0 },
388 { 0, 0, 0, 0, 0, 0, 0, 0 }
389 },
390 { // 8
391 { 0, 1, 1, 1, 1, 1, 0, 0 },
392 { 1, 1, 0, 0, 0, 1, 1, 0 },
393 { 1, 1, 0, 0, 0, 1, 1, 0 },
394 { 0, 1, 1, 1, 1, 1, 0, 0 },
395 { 1, 1, 0, 0, 0, 1, 1, 0 },
396 { 1, 1, 0, 0, 0, 1, 1, 0 },
397 { 0, 1, 1, 1, 1, 1, 0, 0 },
398 { 0, 0, 0, 0, 0, 0, 0, 0 }
399 },
400 { // 9
401 { 0, 1, 1, 1, 1, 1, 0, 0 },
402 { 1, 1, 0, 0, 0, 1, 1, 0 },
403 { 1, 1, 0, 0, 0, 1, 1, 0 },
404 { 0, 1, 1, 1, 1, 1, 1, 0 },
405 { 0, 0, 0, 0, 0, 1, 1, 0 },
406 { 1, 1, 0, 0, 0, 1, 1, 0 },
407 { 0, 1, 1, 1, 1, 1, 0, 0 },
408 { 0, 0, 0, 0, 0, 0, 0, 0 }
409 },
410 { // -
411 { 0, 0, 0, 0, 0, 0, 0, 0 },
412 { 0, 0, 0, 0, 0, 0, 0, 0 },
413 { 0, 0, 0, 0, 0, 0, 0, 0 },
414 { 0, 1, 1, 1, 1, 1, 1, 0 },
415 { 0, 0, 0, 0, 0, 0, 0, 0 },
416 { 0, 0, 0, 0, 0, 0, 0, 0 },
417 { 0, 0, 0, 0, 0, 0, 0, 0 },
418 { 0, 0, 0, 0, 0, 0, 0, 0 }
419 }
420 };
421
422 void emitDigit(std::vector<uint8_t>& buf, size_t& x, size_t& y, uint32_t digit)
423 {
424 for (size_t j = 0; j < 8; j++) {
425 for (size_t i = 0; i < 8; i++) {
426 buf[4 * (256 * (y + j) + (x + i))] = digitGlyps[digit][j][i] ? 255 : 0;
427 }
428 }
429 x += 8;
430 }
431
432 void emitNumber(std::vector<uint8_t>& buf, size_t& x, size_t& y, uint32_t number)
433 {
434
435 std::vector<uint32_t> digits;
436 do {
437 digits.push_back(number % 10);
438 number = number / 10;
439 } while (number);
440
441 for (size_t i = digits.size(); i; i--) {
442 emitDigit(buf, x, y, digits[i - 1]);
443 }
444 }
445
446}
447
448
449
451 const glm::uvec3& tileId,
452 TreeIx treeIx,
453 SlotIx slotIx)
454{
455 assert(slotIx != NoSlotIx);
456 assert(treeIx != NoTreeIx);
457
458 // Format URL
459 std::string path;
460 interpretUrlTemplate(*this, path, tileId);
461 size_t pathHash = Cogs::hash(path);
462
463 // Assert that we are not already loading this tile
464 for (const LoadItem& item : loading) {
465 if (item.treeIx == treeIx) {
466 LOG_WARNING(logger, "Tree index %" PRIu64 " lluu is already loading(%s)", treeIx, path.c_str());
467 }
468 }
469
470
471 // Create new
472 LoadItem& loadItem = loading.emplace_back();
473 loadItem.pathHash = pathHash;
474
475 if (!path.starts_with("http") && !Cogs::IO::exists(path)) {
476
477 // Convenience when working with local not-fully-populated hierarchies
478
479 std::vector<uint8_t> moo(256 * 256 * 4, 0);
480 size_t x = 50;
481 size_t y = 50;
482 emitNumber(moo, x, y, tileId.z);
483 emitDigit(moo, x, y, 10);
484 emitNumber(moo, x, y, tileId.x);
485 emitDigit(moo, x, y, 10);
486 emitNumber(moo, x, y, tileId.y);
487 loadItem.texture = context->textureManager->loadTexture((const void*)moo.data(), Cogs::ResourceDimensions::Texture2D, 256, 256, 1, 1, Cogs::TextureFormat::R8G8B8A8_UNORM_SRGB, 0, TextureHandle::NoHandle, TextureLoadFlags::NoMipMaps);
488 }
489 else {
490 loadItem.texture = context->textureManager->loadTexture(path, NoResourceId, TextureLoadFlags::NoMipMaps);
491 }
492 loadItem.treeIx = treeIx;
493 loadItem.slotIx = slotIx;
494
495 issuedThisFrame++;
496}
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class Variables > variables
Variables service instance.
Definition: Context.h:180
Log implementation class.
Definition: LogManager.h:140
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:50
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
@ NoMipMaps
Do not generate mipmaps.
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
Definition: HashFunctions.h:62
COGSFOUNDATION_API Time currentTime()
High resolution clock time (NTP / UTC time). Returns an implementation defined absolute timestamp,...
static const ResourceHandle_t NoHandle
Handle representing a default (or none if default not present) resource.
void issueFetch(Context *context, const glm::uvec3 &tileId, TreeIx treeIx, SlotIx slotIx)
void update(Context *context, uint32_t currentFrame, uint32_t currentTime, uint32_t timeout, std::string_view urlTemplate)
void processLoadQueue(Context *context, Cache &cache)