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
203 LOG_DEBUG(logger, "Initialized texture manager.");
204}
205
206void Cogs::Core::TextureManager::clear()
207{
208 white = ResourceHandle();
209 whiteCube = ResourceHandle();
211}
212
214{
215 DataFetcherManager::FetchId fetchId = DataFetcherManager::NoFetchId;
216
217 // See if we know about this fetch, and if so, remove this knowledge
218 size_t textureKey = reinterpret_cast<size_t>(handle.get());
219 {
220 LockGuard guard(fetchIds.lock);
221 if (auto it = fetchIds.map.find(textureKey); it != fetchIds.map.end()) {
222 fetchId = it->second;
223 fetchIds.map.erase(it);
224 }
225 }
226
227 // Do cancel the fetch
228 if (fetchId != DataFetcherManager::NoFetchId) {
229 DataFetcherManager::cancelAsyncFetch(context, fetchId);
230 }
231}
232
233Cogs::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)
234{
235 assert((stride == 0) || ( (0 < stride) && (static_cast<size_t>(stride) >= width * Cogs::getBlockSize(format))));
236 assert(width > 0 && height > 0 && depth > 0 && layers > 0);
237
238 int tail = 0;
239 if (stride == 0) {
240 // 32 bit aligned stride:
241 stride = static_cast<int>((width * Cogs::getBlockSize(format) + 3) & ~3);
242 tail = static_cast<int>(stride - width * Cogs::getBlockSize(format));
243 }
244 auto data = static_cast<const uint8_t *>(imageData);
245
246 if (!validateTextureParameters(target, width, height, depth, layers)) {
248 }
249
250 TextureLoadInfo * loadInfo = createLoadInfo();
251 setLoadInfoExtent(loadInfo, target, width, height, depth, layers, format, stride);
252 setLoadInfoFlags(loadInfo, format, flags);
253 loadInfo->resourceId = resourceId;
254
255 if (data) {
256 loadInfo->resourceData.assign(data, data + stride * height * depth * layers - tail);
257 }
258 return loadTexture(loadInfo);
259}
260
261Cogs::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) {
262 assert((stride == 0) || ( (0 < stride) && (static_cast<size_t>(stride) >= width * Cogs::getBlockSize(format))));
263 assert(width > 0 && height > 0 && depth > 0 && layers > 0);
264
265 int tail = 0;
266 if (stride == 0) {
267 // 32 bit aligned stride:
268 stride = static_cast<int>((width * Cogs::getBlockSize(format) + 3) & ~3);
269 tail = static_cast<int>(stride - width * Cogs::getBlockSize(format));
270 }
271 auto data = static_cast<const uint8_t *>(imageData);
272
273 if (!validateTextureParameters(target, width, height, depth, layers)) {
275 }
276
277 TextureLoadInfo * loadInfo = createLoadInfo();
278 setLoadInfoExtent(loadInfo, target, width, height, depth, layers, format, stride);
279 setLoadInfoFlags(loadInfo, format, flags);
280 loadInfo->handle = resourceHandle;
281
282 if (data) {
283 loadInfo->resourceData.assign(data, data + stride * height * depth * layers - tail);
284 }
285 return loadResource(loadInfo);
286}
287
289{
290 TextureLoadInfo * loadInfo = createLoadInfo();
291 loadInfo->resourceId = resourceId;
292 loadInfo->resourcePath = resourceName.to_string();
293 loadInfo->format = ((flags & TextureLoadFlags::LinearColorSpace) != 0) ? TextureFormat::R8G8B8A8_UNORM : TextureFormat::R8G8B8A8_UNORM_SRGB;
294 loadInfo->loadFlags = (ResourceLoadFlags)flags;
295 loadInfo->flip = (flags & TextureLoadFlags::Flip) != 0;
296 // If the texture is flipped on load make sure we don't store the source path
297 // since it doesn't match the in-memory texture anymore.
298 if (loadInfo->flip) loadInfo->loadFlags |= ResourceLoadFlags::DoNotStoreSource;
299
300 if (context->variables->get("resources.textures.autoReload", false)) {
301 loadInfo->loadFlags |= ResourceLoadFlags::AutoReload;
302 }
303
304 return loadResource(loadInfo);
305}
306
307TextureHandle Cogs::Core::TextureManager::loadTextureFromMemory(const void* dataPtr, const size_t dataSize, const StringView& resourcePath, const ResourceId resourceId, TextureLoadFlags flags)
308{
309 TextureLoadInfo* loadInfo = createLoadInfo();
310 loadInfo->resourceId = resourceId;
311 loadInfo->resourcePath = resourcePath.to_string();
312 loadInfo->format = ((flags & TextureLoadFlags::LinearColorSpace) != 0) ? TextureFormat::R8G8B8A8_UNORM : TextureFormat::R8G8B8A8_UNORM_SRGB;
314 loadInfo->flip = (flags & TextureLoadFlags::Flip) != 0;
315 loadInfo->mipMaps = !(bool)((TextureLoadFlags)(flags & TextureLoadFlags::NoMipMaps));
316 // NOTE(Markus): This is a workaround, since dataset from Chevron incorrectly says it is linear, when it is really srgb.
317 // GLTF spec expects srgb for basisu ktx2 extension.
318 loadInfo->getColorSpaceFromLoadInfo = ((flags & TextureLoadFlags::ColorSpaceFromLoadInfo) == TextureLoadFlags::ColorSpaceFromLoadInfo) ? (true) : (false);
319 // If the texture is flipped on load make sure we don't store the source path
320 // since it doesn't match the in-memory texture anymore.
321 if (loadInfo->flip) loadInfo->loadFlags |= ResourceLoadFlags::DoNotStoreSource;
322
323 // Copy data into resourceData
324 loadInfo->resourceData.assign(static_cast<const uint8_t*>(dataPtr), static_cast<const uint8_t*>(dataPtr) + dataSize);
325
326 return loadResource(loadInfo);
327}
328
329
331{
332 return loadResource(loadInfo);
333}
334
336{
337 if (path.empty()) {
338 LOG_ERROR(logger, "Cannot fetch texture using empty path.");
340 }
341
342#if 1
343 auto schemeEndLoc = path.find("://");
344 if (schemeEndLoc != StringView::NoPosition) {
345 auto schemeHash = path.substr(0, schemeEndLoc).hash();
346
348 ParsedValue attributes;
349 auto queryLoc = path.find("?");
350 if (queryLoc != StringView::NoPosition) {
351 parseQueryString(attributes.values, path.substr(queryLoc + 1));
352
353 for (auto & item : attributes.values) {
354 switch (StringView(item.key).hash()) {
355 case Cogs::hash("mipmaps"):
356 case Cogs::hash("mips"): {
357 bool rv = true;
358 item.asBool(rv);
359 if (rv == false) {
360 loadFlags |= TextureLoadFlags::NoMipMaps;
361 }
362 break;
363 }
364 case Cogs::hash("linear"): {
365 bool rv = true;
366 item.asBool(rv);
367 if (rv) {
369 }
370 break;
371 }
372 default:
373 break;
374 }
375
376 }
377 }
378
379 auto nakedPath = path.substr(schemeEndLoc + 3, queryLoc == StringView::NoPosition ? queryLoc : queryLoc - schemeEndLoc - 3);
380 switch (schemeHash)
381 {
382 case Cogs::hash("file"):
383 return loadTexture(nakedPath, NoResourceId, loadFlags);
384 break;
385#if defined( EMSCRIPTEN )
386 case Cogs::hash("http"):
387 case Cogs::hash("https"):
388 return loadTexture(path, NoResourceId, loadFlags);
389 break;
390#endif
391 case Cogs::hash("linear"):
393 return loadTexture(nakedPath, NoResourceId, loadFlags);
394 break;
395 case Cogs::hash("generator"):
396 return context->services->getService<TextureGenerator>()->getTexture(parseEnum(nakedPath, ImageType::None), attributes);
397 break;
398 default:
399 LOG_ERROR(logger, "Unknown URI scheme '%s'", path.substr(0, schemeEndLoc).to_string().c_str());
401 break;
402 }
403 }
404
405#else
406 auto fileLoc = path.find("file://");
407 auto linearLoc = path.find("linear://");
408 auto generatorLoc = path.find("generator://");
409
410 if (linearLoc != StringView::NoPosition) {
411 return loadTexture(path.substr(9), NoResourceId, TextureLoadFlags::LinearColorSpace);
412 } else if (fileLoc != StringView::NoPosition) {
413 return loadTexture(path.substr(7), NoResourceId, TextureLoadFlags::None);
414 } else if (generatorLoc != StringView::NoPosition) {
415 return context->services->getService<TextureGenerator>()->getTexture(parseEnum(path.substr(12), ImageType::None));
416 }
417#endif
418 else if (!path.empty()) {
419 auto name = path[0] == '$' ? path.substr(1) : path;
420
421 auto handle = getByName(name);
422
423 if (!HandleIsValid(handle) && !isQuery) {
424 LOG_ERROR(logger, "Could not resolve texture with name %.*s", StringViewFormat(path));
425 }
426
427 return handle;
428 }
430}
431
433{
434 if (fetchedItems.empty()) return;
435
436 double timeLimitSeconds = 0.001 * context->variables->get(timeLimitName, 0.f);
437 int itemLimit = context->variables->get(itemLimitName, 0);
438
439 int texturesLoaded = 0;
440 Cogs::Timer processTimer = Cogs::Timer::startNew();
441 while (!fetchedItems.empty()) {
442 FetchedItem item = std::move(fetchedItems.front());
443 fetchedItems.pop();
444
445 TextureLoadInfo* loadInfo = item.loadInfo;
446 if (!loadInfo->resourceData.empty()) {
447 invokeLoader(item.loadedLoader, item.loadInfo);
448 }
449 else {
450 if (processFetchedItem(item.loadedLoader, loadInfo, std::move(item.data))) {
451 texturesLoaded++;
452 }
453 }
454
455 // Model loaded successfully
456 if ((0 < itemLimit) && (itemLimit <= texturesLoaded)) {
457 // Hit item limit, stop processing this frame.
458 break;
459 }
460 if ((0.f < timeLimitSeconds) && (timeLimitSeconds <= processTimer.elapsedSeconds())) {
461 // Hit time limit, stop processing this frame.
462 break;
463 }
464 }
465
466 // If we are not done, trigger a new frame so we can continue.
467 if (!fetchedItems.empty()) {
468 // Not done, we need another frame where we can continue.
469 context->engine->setDirty();
470 }
471}
472
473
475{
476 bool preLoad = context->variables->get("resources.textures.preLoad", false);
477
478 if (preLoad && loadInfo->resourcePath.size()) {
479 if (!checkPreloaded(loadInfo)) return;
480 }
481
482 if (!loadInfo->resourcePath.empty()) {
483 loadFromPath(loadInfo);
484 }
485 else {
486
487 // Load from raw data blob
488 if (loadInfo->loadSync()) {
489 loadFromData(loadInfo);
490 setProcessed(loadInfo, !loadInfo->loadSync());
491 }
492 else {
493 context->taskManager->enqueue(TaskManager::ResourceQueue, [this, loadInfo]()
494 {
495 loadFromData(loadInfo);
496 setProcessed(loadInfo, !loadInfo->loadSync());
497 });
498 }
499 };
500
501}
502
503void Cogs::Core::TextureManager::handleReload(ResourceHandleBase handle)
504{
505 TextureHandle texture(handle);
506
507 auto * loadInfo = createLoadInfo();
508 loadInfo->resourceId = texture->getId();
509 loadInfo->resourcePath = texture->getSource().to_string();
511 loadInfo->handle = handle;
512
513 loadResource(loadInfo);
514}
515
516Cogs::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)
517{
518 TextureHandle handle = getOrCreate(resourceId);
519
520 auto texture = get(handle);
521
522 texture->setId(resourceId);
523 texture->description = TextureDescription{};
524 texture->description.target = target;
525 texture->description.width = width;
526 texture->description.height = height;
527 texture->description.depth = depth;
528 texture->description.faces = (target == ResourceDimensions::TextureCube || target == ResourceDimensions::TextureCubeArray) ? 6 : 1;
529 texture->description.layers = layers;
530 texture->description.format = format;
531 texture->externalHandle = externalHandle;
532 texture->hasAlpha = getFormatInfo(format)->elements == 4;
533
535 texture->description.flags |= TextureFlags::GenerateMipMaps;
536 }
537
539 texture->description.flags |= TextureFlags::NoDelete;
540 }
541
542 // Queue the resource for activation.
543 texture->setChanged();
544
545 return handle;
546}
547
549{
550 return context->renderer->getResources()->updateResource(handle);
551}
552
554{
555 context->renderer->getResources()->releaseResource(texture);
556}
557
558bool Cogs::Core::TextureManager::processFetchedItem(ILoadedTextureLoader* loadedLoader, TextureLoadInfo* loadInfo, std::unique_ptr<FileContents> data)
559{
560 // Check if we have been cancelled and remove the fetch id
561 bool cancelled = true;
562 {
563 size_t textureKey = reinterpret_cast<size_t>(loadInfo->handle.get());
564 LockGuard guard(fetchIds.lock);
565 if (auto it = fetchIds.map.find(textureKey); it != fetchIds.map.end()) {
566 fetchIds.map.erase(it);
567 cancelled = false;
568 }
569 }
570
571 bool success = false;
572
573 // If texture has been cancelled, we just stop processing and treat it as failed
574 if (cancelled) {
575 LOG_TRACE(logger, "Cancelled texture %s", loadInfo->resourcePath.c_str());
576 loadInfo->handle->setFailedLoad();
577 }
578
579 // If texture has been abandoned while we're fetching, just drop it.
580 else if (loadInfo->handle->referenceCount() <= 1) {
581 LOG_TRACE(logger, "Abandoned texture received in async callback, skipping further processing");
582 loadInfo->handle->setFailedLoad();
583 }
584
585 // If we have no data, the fetch has failed
586 else if (!data) {
587 LOG_ERROR(logger, "Error fetching texture %s", loadInfo->resourcePath.c_str());
588 loadInfo->handle->setFailedLoad();
589 }
590
591 // And finally we try to actually load the contents
592 else if (loadedLoader->load(context, *loadInfo, data->ptr, data->size)) {
593 success = true;
594 }
595 else {
596 LOG_ERROR(logger, "Error decoding texture %s", loadInfo->resourcePath.c_str());
597 loadInfo->handle->setFailedLoad();
598 }
599
600 setProcessed(loadInfo, !loadInfo->loadSync());
601 return success;
602}
603
604bool Cogs::Core::TextureManager::invokeLoader(ITextureLoader* loader, TextureLoadInfo* loadInfo)
605{
606 assert(loader);
607 bool success = loader->load(context, *loadInfo);
608 if (!success) {
609 LOG_ERROR(logger, "Error loading texture %s.", loadInfo->resourcePath.c_str());
610 loadInfo->handle->setFailedLoad();
611 }
612 setProcessed(loadInfo, !loadInfo->loadSync());
613 return success;
614}
615
616bool Cogs::Core::TextureManager::invokeLoader(ILoadedTextureLoader* loadedLoader, TextureLoadInfo* loadInfo)
617{
618 assert(loadedLoader);
619 bool success = loadedLoader->load(context, *loadInfo, loadInfo->resourceData.data(), loadInfo->resourceData.size());
620 if (!success) {
621 LOG_ERROR(logger, "Error loading texture from %s.", loadInfo->resourceName.c_str());
622 loadInfo->handle->setFailedLoad();
623 }
624 setProcessed(loadInfo, true);
625 return success;
626}
627
628void Cogs::Core::TextureManager::loadFromPath(TextureLoadInfo * loadInfo)
629{
630 assert(!loadInfo->resourcePath.empty());
631
632 ITextureLoader* loader = findLoader(loadInfo);
633 if (!loader) {
634 LOG_ERROR(logger, "No suitable texture loader found for %s.", loadInfo->resourcePath.c_str());
635 loadInfo->handle->setFailedLoad();
636 setProcessed(loadInfo, !loadInfo->loadSync());
637 return;
638 }
639
640 // If we have both path and resource data, the path is just used to determine loader,
641 // but the resourceData contains the actual data
642 if (!loadInfo->resourceData.empty()) {
643
644 ILoadedTextureLoader* loadedLoader = dynamic_cast<ILoadedTextureLoader*>(loader);
645 if(!loadedLoader) {
646 LOG_ERROR(logger, "Texture loader for %s does not support consuming data from an inline blob.", loadInfo->resourcePath.c_str());
647 loadInfo->handle->setFailedLoad();
648 setProcessed(loadInfo, !loadInfo->loadSync());
649 }
650 else if (loadInfo->loadSync()) {
651 invokeLoader(loadedLoader, loadInfo);
652 }
653 else if(context->taskManager->getQueueConcurrency(TaskManager::ResourceQueue)) {
654 context->taskManager->enqueue(TaskManager::ResourceQueue, [this, loadedLoader, loadInfo]() { invokeLoader(loadedLoader, loadInfo); });
655 }
656 else {
657 fetchedItems.push(FetchedItem{ .data = nullptr, .loadInfo = loadInfo, .loadedLoader = loadedLoader });
658 }
659 return;
660 }
661
662 // If load is requested to be sync, we must just try to read the file
663 if (loadInfo->loadSync()) {
664 invokeLoader(loader, loadInfo);
665 return;
666 }
667
668 // Unless the path is in the resource store, we try to load it asynchronously
669 if (!context->resourceStore->hasResource(loadInfo->resourcePath) && loadInfo->protocol != ResourceProtocol::Archive) {
670
671 // Async loaders are loaders where the loader does the actual async stuff. Only relevant implementation
672 // is the WebTexLoader that handles async load + DOM decoding in js land. This loader takes the ownership
673 // of the loadInfo and will call setProcessed with it.
674 if (IAsyncTextureLoader* asyncLoader = dynamic_cast<IAsyncTextureLoader*>(loader); asyncLoader) {
675 asyncLoader->load(context, loadInfo);
676 return;
677 }
678
679 // LoadedLoaders are loaders that can interpret a chunk of memory. With such a loader, we can do an async
680 // fetch of a blob of data and pass the data to the loader when we receive it.
681 if (ILoadedTextureLoader* loadedLoader = dynamic_cast<ILoadedTextureLoader*>(loader); loadedLoader) {
682
683 // We have a potential race condition since the callback that removes the
684 // cancellation id can either run during the fetch call or after, so we add
685 // an item now so we can detect and handle this situation.
686 size_t textureKey = reinterpret_cast<size_t>(loadInfo->handle.get());
687 {
688 LockGuard guard(fetchIds.lock);
689 fetchIds.map[textureKey] = DataFetcherManager::NoFetchId;
690 }
691
692 // Handler that runs when the fetch has finished
693 FileContents::Callback handleResult = [this, loadedLoader, loadInfo](std::unique_ptr<FileContents> data) {
694 // If we are in the main thread, we queue the response to be processed during the engine update
695 if (main == std::this_thread::get_id()) {
696 fetchedItems.push(FetchedItem{ .data = std::move(data), .loadInfo = loadInfo, .loadedLoader = loadedLoader });
697 context->engine->setDirty();
698 return;
699 }
700 processFetchedItem(loadedLoader, loadInfo, std::move(data));
701 };
702
703 // Fire off the fetch
704 DataFetcherManager::FetchId fetchId = DataFetcherManager::fetchAsync(context, loadInfo->resourcePath, handleResult, 0, 0, true);
705
706 // Update the map running fetches
707 {
708 LockGuard guard(fetchIds.lock);
709 if (auto it = fetchIds.map.find(textureKey); it != fetchIds.map.end()) {
710 it->second = fetchId;
711 }
712 }
713
714 // Fetch handler has taken ownership of the loadInfo
715 return;
716 }
717 }
718
719 // Load is not sync and loader does not support async etc, so we just fire off a task loading it from file
720 context->taskManager->enqueue(TaskManager::ResourceQueue, [this, loader, loadInfo]() { invokeLoader(loader, loadInfo); });
721}
722
723void Cogs::Core::TextureManager::loadFromData(TextureLoadInfo * loadInfo)
724{
725 auto texture = lock(loadInfo->handle);
726
727 texture->description.target = loadInfo->target;
728
729 switch (loadInfo->target) {
730 case ResourceDimensions::Texture1D:
731 texture->setData(loadInfo->target,
732 loadInfo->resourceData.data(),
733 loadInfo->resourceData.size(),
734 loadInfo->width,
735 1 /* height */,
736 1 /* depth */,
737 1 /* layers */,
738 1 /* faces */,
739 1 /* levels*/,
740 loadInfo->format,
741 loadInfo->mipMaps);
742 break;
743 case ResourceDimensions::Texture1DArray:
744 texture->setData(loadInfo->target,
745 loadInfo->resourceData.data(),
746 loadInfo->resourceData.size(),
747 loadInfo->width,
748 1 /* height */,
749 1 /* depth */,
750 loadInfo->layers,
751 1 /* faces */,
752 1 /* levels*/,
753 loadInfo->format,
754 loadInfo->mipMaps);
755 break;
756 case ResourceDimensions::Texture2D: {
757 void* data;
758 uint8_t* copy = nullptr;
759
760 if (loadInfo->flip) {
761 copy = new uint8_t[loadInfo->resourceData.size()];
762
763 const uint8_t* read = loadInfo->resourceData.data();
764 uint8_t* write = copy + loadInfo->resourceData.size();
765 size_t stride = loadInfo->stride;
766
767 assert(loadInfo->resourceData.size() == loadInfo->height * stride);
768
769 for (int y = loadInfo->height; y--; ) {
770 write -= stride;
771 memcpy(write, read, stride);
772 read += stride;
773 }
774 data = copy;
775 }
776 else {
777 data = loadInfo->resourceData.data();
778 }
779 texture->setData(loadInfo->target,
780 data,
781 loadInfo->resourceData.size(),
782 loadInfo->width,
783 loadInfo->height,
784 1 /* depth */,
785 1 /* layers */,
786 1 /* faces */,
787 1 /* levels*/,
788 loadInfo->format,
789 loadInfo->mipMaps);
790 texture->hasAlpha = getFormatInfo(loadInfo->format)->elements == 4;
791
792 delete [] copy;
793 break;
794 }
795 case ResourceDimensions::Texture2DArray:
796 texture->setData(loadInfo->target,
797 loadInfo->resourceData.data(),
798 loadInfo->resourceData.size(),
799 loadInfo->width,
800 loadInfo->height,
801 1 /* depth */,
802 loadInfo->layers,
803 1 /* faces */,
804 1 /* levels*/,
805 loadInfo->format,
806 loadInfo->mipMaps);
807 break;
808 case ResourceDimensions::Texture3D:
809 texture->setData(loadInfo->target,
810 loadInfo->resourceData.data(),
811 loadInfo->resourceData.size(),
812 loadInfo->width,
813 loadInfo->height,
814 loadInfo->depth,
815 1 /* layers */,
816 1 /* faces */,
817 1 /* levels*/,
818 loadInfo->format,
819 loadInfo->mipMaps);
820 break;
821 case ResourceDimensions::Texture3DArray:
822 texture->setData(loadInfo->target,
823 loadInfo->resourceData.data(),
824 loadInfo->resourceData.size(),
825 loadInfo->width,
826 loadInfo->height,
827 loadInfo->depth,
828 loadInfo->layers,
829 1 /* faces */,
830 1 /* levels*/,
831 loadInfo->format,
832 loadInfo->mipMaps);
833 break;
834 case ResourceDimensions::TextureCube:
835 texture->setData(loadInfo->target,
836 loadInfo->resourceData.data(),
837 loadInfo->resourceData.size(),
838 loadInfo->width,
839 loadInfo->height,
840 1 /* depth*/,
841 1 /* layers */,
842 6 /* faces */,
843 1 /* levels*/,
844 loadInfo->format,
845 loadInfo->mipMaps);
846 break;
847 case ResourceDimensions::TextureCubeArray:
848 texture->setData(loadInfo->target,
849 loadInfo->resourceData.data(),
850 loadInfo->resourceData.size(),
851 loadInfo->width,
852 loadInfo->height,
853 1 /* depth*/,
854 loadInfo->layers,
855 6 /* faces */,
856 1 /* levels*/,
857 loadInfo->format,
858 loadInfo->mipMaps);
859 break;
860 default:
861 assert(false && "Unhandled texture target");
862 break;
863 }
864}
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:139
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:24
static constexpr size_t NoPosition
No position.
Definition: StringView.h:43
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:200
Old timer class.
Definition: Timer.h:37
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:47
@ 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.
@ 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:180
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:32
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:259
@ NoDelete
The ownership of the underlying texture resource is outside of cogs and cogs will not delete it.
Definition: Flags.h:136
@ 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