#include "main/references.h" #include "main/logging.h" #include "main/initialization.h" #include "render/render_state.h" #include "resource/library.h" #include "compat/misc.h" #include "str_util.h" #include "num_util.h" #include "threads.h" #include "files.h" #include #include #include #include #include #include #include #include #include "main/profiler.h" extern bool cancelAssets; namespace resource { umap INIT_VAR(spriteModes) { spriteModes["Horizontal"] = render::SM_Horizontal; spriteModes["Vertical"] = render::SM_Vertical; } INIT_VAR_END; umap INIT_VAR(cullingModes) { cullingModes["None"] = render::FC_None; cullingModes["Front"] = render::FC_Front; cullingModes["Back"] = render::FC_Back; cullingModes["Both"] = render::FC_Both; } INIT_VAR_END; umap INIT_VAR(depthTests) { depthTests["Never"] = render::DT_Never; depthTests["Less"] = render::DT_Less; depthTests["LessEqual"] = render::DT_LessEqual; depthTests["Equal"] = render::DT_Equal; depthTests["GreaterEqual"] = render::DT_GreaterEqual; depthTests["Greater"] = render::DT_Greater; depthTests["Always"] = render::DT_Always; depthTests["NoDepthTest"] = render::DT_NoDepthTest; } INIT_VAR_END; umap INIT_VAR(textureWraps) { textureWraps["Repeat"] = render::TW_Repeat; textureWraps["Clamp"] = render::TW_Clamp; textureWraps["ClampEdge"] = render::TW_ClampEdge; textureWraps["Mirror"] = render::TW_Mirror; } INIT_VAR_END; umap INIT_VAR(textureFilters) { textureFilters["Linear"] = render::TF_Linear; textureFilters["Nearest"] = render::TF_Nearest; } INIT_VAR_END; umap INIT_VAR(drawModes) { drawModes["Line"] = render::DM_Line; drawModes["Fill"] = render::DM_Fill; } INIT_VAR_END; umap INIT_VAR(baseMats) { baseMats["Solid"] = render::MAT_Solid; baseMats["Add"] = render::MAT_Add; baseMats["Alpha"] = render::MAT_Alpha; baseMats["Font"] = render::MAT_Font; baseMats["Overlay"] = render::MAT_Overlay; } INIT_VAR_END; const unsigned DRIVER_MIPMAP_SIZE = 512 * 512; struct QueuedTexture { int priority; std::vector images; render::Texture* tex; std::string filename; bool mipmap; bool cachePixels; mutable unsigned row, lod; QueuedTexture(render::Texture* dest, int Priority, Image* source, bool Mipmap, bool CachePixels) : priority(Priority), tex(dest), mipmap(Mipmap), cachePixels(CachePixels), row(0), lod(0) { images.push_back(source); } QueuedTexture(render::Texture* dest, int Priority, const std::string& source, bool Mipmap, bool CachePixels) : priority(Priority), tex(dest), filename(source), mipmap(Mipmap), cachePixels(CachePixels), row(0), lod(0) { } //Loads the image, returning the number of bytes loaded // If true was returned, the image is done loading or there was a failure // The number of bytes loaded may exceed maxBytes - it is only a soft limit bool load(unsigned maxBytes, unsigned& loadedBytes); bool operator<(const QueuedTexture& other) const { return priority < other.priority; } void clearImages() { for(auto i = images.begin(); i != images.end(); ++i) delete *i; images.clear(); } }; threads::Mutex queuedImagesLock; std::priority_queue queuedImages; threads::Mutex queuedTexturesLock; std::priority_queue queuedTextures; threads::Mutex texIdentLock; std::unordered_map texNames; unsigned maxTexSize = 0; void Library::clearTextures() { texNames.clear(); maxTexSize = 0; } bool Library::hasQueuedImages() { return !queuedImages.empty(); } unsigned getMaxTextureSize() { if(maxTexSize == 0) { auto* texQuality = devices.settings.engine.getSetting("iTextureQuality"); if(texQuality) { switch(texQuality->getInteger()) { case 0: maxTexSize = 256; break; case 1: maxTexSize = 512; break; case 2: maxTexSize = 1024; break; case 3: maxTexSize = 2048; break; case 4: maxTexSize = 4096; break; default: case 5: maxTexSize = 8192; break; } } else { maxTexSize = 65536; } } return maxTexSize * maxTexSize; } bool Library::processImages(int maxPriority, int amount) { bool processedAny = false; int processed = 0; while(!queuedImages.empty()) { queuedImagesLock.lock(); if(queuedImages.empty()) { queuedImagesLock.release(); break; } auto elem = queuedImages.top(); if(elem.priority < maxPriority) { queuedImagesLock.release(); break; } if(elem.tex) textures.push_back(elem.tex); queuedImages.pop(); queuedImagesLock.release(); if(cancelAssets) continue; processedAny = true; if(!elem.filename.empty()) { Image* img = loadImage(elem.filename.c_str()); if(!img) { error("Error: Could not load image '%s'", elem.filename.c_str()); continue; } unsigned texSize = getMaxTextureSize(); while(img->width * img->height > texSize) { auto* prev = img; img = img->makeMipmap(); delete prev; } elem.images.push_back(img); //Loading images this way causes weirdness /*if(elem.mipmap && img->width * img->height > DRIVER_MIPMAP_SIZE) { while(img->width > 2 && img->height > 2) { img = img->makeMipmap(); if(img) elem.images.push_back(img); else break; } }*/ } ++processed; queuedTexturesLock.lock(); queuedTextures.push(elem); queuedTexturesLock.release(); if(processed >= amount) break; } return processedAny; } bool Library::hasQueuedTextures() { return !queuedTextures.empty(); } bool QueuedTexture::load(unsigned maxBytes, unsigned& loadedBytes) { if(images.empty()) return true; if(!tex) { clearImages(); return true; } if(loadedBytes >= maxBytes) return false; Image* img = images.front(); unsigned rowBytes = img->width * ColorDepths[img->format]; unsigned loadRows = (maxBytes - loadedBytes) / rowBytes; if(loadRows == 0) loadRows = 1; if(loadRows > img->height - row) loadRows = img->height - row; loadedBytes += rowBytes * loadRows; if(lod == 0/* && loadRows == img->height && img->width * img->height <= DRIVER_MIPMAP_SIZE*/) { tex->load(*img, mipmap, cachePixels); clearImages(); return true; } else { if(row == 0) tex->loadStart(*img, mipmap, cachePixels, lod); tex->loadPartial(*img, recti(0, row, img->width, row+loadRows), cachePixels, lod); row += loadRows; if(row == img->height) { delete img; images.erase(images.begin()); tex->loadFinish(mipmap && images.empty(), lod); if(!mipmap || images.empty()) return true; //Reset for the next LOD lod += 1; row = 0; return load(maxBytes, loadedBytes); } } return false; } bool Library::processTextures(int maxPriority, bool singleFrame) { bool processedAny = false; const unsigned frameByteLimit = 1024 * 1024 * 4; int64_t byteLimit = singleFrame ? frameByteLimit : 0xffffffff; while(!queuedTextures.empty()) { queuedTexturesLock.lock(); if(queuedTextures.empty()) { queuedTexturesLock.release(); break; } auto tex = queuedTextures.top(); if(tex.priority < maxPriority) { queuedTexturesLock.release(); break; } queuedTextures.pop(); //Track filenames even if we can't load the resource at the moment, in case the file is created while we're running if(tex.tex && !tex.filename.empty()) texture_files[tex.filename] = tex.tex; queuedTexturesLock.release(); if(cancelAssets) continue; processedAny = true; unsigned loaded = 0; if(!tex.load(byteLimit, loaded)) { threads::Lock lock(queuedTexturesLock); queuedTextures.push(tex); } byteLimit -= (int64_t)loaded; if(byteLimit <= 0) break; } return processedAny; } render::Texture* queueImage(const std::string& abs_file, int priority = -20, bool mipmap = true, bool cachePixels = false, bool cubemap = false) { bool queue = false; render::Texture* tex; { texIdentLock.lock(); render::Texture*& pTex = texNames[abs_file]; if(pTex == 0) { if(cubemap) pTex = render::RenderDriver::createCubemap(); else pTex = render::RenderDriver::createTexture(); queue = true; } tex = pTex; texIdentLock.release(); } if(queue) { queuedImagesLock.lock(); resource::QueuedTexture queued = resource::QueuedTexture(tex, priority, abs_file, mipmap, cachePixels); queuedImages.push(queued); queuedImagesLock.release(); } return tex; } render::Texture* queueImage(Image* img, int priority, bool mipmap, bool cachePixels) { render::Texture* tex = render::RenderDriver::createTexture(); queuedTexturesLock.lock(); resource::QueuedTexture queued = resource::QueuedTexture(tex, priority, img, mipmap, cachePixels); queuedTextures.push(queued); queuedTexturesLock.release(); return tex; } void queueTextureUpdate(render::Texture* tex, Image* img, int priority, bool mipmap, bool cachePixels) { queuedTexturesLock.lock(); resource::QueuedTexture queued = resource::QueuedTexture(tex, priority, img, mipmap, cachePixels); queuedTextures.push(queued); queuedTexturesLock.release(); } void Library::loadMaterials(const std::string& filename) { DataHandler datahandler; render::RenderState* state = nullptr; render::SpriteSheet* sheet = nullptr; render::MaterialGroup* group = nullptr; std::string matName; int priority = -61; bool texdefs = false; //Initialization handling datahandler("SpriteSheet", [&](std::string& value) { matName = value; priority = -61; group = nullptr; sheet = new render::SpriteSheet(); state = &sheet->material; spritesheets[matName] = sheet; sheet_indices[sheet] = (unsigned)spritesheet_names.size(); spritesheet_names.push_back(matName); texdefs = false; }); datahandler("Material", [&](std::string& value) { matName = value; sheet = 0; group = nullptr; priority = -61; if(materials.find(matName) != materials.end()) { error("Duplicate material: %s", matName.c_str()); state = materials[matName]; *state = render::RenderState(); } else { state = new render::RenderState(); state->constant = true; } materials[matName] = state; mat_indices[state] = (unsigned)material_names.size(); material_names.push_back(matName); texdefs = false; }); datahandler("MaterialGroup", [&](std::string& value) { matName = value; sheet = nullptr; state = nullptr; group = new render::MaterialGroup(); group->prefix = value + "_"; matGroups[value] = group; }); datahandler("Template", [&](std::string& value) { if(!group) return; auto it = materials.find(value); if(it != materials.end()) group->base = *it->second; else error("Could not find template material '%s' for '%s'", value.c_str(), matName.c_str()); }); datahandler("Prefix", [&](std::string& value) { if(!group) return; group->prefix = value; }); datahandler("Folder", [&](std::string& value) { if(!group) return; auto& g = *group; auto& mats = materials; auto& inds = mat_indices; auto& names = material_names; int texPriority = priority; devices.mods.listFiles(value, "*.png", [&](const std::string& filename) { std::string id = g.prefix + getBasename(filename, false); makeIdentifier(id); auto* mat = new render::RenderState(); *mat = g.base; mat->constant = true; mats[id] = mat; inds[mat] = (unsigned)names.size(); names.push_back(id); g.names.push_back(id); g.materials.push_back(mat); if(load_resources) { mat->textures[0] = queueImage(filename, texPriority, mat->mipmap, mat->cachePixels); if(watch_resources) devices.library.watchTexture(filename); } }, true); }); datahandler("Inherit", [&](std::string& value) { if(!state) return; auto it = materials.find(value); if(it != materials.end()) *state = *it->second; else error("Could not find material '%s' to inherit for '%s'", value.c_str(), matName.c_str()); }); //Renderstate members HANDLE_BOOL(datahandler, "DepthWrite", state, depthWrite); HANDLE_ENUM(datahandler, "DepthTest", state, depthTest, depthTests); HANDLE_ENUM(datahandler, "Culling", state, culling, cullingModes); HANDLE_BOOL(datahandler, "Lighting", state, lighting); HANDLE_BOOL(datahandler, "NormalizeNormals", state, normalizeNormals); HANDLE_ENUM_W(datahandler, "Shader", state, shader, shaders, load_resources); HANDLE_NUM(datahandler, "Shininess", state, shininess); HANDLE_ENUM(datahandler, "WrapVertical", state, wrapVertical, textureWraps); HANDLE_ENUM(datahandler, "WrapHorizontal", state, wrapHorizontal, textureWraps); HANDLE_ENUM(datahandler, "FilterMin", state, filterMin, textureFilters); HANDLE_ENUM(datahandler, "FilterMag", state, filterMag, textureFilters); HANDLE_ENUM(datahandler, "DrawMode", state, drawMode, drawModes); HANDLE_ENUM(datahandler, "Blend", state, baseMat, baseMats); datahandler("Mipmap", [&](std::string& value) { if(!state) return; if(texdefs) { warn("Warning: Mipmap statement should be before texture" " declarations.\n %s", datahandler.position().c_str()); } state->mipmap = toBool(value); }); datahandler("CachePixels", [&](std::string& value) { if(!state) return; state->cachePixels = toBool(value); }); datahandler("LoadPriority", [&](std::string& value) { if(value == "Critical" || value == "Menu") priority = 10; else if(value == "Game") priority = -10; else if(value == "High") priority = -31; else if(value == "Low") priority = -91; else priority = -111 + toNumber(value); }); datahandler("Alpha", [&](std::string& value) { if(!state) return; if(toBool(value)) state->baseMat = render::MAT_Alpha; else state->baseMat = render::MAT_Solid; }); datahandler("Diffuse", [&](std::string& value) { int r,g,b,a; if(int args = sscanf(value.c_str(),"#%2x%2x%2x%2x", &r,&g,&b,&a)) { if(args == 3) state->diffuse = Colorf(Color(r,g,b)); else if(args == 4) state->diffuse = Colorf(Color(r,g,b,a)); } else { sscanf(value.c_str(), "%f,%f,%f,%f", &state->diffuse.r, &state->diffuse.g, &state->diffuse.b, &state->diffuse.a); } }); datahandler("Specular", [&](std::string& value) { int r,g,b,a; if(int args = sscanf(value.c_str(),"#%2x%2x%2x%2x", &r,&g,&b,&a)) { if(args == 3) state->specular = Colorf(Color(r,g,b)); else if(args == 4) state->specular = Colorf(Color(r,g,b,a)); } else { sscanf(value.c_str(), "%f,%f,%f,%f", &state->specular.r, &state->specular.g, &state->specular.b, &state->specular.a); } }); //Spritesheet members HANDLE_ENUM(datahandler, "Mode", sheet, mode, spriteModes); datahandler("Size", [&](std::string& value) { if(!sheet) return; std::vector args; split(value, args, ','); if(args.size() != 2) return; sheet->width = toNumber(args[0]); sheet->height = toNumber(args[1]); }); datahandler("Spacing", [&](std::string& value) { if(!sheet) return; sheet->spacing = toNumber(value); }); datahandler.defaultHandler([&](std::string& key, std::string& value) { if(key.size() >= 7 && (key.compare(0, 7, "Texture") == 0 || key.compare(0, 7, "Cubemap") == 0)) { //Figure out the texture number to set int texNum = 0; texdefs = true; if (key.size() > 7) { std::string num = key.substr(7, key.size() - 7); texNum = min_(toNumber(num)-1, RENDER_MAX_TEXTURES); } value = getAbsolutePath( devices.mods.resolve(value) ); if(load_resources) { state->textures[texNum] = queueImage(value, priority, state->mipmap, state->cachePixels, key[0] == 'C'); if(watch_resources) watchTexture(value); } } }); datahandler.read(filename); } std::string Library::getSpriteDesc(const render::Sprite& sprt) const { std::string ret; if(sprt.mat) { auto it = mat_indices.find(sprt.mat); if(it != mat_indices.end()) ret = material_names[it->second]; } else { auto it = sheet_indices.find(sprt.sheet); if(it == sheet_indices.end()) return ret; ret += spritesheet_names[it->second]; ret += "::"; ret += toString(sprt.index); } if(sprt.color.color != 0xffffffff) { ret += "*"; ret += toString(sprt.color); } return ret; } };