From 70b8dcdc3a91c9c6e02af1bc830c99f23b428ac8 Mon Sep 17 00:00:00 2001 From: fgenesis Date: Wed, 31 May 2023 00:55:16 +0200 Subject: [PATCH] Rework texture loading, part 1 Major code/logic cleanups + it has a multithreaded batch mode now. The VFS code doesn't like multiple threads at all, so for now there's a big lock in front of where it matters. Changed precacher, map/tileset, and worldmap loading to batched mode. Still TODO: - fix broken mod preview images - reloading resources on debug key - make mod recache entirely unnecessary - actually drop resources when no longer needed --- Aquaria/AnimationEditor.cpp | 2 +- Aquaria/DSQ.cpp | 44 +-- Aquaria/Game.cpp | 348 +++++++++----------- Aquaria/Game.h | 9 +- Aquaria/Intro.cpp | 2 +- Aquaria/MiniMapRender.cpp | 21 +- Aquaria/Mod.cpp | 12 +- Aquaria/WorldMapRender.cpp | 25 +- BBGE/Base.cpp | 9 +- BBGE/CMakeLists.txt | 2 + BBGE/Core.cpp | 178 ++-------- BBGE/Core.h | 33 +- BBGE/Precacher.cpp | 125 +++---- BBGE/Precacher.h | 21 +- BBGE/Quad.cpp | 3 +- BBGE/RenderObject.cpp | 20 +- BBGE/Texture.cpp | 256 ++------------ BBGE/Texture.h | 25 +- BBGE/TextureMgr.cpp | 318 ++++++++++++++++++ BBGE/TextureMgr.h | 53 +++ ExternalLibs/ttvfs_cfileapi/ttvfs_stdio.cpp | 24 +- ExternalLibs/ttvfs_cfileapi/ttvfs_stdio.h | 3 + 22 files changed, 737 insertions(+), 796 deletions(-) create mode 100644 BBGE/TextureMgr.cpp create mode 100644 BBGE/TextureMgr.h diff --git a/Aquaria/AnimationEditor.cpp b/Aquaria/AnimationEditor.cpp index df4b6f3..77e2697 100644 --- a/Aquaria/AnimationEditor.cpp +++ b/Aquaria/AnimationEditor.cpp @@ -1153,7 +1153,7 @@ void AnimationEditor::clearRot() if (editingBone) { if(core->getCtrlState()) - editingBone->texture->reload(); + core->texmgr.load(editingBone->texture->name, TextureMgr::OVERWRITE); else if(splinegrid) splinegrid->resetControlPoints(); else diff --git a/Aquaria/DSQ.cpp b/Aquaria/DSQ.cpp index 05f09c1..7acbc99 100644 --- a/Aquaria/DSQ.cpp +++ b/Aquaria/DSQ.cpp @@ -441,27 +441,27 @@ void DSQ::loadFonts() font.load(file, 1, false); font.fontTopColor = Vector(0.9f,0.9f,1); font.fontBtmColor = Vector(0.5f,0.8f,1); - font.overrideTexture = core->addTexture("font"); + font.overrideTexture = this->getTexture("font"); smallFont.load(file, 0.6f, false); smallFont.fontTopColor = Vector(0.9f,0.9f,1); smallFont.fontBtmColor = Vector(0.5f,0.8f,1); - smallFont.overrideTexture = core->addTexture("font"); + smallFont.overrideTexture = this->getTexture("font"); smallFontRed.load(file, 0.6f, false); smallFontRed.fontTopColor = Vector(1,0.9f,0.9f); smallFontRed.fontBtmColor = Vector(1,0.8f,0.5f); - smallFontRed.overrideTexture = core->addTexture("font"); + smallFontRed.overrideTexture = this->getTexture("font"); subsFont.load(file, 0.5f, false); subsFont.fontTopColor = Vector(1,1,1); subsFont.fontBtmColor = Vector(0.5f,0.8f,1); - subsFont.overrideTexture = core->addTexture("font"); + subsFont.overrideTexture = this->getTexture("font"); goldFont.load(file, 1, false); goldFont.fontTopColor = Vector(1,0.9f,0.5f); goldFont.fontBtmColor = Vector(0.6f,0.5f,0.25f); - goldFont.overrideTexture = core->addTexture("font"); + goldFont.overrideTexture = this->getTexture("font"); file = localisePath("data/font.ttf"); @@ -844,11 +844,10 @@ void DSQ::init() addStateInstance(new Nag); - - this->setBaseTextureDirectory("gfx/"); - voiceOversEnabled = true; + this->setExtraTexturePath(NULL); + if(!user.load(false)) @@ -1255,6 +1254,7 @@ void DSQ::init() } addRenderObject(fpsText, LR_DEBUG_TEXT); + precacher.setBaseDir(this->getBaseTexturePath()); precacher.precacheList("data/precache.txt", loadBitForTexPrecache); setTexturePointers(); @@ -1899,20 +1899,10 @@ void DSQ::refreshResourcesForPatch(const std::string& name) int reloaded = 0; if(files.size()) { - for(size_t i = 0; i < dsq->resources.size(); ++i) - { - Texture *r = dsq->resources[i]; - for(size_t i = 0; i < files.size(); ++i) - if(files[i] == r->name) - { - ++reloaded; - r->reload(); - break; - } - } + texmgr.loadBatch(NULL, &files[0], files.size(), TextureMgr::OVERWRITE); } os.str(""); - os << "refreshResourcesForPatch - " << reloaded << " textures reloaded"; + os << "refreshResourcesForPatch - " << files.size() << " textures reloaded"; debugLog(os.str()); } #else @@ -1995,7 +1985,7 @@ void DSQ::shutdown() Network::shutdown(); scriptInterface.shutdown(); - precacher.clean(); + precacher.clear(); core->particleManager->clearParticleBank(); @@ -2086,11 +2076,11 @@ void DSQ::shutdown() void DSQ::setTexturePointers() { - texCursor = core->addTexture("cursor"); - texCursorLook = core->addTexture("cursor-look"); - texCursorBurst = core->addTexture("cursor-burst"); - texCursorSwim = core->addTexture("cursor-swim"); - texCursorSing = core->addTexture("cursor-sing"); + texCursor = this->getTexture("cursor"); + texCursorLook = this->getTexture("cursor-look"); + texCursorBurst = this->getTexture("cursor-burst"); + texCursorSwim = this->getTexture("cursor-swim"); + texCursorSing = this->getTexture("cursor-sing"); if (cursor) cursor->setTexturePointer(texCursor); @@ -3928,7 +3918,7 @@ void DSQ::onUpdate(float dt) if (isDeveloperKeys() && fpsText && cmDebug && cmDebug->alpha == 1) { std::ostringstream os; - os << "FPS: " << core->fps << " | ROC: " << core->renderObjectCount << " | RC: " << g_dbg_numRenderCalls << " | RES: " << core->resources.size(); + os << "FPS: " << core->fps << " | ROC: " << core->renderObjectCount << " | RC: " << g_dbg_numRenderCalls << " | RES: " << texmgr.getNumLoaded(); os << " | p: " << core->processedRenderObjectCount << " | t: " << core->totalRenderObjectCount; os << " | s: " << dsq->continuity.seconds; os << " | sndQ: " << core->dbg_numThreadDecoders; diff --git a/Aquaria/Game.cpp b/Aquaria/Game.cpp index 2ee6874..8c1c2a4 100644 --- a/Aquaria/Game.cpp +++ b/Aquaria/Game.cpp @@ -231,7 +231,6 @@ Game::Game() : StateObject() Game::~Game() { delete themenu; - tileCache.clean(); game = 0; } @@ -410,7 +409,7 @@ void Game::fillGridFromQuad(Quad *q, ObsType obsType, bool trim) tpos.y -= h2; int w = 0, h = 0; - unsigned int size = 0; + size_t size = 0; unsigned char *data = q->texture->getBufferAndSize(&w, &h, &size); if (!data) { @@ -1082,7 +1081,7 @@ void Game::generateCollisionMask(Bone *q, float overrideCollideRadius /* = 0 */) tpos.y -= h2; int w = 0, h = 0; - unsigned int size = 0; + size_t size = 0; unsigned char *data = q->texture->getBufferAndSize(&w, &h, &size); if (!data) { @@ -1285,7 +1284,6 @@ bool Game::loadSceneXML(std::string scene) { bgSfxLoop = ""; airSfxLoop = ""; - elementTemplatePack = "Main"; entitySaveData.clear(); std::string fn = getSceneFilename(scene); if (!exists(fn)) @@ -1317,17 +1315,13 @@ bool Game::loadSceneXML(std::string scene) if (level) { XMLElement *levelSF = saveFile->NewElement("Level"); - if (level->Attribute("tileset")) + const char *tileset = level->Attribute("tileset"); + if(!tileset) + tileset = level->Attribute("elementTemplatePack"); // legacy, still present in some very old maps + if (tileset) { - elementTemplatePack = level->Attribute("tileset"); - loadElementTemplates(elementTemplatePack); - levelSF->SetAttribute("tileset", elementTemplatePack.c_str()); - } - else if (level->Attribute("elementTemplatePack")) - { - elementTemplatePack = level->Attribute("elementTemplatePack"); - loadElementTemplates(elementTemplatePack); - levelSF->SetAttribute("tileset", elementTemplatePack.c_str()); + loadElementTemplates(tileset); + levelSF->SetAttribute("tileset", tileset); } else return false; @@ -1729,206 +1723,195 @@ bool Game::loadSceneXML(std::string scene) saveFile->InsertEndChild(newSF); } - std::vector loadedElements; - loadedElements.reserve(200); + struct ElementDef + { + ElementDef(int lr) + : layer(lr), idx(0), x(0), y(0), rot(0), fh(0), fv(0), flags(0), efxIdx(0), repeat(0) + , tag(0), sx(1), sy(1), rsx(1), rsy(1) + {} + + int layer, idx, x, y, rot, fh, fv, flags, efxIdx, repeat, tag; + float sx, sy, rsx, rsy; + }; + std::vector elemsDefs; + elemsDefs.reserve(256); + XMLElement *simpleElements = doc.FirstChildElement("SE"); while (simpleElements) { - int idx, x, y, rot; - float sz,sz2; - loadedElements.clear(); - if (simpleElements->Attribute("d")) + const size_t defsBeginIdx = elemsDefs.size(); + const int layer = atoi(simpleElements->Attribute("l")); + + if (const char *attr = simpleElements->Attribute("d")) { - SimpleIStringStream is(simpleElements->Attribute("d")); - while (is >> idx) + SimpleIStringStream is(attr, SimpleIStringStream::REUSE); + ElementDef d(4); // legacy crap + while (is >> d.idx) { - is >> x >> y >> rot; - Element *e = createElement(idx, Vector(x,y), 4); - e->rotation.z = rot; - loadedElements.push_back(e); + is >> d.x >> d.y >> d.rot; + elemsDefs.push_back(d); } } - if (simpleElements->Attribute("e")) + if (const char *attr = simpleElements->Attribute("e")) { - SimpleIStringStream is2(simpleElements->Attribute("e")); - int l = atoi(simpleElements->Attribute("l")); - while(is2 >> idx) + SimpleIStringStream is(attr, SimpleIStringStream::REUSE); + ElementDef d(layer); + while(is >> d.idx) { - is2 >> x >> y >> rot; - Element *e = createElement(idx, Vector(x,y), l); - e->rotation.z = rot; - loadedElements.push_back(e); + is >> d.x >> d.y >> d.rot; + elemsDefs.push_back(d); } } - if (simpleElements->Attribute("f")) + if (const char *attr = simpleElements->Attribute("f")) { - SimpleIStringStream is2(simpleElements->Attribute("f")); - int l = atoi(simpleElements->Attribute("l")); - while(is2 >> idx) + SimpleIStringStream is(attr, SimpleIStringStream::REUSE); + ElementDef d(layer); + while(is >> d.idx) { - is2 >> x >> y >> rot >> sz; - Element *e = createElement(idx, Vector(x,y), l); - e->scale = Vector(sz,sz); - e->rotation.z = rot; - loadedElements.push_back(e); + is >> d.x >> d.y >> d.rot >> d.sx; + d.sy = d.sx; + elemsDefs.push_back(d); } } - if (simpleElements->Attribute("g")) + if (const char *attr = simpleElements->Attribute("g")) { - SimpleIStringStream is2(simpleElements->Attribute("g")); - int l = atoi(simpleElements->Attribute("l")); - while(is2 >> idx) + SimpleIStringStream is(attr, SimpleIStringStream::REUSE); + ElementDef d(layer); + while(is >> d.idx) { - int fh, fv; - is2 >> x >> y >> rot >> sz >> fh >> fv; - Element *e = createElement(idx, Vector(x,y), l); - if (fh) - e->flipHorizontal(); - if (fv) - e->flipVertical(); - e->scale = Vector(sz,sz); - e->rotation.z = rot; - loadedElements.push_back(e); + is >> d.x >> d.y >> d.rot >> d.sx >> d.fh >> d.fv; + d.sy = d.sx; + elemsDefs.push_back(d); } } - if (simpleElements->Attribute("h")) + if (const char *attr = simpleElements->Attribute("h")) { - SimpleIStringStream is2(simpleElements->Attribute("h")); - int l = atoi(simpleElements->Attribute("l")); - while(is2 >> idx) + SimpleIStringStream is(attr, SimpleIStringStream::REUSE); + ElementDef d(layer); + while(is >> d.idx) { - int fh, fv; - int flags; - is2 >> x >> y >> rot >> sz >> fh >> fv >> flags; - Element *e = createElement(idx, Vector(x,y), l); - e->elementFlag = (ElementFlag)flags; - if (e->elementFlag >= EF_MAX || e->elementFlag < EF_NONE) - e->elementFlag = EF_NONE; - if (fh) - e->flipHorizontal(); - if (fv) - e->flipVertical(); - e->scale = Vector(sz,sz); - e->rotation.z = rot; - loadedElements.push_back(e); + is >> d.x >> d.y >> d.rot >> d.sx >> d.fh >> d.fv >> d.flags; + d.sy = d.sx; + elemsDefs.push_back(d); } } - if (simpleElements->Attribute("i")) + if (const char *attr = simpleElements->Attribute("i")) { - SimpleIStringStream is2(simpleElements->Attribute("i")); - int l = atoi(simpleElements->Attribute("l")); - while(is2 >> idx) - { - int fh, fv; - int flags; - int efxIdx; - is2 >> x >> y >> rot >> sz >> fh >> fv >> flags >> efxIdx; - if (sz < MIN_SIZE) - sz = MIN_SIZE; - Element *e = createElement(idx, Vector(x,y), l); - e->elementFlag = (ElementFlag)flags; - if (fh) - e->flipHorizontal(); - if (fv) - e->flipVertical(); - e->scale = Vector(sz,sz); - e->rotation.z = rot; - e->setElementEffectByIndex(efxIdx); - loadedElements.push_back(e); + SimpleIStringStream is(attr, SimpleIStringStream::REUSE); + ElementDef d(layer); + while(is >> d.idx) + { + is >> d.x >> d.y >> d.rot >> d.sx >> d.fh >> d.fv >> d.flags >> d.efxIdx; + d.sy = d.sx; + elemsDefs.push_back(d); } } - if (simpleElements->Attribute("j")) + if (const char *attr = simpleElements->Attribute("j")) { - SimpleIStringStream is2(simpleElements->Attribute("j")); - int l = atoi(simpleElements->Attribute("l")); - while(is2 >> idx) + SimpleIStringStream is(attr, SimpleIStringStream::REUSE); + ElementDef d(layer); + while(is >> d.idx) { - int fh, fv; - int flags; - int efxIdx; - int repeat; - is2 >> x >> y >> rot >> sz >> fh >> fv >> flags >> efxIdx >> repeat; - if (sz < MIN_SIZE) - sz = MIN_SIZE; - Element *e = createElement(idx, Vector(x,y), l); - e->elementFlag = (ElementFlag)flags; - if (fh) - e->flipHorizontal(); - if (fv) - e->flipVertical(); - - e->scale = Vector(sz,sz); - e->rotation.z = rot; - e->setElementEffectByIndex(efxIdx); - if (repeat) - e->repeatTextureToFill(true); - loadedElements.push_back(e); + is >> d.x >> d.y >> d.rot >> d.sx >> d.fh >> d.fv >> d.flags >> d.efxIdx >> d.repeat; + d.sy = d.sx; + elemsDefs.push_back(d); } } - if (simpleElements->Attribute("k")) + if (const char *attr = simpleElements->Attribute("k")) { - SimpleIStringStream is2(simpleElements->Attribute("k")); - int l = atoi(simpleElements->Attribute("l")); - while(is2 >> idx) + SimpleIStringStream is(attr, SimpleIStringStream::REUSE); + ElementDef d(layer); + while(is >> d.idx) { - int fh, fv; - int flags; - int efxIdx; - int repeat; - is2 >> x >> y >> rot >> sz >> sz2 >> fh >> fv >> flags >> efxIdx >> repeat; - if (sz < MIN_SIZE) - sz = MIN_SIZE; - if (sz2 < MIN_SIZE) - sz2 = MIN_SIZE; - Element *e = createElement(idx, Vector(x,y), l); - e->elementFlag = (ElementFlag)flags; - if (fh) - e->flipHorizontal(); - if (fv) - e->flipVertical(); - - e->scale = Vector(sz,sz2); - e->rotation.z = rot; - e->setElementEffectByIndex(efxIdx); - if (repeat) - e->repeatTextureToFill(true); - - loadedElements.push_back(e); + is >> d.x >> d.y >> d.rot >> d.sx >> d.sy >> d.fh >> d.fv >> d.flags >> d.efxIdx >> d.repeat; + elemsDefs.push_back(d); } } - if (simpleElements->Attribute("repeatScale")) + + // done loading raw data, now for some possible extensions added later + + if (const char *attr = simpleElements->Attribute("repeatScale")) { - SimpleIStringStream is2(simpleElements->Attribute("repeatScale"), SimpleIStringStream::REUSE); - for(size_t i = 0; i < loadedElements.size(); ++i) + SimpleIStringStream is(attr, SimpleIStringStream::REUSE); + for(size_t i = defsBeginIdx; i < elemsDefs.size(); ++i) { - Element *e = loadedElements[i]; - if(e->isRepeatingTextureToFill()) + ElementDef& d = elemsDefs[i]; + if(d.repeat) { - float repeatScaleX = 1, repeatScaleY = 1; - if(!(is2 >> repeatScaleX >> repeatScaleY)) + if(!(is >> d.rsx >> d.rsx)) break; - e->repeatToFillScale.x = repeatScaleX; - e->repeatToFillScale.y = repeatScaleY; - e->refreshRepeatTextureToFill(); } } } - if (simpleElements->Attribute("tag")) + if (const char *attr = simpleElements->Attribute("tag")) { - SimpleIStringStream is2(simpleElements->Attribute("tag"), SimpleIStringStream::REUSE); - for(size_t i = 0; i < loadedElements.size(); ++i) + SimpleIStringStream is(attr, SimpleIStringStream::REUSE); + for(size_t i = defsBeginIdx; i < elemsDefs.size(); ++i) { - Element *e = loadedElements[i]; - int tag = 0; - if(!(is2 >> tag)) + ElementDef& d = elemsDefs[i]; + if(!(is >> d.tag)) break; - e->setTag(tag); } } simpleElements = simpleElements->NextSiblingElement("SE"); } + // figure out which textures in the tileset are used and preload those that are actually used + { + unsigned char usedIdx[1024] = {0}; + for(size_t i = 0; i < elemsDefs.size(); ++i) + { + unsigned idx = elemsDefs[i].idx; + if(idx < Countof(usedIdx)) + usedIdx[idx] = 1; + } + std::vector usedTex; + usedTex.reserve(elementTemplates.size()); // optimistically assume all textures in the tileset are used + + for (size_t i = 0; i < elementTemplates.size(); i++) + { + unsigned idx = elementTemplates[i].idx; + if (idx < Countof(usedIdx) && usedIdx[idx]) + usedTex.push_back(elementTemplates[i].gfx); + } + std::sort(usedTex.begin(), usedTex.end()); + // drop duplicates + usedTex.resize(std::distance(usedTex.begin(), std::unique(usedTex.begin(), usedTex.end()))); + + std::ostringstream os; + os << "Scene has " << elemsDefs.size() << " elements that use " << usedTex.size() + << " distinct textures out of the " << elementTemplates.size() << " entries in the tileset"; + debugLog(os.str()); + + // preload all used textures + if(usedTex.size()) + dsq->texmgr.loadBatch(NULL, &usedTex[0], usedTex.size()); + } + + // Now that all SE tags have been processed, spawn them + for(size_t i = 0; i < elemsDefs.size(); ++i) + { + const ElementDef& d = elemsDefs[i]; + + Element *e = createElement(d.idx, Vector(d.x,d.y), d.layer); + e->elementFlag = (ElementFlag)d.flags; + if (d.fh) + e->flipHorizontal(); + if (d.fv) + e->flipVertical(); + + e->scale = Vector(d.sx, d.sy); + e->rotation.z = d.rot; + e->repeatToFillScale.x = d.rsx; + e->repeatToFillScale.y = d.rsy; + if(d.efxIdx) + e->setElementEffectByIndex(d.efxIdx); + if (d.repeat) + e->repeatTextureToFill(true); // also applies repeatToFillScale + e->setTag(d.tag); + } + XMLElement *element = doc.FirstChildElement("Element"); while (element) { @@ -2783,7 +2766,6 @@ void Game::applyState() //sceneColor = Vector(0.75, 0.75, 0.8); sceneColor = Vector(1,1,1); sceneName = ""; - elementTemplatePack =""; clearGrid(); bg = 0; bg2 = 0; @@ -2950,9 +2932,9 @@ void Game::applyState() bindInput(); if (verbose) debugLog("Loading Scene"); - if (!loadScene(sceneToLoad)) + loadScene(sceneToLoad); { - loadElementTemplates(elementTemplatePack); + debugLog("Failed to load scene [" + sceneToLoad + "]"); } if (verbose) debugLog("...Done"); @@ -4934,33 +4916,18 @@ void Game::loadElementTemplates(std::string pack) elementTemplates.clear(); - // HACK: need to uncache things! causes memory leak currently - bool doPrecache=false; std::string fn; - if (dsq->mod.isActive()) fn = dsq->mod.getPath() + "tilesets/" + pack + ".txt"; else fn = "data/tilesets/" + pack + ".txt"; - - if (lastTileset == fn) - { - doPrecache=false; - } - - lastTileset = fn; if (!exists(fn)) { - errorLog ("Could not open element template pack [" + fn + "]"); + errorLog ("Could not open tileset [" + fn + "]"); return; } - if (doPrecache) - { - tileCache.clean(); - } - InStream in(fn.c_str()); std::string line; while (std::getline(in, line)) @@ -4977,21 +4944,12 @@ void Game::loadElementTemplates(std::string pack) t.w = w; t.h = h; elementTemplates.push_back(t); - if (doPrecache) - tileCache.precacheTex(gfx); } in.close(); - for (size_t i = 0; i < elementTemplates.size(); i++) - { - for (size_t j = i; j < elementTemplates.size(); j++) - { - if (elementTemplates[i].idx > elementTemplates[j].idx) - { - std::swap(elementTemplates[i], elementTemplates[j]); - } - } - } + + std::sort(elementTemplates.begin(), elementTemplates.end()); + for (int i = 0; i < 27; i++) { elementTemplates.push_back(getElementTemplateForLetter(i)); diff --git a/Aquaria/Game.h b/Aquaria/Game.h index 1c5e601..600fe63 100644 --- a/Aquaria/Game.h +++ b/Aquaria/Game.h @@ -88,6 +88,7 @@ class ElementTemplate { public: ElementTemplate() { alpha = 1; cull = true; w=-1; h=-1; idx=-1; tu1=tu2=tv1=tv2=0; } + inline bool operator<(const ElementTemplate& o) const { return idx < o.idx; } std::string gfx; std::vector grid; int w,h; @@ -293,8 +294,6 @@ public: std::vector entitySaveData; int getIdxForEntityType(std::string type); - Precacher tileCache; - void setCameraFollow(Vector *position); Shot *fireShot(const std::string &bankShot, Entity *firer, Entity *target=0, const Vector &pos=Vector(0,0,0), const Vector &aim=Vector(0,0,0), bool playSfx=true); void playBurstSound(bool wallJump=false); @@ -474,9 +473,6 @@ protected: bool controlHint_ignoreClear; BitmapText *controlHint_text; - std::string lastTileset; - - void createLi(); void createPets(); Quad *backdropQuad; @@ -498,9 +494,6 @@ protected: Vector getClosestPointOnTriangle(Vector a, Vector b, Vector c, Vector p); Vector getClosestPointOnLine(Vector a, Vector b, Vector p); - std::string elementTemplatePack; - - Vector *cameraFollow; RenderObject *cameraFollowObject; Entity *cameraFollowEntity; diff --git a/Aquaria/Intro.cpp b/Aquaria/Intro.cpp index f8c563f..9be361b 100644 --- a/Aquaria/Intro.cpp +++ b/Aquaria/Intro.cpp @@ -77,7 +77,7 @@ void Intro::endIntro() dsq->sound->clearLocalSounds(); - cachy.clean(); + cachy.clear(); dsq->toggleBlackBars(0); diff --git a/Aquaria/MiniMapRender.cpp b/Aquaria/MiniMapRender.cpp index 34d9a5a..3a6744a 100644 --- a/Aquaria/MiniMapRender.cpp +++ b/Aquaria/MiniMapRender.cpp @@ -106,19 +106,14 @@ void MinimapIcon::update(float dt) // pretty much copied from RenderObject::setTexture() static bool _setTex(CountedPtr &tex, std::string name) { - stringToLowerUserData(name); - if (name.empty()) { tex = NULL; return false; } - if(tex && tex->getLoadResult() == TEX_SUCCESS && name == tex->name) - return true; // no texture change - - tex = core->addTexture(name); - return tex && tex->getLoadResult() == TEX_SUCCESS; + tex = core->getTexture(name); + return tex->success; } bool MinimapIcon::setTexture(std::string name) @@ -168,12 +163,12 @@ MiniMapRender::MiniMapRender() : RenderObject() cull = false; lightLevel = 1.0; - texWaterBit = core->addTexture("gui/minimap/waterbit"); - texMinimapBtm = core->addTexture("gui/minimap/btm"); - texMinimapTop = core->addTexture("gui/minimap/top"); - texNaija = core->addTexture("gems/naija-token"); - texHealthBar = core->addTexture("particles/glow-masked"); - texMarker = core->addTexture("gui/minimap/marker"); + texWaterBit = core->getTexture("gui/minimap/waterbit"); + texMinimapBtm = core->getTexture("gui/minimap/btm"); + texMinimapTop = core->getTexture("gui/minimap/top"); + texNaija = core->getTexture("gems/naija-token"); + texHealthBar = core->getTexture("particles/glow-masked"); + texMarker = core->getTexture("gui/minimap/marker"); buttons.clear(); diff --git a/Aquaria/Mod.cpp b/Aquaria/Mod.cpp index 268c84e..7c39605 100644 --- a/Aquaria/Mod.cpp +++ b/Aquaria/Mod.cpp @@ -43,7 +43,7 @@ Mod::Mod() Mod::~Mod() { - modcache.clean(); + modcache.clear(); } /* @@ -152,7 +152,7 @@ void Mod::load(const std::string &p) } } - dsq->secondaryTexturePath = path + "graphics/"; + dsq->setExtraTexturePath((path + "graphics/").c_str()); dsq->sound->audioPath2 = path + "audio/"; dsq->sound->setVoicePath2(path + "audio/"); @@ -180,13 +180,13 @@ void Mod::recache() { if(doRecache) { - dsq->precacher.clean(); + core->texmgr.reloadAll(TextureMgr::KEEP_IF_SAME); dsq->unloadResources(); } if(active) { - modcache.setBaseDir(dsq->secondaryTexturePath); + modcache.setBaseDir(dsq->getExtraTexturePath()); std::string fname = path; if(fname[fname.length() - 1] != '/') fname += '/'; @@ -200,7 +200,7 @@ void Mod::recache() } else { - modcache.clean(); + modcache.clear(); } if(doRecache) @@ -292,7 +292,7 @@ void Mod::setActive(bool a) mapRevealMethod = REVEAL_UNSPECIFIED; setLocalisationModPath(""); name = path = ""; - dsq->secondaryTexturePath = ""; + dsq->setExtraTexturePath(NULL); dsq->sound->audioPath2 = ""; dsq->sound->setVoicePath2(""); SkeletalSprite::secondaryAnimationPath = ""; diff --git a/Aquaria/WorldMapRender.cpp b/Aquaria/WorldMapRender.cpp index 64e313b..4ad8bf7 100644 --- a/Aquaria/WorldMapRender.cpp +++ b/Aquaria/WorldMapRender.cpp @@ -494,7 +494,7 @@ static unsigned char *tileDataToAlpha(WorldMapTile *tile) const unsigned int scaleY = texHeight / MAPVIS_SUBDIV; unsigned char *savedTexData = new unsigned char[texWidth * texHeight * 4]; - tile->q->texture->read(0, 0, texWidth, texHeight, savedTexData); + tile->q->texture->readRGBA(savedTexData); unsigned char *texData = new unsigned char[texWidth * texHeight * 4]; memcpy(texData, savedTexData, texWidth * texHeight * 4); @@ -538,7 +538,7 @@ static unsigned char *tileDataToAlpha(WorldMapTile *tile) } } - tile->q->texture->write(0, 0, texWidth, texHeight, texData); + tile->q->texture->writeRGBA(0, 0, texWidth, texHeight, texData); delete[] texData; return savedTexData; @@ -546,7 +546,7 @@ static unsigned char *tileDataToAlpha(WorldMapTile *tile) static void resetTileAlpha(WorldMapTile *tile, const unsigned char *savedTexData) { - tile->q->texture->write(0, 0, tile->q->texture->width, tile->q->texture->height, savedTexData); + tile->q->texture->writeRGBA(0, 0, tile->q->texture->width, tile->q->texture->height, savedTexData); } @@ -629,25 +629,29 @@ WorldMapRender::WorldMapRender() : RenderObject(), ActionMapper() - int num = dsq->continuity.worldMap.getNumWorldMapTiles(); + const size_t num = dsq->continuity.worldMap.getNumWorldMapTiles(); std::string n = dsq->game->sceneName; stringToUpper(n); - for (int i = 0; i < num; i++) + std::vector textodo(num); + std::vector texs(num, NULL); + textodo.reserve(num); + for (size_t i = 0; i < num; i++) { WorldMapTile *tile = dsq->continuity.worldMap.getWorldMapTile(i); if (tile) { if (tile->name == n) - { activeTile = tile; - break; - } + textodo[i] = "gui/worldmap/" + tile->name; } } tiles.clear(); - for (int i = 0; i < num; i++) + if(num) + dsq->texmgr.loadBatch(&texs[0], &textodo[0], num); + + for (size_t i = 0; i < num; i++) { WorldMapTile *tile = dsq->continuity.worldMap.getWorldMapTile(i); if (tile) @@ -655,8 +659,7 @@ WorldMapRender::WorldMapRender() : RenderObject(), ActionMapper() Vector pos(tile->gridPos.x, tile->gridPos.y); Quad *q = new Quad; - std::string tn = "Gui/WorldMap/" + tile->name; - q->setTexture(tn); + q->setTexturePointer(texs[i]); q->position = pos; q->alphaMod = 0; q->drawOrder = Quad::GRID_DRAW_WORLDMAP; diff --git a/BBGE/Base.cpp b/BBGE/Base.cpp index 691017f..fba6755 100644 --- a/BBGE/Base.cpp +++ b/BBGE/Base.cpp @@ -232,10 +232,14 @@ bool exists(const std::string &f, bool makeFatal, bool skipVFS) #ifdef BBGE_BUILD_VFS if (!skipVFS) { - e = !!vfs.GetFile(f.c_str()); + e = !!vfgetfile(f.c_str()); } else -#endif + { + std::string tmp = adjustFilenameCase(f); + e = ttvfs::FileExists(tmp.c_str()); + } +#else if (!e) { std::string tmp = adjustFilenameCase(f); @@ -246,6 +250,7 @@ bool exists(const std::string &f, bool makeFatal, bool skipVFS) fclose(file); } } +#endif if (makeFatal && !e) { diff --git a/BBGE/CMakeLists.txt b/BBGE/CMakeLists.txt index 3d51a3b..8eaa5af 100644 --- a/BBGE/CMakeLists.txt +++ b/BBGE/CMakeLists.txt @@ -105,6 +105,8 @@ set(BBGE_SRCS StringBank.h Texture.cpp Texture.h + TextureMgr.cpp + TextureMgr.h TTFFont.cpp TTFFont.h Vector.cpp diff --git a/BBGE/Core.cpp b/BBGE/Core.cpp index 04f5314..1362bf9 100644 --- a/BBGE/Core.cpp +++ b/BBGE/Core.cpp @@ -350,8 +350,6 @@ Core::Core(const std::string &filesystem, const std::string& extraDataDir, int n debugLogActive = true; debugOutputActive = false; - debugLogTextures = true; - grabInput = false; srand(time(NULL)); @@ -398,6 +396,8 @@ Core::Core(const std::string &filesystem, const std::string& extraDataDir, int n initRenderObjectLayers(numRenderLayers); initPlatform(filesystem); + + texmgr.spawnThreads(3); } void Core::initPlatform(const std::string &filesystem) @@ -1824,20 +1824,6 @@ void Core::showBuffer() window->present(); } -// WARNING: only for use during shutdown -// otherwise, textures will try to remove themselves -// when destroy is called on them -void Core::clearResources() -{ - if(resources.size()) - { - debugLog("Warning: The following resources were not cleared:"); - for(size_t i = 0; i < resources.size(); ++i) - debugLog(resources[i]->name); - resources.clear(); // nothing we can do; refcounting is messed up - } -} - void Core::shutdownInputLibrary() { } @@ -1897,7 +1883,7 @@ void Core::shutdown() debugLog("OK"); debugLog("Clear All Resources..."); - clearResources(); + texmgr.shutdown(); debugLog("OK"); @@ -1956,118 +1942,9 @@ bool Core::exists(const std::string &filename) return ::exists(filename, false); // defined in Base.cpp } -CountedPtr Core::findTexture(const std::string &name) -{ - int sz = resources.size(); - for (int i = 0; i < sz; i++) - { - //out << resources[i]->name << " is " << name << " ?" << std::endl; - //NOTE: ensure all names are lowercase before this point - if (resources[i]->name == name) - { - return resources[i]; - } - } - return 0; -} - -// This handles unix/win32 relative paths: ./rel/path -// Unix abs paths: /home/user/... -// Win32 abs paths: C:/Stuff/.. and also C:\Stuff\... -#define ISPATHROOT(x) (x[0] == '.' || x[0] == '/' || ((x).length() > 1 && x[1] == ':')) - -std::string Core::getTextureLoadName(const std::string &texture) -{ - std::string loadName = texture; - - if (texture.empty() || !ISPATHROOT(texture)) - { - if (texture.find(baseTextureDirectory) == std::string::npos) - loadName = baseTextureDirectory + texture; - } - return loadName; -} - -CountedPtr Core::doTextureAdd(const std::string &texture, const std::string &loadName, std::string internalTextureName) -{ - if (texture.empty() || !ISPATHROOT(texture)) - { - if (texture.find(baseTextureDirectory) != std::string::npos) - internalTextureName = internalTextureName.substr(baseTextureDirectory.size(), internalTextureName.size()); - } - - if (internalTextureName.size() > 4) - { - if (internalTextureName[internalTextureName.size()-4] == '.') - { - internalTextureName = internalTextureName.substr(0, internalTextureName.size()-4); - } - } - - stringToLowerUserData(internalTextureName); - CountedPtr t = core->findTexture(internalTextureName); - if (t) - return t; - - t = new Texture; - t->name = internalTextureName; - - if(t->load(loadName, true)) - { - std::ostringstream os; - os << "LOADED TEXTURE FROM DISK: [" << internalTextureName << "] idx: " << resources.size()-1; - debugLog(os.str()); - } - else - { - t->width = 64; - t->height = 64; - } - - return t; -} - -CountedPtr Core::addTexture(const std::string &textureName) +CountedPtr Core::getTexture(const std::string &name) { - if (textureName.empty()) - return NULL; - - CountedPtr ptex; - std::string texture = textureName; - stringToLowerUserData(texture); - std::string internalTextureName = texture; - std::string loadName = getTextureLoadName(texture); - - if (!texture.empty() && texture[0] == '@') - { - texture = secondaryTexturePath + texture.substr(1, texture.size()); - loadName = texture; - } - else if (!secondaryTexturePath.empty() && texture[0] != '.' && texture[0] != '/') - { - std::string t = texture; - std::string ln = loadName; - texture = secondaryTexturePath + texture; - loadName = texture; - ptex = doTextureAdd(texture, loadName, internalTextureName); - if (!ptex || ptex->getLoadResult() == TEX_FAILED) - ptex = doTextureAdd(t, ln, internalTextureName); - } - else - ptex = doTextureAdd(texture, loadName, internalTextureName); - - addTexture(ptex.content()); - - if(debugLogTextures) - { - if(!ptex || ptex->getLoadResult() != TEX_SUCCESS) - { - std::ostringstream os; - os << "FAILED TO LOAD TEXTURE: [" << internalTextureName << "] idx: " << resources.size()-1; - debugLog(os.str()); - } - } - return ptex; + return texmgr.getOrLoad(name); } void Core::addRenderObject(RenderObject *o, unsigned layer) @@ -2089,10 +1966,10 @@ void Core::switchRenderObjectLayer(RenderObject *o, unsigned toLayer) void Core::unloadResources() { - for (size_t i = 0; i < resources.size(); i++) + /*for (size_t i = 0; i < resources.size(); i++) { resources[i]->unload(); - } + }*/ } void Core::onReloadResources() @@ -2101,43 +1978,30 @@ void Core::onReloadResources() void Core::reloadResources() { - for (size_t i = 0; i < resources.size(); i++) + /*for (size_t i = 0; i < resources.size(); i++) { resources[i]->reload(); } - onReloadResources(); + onReloadResources();*/ } -void Core::addTexture(Texture *r) +const std::string & Core::getBaseTexturePath() const { - for(size_t i = 0; i < resources.size(); ++i) - if(resources[i] == r) - return; - - resources.push_back(r); - if (r->name.empty()) - { - debugLog("Empty name resource added"); - } + return texmgr.loadFromPaths.back(); } -void Core::removeTexture(Texture *res) +void Core::setExtraTexturePath(const char * dir) { - std::vector copy; - copy.swap(resources); - - for (size_t i = 0; i < copy.size(); ++i) - { - if (copy[i] == res) - { - copy[i]->destroy(); - copy[i] = copy.back(); - copy.pop_back(); - break; - } - } + texmgr.loadFromPaths.resize(size_t(1) + !!dir); + size_t w = 0; + if(dir) + texmgr.loadFromPaths[w++] = dir; + texmgr.loadFromPaths[w] = "gfx/"; +} - resources.swap(copy); +const char *Core::getExtraTexturePath() const +{ + return texmgr.loadFromPaths.size() > 1 ? texmgr.loadFromPaths[0].c_str() : NULL; } void Core::removeRenderObject(RenderObject *r, RemoveRenderObjectFlag flag) diff --git a/BBGE/Core.h b/BBGE/Core.h index bd0e727..590e675 100644 --- a/BBGE/Core.h +++ b/BBGE/Core.h @@ -28,6 +28,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "StateManager.h" #include "Localization.h" #include "Window.h" +#include "TextureMgr.h" #include "DarkLayer.h" @@ -231,8 +232,6 @@ public: // state functions - std::string getTextureLoadName(const std::string &texture); - void setMousePosition(const Vector &p); void setFullscreen(bool full); @@ -240,12 +239,7 @@ public: void enable2D(int pixelScaleX, int pixelScaleY); void addRenderObject(RenderObject *o, unsigned layer); void switchRenderObjectLayer(RenderObject *o, unsigned toLayer); - void addTexture(Texture *r); - CountedPtr findTexture(const std::string &name); - void removeTexture(Texture *res); - void clearResources(); - - CountedPtr addTexture(const std::string &texture); + CountedPtr getTexture(const std::string &name); enum RemoveRenderObjectFlag { DESTROY_RENDER_OBJECT=0, DO_NOT_DESTROY_RENDER_OBJECT }; void removeRenderObject(RenderObject *r, RemoveRenderObjectFlag flag = DESTROY_RENDER_OBJECT); @@ -279,8 +273,6 @@ public: void print(int x, int y, const char *str, float sz=1); - std::vector resources; - RenderObjectLayer *getRenderObjectLayer(int i); std::vector renderObjectLayerOrder; @@ -311,15 +303,9 @@ public: ParticleManager *particleManager; - - - void setBaseTextureDirectory(const std::string &newBaseTextureDirectory) - { this->baseTextureDirectory = newBaseTextureDirectory; } - std::string getBaseTextureDirectory() - { - return baseTextureDirectory; - } - + void setExtraTexturePath(const char *dir); // pass NULL to disable secondary + const char *getExtraTexturePath() const; // NULL when no secondary + const std::string& getBaseTexturePath() const; virtual bool canChangeState(); void resetTimer(); @@ -376,8 +362,6 @@ public: bool joystickEnabled; - bool debugLogTextures; - void setup_opengl(); void setClearColor(const Vector &c); int flipMouseButtons; @@ -388,8 +372,6 @@ public: ParticleEffect* createParticleEffect(const std::string &name, const Vector &position, int layer, float rotz=0); - std::string secondaryTexturePath; - float get_old_dt() { return old_dt; } float get_current_dt() { return current_dt; } @@ -431,6 +413,8 @@ public: void initLocalization(); + TextureMgr texmgr; + protected: CoreWindow *window; @@ -450,8 +434,6 @@ protected: virtual void onReloadResources(); - CountedPtr doTextureAdd(const std::string &texture, const std::string &name, std::string internalTextureName); - bool lib_graphics, lib_sound, lib_input; Vector clearColor; virtual void unloadDevice(); @@ -495,7 +477,6 @@ protected: bool shuttingDown; bool quitNestedMainFlag; int nestedMains; - std::string baseTextureDirectory; int nowTicks, thenTicks; diff --git a/BBGE/Precacher.cpp b/BBGE/Precacher.cpp index 3602012..bb4e417 100644 --- a/BBGE/Precacher.cpp +++ b/BBGE/Precacher.cpp @@ -25,129 +25,104 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Precacher::Precacher() { - loadProgressCallback = NULL; - cleaned = true; } Precacher::~Precacher() { - if (!cleaned) - errorLog ("Precacher shutdown unclean"); } void Precacher::setBaseDir(const std::string& dir) { - basedirOverride = dir; + basedir = dir; } -void Precacher::clean() +void Precacher::clear() { - for (unsigned int i = 0; i < renderObjects.size(); i++) - { - RenderObject *r = renderObjects[i]; - r->destroy(); - delete r; - } - renderObjects.clear(); - cleaned = true; + texkeep.clear(); } -void Precacher::loadTextureRange(const std::string &file, const std::string &type, int start, int end) -{ - for (int t = start; t < end; t++) - { - - std::ostringstream num_os; - num_os << t; - - std::ostringstream os; - os << file; - - if(num_os.str().size() <= 4) { - for (size_t j = 0; j < 4 - num_os.str().size(); j++) { - os << "0"; - } - } - - os << t; - - os << type; - - precacheTex(os.str()); - } -} - -void precacherCallback(const std::string &file, void *param) +void Precacher::_Callback(const std::string &file, void *param) { Precacher *p = (Precacher*)param; - p->precacheTex(file); + p->_precacheTex(file); } // precacheTex // caches one texture // also support simple wildcard to cache multiple textures // e.g. naija/*.png -void Precacher::precacheTex(const std::string &tex) +void Precacher::_precacheTex(const std::string &tex) { - if (tex.find("txt") != std::string::npos) - { - errorLog("Call precacheList to precache a text file of gfx names, not precacheTex!"); - } + assert(!basedir.empty()); if (tex.empty()) return; - std::string basedir = basedirOverride.empty() ? core->getBaseTextureDirectory() : basedirOverride; - - if (core->debugLogTextures) - debugLog("PRECACHING: " + tex); - if (tex.find('*')!=std::string::npos) { - if (core->debugLogTextures) - debugLog("searching directory"); - - int loc = tex.find('*'); + size_t loc = tex.find('*'); std::string path = tex.substr(0, loc); std::string type = tex.substr(loc+1, tex.size()); path = basedir + path; - forEachFile(path, type, precacherCallback, this); - return; + forEachFile(path, type, _Callback, this); } else { - if (loadProgressCallback) - loadProgressCallback(); std::string t = tex; if (tex.find(basedir) != std::string::npos) { t = tex.substr(basedir.size(), tex.size()); } - Quad *q = new Quad; - q->setTexture(t); - q->alpha = 0; - renderObjects.push_back(q); - cleaned = false; + todo.push_back(t); + } +} + +static void texLoadProgressCallback(size_t done, void *ud) +{ + Precacher::ProgressCallback cb = (Precacher::ProgressCallback)(ud); + cb(); +} + +void Precacher::doCache(ProgressCallback progress) +{ + if(!todo.empty()) + { + std::ostringstream os; + os << "Precacher: Batch-loading " << todo.size() << " textures..."; + debugLog(os.str()); + std::vector tmp(todo.size()); + core->texmgr.loadBatch(&tmp[0], &todo[0], todo.size(), TextureMgr::KEEP, + progress ? texLoadProgressCallback : NULL, progress); + todo.clear(); + texkeep.reserve(texkeep.size() + tmp.size()); + for(size_t i = 0; i < tmp.size(); ++i) + texkeep.push_back(tmp[i]); } + debugLog("Precacher: done"); } -void Precacher::precacheList(const std::string &list, void progressCallback()) +void Precacher::precacheList(const std::string &list, ProgressCallback progress) { - loadProgressCallback = progressCallback; + assert(todo.empty()); InStream in(list.c_str()); std::string t; while (std::getline(in, t)) { - if (!t.empty()) + while (!t.empty()) { -#if defined(BBGE_BUILD_UNIX) - - t = t.substr(0,t.size()-1); - debugLog("precache["+t+"]"); -#endif - stringToLower(t); - precacheTex(t); + if(t.back() == '\r' || t.back() == '\n') // linux doesn't like CRLF, make sure to trim that off + t.pop_back(); + else + break; } + + if(!t.empty()) + _precacheTex(t); } in.close(); - loadProgressCallback = NULL; + doCache(progress); } +void Precacher::precacheTex(const std::string& tex, ProgressCallback progress) +{ + _precacheTex(tex); + doCache(progress); +} diff --git a/BBGE/Precacher.h b/BBGE/Precacher.h index ce39b41..eed5deb 100644 --- a/BBGE/Precacher.h +++ b/BBGE/Precacher.h @@ -23,25 +23,28 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #include +#include "Texture.h" class RenderObject; class Precacher { public: + typedef void (*ProgressCallback)(void); + Precacher(); ~Precacher(); - void precacheTex(const std::string &tex); - void precacheList(const std::string &list, void progressCallback() = NULL); - void clean(); - void loadTextureRange(const std::string &file, const std::string &type, int start, int end); + void precacheList(const std::string &list, ProgressCallback progress = NULL); + void precacheTex(const std::string &tex, ProgressCallback progress = NULL); + void clear(); void setBaseDir(const std::string& dir); - - std::vector renderObjects; private: - bool cleaned; - void (*loadProgressCallback)(); - std::string basedirOverride; + static void _Callback(const std::string &file, void *param); + void _precacheTex(const std::string &tex); + std::string basedir; + std::vector > texkeep; + std::vector todo; + void doCache(ProgressCallback progress = NULL); }; #endif diff --git a/BBGE/Quad.cpp b/BBGE/Quad.cpp index 01a038a..8f820ff 100644 --- a/BBGE/Quad.cpp +++ b/BBGE/Quad.cpp @@ -349,8 +349,7 @@ void Quad::renderGrid(const RenderState& rs) const } } glEnd(); - if (texture) - glBindTexture(GL_TEXTURE_2D, texture->textures[0]); + RenderObject::lastTextureApplied = 0; } } diff --git a/BBGE/RenderObject.cpp b/BBGE/RenderObject.cpp index 88fda96..e1d42f4 100644 --- a/BBGE/RenderObject.cpp +++ b/BBGE/RenderObject.cpp @@ -491,11 +491,11 @@ nofollow: { if (texture) { - if (texture->textures[0] != lastTextureApplied || repeatTexture != lastTextureRepeat) + if (texture->gltexid != lastTextureApplied || repeatTexture != lastTextureRepeat) { texture->apply(repeatTexture); lastTextureRepeat = repeatTexture; - lastTextureApplied = texture->textures[0]; + lastTextureApplied = texture->gltexid; } } else @@ -866,21 +866,9 @@ void RenderObject::freeMotionBlur() bool RenderObject::setTexture(const std::string &n) { - std::string name = n; - stringToLowerUserData(name); - - if (name.empty()) - { - setTexturePointer(NULL); - return false; - } - - if(texture && texture->getLoadResult() == TEX_SUCCESS && name == texture->name) - return true; // no texture change - - CountedPtr tex = core->addTexture(name); + CountedPtr tex = core->getTexture(n); setTexturePointer(tex); - return tex && tex->getLoadResult() == TEX_SUCCESS; + return tex->success; } void RenderObject::addChild(RenderObject *r, ParentManaged pm, RenderBeforeParent rbp, ChildOrder order) diff --git a/BBGE/Texture.cpp b/BBGE/Texture.cpp index ea81405..df4ffaa 100644 --- a/BBGE/Texture.cpp +++ b/BBGE/Texture.cpp @@ -18,8 +18,10 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#include +#include "Base.h" #include "Texture.h" -#include "Core.h" #include "Image.h" #include "ByteBuffer.h" #include "RenderBase.h" @@ -31,41 +33,31 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Texture::Texture() { - textures[0] = 0; + gltexid = 0; width = height = 0; _repeating = false; ow = oh = -1; - loadResult = TEX_FAILED; _mipmap = false; + success = false; } Texture::~Texture() { - destroy(); + unload(); } -void Texture::read(int tx, int ty, int w, int h, unsigned char *pixels) +void Texture::readRGBA(unsigned char *pixels) { - if (tx == 0 && ty == 0 && w == this->width && h == this->height) - { - glBindTexture(GL_TEXTURE_2D, textures[0]); - glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - glBindTexture(GL_TEXTURE_2D, 0); - } - else - { - std::ostringstream os; - os << "Unable to read a texture subimage (size = " - << this->width << "x" << this->height << ", requested = " - << tx << "," << ty << "+" << w << "x" << h << ")"; - debugLog(os.str()); - } + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glBindTexture(GL_TEXTURE_2D, gltexid); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + glBindTexture(GL_TEXTURE_2D, 0); } -void Texture::write(int tx, int ty, int w, int h, const unsigned char *pixels) +void Texture::writeRGBA(int tx, int ty, int w, int h, const unsigned char *pixels) { - glBindTexture(GL_TEXTURE_2D, textures[0]); + glBindTexture(GL_TEXTURE_2D, gltexid); glTexSubImage2D(GL_TEXTURE_2D, 0, tx, @@ -78,158 +70,24 @@ void Texture::write(int tx, int ty, int w, int h, const unsigned char *pixels) ); glBindTexture(GL_TEXTURE_2D, 0); - /* - target Specifies the target texture. Must be - GL_TEXTURE_2D. - - level Specifies the level-of-detail number. Level 0 is - the base image level. Level n is the nth mipmap - reduction image. - - xoffset Specifies a texel offset in the x direction within - the texture array. - - yoffset Specifies a texel offset in the y direction within - the texture array. - - width Specifies the width of the texture subimage. - - height Specifies the height of the texture subimage. - - format Specifies the format of the pixel data. The - following symbolic values are accepted: - GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE, - GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, and - GL_LUMINANCE_ALPHA. - - type Specifies the data type of the pixel data. The - following symbolic values are accepted: - GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, - GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, - GL_INT, and GL_FLOAT. - - pixels Specifies a pointer to the image data in memory. - */ } void Texture::unload() { - if (textures[0]) + if (gltexid) { ow = width; oh = height; - if (core->debugLogTextures) - { - debugLog("UNLOADING TEXTURE: " + name); - } - - - glDeleteTextures(1, &textures[0]); - textures[0] = 0; + glDeleteTextures(1, &gltexid); + gltexid = 0; } } -void Texture::destroy() -{ - unload(); - - core->removeTexture(this); -} - -void Texture::reload() -{ - debugLog("RELOADING TEXTURE: " + name + " with loadName " + loadName + "..."); - - unload(); - load(loadName, _mipmap); - - - debugLog("DONE"); -} - -bool Texture::load(std::string file, bool mipmap) -{ - loadResult = TEX_FAILED; - if (file.size()<4) - { - errorLog("Texture Name is Empty or Too Short"); - return false; - } - - stringToLowerUserData(file); - file = adjustFilenameCase(file); - - loadName = file; - _mipmap = mipmap; - - size_t pos = file.find_last_of('.'); - - if (pos != std::string::npos) - { - // make sure this didn't catch the '.' in /home/username/.Aquaria/* --ryan. - const std::string userdata = core->getUserDataFolder(); - const size_t len = userdata.length(); - if (pos < len) - pos = std::string::npos; - } - - bool found = exists(file); - - if(!found && exists(file + ".png")) - { - found = true; - file += ".png"; - } - - // .tga/.zga are never used as game graphics anywhere except save slot thumbnails. - // if so, their file names are passed exact, not with a missing extension - - bool ok = false; - if (found) - { - file = localisePathInternalModpath(file); - file = adjustFilenameCase(file); - - - std::string post = file.substr(file.size()-3, 3); - stringToLower(post); - - ImageData img = {}; - if (post == "zga") - { - img = imageLoadZGA(file.c_str()); - if(img.pixels) - mipmap = false; - else - debugLog("Can't load ZGA File: " + file); - } - else - { - img = imageLoadGeneric(file.c_str(), false); - if(!img.pixels) - debugLog("unknown image file type: " + file); - } - - if(img.pixels) - { - ok = loadInternal(img, mipmap); - free(img.pixels); - } - } - else - { - // load default image / leave white - if (core->debugLogTextures) - debugLog("***Could not find texture: " + file); - } - return ok; -} - static const GLenum repeatLUT[] = { GL_CLAMP_TO_EDGE, GL_REPEAT }; void Texture::apply(bool repeat) const { - glBindTexture(GL_TEXTURE_2D, textures[0]); + glBindTexture(GL_TEXTURE_2D, gltexid); if(repeat != _repeating) { _repeating = repeat; @@ -252,7 +110,7 @@ static const GlTexFormat formatLUT[] = { GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, 3 } }; -bool Texture::loadInternal(const ImageData& img, bool mipmap) +bool Texture::upload(const ImageData& img, bool mipmap) { if(!img.pixels || !img.channels || img.channels > 4 || !img.w || !img.h) return false; @@ -266,8 +124,9 @@ bool Texture::loadInternal(const ImageData& img, bool mipmap) // no padding glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glGenTextures(1, &textures[0]); - glBindTexture(GL_TEXTURE_2D, textures[0]); + if(!gltexid) + glGenTextures(1, &gltexid); + glBindTexture(GL_TEXTURE_2D, gltexid); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); _repeating = false; @@ -345,79 +204,24 @@ bool Texture::loadInternal(const ImageData& img, bool mipmap) width = img.w; height = img.h; - loadResult = TEX_SUCCESS; return true; } -unsigned char * Texture::getBufferAndSize(int *wparam, int *hparam, unsigned int *sizeparam) +unsigned char * Texture::getBufferAndSize(int *wparam, int *hparam, size_t *sizeparam) { - unsigned char *data = NULL; - unsigned int size = 0; - int tw = 0, th = 0; - int w = 0, h = 0; - - // This can't happen. If it does we're doomed. - if(width <= 0 || height <= 0) - goto fail; - - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glBindTexture(GL_TEXTURE_2D, textures[0]); - - // As returned by graphics driver - - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &w); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &h); - - // As we know it - but round to nearest power of 2 - OpenGL does this internally anyways. - tw = bithacks::clp2(width); // known to be > 0. - th = bithacks::clp2(height); - - if (w != tw || h != th) - { - std::ostringstream os; - os << "Texture::getBufferAndSize() WARNING: width/height disagree: "; - os << "Driver says (" << w << ", " << h << "); "; - os << "Texture says (" << width << ", " << height << "); "; - os << "Rounded to (" << tw << ", " << th << ")"; - debugLog(os.str()); - // choose max. for size calculation - w = w > tw ? w : tw; - h = h > th ? h : th; - } - - size = w * h * 4; - if (!size) - goto fail; - - data = (unsigned char*)malloc(size + 32); + const size_t bytes = size_t(width) * size_t(height) * 4; + unsigned char *data = (unsigned char*)malloc(bytes); if (!data) { std::ostringstream os; - os << "Game::fillGridFromQuad allocation failure, size = " << size; + os << "Game::getBufferAndSize allocation failure, bytes = " << bytes; errorLog(os.str()); - goto fail; - } - memcpy(data + size, "SAFE", 5); - glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - glBindTexture(GL_TEXTURE_2D, 0); - - // Not sure but this might be the case with nouveau drivers on linux... still investigating. -- fg - if(memcmp(data + size, "SAFE", 5)) - { - errorLog("Texture::getBufferAndSize(): Broken graphics driver! Wrote past end of buffer!"); - free(data); // in case we are here, this will most likely cause a crash. - goto fail; + return NULL; } + this->readRGBA(data); - *wparam = w; - *hparam = h; - *sizeparam = size; + *wparam = width; + *hparam = height; + *sizeparam = bytes; return data; - - -fail: - *wparam = 0; - *hparam = 0; - *sizeparam = 0; - return NULL; } diff --git a/BBGE/Texture.h b/BBGE/Texture.h index 734a274..25e8fe1 100644 --- a/BBGE/Texture.h +++ b/BBGE/Texture.h @@ -39,39 +39,30 @@ public: Texture(); ~Texture(); - bool load(std::string file, bool mipmap); void apply(bool repeat = false) const; void unload(); - void destroy(); - + unsigned gltexid; int width, height; - unsigned textures[1]; - - void reload(); + void writeRGBA(int tx, int ty, int w, int h, const unsigned char *pixels); + void readRGBA(unsigned char *pixels); - void write(int tx, int ty, int w, int h, const unsigned char *pixels); - void read(int tx, int ty, int w, int h, unsigned char *pixels); + unsigned char *getBufferAndSize(int *w, int *h, size_t *size); // returned memory must be free()'d - unsigned char *getBufferAndSize(int *w, int *h, unsigned int *size); // returned memory must be free()'d + std::string name, filename; + bool upload(const ImageData& img, bool mipmap); - std::string name; - - TextureLoadResult getLoadResult() const { return loadResult; } + bool success; protected: - std::string loadName; - - bool loadInternal(const ImageData& img, bool mipmap); int ow, oh; - TextureLoadResult loadResult; bool _mipmap; private: mutable bool _repeating; // modified during rendering }; -#define UNREFTEX(x) if (x) {x = NULL;} +#define UNREFTEX(x) {x = NULL;} #endif diff --git a/BBGE/TextureMgr.cpp b/BBGE/TextureMgr.cpp new file mode 100644 index 0000000..4429313 --- /dev/null +++ b/BBGE/TextureMgr.cpp @@ -0,0 +1,318 @@ +#include "TextureMgr.h" +#include +#include +#include "MT.h" +#include "Image.h" +#include "Base.h" +#include "Localization.h" + +struct TexLoadTmp +{ + TexLoadTmp() : curTex(NULL), success(false) { img.pixels = NULL; } + std::string name, filename; + ImageData img; + TextureMgr::LoadMode loadmode; + Texture *curTex; // immutable + bool success; // if this is true and img.pixels is NULL, don't change anything + //bool mipmap; +}; + +typedef ImageData (*ImageLoadFunc)(const char *fn); + +struct TexLoader +{ + TexLoader(ImageLoadFunc f, const std::string& s, const std::string& name = std::string()) + : loader(f), fn(s), name(name) {} + ImageLoadFunc loader; + std::string fn; + std::string name; // usually empty, but may specify tex name in case cleanups had to be done. always lowercase. + ImageData load() const { return loader(fn.c_str()); } +}; + +static ImageData loadGeneric(const char *fn) +{ + return imageLoadGeneric(fn, false); +} + +static const std::string fixup(const std::string& fn) +{ + std::string file = localisePathInternalModpath(fn); + file = adjustFilenameCase(file); + return file; +} + + +struct ExtAndLoader +{ + const char *ext; + ImageLoadFunc func; +}; + +static const ExtAndLoader s_extAndLoader[] = +{ + { "png", loadGeneric }, + { "jpg", loadGeneric }, + { "zga", imageLoadZGA }, + { "tga", loadGeneric }, + { NULL, NULL } +}; + +static TexLoader getFullnameAndLoader(const std::string& name, const std::string& basedir) +{ + // TODO: use localisePath() + // TODO: assert that we don't start with . or / + + if(exists(name)) + { + // check first if name exists and has a known extension, if so, use correct loader + size_t lastdot = name.rfind('.'); + size_t lastslash = name.rfind('/'); + if(lastdot != std::string::npos && (lastslash == std::string::npos || lastslash < lastdot)) + { + std::string ext = name.substr(lastdot + 1); + for(const ExtAndLoader *exl = &s_extAndLoader[0]; exl->func; ++exl) + { + if(ext == exl->ext) + { + // remove basedir and extension + std::string texname = name.substr(basedir.length(), name.length() - (basedir.length() + ext.length() + 1)); // strip extension + return TexLoader(exl->func, fixup(name), texname ); + } + } + } + } + + std::string fn = name; + for(const ExtAndLoader *exl = &s_extAndLoader[0]; exl->func; ++exl) + { + fn.resize(name.length()); + fn += '.'; + fn += exl->ext; + if(exists(fn)) + return TexLoader(exl->func, fixup(fn)); + } + + return TexLoader(NULL, std::string()); +} + +void TextureMgr::th_loadFromFile(TexLoadTmp& tt) const +{ + std::string withoutExt; + for(size_t i = 0; i < loadFromPaths.size(); ++i) + { + withoutExt = loadFromPaths[i] + tt.name; + TexLoader ldr = getFullnameAndLoader(withoutExt, loadFromPaths[i]); + if(!ldr.name.empty()) // name was cleaned up, use the updated one + tt.name = ldr.name; + if(ldr.loader) + { + if(tt.loadmode < OVERWRITE && tt.curTex && tt.curTex->success && tt.curTex->filename == ldr.fn) + { + tt.success = true; + break; + } + + tt.filename = ldr.fn; + tt.img = ldr.load(); + tt.success = !!tt.img.pixels; + return; + } + } + + tt.img.pixels = NULL; +} + + +TextureMgr::TextureMgr() + : sem(SDL_CreateSemaphore(0)) +{ +} + +TextureMgr::~TextureMgr() +{ + for(size_t i = 0; i < threads.size(); ++i) + worktodo.push(NULL); // signal all threads to exit + for(size_t i = 0; i < threads.size(); ++i) + SDL_WaitThread((SDL_Thread*)threads[i], NULL); + + SDL_DestroySemaphore((SDL_sem*)sem); +} + +size_t TextureMgr::spawnThreads(size_t n) +{ + for(size_t i = 0; i < n; ++i) + { + SDL_Thread *worker; +#if SDL_VERSION_ATLEAST(2,0,0) + worker = SDL_CreateThread(Th_Main, "texldr", this); +#else + worker = SDL_CreateThread(Th_Main, this); +#endif + if(!worker) + return i; + + threads.push_back(worker); + } + return n; +} + +size_t TextureMgr::getNumLoaded() const +{ + return cache.size(); +} + +Texture* TextureMgr::getOrLoad(const std::string& name) +{ + return load(name, KEEP); +} + +void TextureMgr::shutdown() +{ + for(TexCache::iterator it = cache.begin(); it != cache.end(); ++it) + it->second->unload(); + cache.clear(); +} + +int TextureMgr::Th_Main(void* ud) +{ + TextureMgr *self = (TextureMgr*)ud; + self->thMain(); + return 0; +} + +void TextureMgr::thMain() +{ + for(;;) + { + void *p; + worktodo.pop(p); + if(!p) // a pushed NULL is the signal to exit + break; + + TexLoadTmp *tt = (TexLoadTmp*)p; + th_loadFromFile(*tt); + + workdone.push(p); + } +} + +Texture *TextureMgr::finalize(const TexLoadTmp& tt) +{ + Texture *tex = tt.curTex; + if(!tex) + { + tex = new Texture; // always make sure a valid Texture object comes out + tex->name = tt.name; // this doesn't ever change + cache[tt.name] = tex; // didn't exist, cache now + } + + tex->filename = tt.filename; + tex->success = tt.success; + + if(!tt.success) + { + debugLog("FAILED TO LOAD TEXTURE: [" + tt.name + "]"); + tex->unload(); + tex->width = 64; + tex->height = 64; + } + if(tt.img.pixels) + { + //debugLog("LOADED TEXTURE FROM DISK: [" + tt.name + "]"); + tex->upload(tt.img, /*tt.mipmap*/ true); + free(tt.img.pixels); + } + return tex; +} + +void TextureMgr::loadBatch(Texture * pdst[], const std::string texnames[], size_t n, LoadMode mode, ProgressCallback cb, void *cbUD) +{ + if(threads.empty()) + { + for(size_t i = 0; i < n; ++i) + { + Texture *tex = load(texnames[i], mode); + if(pdst) + pdst[i] = tex; + } + return; + } + + // Important that this is pre-allocated. We store pointers to elements and + // send them to threads, so this must never reallocate. + std::vector tmp(n); + + size_t inprogress = 0, doneCB = 0; + for(size_t i = 0; i < n; ++i) + { + TexLoadTmp& tt = tmp[i]; + tt.name = texnames[i]; + stringToLower(tt.name); + TexCache::iterator it = cache.find(tt.name); + tt.curTex = it != cache.end() ? it->second.content() : NULL; + if(mode == KEEP && tt.curTex && tt.curTex->success) + { + if(pdst) + pdst[i] = tt.curTex; + if(cb) + cb(++doneCB, cbUD); + continue; + } + + tt.loadmode = mode; + + worktodo.push(&tt); + ++inprogress; + } + + for(size_t i = 0; i < inprogress; ++i) + { + void *p; + workdone.pop(p); + const TexLoadTmp& tt = *(const TexLoadTmp*)p; + Texture *tex = finalize(tt); + if(pdst) + pdst[i] = tex; + if(cb) + cb(++doneCB, cbUD); + } +} + +Texture* TextureMgr::load(const std::string& texname, LoadMode mode) +{ + TexLoadTmp tt; + tt.name = texname; + stringToLower(tt.name); + TexCache::iterator it = cache.find(tt.name); + tt.curTex = it != cache.end() ? it->second.content() : NULL; + + // texname "" will never load, so don't even try once we have a valid object + if(tt.curTex && (texname.empty() || (mode == KEEP && tt.curTex->success))) + return tt.curTex; + + tt.loadmode = mode; + th_loadFromFile(tt); + return finalize(tt); +} + +void TextureMgr::reloadAll(LoadMode mode) +{ + std::vector todo; + for(TexCache::iterator it = cache.begin(); it != cache.end(); ++it) + { + Texture *tex = it->second.content(); + if(mode == KEEP && tex->success) + continue; + + todo.push_back(tex->name); + } + + std::ostringstream os; + os << "TextureMgr: Potentially reloading up to " << todo.size() << " textures..."; + debugLog(os.str()); + + if(!todo.empty()) + loadBatch(NULL, &todo[0],todo.size(), mode); +} + + diff --git a/BBGE/TextureMgr.h b/BBGE/TextureMgr.h new file mode 100644 index 0000000..2fc04d5 --- /dev/null +++ b/BBGE/TextureMgr.h @@ -0,0 +1,53 @@ +#ifndef BBGE_TEXTUREMGR_H +#define BBGE_TEXTUREMGR_H + +#include +#include "Texture.h" +#include "MT.h" + + +struct TexLoadTmp; + +class TextureMgr +{ +public: + TextureMgr(); + ~TextureMgr(); + + typedef void (*ProgressCallback)(size_t done, void*); + + std::vector loadFromPaths; + size_t spawnThreads(size_t n); + size_t getNumLoaded() const; + Texture *getOrLoad(const std::string& name); + void clearUnused(); // clear everything whose refcount is 1 + void shutdown(); + + enum LoadMode + { + KEEP, // if already exists, keep unchanged + KEEP_IF_SAME, // load if we resolve to a different file than the texture that's already there, if any. + OVERWRITE, // always overwrite + }; + + void loadBatch(Texture *pdst[], const std::string texnames[], size_t n, LoadMode mode = KEEP, ProgressCallback cb = 0, void *cbUD = 0); + Texture *load(const std::string& texname, LoadMode mode); + void reloadAll(LoadMode mode); + +private: + typedef std::map > TexCache; + TexCache cache; + + BlockingQueue worktodo; + BlockingQueue workdone; + std::vector threads; + void *sem; // SDL_sem* + + static int Th_Main(void *self); + void thMain(); // for int + + void th_loadFromFile(TexLoadTmp& tt) const; + Texture *finalize(const TexLoadTmp& tt); +}; + +#endif diff --git a/ExternalLibs/ttvfs_cfileapi/ttvfs_stdio.cpp b/ExternalLibs/ttvfs_cfileapi/ttvfs_stdio.cpp index 60ea35c..70f138d 100644 --- a/ExternalLibs/ttvfs_cfileapi/ttvfs_stdio.cpp +++ b/ExternalLibs/ttvfs_cfileapi/ttvfs_stdio.cpp @@ -6,6 +6,10 @@ #include #include +// HACK: add a big lock to make this thing not crash when multiple threads are active +#include "../../BBGE/MT.h" +static Lockable lock; + static ttvfs::Root *vfs = NULL; @@ -14,8 +18,15 @@ void ttvfs_setroot(ttvfs::Root *root) vfs = root; } +VFILE* vfgetfile(const char* fn) +{ + MTGuard g(lock); + return vfs->GetFile(fn); +} + VFILE *vfopen(const char *fn, const char *mode) { + MTGuard g(lock); VFILE *vf = vfs->GetFile(fn); if (!vf || !vf->open(mode)) return NULL; @@ -30,6 +41,7 @@ size_t vfread(void *ptr, size_t size, size_t count, VFILE *vf) int vfclose(VFILE *vf) { + MTGuard g(lock); vf->close(); vf->decref(); return 0; @@ -105,11 +117,15 @@ InStream::InStream(const char *fn) bool InStream::open(const char *fn) { - ttvfs::File *vf = vfs->GetFile(fn); - if(!vf || !vf->open("r")) + ttvfs::File *vf = NULL; { - setstate(std::ios::failbit); - return false; + MTGuard g(lock); + vf = vfs->GetFile(fn); + if(!vf || !vf->open("r")) + { + setstate(std::ios::failbit); + return false; + } } size_t sz = (size_t)vf->size(); std::string s; diff --git a/ExternalLibs/ttvfs_cfileapi/ttvfs_stdio.h b/ExternalLibs/ttvfs_cfileapi/ttvfs_stdio.h index 364d262..6c6b73f 100644 --- a/ExternalLibs/ttvfs_cfileapi/ttvfs_stdio.h +++ b/ExternalLibs/ttvfs_cfileapi/ttvfs_stdio.h @@ -36,6 +36,9 @@ typedef ttvfs::File VFILE; void ttvfs_setroot(ttvfs::Root *root); +// HACK +VFILE *vfgetfile(const char *fn); + // Note that vfopen() returns the same pointer for the same file name, // so effectively a file is a singleton object. VFILE *vfopen(const char *fn, const char *mode);