1#include "TextureManager.h"
3#include "Foundation/Logging/Logger.h"
4#include "Foundation/Platform/Timer.h"
6#include "Rendering/ITextures.h"
8#include "Renderer/RenderTexture.h"
10#include "DataFetcherManager.h"
12#include "Services/Services.h"
13#include "Services/Features.h"
14#include "Services/TaskManager.h"
15#include "Services/Variables.h"
17#include "Generators/TextureGenerator.h"
19#include "Resources/ResourceStore.h"
31 constexpr Cogs::StringView timeLimitName =
"resources.textures.mainThreadTimeLimitMs";
32 constexpr Cogs::StringView itemLimitName =
"resources.textures.mainThreadItemLimit";
34 bool validateTextureParameters(Cogs::ResourceDimensions target,
int width,
int height,
int depth,
int layers) {
37 case Cogs::ResourceDimensions::Texture1D:
43 case Cogs::ResourceDimensions::Texture1DArray:
49 case Cogs::ResourceDimensions::Texture2D:
55 case Cogs::ResourceDimensions::Texture2DArray:
61 case Cogs::ResourceDimensions::Texture3D:
67 case Cogs::ResourceDimensions::Texture3DArray:
73 case Cogs::ResourceDimensions::TextureCube:
79 case Cogs::ResourceDimensions::TextureCubeArray:
86 LOG_ERROR(logger,
"loadTexture: Unhandled target type %d", (
int)target);
95 loadInfo->flip = (flags & TextureLoadFlags::Flip) != 0;
98 loadInfo->mipMaps =
false;
99 if ((flags & TextureLoadFlags::NoMipMaps) != TextureLoadFlags::NoMipMaps) {
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;
109 void setLoadInfoExtent(
TextureLoadInfo* loadInfo, Cogs::ResourceDimensions target,
int width,
int height,
int depth,
int layers, Cogs::TextureFormat format,
int stride)
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;
124 reportLeaks(
"Texture");
129 ResourceManager::initialize();
130 main = std::this_thread::get_id();
132 if (!context->variables->exist(timeLimitName)) {
133 context->variables->set(timeLimitName, 1.f);
135 if (!context->variables->exist(itemLimitName)) {
136 context->variables->set(itemLimitName, 10);
142 unsigned char bytes [] = {
149 defaultResource = create();
150 Texture* texture = get(defaultResource);
152 texture->
setData(Cogs::ResourceDimensions::Texture2D, bytes, 4 * 4, 2, 2, TextureFormat::R8G8B8A8_UNORM,
true);
154 setResourceId(texture, getNextResourceId());
156 const uint8_t whiteBytes[] = {
163 const uint8_t blackBytes[] = {
170 const uint8_t transparentBytes[] = {
180 whiteCube = create();
182 whiteCube->description.target = ResourceDimensions::TextureCube;
183 whiteCube->description.width = 2;
184 whiteCube->description.height = 2;
185 whiteCube->description.faces = 6;
187 whiteCube->description.format = TextureFormat::R8G8B8A8_UNORM;
188 whiteCube->storage.init({ 2, 2, 1 }, 1, 6, 1, whiteCube->description.format);
190 auto whiteCubeData =
static_cast<uint8_t *
>(whiteCube->storage.getData());
191 auto whiteCubeSize = whiteCube->storage.getSize();
193 for (
size_t i = 0; i < whiteCubeSize; ++i) {
194 whiteCubeData[i] = 255;
197 whiteCube->setChanged();
199 white->setName(
"Cogs.WhiteTexture");
200 whiteCube->setName(
"Cogs.WhiteCubeMap");
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;
209 whiteDepthArray->setChanged();
210 whiteDepthArray->setName(
"Cogs.WhiteDepthArray");
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;
219 whiteDepthCube->setChanged();
220 whiteDepthCube->setName(
"Cogs.WhiteDepthCube");
223 LOG_DEBUG(logger,
"Initialized texture manager.");
226void Cogs::Core::TextureManager::clear()
228 white = ResourceHandle();
229 whiteCube = ResourceHandle();
230 whiteDepthArray = ResourceHandle();
231 whiteDepthCube = ResourceHandle();
237 DataFetcherManager::FetchId fetchId = DataFetcherManager::NoFetchId;
240 size_t textureKey =
reinterpret_cast<size_t>(handle.get());
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);
250 if (fetchId != DataFetcherManager::NoFetchId) {
251 DataFetcherManager::cancelAsyncFetch(context, fetchId);
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);
263 stride =
static_cast<int>((width * Cogs::getBlockSize(format) + 3) & ~3);
264 tail =
static_cast<int>(stride - width * Cogs::getBlockSize(format));
266 auto data =
static_cast<const uint8_t *
>(imageData);
268 if (!validateTextureParameters(target, width, height, depth, layers)) {
273 setLoadInfoExtent(loadInfo, target, width, height, depth, layers, format, stride);
274 setLoadInfoFlags(loadInfo, format, flags);
278 loadInfo->
resourceData.assign(data, data + stride * height * depth * layers - tail);
280 return loadTexture(loadInfo);
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);
290 stride =
static_cast<int>((width * Cogs::getBlockSize(format) + 3) & ~3);
291 tail =
static_cast<int>(stride - width * Cogs::getBlockSize(format));
293 auto data =
static_cast<const uint8_t *
>(imageData);
295 if (!validateTextureParameters(target, width, height, depth, layers)) {
300 setLoadInfoExtent(loadInfo, target, width, height, depth, layers, format, stride);
301 setLoadInfoFlags(loadInfo, format, flags);
302 loadInfo->
handle = resourceHandle;
305 loadInfo->
resourceData.assign(data, data + stride * height * depth * layers - tail);
307 return loadResource(loadInfo);
322 if (context->variables->get(
"resources.textures.autoReload",
false)) {
323 loadInfo->
loadFlags |= ResourceLoadFlags::AutoReload;
326 return loadResource(loadInfo);
346 loadInfo->
resourceData.assign(
static_cast<const uint8_t*
>(dataPtr),
static_cast<const uint8_t*
>(dataPtr) + dataSize);
348 return loadResource(loadInfo);
354 return loadResource(loadInfo);
360 LOG_ERROR(logger,
"Cannot fetch texture using empty path.");
365 auto schemeEndLoc = path.find(
"://");
367 auto schemeHash = path.substr(0, schemeEndLoc).hash();
371 auto queryLoc = path.find(
"?");
373 parseQueryString(attributes.values, path.substr(queryLoc + 1));
375 for (
auto & item : attributes.values) {
401 auto nakedPath = path.substr(schemeEndLoc + 3, queryLoc ==
StringView::NoPosition ? queryLoc : queryLoc - schemeEndLoc - 3);
405 return loadTexture(nakedPath, NoResourceId, loadFlags);
407#if defined( __EMSCRIPTEN__ )
410 return loadTexture(path, NoResourceId, loadFlags);
415 return loadTexture(nakedPath, NoResourceId, loadFlags);
418 return context->services->getService<
TextureGenerator>()->getTexture(parseEnum(nakedPath, ImageType::None), attributes);
421 LOG_ERROR(logger,
"Unknown URI scheme '%s'", path.substr(0, schemeEndLoc).to_string().c_str());
428 auto fileLoc = path.find(
"file://");
429 auto linearLoc = path.find(
"linear://");
430 auto generatorLoc = path.find(
"generator://");
437 return context->services->getService<
TextureGenerator>()->getTexture(parseEnum(path.substr(12), ImageType::None));
440 else if (!path.empty()) {
441 auto name = path[0] ==
'$' ? path.substr(1) : path;
443 auto handle = getByName(name);
446 LOG_ERROR(logger,
"Could not resolve texture with name %.*s", StringViewFormat(path));
456 if (fetchedItems.empty())
return;
458 double timeLimitSeconds = 0.001 * context->variables->get(timeLimitName, 0.f);
459 int itemLimit = context->variables->get(itemLimitName, 0);
461 int texturesLoaded = 0;
462 Cogs::Timer processTimer = Cogs::Timer::startNew();
463 while (!fetchedItems.empty()) {
464 FetchedItem item = std::move(fetchedItems.front());
469 invokeLoader(item.loadedLoader, item.loadInfo);
472 if (processFetchedItem(item.loadedLoader, loadInfo, std::move(item.data))) {
478 if ((0 < itemLimit) && (itemLimit <= texturesLoaded)) {
482 if ((0.f < timeLimitSeconds) && (timeLimitSeconds <= processTimer.elapsedSeconds())) {
489 if (!fetchedItems.empty()) {
491 context->engine->setDirty();
498 bool preLoad = context->variables->get(
"resources.textures.preLoad",
false);
501 if (!checkPreloaded(loadInfo))
return;
505 loadFromPath(loadInfo);
510 if (loadInfo->loadSync()) {
511 loadFromData(loadInfo);
512 setProcessed(loadInfo, !loadInfo->loadSync());
517 loadFromData(loadInfo);
518 setProcessed(loadInfo, !loadInfo->loadSync());
529 auto * loadInfo = createLoadInfo();
531 loadInfo->
resourcePath = texture->getSource().to_string();
533 loadInfo->
handle = handle;
535 loadResource(loadInfo);
542 auto texture = get(handle);
544 texture->setId(resourceId);
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;
565 texture->setChanged();
572 return context->renderer->getResources()->updateResource(handle);
577 context->renderer->getResources()->releaseResource(texture);
583 bool cancelled =
true;
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);
593 bool success =
false;
597 LOG_TRACE(logger,
"Cancelled texture %s", loadInfo->
resourcePath.c_str());
598 loadInfo->
handle->setFailedLoad();
603 LOG_TRACE(logger,
"Abandoned texture received in async callback, skipping further processing");
604 loadInfo->
handle->setFailedLoad();
609 LOG_ERROR(logger,
"Error fetching texture %s", loadInfo->
resourcePath.c_str());
610 loadInfo->
handle->setFailedLoad();
614 else if (loadedLoader->load(context, *loadInfo, data->ptr, data->size)) {
618 LOG_ERROR(logger,
"Error decoding texture %s", loadInfo->
resourcePath.c_str());
619 loadInfo->
handle->setFailedLoad();
622 setProcessed(loadInfo, !loadInfo->loadSync());
629 bool success = loader->load(context, *loadInfo);
631 LOG_ERROR(logger,
"Error loading texture %s.", loadInfo->
resourcePath.c_str());
632 loadInfo->
handle->setFailedLoad();
634 setProcessed(loadInfo, !loadInfo->loadSync());
640 assert(loadedLoader);
641 bool success = loadedLoader->load(context, *loadInfo, loadInfo->
resourceData.data(), loadInfo->
resourceData.size());
643 LOG_ERROR(logger,
"Error loading texture from %s.", loadInfo->
resourceName.c_str());
644 loadInfo->
handle->setFailedLoad();
646 setProcessed(loadInfo,
true);
650void Cogs::Core::TextureManager::loadFromPath(
TextureLoadInfo * loadInfo)
656 LOG_ERROR(logger,
"No suitable texture loader found for %s.", loadInfo->
resourcePath.c_str());
657 loadInfo->
handle->setFailedLoad();
658 setProcessed(loadInfo, !loadInfo->loadSync());
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());
672 else if (loadInfo->loadSync()) {
673 invokeLoader(loadedLoader, loadInfo);
676 context->taskManager->enqueue(
TaskManager::ResourceQueue, [
this, loadedLoader, loadInfo]() { invokeLoader(loadedLoader, loadInfo); });
679 fetchedItems.push(FetchedItem{ .data =
nullptr, .loadInfo = loadInfo, .loadedLoader = loadedLoader });
685 if (loadInfo->loadSync()) {
686 invokeLoader(loader, loadInfo);
691 if (!context->resourceStore->hasResource(loadInfo->
resourcePath) && loadInfo->protocol != ResourceProtocol::Archive) {
697 asyncLoader->load(context, loadInfo);
708 size_t textureKey =
reinterpret_cast<size_t>(loadInfo->
handle.get());
710 LockGuard guard(fetchIds.lock);
711 fetchIds.map[textureKey] = DataFetcherManager::NoFetchId;
715 FileContents::Callback handleResult = [
this, loadedLoader, loadInfo](std::unique_ptr<FileContents> data) {
717 if (main == std::this_thread::get_id()) {
718 fetchedItems.push(FetchedItem{ .data = std::move(data), .loadInfo = loadInfo, .loadedLoader = loadedLoader });
719 context->engine->setDirty();
722 processFetchedItem(loadedLoader, loadInfo, std::move(data));
726 DataFetcherManager::FetchId fetchId = DataFetcherManager::fetchAsync(context, loadInfo->
resourcePath, handleResult, 0, 0,
true);
730 LockGuard guard(fetchIds.lock);
731 if (
auto it = fetchIds.map.find(textureKey); it != fetchIds.map.end()) {
732 it->second = fetchId;
745void Cogs::Core::TextureManager::loadFromData(
TextureLoadInfo * loadInfo)
747 auto texture = lock(loadInfo->
handle);
749 texture->description.target = loadInfo->target;
751 switch (loadInfo->target) {
752 case ResourceDimensions::Texture1D:
753 texture->setData(loadInfo->target,
765 case ResourceDimensions::Texture1DArray:
766 texture->setData(loadInfo->target,
778 case ResourceDimensions::Texture2D: {
780 uint8_t* copy =
nullptr;
782 if (loadInfo->flip) {
787 size_t stride = loadInfo->stride;
789 assert(loadInfo->
resourceData.size() == loadInfo->height * stride);
791 for (
int y = loadInfo->height; y--; ) {
793 memcpy(write, read, stride);
801 texture->setData(loadInfo->target,
812 texture->hasAlpha = getFormatInfo(loadInfo->format)->
elements == 4;
817 case ResourceDimensions::Texture2DArray:
818 texture->setData(loadInfo->target,
830 case ResourceDimensions::Texture3D:
831 texture->setData(loadInfo->target,
843 case ResourceDimensions::Texture3DArray:
844 texture->setData(loadInfo->target,
856 case ResourceDimensions::TextureCube:
857 texture->setData(loadInfo->target,
869 case ResourceDimensions::TextureCubeArray:
870 texture->setData(loadInfo->target,
883 assert(
false &&
"Unhandled texture target");
void clear() override
Clear the resource manager, cleaning up resources held by member handles.
static constexpr TaskQueueId ResourceQueue
Resource task queue.
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.
Provides a weakly referenced view over the contents of a string.
static constexpr size_t NoPosition
No position.
std::string to_string() const
String conversion method.
constexpr size_t hash() const noexcept
Get the hash code of the string.
High-resolution performance timer.
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
ActivationResult
Defines results for resource activation.
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.
@ DoNotStoreSource
Don't store the source.
TextureLoadFlags
Texture loading flags. May be combined with resource load flags.
@ 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
constexpr size_t hash() noexcept
Simple getter function that returns the initial value for fnv1a hashing.
Stores the parsed output of a key/value pair.
void setName(const StringView &name)
Set the user friendly name of the resource.
uint32_t referenceCount() const
Get the current reference count.
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.
void setData(ResourceDimensions target, const void *data, size_t size, int width, int height, TextureFormat format, bool generateMipMap)
Set the texture data.
@ DepthBuffer
The texture can be used as a depth target and have depth buffer values written into.
@ NoDelete
The ownership of the underlying texture resource is outside of cogs and cogs will not delete it.
@ RenderTarget
The texture can be used as a render target and drawn into.
@ GenerateMipMaps
The texture supports automatic mipmap generation performed by the graphics device.
@ Texture
Texture usage, see Default.
@ CubeMap
The texture can be used as a cube map.