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");
203 LOG_DEBUG(logger,
"Initialized texture manager.");
206void Cogs::Core::TextureManager::clear()
208 white = ResourceHandle();
209 whiteCube = ResourceHandle();
215 DataFetcherManager::FetchId fetchId = DataFetcherManager::NoFetchId;
218 size_t textureKey =
reinterpret_cast<size_t>(handle.get());
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);
228 if (fetchId != DataFetcherManager::NoFetchId) {
229 DataFetcherManager::cancelAsyncFetch(context, fetchId);
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);
241 stride =
static_cast<int>((width * Cogs::getBlockSize(format) + 3) & ~3);
242 tail =
static_cast<int>(stride - width * Cogs::getBlockSize(format));
244 auto data =
static_cast<const uint8_t *
>(imageData);
246 if (!validateTextureParameters(target, width, height, depth, layers)) {
251 setLoadInfoExtent(loadInfo, target, width, height, depth, layers, format, stride);
252 setLoadInfoFlags(loadInfo, format, flags);
256 loadInfo->
resourceData.assign(data, data + stride * height * depth * layers - tail);
258 return loadTexture(loadInfo);
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);
268 stride =
static_cast<int>((width * Cogs::getBlockSize(format) + 3) & ~3);
269 tail =
static_cast<int>(stride - width * Cogs::getBlockSize(format));
271 auto data =
static_cast<const uint8_t *
>(imageData);
273 if (!validateTextureParameters(target, width, height, depth, layers)) {
278 setLoadInfoExtent(loadInfo, target, width, height, depth, layers, format, stride);
279 setLoadInfoFlags(loadInfo, format, flags);
280 loadInfo->
handle = resourceHandle;
283 loadInfo->
resourceData.assign(data, data + stride * height * depth * layers - tail);
285 return loadResource(loadInfo);
300 if (context->variables->get(
"resources.textures.autoReload",
false)) {
301 loadInfo->
loadFlags |= ResourceLoadFlags::AutoReload;
304 return loadResource(loadInfo);
324 loadInfo->
resourceData.assign(
static_cast<const uint8_t*
>(dataPtr),
static_cast<const uint8_t*
>(dataPtr) + dataSize);
326 return loadResource(loadInfo);
332 return loadResource(loadInfo);
338 LOG_ERROR(logger,
"Cannot fetch texture using empty path.");
343 auto schemeEndLoc = path.find(
"://");
345 auto schemeHash = path.substr(0, schemeEndLoc).hash();
349 auto queryLoc = path.find(
"?");
351 parseQueryString(attributes.values, path.substr(queryLoc + 1));
353 for (
auto & item : attributes.values) {
379 auto nakedPath = path.substr(schemeEndLoc + 3, queryLoc ==
StringView::NoPosition ? queryLoc : queryLoc - schemeEndLoc - 3);
383 return loadTexture(nakedPath, NoResourceId, loadFlags);
385#if defined( EMSCRIPTEN )
388 return loadTexture(path, NoResourceId, loadFlags);
393 return loadTexture(nakedPath, NoResourceId, loadFlags);
396 return context->services->getService<
TextureGenerator>()->getTexture(parseEnum(nakedPath, ImageType::None), attributes);
399 LOG_ERROR(logger,
"Unknown URI scheme '%s'", path.substr(0, schemeEndLoc).to_string().c_str());
406 auto fileLoc = path.find(
"file://");
407 auto linearLoc = path.find(
"linear://");
408 auto generatorLoc = path.find(
"generator://");
415 return context->services->getService<
TextureGenerator>()->getTexture(parseEnum(path.substr(12), ImageType::None));
418 else if (!path.empty()) {
419 auto name = path[0] ==
'$' ? path.substr(1) : path;
421 auto handle = getByName(name);
424 LOG_ERROR(logger,
"Could not resolve texture with name %.*s", StringViewFormat(path));
434 if (fetchedItems.empty())
return;
436 double timeLimitSeconds = 0.001 * context->variables->get(timeLimitName, 0.f);
437 int itemLimit = context->variables->get(itemLimitName, 0);
439 int texturesLoaded = 0;
440 Cogs::Timer processTimer = Cogs::Timer::startNew();
441 while (!fetchedItems.empty()) {
442 FetchedItem item = std::move(fetchedItems.front());
447 invokeLoader(item.loadedLoader, item.loadInfo);
450 if (processFetchedItem(item.loadedLoader, loadInfo, std::move(item.data))) {
456 if ((0 < itemLimit) && (itemLimit <= texturesLoaded)) {
460 if ((0.f < timeLimitSeconds) && (timeLimitSeconds <= processTimer.elapsedSeconds())) {
467 if (!fetchedItems.empty()) {
469 context->engine->setDirty();
476 bool preLoad = context->variables->get(
"resources.textures.preLoad",
false);
479 if (!checkPreloaded(loadInfo))
return;
483 loadFromPath(loadInfo);
488 if (loadInfo->loadSync()) {
489 loadFromData(loadInfo);
490 setProcessed(loadInfo, !loadInfo->loadSync());
495 loadFromData(loadInfo);
496 setProcessed(loadInfo, !loadInfo->loadSync());
507 auto * loadInfo = createLoadInfo();
509 loadInfo->
resourcePath = texture->getSource().to_string();
511 loadInfo->
handle = handle;
513 loadResource(loadInfo);
520 auto texture = get(handle);
522 texture->setId(resourceId);
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;
543 texture->setChanged();
550 return context->renderer->getResources()->updateResource(handle);
555 context->renderer->getResources()->releaseResource(texture);
561 bool cancelled =
true;
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);
571 bool success =
false;
575 LOG_TRACE(logger,
"Cancelled texture %s", loadInfo->
resourcePath.c_str());
576 loadInfo->
handle->setFailedLoad();
581 LOG_TRACE(logger,
"Abandoned texture received in async callback, skipping further processing");
582 loadInfo->
handle->setFailedLoad();
587 LOG_ERROR(logger,
"Error fetching texture %s", loadInfo->
resourcePath.c_str());
588 loadInfo->
handle->setFailedLoad();
592 else if (loadedLoader->load(context, *loadInfo, data->ptr, data->size)) {
596 LOG_ERROR(logger,
"Error decoding texture %s", loadInfo->
resourcePath.c_str());
597 loadInfo->
handle->setFailedLoad();
600 setProcessed(loadInfo, !loadInfo->loadSync());
607 bool success = loader->load(context, *loadInfo);
609 LOG_ERROR(logger,
"Error loading texture %s.", loadInfo->
resourcePath.c_str());
610 loadInfo->
handle->setFailedLoad();
612 setProcessed(loadInfo, !loadInfo->loadSync());
618 assert(loadedLoader);
619 bool success = loadedLoader->load(context, *loadInfo, loadInfo->
resourceData.data(), loadInfo->
resourceData.size());
621 LOG_ERROR(logger,
"Error loading texture from %s.", loadInfo->
resourceName.c_str());
622 loadInfo->
handle->setFailedLoad();
624 setProcessed(loadInfo,
true);
628void Cogs::Core::TextureManager::loadFromPath(
TextureLoadInfo * loadInfo)
634 LOG_ERROR(logger,
"No suitable texture loader found for %s.", loadInfo->
resourcePath.c_str());
635 loadInfo->
handle->setFailedLoad();
636 setProcessed(loadInfo, !loadInfo->loadSync());
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());
650 else if (loadInfo->loadSync()) {
651 invokeLoader(loadedLoader, loadInfo);
654 context->taskManager->enqueue(
TaskManager::ResourceQueue, [
this, loadedLoader, loadInfo]() { invokeLoader(loadedLoader, loadInfo); });
657 fetchedItems.push(FetchedItem{ .data =
nullptr, .loadInfo = loadInfo, .loadedLoader = loadedLoader });
663 if (loadInfo->loadSync()) {
664 invokeLoader(loader, loadInfo);
669 if (!context->resourceStore->hasResource(loadInfo->
resourcePath) && loadInfo->protocol != ResourceProtocol::Archive) {
675 asyncLoader->load(context, loadInfo);
686 size_t textureKey =
reinterpret_cast<size_t>(loadInfo->
handle.get());
688 LockGuard guard(fetchIds.lock);
689 fetchIds.map[textureKey] = DataFetcherManager::NoFetchId;
693 FileContents::Callback handleResult = [
this, loadedLoader, loadInfo](std::unique_ptr<FileContents> data) {
695 if (main == std::this_thread::get_id()) {
696 fetchedItems.push(FetchedItem{ .data = std::move(data), .loadInfo = loadInfo, .loadedLoader = loadedLoader });
697 context->engine->setDirty();
700 processFetchedItem(loadedLoader, loadInfo, std::move(data));
704 DataFetcherManager::FetchId fetchId = DataFetcherManager::fetchAsync(context, loadInfo->
resourcePath, handleResult, 0, 0,
true);
708 LockGuard guard(fetchIds.lock);
709 if (
auto it = fetchIds.map.find(textureKey); it != fetchIds.map.end()) {
710 it->second = fetchId;
723void Cogs::Core::TextureManager::loadFromData(
TextureLoadInfo * loadInfo)
725 auto texture = lock(loadInfo->
handle);
727 texture->description.target = loadInfo->target;
729 switch (loadInfo->target) {
730 case ResourceDimensions::Texture1D:
731 texture->setData(loadInfo->target,
743 case ResourceDimensions::Texture1DArray:
744 texture->setData(loadInfo->target,
756 case ResourceDimensions::Texture2D: {
758 uint8_t* copy =
nullptr;
760 if (loadInfo->flip) {
765 size_t stride = loadInfo->stride;
767 assert(loadInfo->
resourceData.size() == loadInfo->height * stride);
769 for (
int y = loadInfo->height; y--; ) {
771 memcpy(write, read, stride);
779 texture->setData(loadInfo->target,
790 texture->hasAlpha = getFormatInfo(loadInfo->format)->
elements == 4;
795 case ResourceDimensions::Texture2DArray:
796 texture->setData(loadInfo->target,
808 case ResourceDimensions::Texture3D:
809 texture->setData(loadInfo->target,
821 case ResourceDimensions::Texture3DArray:
822 texture->setData(loadInfo->target,
834 case ResourceDimensions::TextureCube:
835 texture->setData(loadInfo->target,
847 case ResourceDimensions::TextureCubeArray:
848 texture->setData(loadInfo->target,
861 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.
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.
@ 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.
@ NoDelete
The ownership of the underlying texture resource is outside of cogs and cogs will not delete it.
@ 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.