From d8d890bfbb14cff6fdd4ceb950a275e46c72c07d Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Mon, 25 Sep 2023 23:48:03 -0400 Subject: [PATCH] Move gar loading to a different thread and prevent unnecessary updates --- armoury/include/fullmodelviewer.h | 1 + armoury/include/gearview.h | 29 ++- armoury/include/singlegearview.h | 3 + armoury/src/fullmodelviewer.cpp | 16 +- armoury/src/gearview.cpp | 283 ++++++++++++++++++------------ armoury/src/mainwindow.cpp | 3 + armoury/src/singlegearview.cpp | 31 +++- parts/mdl/mdlpart.cpp | 13 ++ parts/mdl/mdlpart.h | 4 + 9 files changed, 254 insertions(+), 129 deletions(-) diff --git a/armoury/include/fullmodelviewer.h b/armoury/include/fullmodelviewer.h index dd4bbcd..ffb8480 100644 --- a/armoury/include/fullmodelviewer.h +++ b/armoury/include/fullmodelviewer.h @@ -18,6 +18,7 @@ public: Q_SIGNALS: void gearChanged(); + void loadingChanged(bool loading); public Q_SLOTS: void clear(); diff --git a/armoury/include/gearview.h b/armoury/include/gearview.h index 1e7a1e1..94fa021 100644 --- a/armoury/include/gearview.h +++ b/armoury/include/gearview.h @@ -24,6 +24,11 @@ struct GearInfo { } }; +inline bool operator==(const GearInfo &a, const GearInfo &b) +{ + return a.name == b.name && a.slot == b.slot; +} + struct GameData; class GearView : public QWidget { @@ -51,6 +56,7 @@ public: Q_SIGNALS: void gearChanged(); void modelReloaded(); + void loadingChanged(bool loading); void raceChanged(); void subraceChanged(); @@ -63,8 +69,8 @@ Q_SIGNALS: void tailChanged(); public Q_SLOTS: - void clear(); void addGear(GearInfo& gear); + void removeGear(GearInfo &gear); void setRace(Race race); void setSubrace(Subrace subrace); @@ -76,7 +82,6 @@ public Q_SLOTS: void setEar(int bodyVer); void setTail(int bodyVer); - void reloadModel(); void reloadRaceDeforms(); private: @@ -84,11 +89,29 @@ private: uint32_t maxLod = 0; - std::vector gears; + struct LoadedGear { + GearInfo info; + physis_MDL mdl; + }; + + std::vector loadedGears; + std::vector queuedGearAdditions; + std::vector queuedGearRemovals; + bool gearDirty = false; + std::optional face = 1, hair = 1, ear = 1, tail; + bool faceDirty = false, hairDirty = false, earDirty = false, tailDirty = false; + bool raceDirty = false; MDLPart* mdlPart = nullptr; GameData* data; FileCache& cache; + + bool updating = false; + void updatePart(); + bool needsUpdate() const; + + void gearUpdate(LoadedGear &gear); + void queueGearUpdate(LoadedGear &gear); }; \ No newline at end of file diff --git a/armoury/include/singlegearview.h b/armoury/include/singlegearview.h index c464c18..20807c1 100644 --- a/armoury/include/singlegearview.h +++ b/armoury/include/singlegearview.h @@ -34,6 +34,8 @@ public Q_SLOTS: void setGender(Gender gender); void setLevelOfDetail(int lod); + void setFMVAvailable(bool available); + private Q_SLOTS: void reloadGear(); @@ -50,6 +52,7 @@ private: QPushButton *addToFMVButton, *exportButton; bool loadingComboData = false; + bool fmvAvailable = false; GameData* data = nullptr; }; \ No newline at end of file diff --git a/armoury/src/fullmodelviewer.cpp b/armoury/src/fullmodelviewer.cpp index 3afdde6..e3371ba 100644 --- a/armoury/src/fullmodelviewer.cpp +++ b/armoury/src/fullmodelviewer.cpp @@ -124,6 +124,13 @@ FullModelViewer::FullModelViewer(GameData* data, FileCache& cache) : data(data) } connect(this, &FullModelViewer::gearChanged, this, &FullModelViewer::reloadGear); + connect(gearView, &GearView::loadingChanged, this, &FullModelViewer::loadingChanged); + connect(this, &FullModelViewer::loadingChanged, this, [this, tabWidget](const bool loading) { + raceCombo->setEnabled(!loading); + subraceCombo->setEnabled(!loading); + genderCombo->setEnabled(!loading); + tabWidget->setEnabled(!loading); + }); reloadGear(); } @@ -138,10 +145,14 @@ void FullModelViewer::clear() { void FullModelViewer::addGear(GearInfo& info) { switch (info.slot) { case Slot::Body: + if (topSlot ? *topSlot != info : true) { topSlot = info; + } break; case Slot::Legs: + if (bottomSlot ? *bottomSlot != info : true) { bottomSlot = info; + } break; default: break; @@ -150,9 +161,8 @@ void FullModelViewer::addGear(GearInfo& info) { Q_EMIT gearChanged(); } -void FullModelViewer::reloadGear() { - gearView->clear(); - +void FullModelViewer::reloadGear() +{ if (topSlot.has_value()) { gearView->addGear(*topSlot); } else { diff --git a/armoury/src/gearview.cpp b/armoury/src/gearview.cpp index cebf2f8..8b46cb7 100644 --- a/armoury/src/gearview.cpp +++ b/armoury/src/gearview.cpp @@ -4,7 +4,9 @@ #include "gearview.h" #include +#include #include +#include #include "filecache.h" #include "magic_enum.hpp" @@ -18,36 +20,30 @@ GearView::GearView(GameData* data, FileCache& cache) : data(data), cache(cache) layout->addWidget(mdlPart); setLayout(layout); - connect(this, &GearView::gearChanged, this, [=] { - reloadModel(); - }); - connect(this, &GearView::raceChanged, this, [=] { - reloadRaceDeforms(); - reloadModel(); - }); - connect(this, &GearView::subraceChanged, this, [=] { - reloadRaceDeforms(); - reloadModel(); - }); - connect(this, &GearView::genderChanged, this, [=] { - reloadRaceDeforms(); - reloadModel(); - }); - connect(this, &GearView::levelOfDetailChanged, this, &GearView::reloadModel); + mdlPart->requestUpdate = [this] { + if (updating) { + return; + } - connect(this, &GearView::faceChanged, this, &GearView::reloadModel); - connect(this, &GearView::hairChanged, this, &GearView::reloadModel); - connect(this, &GearView::earChanged, this, &GearView::reloadModel); - connect(this, &GearView::tailChanged, this, &GearView::reloadModel); + if (needsUpdate()) { + updating = true; + + Q_EMIT loadingChanged(true); + + QtConcurrent::run(QThreadPool::globalInstance(), [this] { + updatePart(); + Q_EMIT loadingChanged(false); + }); + } + }; } std::vector> GearView::supportedRaces() const { std::vector> races; - for (const auto& gear : gears) { + for (const auto &gear : loadedGears) { for (auto [race, race_name] : magic_enum::enum_entries()) { for (auto subrace : physis_get_supported_subraces(race).subraces) { - auto equip_path = - physis_build_equipment_path(gear.modelInfo.primaryID, race, subrace, currentGender, gear.slot); + auto equip_path = physis_build_equipment_path(gear.info.modelInfo.primaryID, race, subrace, currentGender, gear.info.slot); if (cache.fileExists(QLatin1String(equip_path))) races.emplace_back(race, subrace); @@ -60,10 +56,9 @@ std::vector> GearView::supportedRaces() const { std::vector GearView::supportedGenders() const { std::vector genders; - for (const auto& gear : gears) { + for (const auto &gear : loadedGears) { for (auto [gender, gender_name] : magic_enum::enum_entries()) { - auto equip_path = physis_build_equipment_path( - gear.modelInfo.primaryID, currentRace, Subrace::Midlander, currentGender, gear.slot); + auto equip_path = physis_build_equipment_path(gear.info.modelInfo.primaryID, currentRace, Subrace::Midlander, currentGender, gear.info.slot); if (cache.fileExists(QLatin1String(equip_path))) genders.push_back(gender); @@ -81,16 +76,22 @@ void GearView::exportModel(const QString& fileName) { mdlPart->exportModel(fileName); } -void GearView::clear() { - gears.clear(); +void GearView::addGear(GearInfo &gear) +{ + qDebug() << "Adding gear" << gear.name.c_str(); + + queuedGearAdditions.emplace_back(gear); + gearDirty = true; Q_EMIT gearChanged(); } -void GearView::addGear(GearInfo& gear) { - qDebug() << "Adding gear" << gear.name.c_str(); +void GearView::removeGear(GearInfo &gear) +{ + qDebug() << "Removing gear" << gear.name.c_str(); - gears.push_back(gear); + queuedGearRemovals.emplace_back(gear); + gearDirty = true; Q_EMIT gearChanged(); } @@ -102,9 +103,8 @@ void GearView::setRace(Race race) { currentRace = race; - auto supportedSubraces = physis_get_supported_subraces(race); - if (supportedSubraces.subraces[0] == currentSubrace || supportedSubraces.subraces[1] == currentSubrace) { - } else { + const auto supportedSubraces = physis_get_supported_subraces(race); + if (supportedSubraces.subraces[0] != currentSubrace && supportedSubraces.subraces[1] != currentSubrace) { setSubrace(supportedSubraces.subraces[0]); } @@ -114,6 +114,8 @@ void GearView::setRace(Race race) { setTail(-1); } + raceDirty = true; + Q_EMIT raceChanged(); } @@ -123,6 +125,12 @@ void GearView::setSubrace(Subrace subrace) { } currentSubrace = subrace; + + // Hyur is the only race that has two different subraces + if (currentRace == Race::Hyur) { + raceDirty = true; + } + Q_EMIT subraceChanged(); } @@ -132,6 +140,9 @@ void GearView::setGender(Gender gender) { } currentGender = gender; + + raceDirty = true; + Q_EMIT genderChanged(); } @@ -141,115 +152,173 @@ void GearView::setLevelOfDetail(int lod) { } currentLod = lod; + Q_EMIT levelOfDetailChanged(); } -void GearView::setFace(int bodyVer) { - if (face == bodyVer) { +void GearView::setFace(const int faceCode) +{ + if (face == faceCode) { return; } - if (bodyVer == -1) { + if (faceCode == -1) { face = std::nullopt; } else { - face = bodyVer; + face = faceCode; } + faceDirty = true; Q_EMIT faceChanged(); } -void GearView::setHair(int bodyVer) { - if (hair == bodyVer) { +void GearView::setHair(int hairCode) +{ + if (hair == hairCode) { return; } - if (bodyVer == -1) { + if (hairCode == -1) { hair = std::nullopt; } else { - hair = bodyVer; + hair = hairCode; } + hairDirty = true; Q_EMIT hairChanged(); } -void GearView::setEar(int bodyVer) { - if (ear == bodyVer) { +void GearView::setEar(const int earCode) +{ + if (ear == earCode) { return; } - if (bodyVer == -1) { + if (earCode == -1) { ear = std::nullopt; } else { - ear = bodyVer; + ear = earCode; } + earDirty = true; Q_EMIT earChanged(); } -void GearView::setTail(int bodyVer) { - if (tail == bodyVer) { +void GearView::setTail(const int tailCode) +{ + if (tail == tailCode) { return; } - if (bodyVer == -1) { + if (tailCode == -1) { tail = std::nullopt; } else { - tail = bodyVer; + tail = tailCode; } + tailDirty = true; Q_EMIT tailChanged(); } -void GearView::reloadModel() { - mdlPart->clear(); +void GearView::reloadRaceDeforms() +{ + qDebug() << "Loading race deform matrices for " << magic_enum::enum_name(currentRace).data() << magic_enum::enum_name(currentSubrace).data() + << magic_enum::enum_name(currentGender).data(); + const int raceCode = physis_get_race_code(currentRace, currentSubrace, currentGender); + qDebug() << "Race code: " << raceCode; - maxLod = 0; + QString skelName = QString{"c%1b0001.skel"}.arg(raceCode, 4, 10, QLatin1Char{'0'}); + mdlPart->setSkeleton(physis_skeleton_from_skel(physis_read_file(skelName.toStdString().c_str()))); - for (const auto& gear : gears) { - auto mdl_data = cache.lookupFile(physis_build_equipment_path( - gear.modelInfo.primaryID, currentRace, currentSubrace, currentGender, gear.slot)); - - // attempt to load the next best race - // currently hardcoded to hyur midlander - Race fallbackRace = currentRace; - Subrace fallbackSubrace = currentSubrace; - if (mdl_data.size == 0) { - mdl_data = cache.lookupFile(physis_build_equipment_path( - gear.modelInfo.primaryID, Race::Hyur, Subrace::Midlander, currentGender, gear.slot)); - fallbackRace = Race::Hyur; - fallbackSubrace = Subrace::Midlander; + // racial deforms don't work on Hyur Midlander, not needed? TODO not sure + if (currentSubrace != Subrace::Midlander) { + QString deformName = QString{"c%1_deform.json"}.arg(raceCode, 4, 10, QLatin1Char{'0'}); + mdlPart->loadRaceDeformMatrices(physis_read_file(deformName.toStdString().c_str())); + } else { + for (auto &data : mdlPart->boneData) { + data.deformRaceMatrix = glm::mat4(1.0f); } + } +} - if (mdl_data.size > 0) { - auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data); +MDLPart &GearView::part() const +{ + return *mdlPart; +} - std::vector materials; - for (int i = 0; i < mdl.num_material_names; i++) { - const char* material_name = mdl.material_names[i]; +void GearView::updatePart() +{ + qInfo() << raceDirty << gearDirty << updating; + if (raceDirty) { + // if race changes, all of the models need to be reloaded. + // TODO: in the future, we can be a bit smarter about this, lots of races use the same model (hyur) + for (auto &part : loadedGears) { + mdlPart->removeModel(part.mdl); + } + queuedGearAdditions = loadedGears; + loadedGears.clear(); + gearDirty = true; + } - const std::string mtrl_path = gear.getMtrlPath(material_name); - const std::string skinmtrl_path = physis_build_skin_material_path(physis_get_race_code(fallbackRace, fallbackSubrace, currentGender), 1, material_name); + if (gearDirty) { + for (auto &gearAddition : queuedGearAdditions) { + auto mdl_data = cache.lookupFile( + physis_build_equipment_path(gearAddition.info.modelInfo.primaryID, currentRace, currentSubrace, currentGender, gearAddition.info.slot)); - if (cache.fileExists(QLatin1String(mtrl_path.c_str()))) { - auto mat = physis_material_parse(cache.lookupFile(mtrl_path.c_str())); - materials.push_back(mat); - } - - if (cache.fileExists(QLatin1String(skinmtrl_path.c_str()))) { - auto mat = physis_material_parse(cache.lookupFile(skinmtrl_path.c_str())); - materials.push_back(mat); - } + // attempt to load the next best race + // currently hardcoded to hyur midlander + Race fallbackRace = currentRace; + Subrace fallbackSubrace = currentSubrace; + if (mdl_data.size == 0) { + mdl_data = cache.lookupFile( + physis_build_equipment_path(gearAddition.info.modelInfo.primaryID, Race::Hyur, Subrace::Midlander, currentGender, gearAddition.info.slot)); + fallbackRace = Race::Hyur; + fallbackSubrace = Subrace::Midlander; } - maxLod = std::max(mdl.num_lod, maxLod); + if (mdl_data.size > 0) { + auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data); - mdlPart->addModel(mdl, materials, currentLod); + std::vector materials; + for (int i = 0; i < mdl.num_material_names; i++) { + const char *material_name = mdl.material_names[i]; + + const std::string mtrl_path = gearAddition.info.getMtrlPath(material_name); + const std::string skinmtrl_path = + physis_build_skin_material_path(physis_get_race_code(fallbackRace, fallbackSubrace, currentGender), 1, material_name); + + if (cache.fileExists(QLatin1String(mtrl_path.c_str()))) { + auto mat = physis_material_parse(cache.lookupFile(mtrl_path.c_str())); + materials.push_back(mat); + } + + if (cache.fileExists(QLatin1String(skinmtrl_path.c_str()))) { + auto mat = physis_material_parse(cache.lookupFile(skinmtrl_path.c_str())); + materials.push_back(mat); + } + } + + maxLod = std::max(mdl.num_lod, maxLod); + + mdlPart->addModel(mdl, materials, currentLod); + gearAddition.mdl = mdl; + loadedGears.push_back(gearAddition); + } + } + + for (auto &queuedRemoval : queuedGearRemovals) { + mdlPart->removeModel(queuedRemoval.mdl); + loadedGears.erase(std::remove_if(loadedGears.begin(), + loadedGears.end(), + [queuedRemoval](const LoadedGear other) { + return queuedRemoval.info == other.info; + }), + loadedGears.end()); } } if (face) { - auto mdl_data = cache.lookupFile(physis_build_character_path( - CharacterCategory::Face, *face, currentRace, currentSubrace, currentGender)); + auto mdl_data = cache.lookupFile(physis_build_character_path(CharacterCategory::Face, *face, currentRace, currentSubrace, currentGender)); if (mdl_data.size > 0) { auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data); @@ -270,8 +339,7 @@ void GearView::reloadModel() { } if (hair) { - auto mdl_data = cache.lookupFile(physis_build_character_path( - CharacterCategory::Hair, *hair, currentRace, currentSubrace, currentGender)); + auto mdl_data = cache.lookupFile(physis_build_character_path(CharacterCategory::Hair, *hair, currentRace, currentSubrace, currentGender)); if (mdl_data.size > 0) { auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data); @@ -292,8 +360,7 @@ void GearView::reloadModel() { } if (ear) { - auto mdl_data = cache.lookupFile(physis_build_character_path( - CharacterCategory::Hair, *ear, currentRace, currentSubrace, currentGender)); + auto mdl_data = cache.lookupFile(physis_build_character_path(CharacterCategory::Hair, *ear, currentRace, currentSubrace, currentGender)); if (mdl_data.size > 0) { auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data); @@ -314,8 +381,7 @@ void GearView::reloadModel() { } if (tail) { - auto mdl_data = cache.lookupFile(physis_build_character_path( - CharacterCategory::Tail, *tail, currentRace, currentSubrace, currentGender)); + auto mdl_data = cache.lookupFile(physis_build_character_path(CharacterCategory::Tail, *tail, currentRace, currentSubrace, currentGender)); if (mdl_data.size > 0) { auto mdl = physis_mdl_parse(mdl_data.size, mdl_data.data); @@ -330,31 +396,18 @@ void GearView::reloadModel() { } } - Q_EMIT modelReloaded(); + raceDirty = false; + gearDirty = false; + updating = false; + faceDirty = false; + hairDirty = false; + earDirty = false; + tailDirty = false; } -void GearView::reloadRaceDeforms() { - qDebug() << "Loading race deform matrices for " << magic_enum::enum_name(currentRace).data() - << magic_enum::enum_name(currentSubrace).data() << magic_enum::enum_name(currentGender).data(); - const int raceCode = physis_get_race_code(currentRace, currentSubrace, currentGender); - qDebug() << "Race code: " << raceCode; - - QString skelName = QString{"c%1b0001.skel"}.arg(raceCode, 4, 10, QLatin1Char{'0'}); - mdlPart->setSkeleton(physis_skeleton_from_skel(physis_read_file(skelName.toStdString().c_str()))); - - // racial deforms don't work on Hyur Midlander, not needed? TODO not sure - if (currentSubrace != Subrace::Midlander) { - QString deformName = QString{"c%1_deform.json"}.arg(raceCode, 4, 10, QLatin1Char{'0'}); - mdlPart->loadRaceDeformMatrices(physis_read_file(deformName.toStdString().c_str())); - } else { - for (auto& data : mdlPart->boneData) { - data.deformRaceMatrix = glm::mat4(1.0f); - } - } -} - -MDLPart& GearView::part() const { - return *mdlPart; +bool GearView::needsUpdate() const +{ + return gearDirty || raceDirty || faceDirty || hairDirty || earDirty || tailDirty; } #include "moc_gearview.cpp" diff --git a/armoury/src/mainwindow.cpp b/armoury/src/mainwindow.cpp index a0e36a1..79acff5 100644 --- a/armoury/src/mainwindow.cpp +++ b/armoury/src/mainwindow.cpp @@ -83,6 +83,9 @@ MainWindow::MainWindow(GameData* in_data) : data(*in_data), cache(FileCache{*in_ connect(gearView, &SingleGearView::addToFullModelViewer, this, [=](GearInfo& info) { fullModelViewer->addGear(info); }); + connect(fullModelViewer, &FullModelViewer::loadingChanged, this, [=](const bool loading) { + gearView->setFMVAvailable(!loading); + }); layout->addWidget(gearView); fullModelViewer = new FullModelViewer(&data, cache); diff --git a/armoury/src/singlegearview.cpp b/armoury/src/singlegearview.cpp index fa81458..ee3e69d 100644 --- a/armoury/src/singlegearview.cpp +++ b/armoury/src/singlegearview.cpp @@ -100,15 +100,25 @@ SingleGearView::SingleGearView(GameData* data, FileCache& cache) : data(data) { } void SingleGearView::clear() { + if (currentGear) { + gearView->removeGear(*currentGear); + } currentGear.reset(); Q_EMIT gearChanged(); } void SingleGearView::setGear(const GearInfo& info) { - currentGear = info; + if (info != currentGear) { + if (currentGear) { + gearView->removeGear(*currentGear); + } - Q_EMIT gearChanged(); + currentGear = info; + gearView->addGear(*currentGear); + + Q_EMIT gearChanged(); + } } void SingleGearView::setRace(Race race) { @@ -147,19 +157,16 @@ void SingleGearView::setLevelOfDetail(int lod) { Q_EMIT levelOfDetailChanged(); } -void SingleGearView::reloadGear() { - gearView->clear(); - +void SingleGearView::reloadGear() +{ raceCombo->setEnabled(currentGear.has_value()); subraceCombo->setEnabled(currentGear.has_value()); genderCombo->setEnabled(currentGear.has_value()); lodCombo->setEnabled(currentGear.has_value()); - addToFMVButton->setEnabled(currentGear.has_value()); + addToFMVButton->setEnabled(currentGear.has_value() && fmvAvailable); exportButton->setEnabled(currentGear.has_value()); if (currentGear.has_value()) { - gearView->addGear(*currentGear); - loadingComboData = true; const auto oldRace = static_cast(raceCombo->itemData(raceCombo->currentIndex()).toInt()); @@ -212,4 +219,12 @@ void SingleGearView::reloadGear() { } } +void SingleGearView::setFMVAvailable(const bool available) +{ + if (fmvAvailable != available) { + fmvAvailable = available; + addToFMVButton->setEnabled(currentGear.has_value() && available); + } +} + #include "moc_singlegearview.cpp" \ No newline at end of file diff --git a/parts/mdl/mdlpart.cpp b/parts/mdl/mdlpart.cpp index def0bb5..4aa09e6 100644 --- a/parts/mdl/mdlpart.cpp +++ b/parts/mdl/mdlpart.cpp @@ -116,6 +116,9 @@ public: } void render() { + if (part->requestUpdate) + part->requestUpdate(); + glm::vec3 position( part->cameraDistance * sin(part->yaw), part->cameraDistance * part->pitch, @@ -554,4 +557,14 @@ void MDLPart::calculateBone(physis_Skeleton& skeleton, physis_Bone& bone, const } } +void MDLPart::removeModel(const physis_MDL &mdl) +{ + models.erase(std::remove_if(models.begin(), + models.end(), + [mdl](const RenderModel other) { + return mdl.lods == other.model.lods; + }), + models.end()); +} + #include "moc_mdlpart.cpp" \ No newline at end of file diff --git a/parts/mdl/mdlpart.h b/parts/mdl/mdlpart.h index dd7cb23..238f637 100644 --- a/parts/mdl/mdlpart.h +++ b/parts/mdl/mdlpart.h @@ -47,6 +47,8 @@ public: std::vector boneData; + std::function requestUpdate; + Q_SIGNALS: void modelChanged(); void skeletonChanged(); @@ -58,6 +60,8 @@ public Q_SLOTS: /// Adds a new MDL with a list of materials used. void addModel(physis_MDL mdl, std::vector materials, int lod); + void removeModel(const physis_MDL &mdl); + /// Sets the skeleton any skinned MDLs should bind to. void setSkeleton(physis_Skeleton skeleton);