Cogs.Core
TextureManager.cpp
1#include "TextureManager.h"
2
3#include "Foundation/Logging/Logger.h"
4#include "Foundation/Platform/Timer.h"
5
6#include "Rendering/ITextures.h"
7
8#include "Renderer/RenderTexture.h"
9
10#include "DataFetcherManager.h"
11
12#include "Services/Services.h"
13#include "Services/Features.h"
14#include "Services/TaskManager.h"
15#include "Services/Variables.h"
16
17#include "Generators/TextureGenerator.h"
18
19#include "Resources/ResourceStore.h"
20
21#include "Context.h"
22
23#include "Types.h"
24
25namespace
26{
27 using namespace Cogs::Core;
28
29 Cogs::Logging::Log logger = Cogs::Logging::getLogger("TextureManager");
30
31 constexpr Cogs::StringView timeLimitName = "resources.textures.mainThreadTimeLimitMs";
32 constexpr Cogs::StringView itemLimitName = "resources.textures.mainThreadItemLimit";
33
34 bool validateTextureParameters(Cogs::ResourceDimensions target, int width, int height, int depth, int layers) {
35 switch (target)
36 {
37 case Cogs::ResourceDimensions::Texture1D:
38 assert(0 < width);
39 assert(1 == height);
40 assert(1 == depth);
41 assert(1 == layers);
42 break;
43 case Cogs::ResourceDimensions::Texture1DArray:
44 assert(0 < width);
45 assert(1 == height);
46 assert(1 == depth);
47 assert(0 < layers);
48 break;
49 case Cogs::ResourceDimensions::Texture2D:
50 assert(0 < width);
51 assert(0 < height);
52 assert(1 == depth);
53 assert(1 == layers);
54 break;
55 case Cogs::ResourceDimensions::Texture2DArray:
56 assert(0 < width);
57 assert(0 < height);
58 assert(1 == depth);
59 assert(0 < layers);
60 break;
61 case Cogs::ResourceDimensions::Texture3D:
62 assert(0 < width);
63 assert(0 < height);
64 assert(0 < depth);
65 assert(1 == layers);
66 break;
67 case Cogs::ResourceDimensions::Texture3DArray:
68 assert(0 < width);
69 assert(0 < height);
70 assert(0 < depth);
71 assert(0 < layers);
72 break;
73 case Cogs::ResourceDimensions::TextureCube:
74 assert(0 < width);
75 assert(0 < height);
76 assert(1 == depth);
77 assert(1 == layers);
78 break;
79 case Cogs::ResourceDimensions::TextureCubeArray:
80 assert(0 < width);
81 assert(0 < height);
82 assert(1 == depth);
83 assert(0 < layers);
84 break;
85 default:
86 LOG_ERROR(logger, "loadTexture: Unhandled target type %d", (int)target);
87 return false;
88 }
89 return true;
90 }
91
92 void setLoadInfoFlags(TextureLoadInfo* loadInfo, Cogs::TextureFormat format, TextureLoadFlags flags)
93 {
94 loadInfo->loadFlags = (ResourceLoadFlags)flags;
95 loadInfo->flip = (flags & TextureLoadFlags::Flip) != 0;
96
97 // Default to mipmaps for all typed non-integer format unless explicitly disabled
98 loadInfo->mipMaps = false;
99 if ((flags & TextureLoadFlags::NoMipMaps) != TextureLoadFlags::NoMipMaps) {
100 if (const Cogs::FormatInfo* info = Cogs::getFormatInfo(format); info) {
101 const Cogs::FormatFlags unfilterable = Cogs::FormatFlags::Integer | Cogs::FormatFlags::Unsigned | Cogs::FormatFlags::Typeless;
102 if ((info->flags & unfilterable) == Cogs::FormatFlags::None) {
103 loadInfo->mipMaps = true;
104 }
105 }
106 }
107 }
108
109 void setLoadInfoExtent(TextureLoadInfo* loadInfo, Cogs::ResourceDimensions target, int width, int height, int depth, int layers, Cogs::TextureFormat format, int stride)
110 {
111 loadInfo->target = target;
112 loadInfo->width = width;
113 loadInfo->height = height;
114 loadInfo->depth = depth;
115 loadInfo->layers = layers;
116 loadInfo->stride = stride;
117 loadInfo->format = format;
118 }
119
120}
121
123{
124 reportLeaks("Texture");
125}
126
128{
129 ResourceManager::initialize();
130 main = std::this_thread::get_id();
131
132 if (!context->variables->exist(timeLimitName)) {
133 context->variables->set(timeLimitName, 1.f);
134 }
135 if (!context->variables->exist(itemLimitName)) {
136 context->variables->set(itemLimitName, 10);
137 }
138
139 // Note: we use non-srgb formats for these textures so that they can be mipmapped on ES2/WebGL.
140 // Since values are either 0 or 255, the result is identical.
141
142 unsigned char bytes [] = {
143 255, 0, 0, 255,
144 0, 255, 0, 255,
145 0, 0, 255, 255,
146 255, 0, 0, 255,
147 };
148
149 defaultResource = create();
150 Texture* texture = get(defaultResource);
151 texture->setName("Default");
152 texture->setData(Cogs::ResourceDimensions::Texture2D, bytes, 4 * 4, 2, 2, TextureFormat::R8G8B8A8_UNORM, true);
153
154 setResourceId(texture, getNextResourceId());
155
156 const uint8_t whiteBytes[] = {
157 255, 255, 255, 255,
158 255, 255, 255, 255,
159 255, 255, 255, 255,
160 255, 255, 255, 255,
161 };
162
163 const uint8_t blackBytes[] = {
164 0, 0, 0, 255,
165 0, 0, 0, 255,
166 0, 0, 0, 255,
167 0, 0, 0, 255,
168 };
169
170 const uint8_t transparentBytes[] = {
171 0, 0, 0, 0,
172 0, 0, 0, 0,
173 0, 0, 0, 0,
174 0, 0, 0, 0,
175 };
176
177 if (!HandleIsValid(white)) {
178 white = loadTexture2D(whiteBytes, 2, 2, TextureFormat::R8G8B8A8_UNORM, 0, NoResourceId, TextureLoadFlags::ForceSynchronous);
179
180 whiteCube = create();
181
182 whiteCube->description.target = ResourceDimensions::TextureCube;
183 whiteCube->description.width = 2;
184 whiteCube->description.height = 2;
185 whiteCube->description.faces = 6;
186 whiteCube->description.flags = TextureFlags::Texture | TextureFlags::CubeMap;
187 whiteCube->description.format = TextureFormat::R8G8B8A8_UNORM;
188 whiteCube->storage.init({ 2, 2, 1 }, 1, 6, 1, whiteCube->description.format);
189
190 auto whiteCubeData = static_cast<uint8_t *>(whiteCube->storage.getData());
191 auto whiteCubeSize = whiteCube->storage.getSize();
192
193 for (size_t i = 0; i < whiteCubeSize; ++i) {
194 whiteCubeData[i] = 255;
195 }
196
197 whiteCube->setChanged();
198
199 white->setName("Cogs.WhiteTexture");
200 whiteCube->setName("Cogs.WhiteCubeMap");
201
202 whiteDepthArray = create();
203 whiteDepthArray->description.target = ResourceDimensions::Texture2DArray;
204 whiteDepthArray->description.width = 2;
205 whiteDepthArray->description.height = 2;
206 whiteDepthArray->description.layers = 1;
207 whiteDepthArray->description.format = TextureFormat::R32_TYPELESS;
208 whiteDepthArray->description.flags = TextureFlags::DepthBuffer | TextureFlags::Texture;
209 whiteDepthArray->setChanged();
210 whiteDepthArray->setName("Cogs.WhiteDepthArray");
211
212 whiteDepthCube = create();
213 whiteDepthCube->description.target = ResourceDimensions::TextureCube;
214 whiteDepthCube->description.width = 2;
215 whiteDepthCube->description.height = 2;
216 whiteDepthCube->description.faces = 6;
217 whiteDepthCube->description.format = TextureFormat::R32_TYPELESS;
218 whiteDepthCube->description.flags = TextureFlags::DepthBuffer | TextureFlags::Texture | TextureFlags::CubeMap;
219 whiteDepthCube->setChanged();
220 whiteDepthCube->setName("Cogs.WhiteDepthCube");
221 }
222
223 LOG_DEBUG(logger, "Initialized texture manager.");
224}
225
226void Cogs::Core::TextureManager::clear()
227{
228 white = ResourceHandle();
229 whiteCube = ResourceHandle();
230 whiteDepthArray = ResourceHandle();
231 whiteDepthCube = ResourceHandle();
233}
234
236{
237 DataFetcherManager::FetchId fetchId = DataFetcherManager::NoFetchId;
238
239 // See if we know about this fetch, and if so, remove this knowledge
240 size_t textureKey = reinterpret_cast<size_t>(handle.get());
241 {
242 LockGuard guard(fetchIds.lock);
243 if (auto it = fetchIds.map.find(textureKey); it != fetchIds.map.end()) {
244 fetchId = it->second;
245 fetchIds.map.erase(it);
246 }
247 }
248
249 // Do cancel the fetch
250 if (fetchId != DataFetcherManager::NoFetchId) {
251 DataFetcherManager::cancelAsyncFetch(context, fetchId);
252 }
253}
254
255Cogs::Core::TextureHandle Cogs::Core::TextureManager::loadTexture(const void * imageData, ResourceDimensions target, int width, int height, int depth, int layers, TextureFormat format, int stride, const ResourceId resourceId, TextureLoadFlags flags)
256{
257 assert((stride == 0) || ( (0 < stride) && (static_cast<size_t>(stride) >= width * Cogs::getBlockSize(format))));
258 assert(width > 0 && height > 0 && depth > 0 && layers > 0);
259
260 int tail = 0;
261 if (stride == 0) {
262 // 32 bit aligned stride:
263 stride = static_cast<int>((width * Cogs::getBlockSize(format) + 3) & ~3);
264 tail = static_cast<int>(stride - width * Cogs::getBlockSize(format));
265 }
266 auto data = static_cast<const uint8_t *>(imageData);
267
268 if (!validateTextureParameters(target, width, height, depth, layers)) {
270 }
271
272 TextureLoadInfo * loadInfo = createLoadInfo();
273 setLoadInfoExtent(loadInfo, target, width, height, depth, layers, format, stride);
274 setLoadInfoFlags(loadInfo, format, flags);
275 loadInfo->resourceId = resourceId;
276
277 if (data) {
278 loadInfo->resourceData.assign(data, data + stride * height * depth * layers - tail);
279 }
280 return loadTexture(loadInfo);
281}
282
283Cogs::Core::TextureHandle Cogs::Core::TextureManager::loadTexture(const void * imageData, ResourceDimensions target, int width, int height, int depth, int layers, TextureFormat format, int stride, const TextureHandle& resourceHandle, TextureLoadFlags flags) {
284 assert((stride == 0) || ( (0 < stride) && (static_cast<size_t>(stride) >= width * Cogs::getBlockSize(format))));
285 assert(width > 0 && height > 0 && depth > 0 && layers > 0);
286
287 int tail = 0;
288 if (stride == 0) {
289 // 32 bit aligned stride:
290 stride = static_cast<int>((width * Cogs::getBlockSize(format) + 3) & ~3);
291 tail = static_cast<int>(stride - width * Cogs::getBlockSize(format));
292 }
293 auto data = static_cast<const uint8_t *>(imageData);
294
295 if (!validateTextureParameters(target, width, height, depth, layers)) {
297 }
298
299 TextureLoadInfo * loadInfo = createLoadInfo();
300 setLoadInfoExtent(loadInfo, target, width, height, depth, layers, format, stride);
301 setLoadInfoFlags(loadInfo, format, flags);
302 loadInfo->handle = resourceHandle;
303
304 if (data) {
305 loadInfo->resourceData.assign(data, data + stride * height * depth * layers - tail);
306 }
307 return loadResource(loadInfo);
308}
309
311{
312 TextureLoadInfo * loadInfo = createLoadInfo();
313 loadInfo->resourceId = resourceId;
314 loadInfo->resourcePath = resourceName.to_string();
315 loadInfo->format = ((flags & TextureLoadFlags::LinearColorSpace) != 0) ? TextureFormat::R8G8B8A8_UNORM : TextureFormat::R8G8B8A8_UNORM_SRGB;
316 loadInfo->loadFlags = (ResourceLoadFlags)flags;
317 loadInfo->flip = (flags & TextureLoadFlags::Flip) != 0;
318 // If the texture is flipped on load make sure we don't store the source path
319 // since it doesn't match the in-memory texture anymore.
320 if (loadInfo->flip) loadInfo->loadFlags |= ResourceLoadFlags::DoNotStoreSource;
321
322 if (context->variables->get("resources.textures.autoReload", false)) {
323 loadInfo->loadFlags |= ResourceLoadFlags::AutoReload;
324 }
325
326 return loadResource(loadInfo);
327}
328
329TextureHandle Cogs::Core::TextureManager::loadTextureFromMemory(const void* dataPtr, const size_t dataSize, const StringView& resourcePath, const ResourceId resourceId, TextureLoadFlags flags)
330{
331 TextureLoadInfo* loadInfo = createLoadInfo();
332 loadInfo->resourceId = resourceId;
333 loadInfo->resourcePath = resourcePath.to_string();
334 loadInfo->format = ((flags & TextureLoadFlags::LinearColorSpace) != 0) ? TextureFormat::R8G8B8A8_UNORM : TextureFormat::R8G8B8A8_UNORM_SRGB;
336 loadInfo->flip = (flags & TextureLoadFlags::Flip) != 0;
337 loadInfo->mipMaps = !(bool)((TextureLoadFlags)(flags & TextureLoadFlags::NoMipMaps));
338 // NOTE(Markus): This is a workaround, since dataset from Chevron incorrectly says it is linear, when it is really srgb.
339 // GLTF spec expects srgb for basisu ktx2 extension.
340 loadInfo->getColorSpaceFromLoadInfo = ((flags & TextureLoadFlags::ColorSpaceFromLoadInfo) == TextureLoadFlags::ColorSpaceFromLoadInfo) ? (true) : (false);
341 // If the texture is flipped on load make sure we don't store the source path
342 // since it doesn't match the in-memory texture anymore.
343 if (loadInfo->flip) loadInfo->loadFlags |= ResourceLoadFlags::DoNotStoreSource;
344
345 // Copy data into resourceData
346 loadInfo->resourceData.assign(static_cast<const uint8_t*>(dataPtr), static_cast<const uint8_t*>(dataPtr) + dataSize);
347
348 return loadResource(loadInfo);
349}
350
351
353{
354 return loadResource(loadInfo);
355}
356
358{
359 if (path.empty()) {
360 LOG_ERROR(logger, "Cannot fetch texture using empty path.");
362 }
363
364#if 1
365 auto schemeEndLoc = path.find("://");
366 if (schemeEndLoc != StringView::NoPosition) {
367 auto schemeHash = path.substr(0, schemeEndLoc).hash();
368
370 ParsedValue attributes;
371 auto queryLoc = path.find("?");
372 if (queryLoc != StringView::NoPosition) {
373 parseQueryString(attributes.values, path.substr(queryLoc + 1));
374
375 for (auto & item : attributes.values) {
376 switch (StringView(item.key).hash()) {
377 case Cogs::hash("mipmaps"):
378 case Cogs::hash("mips"): {
379 bool rv = true;
380 item.asBool(rv);
381 if (rv == false) {
382 loadFlags |= TextureLoadFlags::NoMipMaps;
383 }
384 break;
385 }
386 case Cogs::hash("linear"): {
387 bool rv = true;
388 item.asBool(rv);
389 if (rv) {
391 }
392 break;
393 }
394 default:
395 break;
396 }
397
398 }
399 }
400
401 auto nakedPath = path.substr(schemeEndLoc + 3, queryLoc == StringView::NoPosition ? queryLoc : queryLoc - schemeEndLoc - 3);
402 switch (schemeHash)
403 {
404 case Cogs::hash("file"):
405 return loadTexture(nakedPath, NoResourceId, loadFlags);
406 break;
407#if defined( __EMSCRIPTEN__ )
408 case Cogs::hash("http"):
409 case Cogs::hash("https"):
410 return loadTexture(path, NoResourceId, loadFlags);
411 break;
412#endif
413 case Cogs::hash("linear"):
415 return loadTexture(nakedPath, NoResourceId, loadFlags);
416 break;
417 case Cogs::hash("generator"):
418 return context->services->getService<TextureGenerator>()->getTexture(parseEnum(nakedPath, ImageType::None), attributes);
419 break;
420 default:
421 LOG_ERROR(logger, "Unknown URI scheme '%s'", path.substr(0, schemeEndLoc).to_string().c_str());
423 break;
424 }
425 }
426
427#else
428 auto fileLoc = path.find("file://");
429 auto linearLoc = path.find("linear://");
430 auto generatorLoc = path.find("generator://");
431
432 if (linearLoc != StringView::NoPosition) {
433 return loadTexture(path.substr(9), NoResourceId, TextureLoadFlags::LinearColorSpace);
434 } else if (fileLoc != StringView::NoPosition) {
435 return loadTexture(path.substr(7), NoResourceId, TextureLoadFlags::None);
436 } else if (generatorLoc != StringView::NoPosition) {
437 return context->services->getService<TextureGenerator>()->getTexture(parseEnum(path.substr(12), ImageType::None));
438 }
439#endif
440 else if (!path.empty()) {
441 auto name = path[0] == '$' ? path.substr(1) : path;
442
443 auto handle = getByName(name);
444
445 if (!HandleIsValid(handle) && !isQuery) {
446 LOG_ERROR(logger, "Could not resolve texture with name %.*s", StringViewFormat(path));
447 }
448
449 return handle;
450 }
452}
453
455{
456 if (fetchedItems.empty()) return;
457
458 double timeLimitSeconds = 0.001 * context->variables->get(timeLimitName, 0.f);
459 int itemLimit = context->variables->get(itemLimitName, 0);
460
461 int texturesLoaded = 0;
462 Cogs::Timer processTimer = Cogs::Timer::startNew();
463 while (!fetchedItems.empty()) {
464 FetchedItem item = std::move(fetchedItems.front());
465 fetchedItems.pop();
466
467 TextureLoadInfo* loadInfo = item.loadInfo;
468 if (!loadInfo->resourceData.empty()) {
469 invokeLoader(item.loadedLoader, item.loadInfo);
470 }
471 else {
472 if (processFetchedItem(item.loadedLoader, loadInfo, std::move(item.data))) {
473 texturesLoaded++;
474 }
475 }
476
477 // Model loaded successfully
478 if ((0 < itemLimit) && (itemLimit <= texturesLoaded)) {
479 // Hit item limit, stop processing this frame.
480 break;
481 }
482 if ((0.f < timeLimitSeconds) && (timeLimitSeconds <= processTimer.elapsedSeconds())) {
483 // Hit time limit, stop processing this frame.
484 break;
485 }
486 }
487
488 // If we are not done, trigger a new frame so we can continue.
489 if (!fetchedItems.empty()) {
490 // Not done, we need another frame where we can continue.
491 context->engine->setDirty();
492 }
493}
494
495
497{
498 bool preLoad = context->variables->get("resources.textures.preLoad", false);
499
500 if (preLoad && loadInfo->resourcePath.size()) {
501 if (!checkPreloaded(loadInfo)) return;
502 }
503
504 if (!loadInfo->resourcePath.empty()) {
505 loadFromPath(loadInfo);
506 }
507 else {
508
509 // Load from raw data blob
510 if (loadInfo->loadSync()) {
511 loadFromData(loadInfo);
512 setProcessed(loadInfo, !loadInfo->loadSync());
513 }
514 else {
515 context->taskManager->enqueue(TaskManager::ResourceQueue, [this, loadInfo]()
516 {
517 loadFromData(loadInfo);
518 setProcessed(loadInfo, !loadInfo->loadSync());
519 });
520 }
521 };
522
523}
524
525void Cogs::Core::TextureManager::handleReload(ResourceHandleBase handle)
526{
527 TextureHandle texture(handle);
528
529 auto * loadInfo = createLoadInfo();
530 loadInfo->resourceId = texture->getId();
531 loadInfo->resourcePath = texture->getSource().to_string();
533 loadInfo->handle = handle;
534
535 loadResource(loadInfo);
536}
537
538Cogs::Core::TextureHandle Cogs::Core::TextureManager::loadExternalTexture(intptr_t externalHandle, ResourceDimensions target, int width, int height, int depth, int layers, TextureFormat format, const ResourceId resourceId, TextureLoadFlags flags)
539{
540 TextureHandle handle = getOrCreate(resourceId);
541
542 auto texture = get(handle);
543
544 texture->setId(resourceId);
545 texture->description = TextureDescription{};
546 texture->description.target = target;
547 texture->description.width = width;
548 texture->description.height = height;
549 texture->description.depth = depth;
550 texture->description.faces = (target == ResourceDimensions::TextureCube || target == ResourceDimensions::TextureCubeArray) ? 6 : 1;
551 texture->description.layers = layers;
552 texture->description.format = format;
553 texture->externalHandle = externalHandle;
554 texture->hasAlpha = getFormatInfo(format)->elements == 4;
555
557 texture->description.flags |= TextureFlags::GenerateMipMaps;
558 }
559
561 texture->description.flags |= TextureFlags::NoDelete;
562 }
563
564 // Queue the resource for activation.
565 texture->setChanged();
566
567 return handle;
568}
569
571{
572 return context->renderer->getResources()->updateResource(handle);
573}
574
576{
577 context->renderer->getResources()->releaseResource(texture);
578}
579
580bool Cogs::Core::TextureManager::processFetchedItem(ILoadedTextureLoader* loadedLoader, TextureLoadInfo* loadInfo, std::unique_ptr<FileContents> data)
581{
582 // Check if we have been cancelled and remove the fetch id
583 bool cancelled = true;
584 {
585 size_t textureKey = reinterpret_cast<size_t>(loadInfo->handle.get());
586 LockGuard guard(fetchIds.lock);
587 if (auto it = fetchIds.map.find(textureKey); it != fetchIds.map.end()) {
588 fetchIds.map.erase(it);
589 cancelled = false;
590 }
591 }
592
593 bool success = false;
594
595 // If texture has been cancelled, we just stop processing and treat it as failed
596 if (cancelled) {
597 LOG_TRACE(logger, "Cancelled texture %s", loadInfo->resourcePath.c_str());
598 loadInfo->handle->setFailedLoad();
599 }
600
601 // If texture has been abandoned while we're fetching, just drop it.
602 else if (loadInfo->handle->referenceCount() <= 1) {
603 LOG_TRACE(logger, "Abandoned texture received in async callback, skipping further processing");
604 loadInfo->handle->setFailedLoad();
605 }
606
607 // If we have no data, the fetch has failed
608 else if (!data) {
609 LOG_ERROR(logger, "Error fetching texture %s", loadInfo->resourcePath.c_str());
610 loadInfo->handle->setFailedLoad();
611 }
612
613 // And finally we try to actually load the contents
614 else if (loadedLoader->load(context, *loadInfo, data->ptr, data->size)) {
615 success = true;
616 }
617 else {
618 LOG_ERROR(logger, "Error decoding texture %s", loadInfo->resourcePath.c_str());
619 loadInfo->handle->setFailedLoad();
620 }
621
622 setProcessed(loadInfo, !loadInfo->loadSync());
623 return success;
624}
625
626bool Cogs::Core::TextureManager::invokeLoader(ITextureLoader* loader, TextureLoadInfo* loadInfo)
627{
628 assert(loader);
629 bool success = loader->load(context, *loadInfo);
630 if (!success) {
631 LOG_ERROR(logger, "Error loading texture %s.", loadInfo->resourcePath.c_str());
632 loadInfo->handle->setFailedLoad();
633 }
634 setProcessed(loadInfo, !loadInfo->loadSync());
635 return success;
636}
637
638bool Cogs::Core::TextureManager::invokeLoader(ILoadedTextureLoader* loadedLoader, TextureLoadInfo* loadInfo)
639{
640 assert(loadedLoader);
641 bool success = loadedLoader->load(context, *loadInfo, loadInfo->resourceData.data(), loadInfo->resourceData.size());
642 if (!success) {
643 LOG_ERROR(logger, "Error loading texture from %s.", loadInfo->resourceName.c_str());
644 loadInfo->handle->setFailedLoad();
645 }
646 setProcessed(loadInfo, true);
647 return success;
648}
649
650void Cogs::Core::TextureManager::loadFromPath(TextureLoadInfo * loadInfo)
651{
652 assert(!loadInfo->resourcePath.empty());
653
654 ITextureLoader* loader = findLoader(loadInfo);
655 if (!loader) {
656 LOG_ERROR(logger, "No suitable texture loader found for %s.", loadInfo->resourcePath.c_str());
657 loadInfo->handle->setFailedLoad();
658 setProcessed(loadInfo, !loadInfo->loadSync());
659 return;
660 }
661
662 // If we have both path and resource data, the path is just used to determine loader,
663 // but the resourceData contains the actual data
664 if (!loadInfo->resourceData.empty()) {
665
666 ILoadedTextureLoader* loadedLoader = dynamic_cast<ILoadedTextureLoader*>(loader);
667 if(!loadedLoader) {
668 LOG_ERROR(logger, "Texture loader for %s does not support consuming data from an inline blob.", loadInfo->resourcePath.c_str());
669 loadInfo->handle->setFailedLoad();
670 setProcessed(loadInfo, !loadInfo->loadSync());
671 }
672 else if (loadInfo->loadSync()) {
673 invokeLoader(loadedLoader, loadInfo);
674 }
675 else if(context->taskManager->getQueueConcurrency(TaskManager::ResourceQueue)) {
676 context->taskManager->enqueue(TaskManager::ResourceQueue, [this, loadedLoader, loadInfo]() { invokeLoader(loadedLoader, loadInfo); });
677 }
678 else {
679 fetchedItems.push(FetchedItem{ .data = nullptr, .loadInfo = loadInfo, .loadedLoader = loadedLoader });
680 }
681 return;
682 }
683
684 // If load is requested to be sync, we must just try to read the file
685 if (loadInfo->loadSync()) {
686 invokeLoader(loader, loadInfo);
687 return;
688 }
689
690 // Unless the path is in the resource store, we try to load it asynchronously
691 if (!context->resourceStore->hasResource(loadInfo->resourcePath) && loadInfo->protocol != ResourceProtocol::Archive) {
692
693 // Async loaders are loaders where the loader does the actual async stuff. Only relevant implementation
694 // is the WebTexLoader that handles async load + DOM decoding in js land. This loader takes the ownership
695 // of the loadInfo and will call setProcessed with it.
696 if (IAsyncTextureLoader* asyncLoader = dynamic_cast<IAsyncTextureLoader*>(loader); asyncLoader) {
697 asyncLoader->load(context, loadInfo);
698 return;
699 }
700
701 // LoadedLoaders are loaders that can interpret a chunk of memory. With such a loader, we can do an async
702 // fetch of a blob of data and pass the data to the loader when we receive it.
703 if (ILoadedTextureLoader* loadedLoader = dynamic_cast<ILoadedTextureLoader*>(loader); loadedLoader) {
704
705 // We have a potential race condition since the callback that removes the
706 // cancellation id can either run during the fetch call or after, so we add
707 // an item now so we can detect and handle this situation.
708 size_t textureKey = reinterpret_cast<size_t>(loadInfo->handle.get());
709 {
710 LockGuard guard(fetchIds.lock);
711 fetchIds.map[textureKey] = DataFetcherManager::NoFetchId;
712 }
713
714 // Handler that runs when the fetch has finished
715 FileContents::Callback handleResult = [this, loadedLoader, loadInfo](std::unique_ptr<FileContents> data) {
716 // If we are in the main thread, we queue the response to be processed during the engine update
717 if (main == std::this_thread::get_id()) {
718 fetchedItems.push(FetchedItem{ .data = std::move(data), .loadInfo = loadInfo, .loadedLoader = loadedLoader });
719 context->engine->setDirty();
720 return;
721 }
722 processFetchedItem(loadedLoader, loadInfo, std::move(data));
723 };
724
725 // Fire off the fetch
726 DataFetcherManager::FetchId fetchId = DataFetcherManager::fetchAsync(context, loadInfo->resourcePath, handleResult, 0, 0, true);
727
728 // Update the map running fetches
729 {
730 LockGuard guard(fetchIds.lock);
731 if (auto it = fetchIds.map.find(textureKey); it != fetchIds.map.end()) {
732 it->second = fetchId;
733 }
734 }
735
736 // Fetch handler has taken ownership of the loadInfo
737 return;
738 }
739 }
740
741 // Load is not sync and loader does not support async etc, so we just fire off a task loading it from file
742 context->taskManager->enqueue(TaskManager::ResourceQueue, [this, loader, loadInfo]() { invokeLoader(loader, loadInfo); });
743}
744
745void Cogs::Core::TextureManager::loadFromData(TextureLoadInfo * loadInfo)
746{
747 auto texture = lock(loadInfo->handle);
748
749 texture->description.target = loadInfo->target;
750
751 switch (loadInfo->target) {
752 case ResourceDimensions::Texture1D:
753 texture->setData(loadInfo->target,
754 loadInfo->resourceData.data(),
755 loadInfo->resourceData.size(),
756 loadInfo->width,
757 1 /* height */,
758 1 /* depth */,
759 1 /* layers */,
760 1 /* faces */,
761 1 /* levels*/,
762 loadInfo->format,
763 loadInfo->mipMaps);
764 break;
765 case ResourceDimensions::Texture1DArray:
766 texture->setData(loadInfo->target,
767 loadInfo->resourceData.data(),
768 loadInfo->resourceData.size(),
769 loadInfo->width,
770 1 /* height */,
771 1 /* depth */,
772 loadInfo->layers,
773 1 /* faces */,
774 1 /* levels*/,
775 loadInfo->format,
776 loadInfo->mipMaps);
777 break;
778 case ResourceDimensions::Texture2D: {
779 void* data;
780 uint8_t* copy = nullptr;
781
782 if (loadInfo->flip) {
783 copy = new uint8_t[loadInfo->resourceData.size()];
784
785 const uint8_t* read = loadInfo->resourceData.data();
786 uint8_t* write = copy + loadInfo->resourceData.size();
787 size_t stride = loadInfo->stride;
788
789 assert(loadInfo->resourceData.size() == loadInfo->height * stride);
790
791 for (int y = loadInfo->height; y--; ) {
792 write -= stride;
793 memcpy(write, read, stride);
794 read += stride;
795 }
796 data = copy;
797 }
798 else {
799 data = loadInfo->resourceData.data();
800 }
801 texture->setData(loadInfo->target,
802 data,
803 loadInfo->resourceData.size(),
804 loadInfo->width,
805 loadInfo->height,
806 1 /* depth */,
807 1 /* layers */,
808 1 /* faces */,
809 1 /* levels*/,
810 loadInfo->format,
811 loadInfo->mipMaps);
812 texture->hasAlpha = getFormatInfo(loadInfo->format)->elements == 4;
813
814 delete [] copy;
815 break;
816 }
817 case ResourceDimensions::Texture2DArray:
818 texture->setData(loadInfo->target,
819 loadInfo->resourceData.data(),
820 loadInfo->resourceData.size(),
821 loadInfo->width,
822 loadInfo->height,
823 1 /* depth */,
824 loadInfo->layers,
825 1 /* faces */,
826 1 /* levels*/,
827 loadInfo->format,
828 loadInfo->mipMaps);
829 break;
830 case ResourceDimensions::Texture3D:
831 texture->setData(loadInfo->target,
832 loadInfo->resourceData.data(),
833 loadInfo->resourceData.size(),
834 loadInfo->width,
835 loadInfo->height,
836 loadInfo->depth,
837 1 /* layers */,
838 1 /* faces */,
839 1 /* levels*/,
840 loadInfo->format,
841 loadInfo->mipMaps);
842 break;
843 case ResourceDimensions::Texture3DArray:
844 texture->setData(loadInfo->target,
845 loadInfo->resourceData.data(),
846 loadInfo->resourceData.size(),
847 loadInfo->width,
848 loadInfo->height,
849 loadInfo->depth,
850 loadInfo->layers,
851 1 /* faces */,
852 1 /* levels*/,
853 loadInfo->format,
854 loadInfo->mipMaps);
855 break;
856 case ResourceDimensions::TextureCube:
857 texture->setData(loadInfo->target,
858 loadInfo->resourceData.data(),
859 loadInfo->resourceData.size(),
860 loadInfo->width,
861 loadInfo->height,
862 1 /* depth*/,
863 1 /* layers */,
864 6 /* faces */,
865 1 /* levels*/,
866 loadInfo->format,
867 loadInfo->mipMaps);
868 break;
869 case ResourceDimensions::TextureCubeArray:
870 texture->setData(loadInfo->target,
871 loadInfo->resourceData.data(),
872 loadInfo->resourceData.size(),
873 loadInfo->width,
874 loadInfo->height,
875 1 /* depth*/,
876 loadInfo->layers,
877 6 /* faces */,
878 1 /* levels*/,
879 loadInfo->format,
880 loadInfo->mipMaps);
881 break;
882 default:
883 assert(false && "Unhandled texture target");
884 break;
885 }
886
888 texture->description.flags |= Cogs::TextureFlags::RenderTarget;
889 }
890}
void clear() override
Clear the resource manager, cleaning up resources held by member handles.
static constexpr TaskQueueId ResourceQueue
Resource task queue.
Definition: TaskManager.h:232
void postProcessLoading() override final
Hook for resource managers to run code at the tail of processLoading.
void handleDeletion(Texture *texture) override
Overridden to handle texture deletion, removing the texture resource from the renderer.
TextureHandle loadTexture(const void *imageData, ResourceDimensions target, int width, int height, int depth, int layers, TextureFormat format, int stride, const ResourceId resourceId, TextureLoadFlags flags)
Load a texture with the given data.
void handleLoad(TextureLoadInfo *loadInfo) override
~TextureManager()
Destructs the texture manager.
ActivationResult handleActivation(TextureHandle handle, Texture *texture) override
Overridden to handle texture activation, updating the texture resource in the renderer.
void cancelTextureLoad(TextureHandle handle)
Notify that the texture isn't needed anymore and the texture load can be cancelled if posible.
TextureHandle loadTextureFromMemory(const void *dataPtr, const size_t dataSize, const StringView &resourcePath, const ResourceId resourceId, TextureLoadFlags flags)
Loads an encoded texture from data in memory.
TextureHandle getTexture(const StringView &path, bool isQuery=false)
Gets the texture with the given path.
void initialize() override
Initialize the texture manager. Creates the default texture resource.
TextureHandle loadExternalTexture(intptr_t externalHandle, ResourceDimensions target, int width, int height, int depth, int layers, TextureFormat format, const ResourceId resourceId, TextureLoadFlags flags)
Loads a texture resource wrapping the external texture data so it may be used in the Engine like an i...
Log implementation class.
Definition: LogManager.h:140
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:50
static constexpr size_t NoPosition
No position.
Definition: StringView.h:69
std::string to_string() const
String conversion method.
Definition: StringView.cpp:9
constexpr size_t hash() const noexcept
Get the hash code of the string.
Definition: StringView.h:226
High-resolution performance timer.
Definition: Timer.h:46
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
ActivationResult
Defines results for resource activation.
Definition: ResourceBase.h:14
bool HandleIsValid(const ResourceHandle_t< T > &handle)
Check if the given resource is valid, that is not equal to NoHandle or InvalidHandle.
ResourceLoadFlags
Flags for describing how to load a resource.
Definition: ResourceFlags.h:16
@ DoNotStoreSource
Don't store the source.
TextureLoadFlags
Texture loading flags. May be combined with resource load flags.
Definition: ResourceFlags.h:50
@ LinearColorSpace
For textures with RGBA format without color space information, mark the data as being in linear color...
@ ColorSpaceFromLoadInfo
by default we want to retrieve colorspace info from the texture data, not from the format specified i...
@ NoDelete
Do not assume ownership of external texture so it won't be deleted by cogs.
@ ForceUnique
Force unique resource load when source resolves to existing resource.
@ RenderTarget
Set the usage flag of the texture to RenderTarget.
@ Flip
Flip the texture data vertically before it is passed to the rendering backend.
@ NoMipMaps
Do not generate mipmaps.
@ ForceSynchronous
Force loading the resource synchronously.
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
Stores the parsed output of a key/value pair.
Definition: Parsing.h:40
void setName(const StringView &name)
Set the user friendly name of the resource.
Definition: ResourceBase.h:298
uint32_t referenceCount() const
Get the current reference count.
Definition: ResourceBase.h:360
Resource handle base class handling reference counting of resources derived from ResourceBase.
static const ResourceHandle_t NoHandle
Handle representing a default (or none if default not present) resource.
std::string resourcePath
Resource path. Used to locate resource.
std::string resourceName
Desired resource name. If no name is given, a default name will be chosen.
ResourceId resourceId
Unique resource identifier. Must be unique among resources of the same kind.
ResourceHandleBase handle
Handle to resource structure for holding actual resource data.
std::vector< uint8_t > resourceData
Resource load data.
ResourceLoadFlags loadFlags
Desired loading flags. Used to specify how the resource will be loaded.
Texture resources contain raster bitmap data to use for texturing.
Definition: Texture.h:91
void setData(ResourceDimensions target, const void *data, size_t size, int width, int height, TextureFormat format, bool generateMipMap)
Set the texture data.
Definition: Texture.cpp:54
uint16_t elements
Number of channels in a data item.
Definition: DataFormat.h:263
@ DepthBuffer
The texture can be used as a depth target and have depth buffer values written into.
Definition: Flags.h:122
@ NoDelete
The ownership of the underlying texture resource is outside of cogs and cogs will not delete it.
Definition: Flags.h:136
@ RenderTarget
The texture can be used as a render target and drawn into.
Definition: Flags.h:120
@ GenerateMipMaps
The texture supports automatic mipmap generation performed by the graphics device.
Definition: Flags.h:124
@ Texture
Texture usage, see Default.
Definition: Flags.h:118
@ CubeMap
The texture can be used as a cube map.
Definition: Flags.h:126