From fedc93912d2541978c4bc29fdef970821c308014 Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Fri, 25 Mar 2016 14:51:59 -1000 Subject: [PATCH] Refactor for original/pc dataspec handling --- CMakeLists.txt | 8 +- DataSpec/DNACommon/PART.hpp | 2 +- DataSpec/DNACommon/TXTR.cpp | 283 +++++++++++++++++++++++++- DataSpec/DNACommon/TXTR.hpp | 1 + DataSpec/SpecMP1.cpp | 14 +- DataSpec/SpecMP2.cpp | 14 +- DataSpec/SpecMP3.cpp | 14 +- Editor/CMakeLists.txt | 3 +- Editor/ProjectManager.cpp | 17 +- Editor/ProjectManager.hpp | 6 +- Editor/ProjectResourceFactory.cpp | 104 ---------- Editor/ProjectResourceFactory.hpp | 85 -------- Editor/ProjectResourceFactoryBase.cpp | 283 ++++++++++++++++++++++++++ Editor/ProjectResourceFactoryBase.hpp | 66 ++++++ Editor/ProjectResourceFactoryMP1.cpp | 141 +++++++++++++ Editor/ProjectResourceFactoryMP1.hpp | 25 +++ Runtime/CResFactory.hpp | 24 ++- Runtime/RetroTypes.hpp | 1 + hecl | 2 +- 19 files changed, 859 insertions(+), 234 deletions(-) delete mode 100644 Editor/ProjectResourceFactory.cpp delete mode 100644 Editor/ProjectResourceFactory.hpp create mode 100644 Editor/ProjectResourceFactoryBase.cpp create mode 100644 Editor/ProjectResourceFactoryBase.hpp create mode 100644 Editor/ProjectResourceFactoryMP1.cpp create mode 100644 Editor/ProjectResourceFactoryMP1.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 01ea58859..763ca5cde 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,14 +72,20 @@ set(HECL_DATASPEC_DECLS namespace DataSpec { extern hecl::Database::DataSpecEntry SpecEntMP1; + extern hecl::Database::DataSpecEntry SpecEntMP1PC; extern hecl::Database::DataSpecEntry SpecEntMP2; + extern hecl::Database::DataSpecEntry SpecEntMP2PC; extern hecl::Database::DataSpecEntry SpecEntMP3; + extern hecl::Database::DataSpecEntry SpecEntMP3PC; }") set(HECL_DATASPEC_PUSHES " /* RetroCommon */ hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP1); + hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP1PC); hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP2); - hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP3);") + hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP2PC); + hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP3); + hecl::Database::DATA_SPEC_REGISTRY.push_back(&DataSpec::SpecEntMP3PC);") add_subdirectory(hecl) add_definitions(${BOO_SYS_DEFINES}) add_subdirectory(specter) diff --git a/DataSpec/DNACommon/PART.hpp b/DataSpec/DNACommon/PART.hpp index 3be1c9302..37ac9d859 100644 --- a/DataSpec/DNACommon/PART.hpp +++ b/DataSpec/DNACommon/PART.hpp @@ -13,7 +13,7 @@ namespace DNAParticle template struct GPSM : BigYAML { - static const char* DNAType() {return "urde::GPSM";} + static const char* DNAType() {return "GPSM";} const char* DNATypeV() const {return DNAType();} VectorElementFactory x0_PSIV; diff --git a/DataSpec/DNACommon/TXTR.cpp b/DataSpec/DNACommon/TXTR.cpp index c18b1d6c6..3d4bfe81b 100644 --- a/DataSpec/DNACommon/TXTR.cpp +++ b/DataSpec/DNACommon/TXTR.cpp @@ -2,13 +2,67 @@ #include #include "TXTR.hpp" #include "PAK.hpp" +#include "athena/FileWriter.hpp" namespace DataSpec { static logvisor::Module Log("libpng"); -/* GX uses this upsampling technique to prevent banding on downsampled texture formats */ +static int CountBits(uint32_t n) +{ + int ret = 0; + for (int i=0 ; i<32 ; ++i) + if (((n >> i) & 1) != 0) + ++ret; + return ret; +} + +/* Box filter algorithm (for mipmapping) */ +static void BoxFilter(const uint8_t* input, unsigned chanCount, + unsigned inWidth, unsigned inHeight, uint8_t* output) +{ + unsigned mipWidth = 1; + unsigned mipHeight = 1; + if (inWidth > 1) + mipWidth = inWidth / 2; + if (inHeight > 1) + mipHeight = inHeight / 2; + + int y,x,c; + for (y=0 ; y 0 && inHeight > 0) + { + ret += inWidth * inHeight; + inWidth /= 2; + inHeight /= 2; + } + return ret; +} + +/* GX uses this upsampling technique with downsampled texture formats */ static inline uint8_t Convert3To8(uint8_t v) { /* Swizzle bits: 00000123 -> 12312312 */ @@ -500,6 +554,11 @@ bool TXTR::Extract(PAKEntryReadStream& rs, const hecl::ProjectPath& outPath) png_init_io(png, fp); png_infop info = png_create_info_struct(png); + png_text textStruct = {}; + textStruct.key = png_charp("urde_nomip"); + if (numMips == 1) + png_set_text(png, info, &textStruct, 1); + switch (format) { case 0: @@ -547,4 +606,226 @@ bool TXTR::Cook(const hecl::ProjectPath& inPath, const hecl::ProjectPath& outPat return false; } +bool TXTR::CookPC(const hecl::ProjectPath& inPath, const hecl::ProjectPath& outPath) +{ + FILE* inf = hecl::Fopen(inPath.getAbsolutePath().c_str(), _S("rb")); + if (!inf) + { + Log.report(logvisor::Error, + _S("Unable to open '%s' for reading"), + inPath.getAbsolutePath().c_str()); + return false; + } + + /* Validate PNG */ + char header[8]; + fread(header, 1, 8, inf); + if (png_sig_cmp((png_const_bytep)header, 0, 8)) + { + Log.report(logvisor::Error, _S("invalid PNG signature in '%s'"), + inPath.getAbsolutePath().c_str()); + fclose(inf); + return false; + } + + /* Setup PNG reader */ + png_structp pngRead = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!pngRead) + { + Log.report(logvisor::Error, "unable to initialize libpng"); + fclose(inf); + return false; + } + png_infop info = png_create_info_struct(pngRead); + if (!info) + { + Log.report(logvisor::Error, "unable to initialize libpng info"); + fclose(inf); + png_destroy_read_struct(&pngRead, nullptr, nullptr); + return false; + } + + if (setjmp(png_jmpbuf(pngRead))) + { + Log.report(logvisor::Error, _S("unable to initialize libpng I/O for '%s'"), + inPath.getAbsolutePath().c_str()); + fclose(inf); + png_destroy_read_struct(&pngRead, &info, nullptr); + return false; + } + + png_init_io(pngRead, inf); + png_set_sig_bytes(pngRead, 8); + + png_read_info(pngRead, info); + + png_uint_32 width = png_get_image_width(pngRead, info); + png_uint_32 height = png_get_image_height(pngRead, info); + png_byte colorType = png_get_color_type(pngRead, info); + png_byte bitDepth = png_get_bit_depth(pngRead, info); + + /* Disable mipmapping if urde_nomip embedded */ + bool mipmap = true; + png_text* textStruct; + int numText; + png_get_text(pngRead, info, &textStruct, &numText); + for (int i=0 ; i>= 1) ++numMips; + } + else + numMips = 1; + + if (bitDepth != 8) + { + Log.report(logvisor::Error, _S("'%s' is not 8 bits-per-channel"), + inPath.getAbsolutePath().c_str()); + fclose(inf); + png_destroy_read_struct(&pngRead, &info, nullptr); + return false; + } + + size_t rowSize = 0; + switch (colorType) + { + case PNG_COLOR_TYPE_GRAY: + rowSize = width; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + rowSize = width * 2; + break; + case PNG_COLOR_TYPE_RGB: + rowSize = width * 3; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + rowSize = width * 4; + break; + default: + Log.report(logvisor::Error, _S("unsupported color type in '%s'"), + inPath.getAbsolutePath().c_str()); + fclose(inf); + png_destroy_read_struct(&pngRead, &info, nullptr); + return false; + } + + /* Intermediate row-read buf (file components) */ + std::unique_ptr rowBuf(new uint8_t[rowSize]); + + /* Final mipmapped buf (RGBA components) */ + std::unique_ptr bufOut; + size_t bufLen = 0; + if (numMips > 1) + bufLen = ComputeMippedTexelCount(width, height) * 4; + else + bufLen = width * height * 4; + bufOut.reset(new uint8_t[bufLen]); + + if (setjmp(png_jmpbuf(pngRead))) + { + Log.report(logvisor::Error, _S("unable to read image in '%s'"), + inPath.getAbsolutePath().c_str()); + fclose(inf); + png_destroy_read_struct(&pngRead, &info, nullptr); + return false; + } + + /* Read and make RGBA */ + for (png_uint_32 r=0 ; r 1) + { + const uint8_t* filterIn = bufOut.get(); + uint8_t* filterOut = bufOut.get() + width * height * 4; + unsigned filterWidth = width; + unsigned filterHeight = height; + for (size_t i=1 ; i hecl::Database::IDataSpec* {return new struct SpecMP1(project);} }; +hecl::Database::DataSpecEntry SpecEntMP1PC = +{ + _S("MP1-PC"), + _S("Data specification for PC-optimized Metroid Prime engine"), + [](hecl::Database::Project& project, hecl::Database::DataSpecTool) + -> hecl::Database::IDataSpec* {return nullptr;} +}; + } diff --git a/DataSpec/SpecMP2.cpp b/DataSpec/SpecMP2.cpp index 30d0b87e4..dff435142 100644 --- a/DataSpec/SpecMP2.cpp +++ b/DataSpec/SpecMP2.cpp @@ -78,11 +78,7 @@ struct SpecMP2 : SpecBase { for (const hecl::SystemString& arg : args) { -#if HECL_UCS2 - std::string lowerArg = hecl::WideToUTF8(arg); -#else - std::string lowerArg = arg; -#endif + std::string lowerArg = hecl::SystemUTF8View(arg).str(); std::transform(lowerArg.begin(), lowerArg.end(), lowerArg.begin(), tolower); if (!lowerArg.compare(0, lowerBase.size(), lowerBase)) good = true; @@ -309,4 +305,12 @@ hecl::Database::DataSpecEntry SpecEntMP2 -> hecl::Database::IDataSpec* {return new struct SpecMP2(project);} ); +hecl::Database::DataSpecEntry SpecEntMP2PC = +{ + _S("MP2-PC"), + _S("Data specification for PC-optimized Metroid Prime 2 engine"), + [](hecl::Database::Project& project, hecl::Database::DataSpecTool) + -> hecl::Database::IDataSpec* {return nullptr;} +}; + } diff --git a/DataSpec/SpecMP3.cpp b/DataSpec/SpecMP3.cpp index 4fa9a635e..ce6c1b6c6 100644 --- a/DataSpec/SpecMP3.cpp +++ b/DataSpec/SpecMP3.cpp @@ -103,11 +103,7 @@ struct SpecMP3 : SpecBase { for (const hecl::SystemString& arg : args) { -#if HECL_UCS2 - std::string lowerArg = hecl::WideToUTF8(arg); -#else - std::string lowerArg = arg; -#endif + std::string lowerArg = hecl::SystemUTF8View(arg).str(); std::transform(lowerArg.begin(), lowerArg.end(), lowerArg.begin(), tolower); if (!lowerArg.compare(0, lowerBase.size(), lowerBase)) good = true; @@ -493,4 +489,12 @@ hecl::Database::DataSpecEntry SpecEntMP3 -> hecl::Database::IDataSpec* {return new struct SpecMP3(project);} ); +hecl::Database::DataSpecEntry SpecEntMP3PC = +{ + _S("MP3-PC"), + _S("Data specification for PC-optimized Metroid Prime 3 engine"), + [](hecl::Database::Project& project, hecl::Database::DataSpecTool) + -> hecl::Database::IDataSpec* {return nullptr;} +}; + } diff --git a/Editor/CMakeLists.txt b/Editor/CMakeLists.txt index e4bd4cd18..0ffd9e784 100644 --- a/Editor/CMakeLists.txt +++ b/Editor/CMakeLists.txt @@ -29,7 +29,8 @@ add_executable(urde WIN32 MACOSX_BUNDLE ParticleEditor.hpp ParticleEditor.cpp atdna_ParticleEditor.cpp InformationCenter.hpp InformationCenter.hpp atdna_InformationCenter.cpp ProjectManager.hpp ProjectManager.cpp - ProjectResourceFactory.hpp ProjectResourceFactory.cpp + ProjectResourceFactoryBase.hpp ProjectResourceFactoryBase.cpp + ProjectResourceFactoryMP1.hpp ProjectResourceFactoryMP1.cpp ViewManager.hpp ViewManager.cpp Resource.hpp Resource.cpp Camera.hpp Camera.cpp) diff --git a/Editor/ProjectManager.cpp b/Editor/ProjectManager.cpp index 165c24bac..353a089e6 100644 --- a/Editor/ProjectManager.cpp +++ b/Editor/ProjectManager.cpp @@ -6,22 +6,9 @@ namespace urde { static logvisor::Module Log("URDE::ProjectManager"); -void ProjectManager::IndexMP1Resources() -{ - const std::vector& specs = m_proj->getDataSpecs(); - for (const hecl::Database::Project::ProjectDataSpec& spec : m_proj->getDataSpecs()) - { - if (&spec.spec == &DataSpec::SpecEntMP1) - { - m_factory.BuildObjectMap(spec); - break; - } - } -} - bool ProjectManager::m_registeredSpecs = false; ProjectManager::ProjectManager(ViewManager &vm) -: m_vm(vm), m_objStore(m_factory) +: m_vm(vm), m_objStore(m_factoryMP1) { if (!m_registeredSpecs) { @@ -103,7 +90,7 @@ bool ProjectManager::openProject(const hecl::SystemString& path) m_vm.ProjectChanged(*m_proj); m_vm.SetupEditorView(r); - IndexMP1Resources(); + m_factoryMP1.IndexMP1Resources(*m_proj); m_vm.BuildTestPART(m_objStore); { diff --git a/Editor/ProjectManager.hpp b/Editor/ProjectManager.hpp index 9f39c671e..8966229e6 100644 --- a/Editor/ProjectManager.hpp +++ b/Editor/ProjectManager.hpp @@ -3,7 +3,7 @@ #include #include -#include "ProjectResourceFactory.hpp" +#include "ProjectResourceFactoryMP1.hpp" #include "Runtime/CSimplePool.hpp" namespace urde @@ -18,11 +18,9 @@ class ProjectManager ViewManager& m_vm; std::unique_ptr m_proj; static bool m_registeredSpecs; - ProjectResourceFactory m_factory; + ProjectResourceFactoryMP1 m_factoryMP1; urde::CSimplePool m_objStore; - void IndexMP1Resources(); - public: ProjectManager(ViewManager& vm); operator bool() const {return m_proj.operator bool();} diff --git a/Editor/ProjectResourceFactory.cpp b/Editor/ProjectResourceFactory.cpp deleted file mode 100644 index 909e1d440..000000000 --- a/Editor/ProjectResourceFactory.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include "ProjectResourceFactory.hpp" -#include "Runtime/IOStreams.hpp" - -#include "Runtime/Particle/CParticleDataFactory.hpp" -#include "Runtime/Particle/CGenDescription.hpp" -#include "Runtime/Particle/CElectricDescription.hpp" -#include "Runtime/Particle/CSwooshDescription.hpp" -#include "Runtime/GuiSys/CGuiFrame.hpp" -#include "Runtime/GuiSys/CRasterFont.hpp" -#include "Runtime/Graphics/CModel.hpp" -#include "Runtime/Graphics/CTexture.hpp" - -namespace urde -{ - -ProjectResourceFactory::ProjectResourceFactory() -{ - m_factoryMgr.AddFactory(FOURCC('TXTR'), urde::FTextureFactory); - m_factoryMgr.AddFactory(FOURCC('PART'), urde::FParticleFactory); - m_factoryMgr.AddFactory(FOURCC('FRME'), urde::RGuiFrameFactoryInGame); - m_factoryMgr.AddFactory(FOURCC('FONT'), urde::FRasterFontFactory); -} - -void ProjectResourceFactory::BuildObjectMap(const hecl::Database::Project::ProjectDataSpec &spec) -{ -#if 0 - m_tagToPath.clear(); - m_catalogNameToTag.clear(); - hecl::SystemString catalogPath = hecl::ProjectPath(spec.cookedPath, hecl::SystemString(spec.spec.m_name) + _S("/catalog.yaml")).getAbsolutePath(); - FILE* catalogFile = hecl::Fopen(catalogPath.c_str(), _S("r")); - athena::io::YAMLDocReader reader; - yaml_parser_set_input_file(reader.getParser(), catalogFile); - reader.parse(); - const athena::io::YAMLNode* catalogRoot = reader.getRootNode(); - if (catalogRoot) - { - m_catalogNameToPath.reserve(catalogRoot->m_mapChildren.size()); - for (const std::pair>& ch : catalogRoot->m_mapChildren) - { - if (ch.second->m_type == YAML_SCALAR_NODE) - m_catalogNameToPath[ch.first] = hecl::ProjectPath(spec.cookedPath, hecl::SystemString(ch.second->m_scalarString)); - } - } - - if (!hecl::StrCmp(spec.spec.m_name, _S("MP3"))) - { - RecursiveAddDirObjects(spec.cookedPath); - } - else - { - RecursiveAddDirObjects(spec.cookedPath); - } -#endif -} - -std::unique_ptr ProjectResourceFactory::Build(const urde::SObjectTag& tag, - const urde::CVParamTransfer& paramXfer) -{ - auto search = m_tagToPath.find(tag); - if (search == m_tagToPath.end()) - return {}; - - //fprintf(stderr, "Loading resource %s\n", search->second.getRelativePath().c_str()); - athena::io::FileReader fr(search->second.getAbsolutePath(), 32 * 1024, false); - if (fr.hasError()) - return {}; - - return m_factoryMgr.MakeObject(tag, fr, paramXfer); -} - -void ProjectResourceFactory::BuildAsync(const urde::SObjectTag& tag, - const urde::CVParamTransfer& paramXfer, - urde::IObj** objOut) -{ - std::unique_ptr obj = Build(tag, paramXfer); - *objOut = obj.release(); -} - -void ProjectResourceFactory::CancelBuild(const urde::SObjectTag&) -{ -} - -bool ProjectResourceFactory::CanBuild(const urde::SObjectTag& tag) -{ - auto search = m_tagToPath.find(tag); - if (search == m_tagToPath.end()) - return false; - - athena::io::FileReader fr(search->second.getAbsolutePath(), 32 * 1024, false); - if (fr.hasError()) - return false; - - return true; -} - -const urde::SObjectTag* ProjectResourceFactory::GetResourceIdByName(const char* name) const -{ - if (m_catalogNameToTag.find(name) == m_catalogNameToTag.end()) - return nullptr; - const urde::SObjectTag& tag = m_catalogNameToTag.at(name); - return &tag; -} - -} diff --git a/Editor/ProjectResourceFactory.hpp b/Editor/ProjectResourceFactory.hpp deleted file mode 100644 index 08f87d58b..000000000 --- a/Editor/ProjectResourceFactory.hpp +++ /dev/null @@ -1,85 +0,0 @@ -#ifndef URDE_PROJECT_RESOURCE_FACTORY_HPP -#define URDE_PROJECT_RESOURCE_FACTORY_HPP - -#include "Runtime/IFactory.hpp" -#include "Runtime/CFactoryMgr.hpp" - -namespace urde -{ - -class ProjectResourceFactory : public urde::IFactory -{ - std::unordered_map m_tagToPath; - std::unordered_map m_catalogNameToTag; - std::unordered_map m_catalogNameToPath; - urde::CFactoryMgr m_factoryMgr; - void RecursiveAddDirObjects(const hecl::ProjectPath& path) - { - hecl::DirectoryEnumerator de = path.enumerateDir(); - const int idLen = 5 + 8; - for (const hecl::DirectoryEnumerator::Entry& ent : de) - { - if (ent.m_isDir) - RecursiveAddDirObjects(hecl::ProjectPath(path, ent.m_name)); - if (ent.m_name.size() == idLen && ent.m_name[4] == _S('_')) - { - hecl::SystemUTF8View entu8(ent.m_name); -#if _WIN32 - u64 id = _strtoui64(entu8.c_str() + 5, nullptr, 16); -#else - u64 id = strtouq(entu8.c_str() + 5, nullptr, 16); -#endif - - if (id) - { - urde::SObjectTag objTag = {hecl::FourCC(entu8.c_str()), id}; - if (m_tagToPath.find(objTag) == m_tagToPath.end()) - m_tagToPath[objTag] = hecl::ProjectPath(path, ent.m_name); - } - } - else - { -#if 0 - hecl::SystemUTF8View nameView(ent.m_name); - auto it = std::find_if(catalog.namedResources.begin(), catalog.namedResources.end(), - [&nameView](const typename DataSpec::NamedResourceCatalog::NamedResource& res) -> bool - { return res.name == nameView.str(); }); - if (it == catalog.namedResources.end()) - continue; - - const typename DataSpec::NamedResourceCatalog::NamedResource& nr = *it; - pshag::SObjectTag objTag = GetTag(nr); - - m_catalogNameToTag[nr.name.c_str()] = objTag; - m_tagToPath[objTag] = HECL::ProjectPath(path, ent.m_name); -#endif - } - } - } - -#if 0 - template - pshag::SObjectTag GetTag(const DataSpec::NamedResourceCatalog::NamedResource &nr, - typename std::enable_if::value>::type* = 0) - { return { nr.type, nr.uid.toUint32() }; } - - template - pshag::SObjectTag GetTag(const typename DataSpec::NamedResourceCatalog::NamedResource& nr, - typename std::enable_if::value>::type* = 0) - { return { nr.type, nr.uid.toUint64() }; } -#endif - -public: - ProjectResourceFactory(); - void BuildObjectMap(const hecl::Database::Project::ProjectDataSpec& spec); - - std::unique_ptr Build(const urde::SObjectTag&, const urde::CVParamTransfer&); - void BuildAsync(const urde::SObjectTag&, const urde::CVParamTransfer&, urde::IObj**); - void CancelBuild(const urde::SObjectTag&); - bool CanBuild(const urde::SObjectTag&); - const urde::SObjectTag* GetResourceIdByName(const char*) const; -}; - -} - -#endif // URDE_PROJECT_RESOURCE_FACTORY_HPP diff --git a/Editor/ProjectResourceFactoryBase.cpp b/Editor/ProjectResourceFactoryBase.cpp new file mode 100644 index 000000000..13bf6634a --- /dev/null +++ b/Editor/ProjectResourceFactoryBase.cpp @@ -0,0 +1,283 @@ +#include "ProjectResourceFactoryBase.hpp" +#include "Runtime/IObj.hpp" + +namespace urde +{ +static logvisor::Module Log("urde::ProjectResourceFactoryBase"); + +void ProjectResourceFactoryBase::Clear() +{ + m_tagToPath.clear(); + m_catalogNameToTag.clear(); +} + +hecl::BlenderConnection& ProjectResourceFactoryBase::GetBackgroundBlender() const +{ + std::experimental::optional& shareConn = + ((ProjectResourceFactoryBase*)this)->m_backgroundBlender; + if (!shareConn) + shareConn.emplace(hecl::VerbosityLevel); + return *shareConn; +} + +void ProjectResourceFactoryBase::ReadCatalog(const hecl::ProjectPath& catalogPath) +{ + FILE* fp = hecl::Fopen(catalogPath.getAbsolutePath().c_str(), _S("r")); + if (!fp) + return; + + athena::io::YAMLDocReader reader; + yaml_parser_set_input_file(reader.getParser(), fp); + bool res = reader.parse(); + fclose(fp); + if (!res) + return; + + const athena::io::YAMLNode* root = reader.getRootNode(); + for (const auto& p : root->m_mapChildren) + { + hecl::ProjectPath path(m_proj->getProjectWorkingPath(), p.second->m_scalarString); + if (path.getPathType() != hecl::ProjectPath::Type::File) + continue; + SObjectTag pathTag = TagFromPath(path); + if (pathTag) + { + std::unique_lock lk(m_backgroundIndexMutex); + m_catalogNameToTag[p.first] = pathTag; + } + } +} + +void ProjectResourceFactoryBase::BackgroundIndexRecursiveProc(const hecl::SystemString& path, int level) +{ + hecl::DirectoryEnumerator dEnum(path, + hecl::DirectoryEnumerator::Mode::DirsThenFilesSorted, + false, false, true); + + /* Enumerate all items */ + for (const hecl::DirectoryEnumerator::Entry& ent : dEnum) + { + if (ent.m_isDir) + BackgroundIndexRecursiveProc(ent.m_path, level+1); + else + { + hecl::ProjectPath path(path, ent.m_name); + if (path.getPathType() != hecl::ProjectPath::Type::File) + continue; + + /* Read catalog.yaml for .pak directory if exists */ + if (level == 1 && !ent.m_name.compare(_S("catalog.yaml"))) + { + ReadCatalog(path); + continue; + } + + /* Classify intermediate into tag */ + SObjectTag pathTag = TagFromPath(path); + if (pathTag) + { + std::unique_lock lk(m_backgroundIndexMutex); + m_tagToPath[pathTag] = path; + } + } + + /* bail if cancelled by client */ + if (!m_backgroundRunning) + break; + } +} + +void ProjectResourceFactoryBase::BackgroundIndexProc() +{ + hecl::ProjectPath specRoot(m_proj->getProjectWorkingPath(), m_origSpec->m_name); + BackgroundIndexRecursiveProc(specRoot.getAbsolutePath(), 0); + m_backgroundRunning = false; + m_backgroundBlender = std::experimental::nullopt; +} + +void ProjectResourceFactoryBase::CancelBackgroundIndex() +{ + if (m_backgroundRunning && m_backgroundIndexTh.joinable()) + { + m_backgroundRunning = false; + m_backgroundIndexTh.join(); + } +} + +void ProjectResourceFactoryBase::BeginBackgroundIndex + (const hecl::Database::Project& proj, + const hecl::Database::DataSpecEntry& origSpec, + const hecl::Database::DataSpecEntry& pcSpec) +{ + CancelBackgroundIndex(); + Clear(); + m_proj = &proj; + m_origSpec = &origSpec; + m_pcSpec = &pcSpec; + m_backgroundRunning = true; + m_backgroundIndexTh = + std::thread(std::bind(&ProjectResourceFactoryBase::BackgroundIndexProc, this)); +} + +CFactoryFnReturn ProjectResourceFactoryBase::MakeObject(const SObjectTag& tag, + const hecl::ProjectPath& path, + const CVParamTransfer& paramXfer) +{ + /* Ensure requested resource is on the filesystem */ + if (path.getPathType() != hecl::ProjectPath::Type::File) + { + Log.report(logvisor::Error, _S("unable to find resource path '%s'"), + path.getAbsolutePath().c_str()); + return {}; + } + + /* Get cooked representation path */ + hecl::ProjectPath cooked = GetCookedPath(tag, path, true); + + /* Perform mod-time comparison */ + if (cooked.getPathType() != hecl::ProjectPath::Type::File || + cooked.getModtime() < path.getModtime()) + { + if (!DoCook(tag, path, cooked, true)) + { + Log.report(logvisor::Error, _S("unable to cook resource path '%s'"), + path.getAbsolutePath().c_str()); + return {}; + } + } + + /* Ensure cooked rep is on the filesystem */ + athena::io::FileReader fr(cooked.getAbsolutePath(), 32 * 1024, false); + if (fr.hasError()) + { + Log.report(logvisor::Error, _S("unable to open cooked resource path '%s'"), + cooked.getAbsolutePath().c_str()); + return {}; + } + + /* All good, build resource */ + return m_factoryMgr.MakeObject(tag, fr, paramXfer); +} + +std::unique_ptr ProjectResourceFactoryBase::Build(const urde::SObjectTag& tag, + const urde::CVParamTransfer& paramXfer) +{ + std::unique_lock lk(m_backgroundIndexMutex); + auto search = m_tagToPath.find(tag); + if (search == m_tagToPath.end()) + { + if (m_backgroundRunning) + { + while (m_backgroundRunning) + { + lk.unlock(); + lk.lock(); + search = m_tagToPath.find(tag); + if (search != m_tagToPath.end()) + break; + } + } + else + return {}; + } + + return MakeObject(tag, search->second, paramXfer); +} + +void ProjectResourceFactoryBase::BuildAsync(const urde::SObjectTag& tag, + const urde::CVParamTransfer& paramXfer, + urde::IObj** objOut) +{ + if (m_asyncLoadList.find(tag) != m_asyncLoadList.end()) + return; + m_asyncLoadList[tag] = CResFactory::SLoadingData(tag, objOut, paramXfer); +} + +void ProjectResourceFactoryBase::CancelBuild(const urde::SObjectTag& tag) +{ + m_asyncLoadList.erase(tag); +} + +bool ProjectResourceFactoryBase::CanBuild(const urde::SObjectTag& tag) +{ + std::unique_lock lk(m_backgroundIndexMutex); + auto search = m_tagToPath.find(tag); + if (search == m_tagToPath.end()) + { + if (m_backgroundRunning) + { + while (m_backgroundRunning) + { + lk.unlock(); + lk.lock(); + search = m_tagToPath.find(tag); + if (search != m_tagToPath.end()) + break; + } + } + else + return false; + } + + if (search->second.getPathType() == hecl::ProjectPath::Type::File) + return true; + + return false; +} + +const urde::SObjectTag* ProjectResourceFactoryBase::GetResourceIdByName(const char* name) const +{ + std::unique_lock lk(((ProjectResourceFactoryBase*)this)->m_backgroundIndexMutex); + auto search = m_catalogNameToTag.find(name); + if (search == m_catalogNameToTag.end()) + { + if (m_backgroundRunning) + { + while (m_backgroundRunning) + { + lk.unlock(); + lk.lock(); + search = m_catalogNameToTag.find(name); + if (search != m_catalogNameToTag.end()) + break; + } + } + else + return nullptr; + } + return &search->second; +} + +void ProjectResourceFactoryBase::AsyncIdle() +{ + std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); + for (auto it=m_asyncLoadList.begin() ; it != m_asyncLoadList.end() ; ++it) + { + /* Allow 8 milliseconds (roughly 1/2 frame-time) for each async build cycle */ + if (std::chrono::duration_cast( + std::chrono::steady_clock::now() - start).count() > 8) + break; + + /* Ensure requested resource is in the index */ + std::unique_lock lk(m_backgroundIndexMutex); + CResFactory::SLoadingData& data = it->second; + auto search = m_tagToPath.find(data.x0_tag); + if (search == m_tagToPath.end()) + { + if (!m_backgroundRunning) + { + Log.report(logvisor::Error, _S("unable to find async load resource (%s, %08X)"), + data.x0_tag.type.toString().c_str(), data.x0_tag.id); + it = m_asyncLoadList.erase(it); + } + continue; + } + + /* Perform build (data not actually loaded asynchronously for now) */ + CFactoryFnReturn ret = MakeObject(data.x0_tag, search->second, data.x18_cvXfer); + *data.xc_targetPtr = ret.release(); + it = m_asyncLoadList.erase(it); + } +} + +} diff --git a/Editor/ProjectResourceFactoryBase.hpp b/Editor/ProjectResourceFactoryBase.hpp new file mode 100644 index 000000000..439415982 --- /dev/null +++ b/Editor/ProjectResourceFactoryBase.hpp @@ -0,0 +1,66 @@ +#ifndef URDE_PROJECT_RESOURCE_FACTORY_BASE_HPP +#define URDE_PROJECT_RESOURCE_FACTORY_BASE_HPP + +#include "Runtime/IFactory.hpp" +#include "Runtime/CFactoryMgr.hpp" +#include "Runtime/CResFactory.hpp" +#include "Runtime/optional.hpp" + +#include +#include + +namespace urde +{ + +class ProjectResourceFactoryBase : public urde::IFactory +{ +protected: + std::unordered_map m_tagToPath; + std::unordered_map m_catalogNameToTag; + void Clear(); + + const hecl::Database::Project* m_proj = nullptr; + const hecl::Database::DataSpecEntry* m_origSpec = nullptr; + const hecl::Database::DataSpecEntry* m_pcSpec = nullptr; + urde::CFactoryMgr m_factoryMgr; + + std::experimental::optional m_backgroundBlender; + std::thread m_backgroundIndexTh; + std::mutex m_backgroundIndexMutex; + bool m_backgroundRunning = false; + + std::unordered_map m_asyncLoadList; + + virtual SObjectTag TagFromPath(const hecl::ProjectPath& path) const=0; + + hecl::BlenderConnection& GetBackgroundBlender() const; + void ReadCatalog(const hecl::ProjectPath& catalogPath); + void BackgroundIndexRecursiveProc(const hecl::SystemString& path, int level); + void BackgroundIndexProc(); + void CancelBackgroundIndex(); + void BeginBackgroundIndex(const hecl::Database::Project& proj, + const hecl::Database::DataSpecEntry& origSpec, + const hecl::Database::DataSpecEntry& pcSpec); + + virtual hecl::ProjectPath GetCookedPath(const SObjectTag& tag, + const hecl::ProjectPath& working, + bool pcTarget) const=0; + virtual bool DoCook(const SObjectTag& tag, const hecl::ProjectPath& working, + const hecl::ProjectPath& cooked, + bool pcTarget)=0; + CFactoryFnReturn MakeObject(const SObjectTag& tag, const hecl::ProjectPath& path, + const CVParamTransfer& paramXfer); + +public: + std::unique_ptr Build(const urde::SObjectTag&, const urde::CVParamTransfer&); + void BuildAsync(const urde::SObjectTag&, const urde::CVParamTransfer&, urde::IObj**); + void CancelBuild(const urde::SObjectTag&); + bool CanBuild(const urde::SObjectTag&); + const urde::SObjectTag* GetResourceIdByName(const char*) const; + + void AsyncIdle(); +}; + +} + +#endif // URDE_PROJECT_RESOURCE_FACTORY_BASE_HPP diff --git a/Editor/ProjectResourceFactoryMP1.cpp b/Editor/ProjectResourceFactoryMP1.cpp new file mode 100644 index 000000000..fd4d5cf9a --- /dev/null +++ b/Editor/ProjectResourceFactoryMP1.cpp @@ -0,0 +1,141 @@ +#include "ProjectResourceFactoryMP1.hpp" +#include "Runtime/IOStreams.hpp" + +#include "Runtime/Particle/CParticleDataFactory.hpp" +#include "Runtime/Particle/CGenDescription.hpp" +#include "Runtime/Particle/CElectricDescription.hpp" +#include "Runtime/Particle/CSwooshDescription.hpp" +#include "Runtime/GuiSys/CGuiFrame.hpp" +#include "Runtime/GuiSys/CRasterFont.hpp" +#include "Runtime/Graphics/CModel.hpp" +#include "Runtime/Graphics/CTexture.hpp" + +#include "DataSpec/DNACommon/TXTR.hpp" + +namespace DataSpec +{ +extern hecl::Database::DataSpecEntry SpecEntMP1; +extern hecl::Database::DataSpecEntry SpecEntMP1PC; +} + +namespace urde +{ + +ProjectResourceFactoryMP1::ProjectResourceFactoryMP1() +{ + m_factoryMgr.AddFactory(FOURCC('TXTR'), urde::FTextureFactory); + m_factoryMgr.AddFactory(FOURCC('PART'), urde::FParticleFactory); + m_factoryMgr.AddFactory(FOURCC('FRME'), urde::RGuiFrameFactoryInGame); + m_factoryMgr.AddFactory(FOURCC('FONT'), urde::FRasterFontFactory); +} + +void ProjectResourceFactoryMP1::IndexMP1Resources(const hecl::Database::Project& proj) +{ + BeginBackgroundIndex(proj, DataSpec::SpecEntMP1, DataSpec::SpecEntMP1PC); +} + +SObjectTag ProjectResourceFactoryMP1::TagFromPath(const hecl::ProjectPath& path) const +{ + if (hecl::IsPathBlend(path)) + { + hecl::BlenderConnection& conn = GetBackgroundBlender(); + if (!conn.openBlend(path)) + return {}; + + switch (conn.getBlendType()) + { + case hecl::BlenderConnection::BlendType::Mesh: + return {SBIG('CMDL'), path.hash()}; + case hecl::BlenderConnection::BlendType::Actor: + return {SBIG('ANCS'), path.hash()}; + case hecl::BlenderConnection::BlendType::Area: + return {SBIG('MREA'), path.hash()}; + case hecl::BlenderConnection::BlendType::World: + return {SBIG('MLVL'), path.hash()}; + case hecl::BlenderConnection::BlendType::MapArea: + return {SBIG('MAPA'), path.hash()}; + case hecl::BlenderConnection::BlendType::MapUniverse: + return {SBIG('MAPU'), path.hash()}; + case hecl::BlenderConnection::BlendType::Frame: + return {SBIG('FRME'), path.hash()}; + default: + return {}; + } + } + else if (hecl::IsPathPNG(path)) + { + return {SBIG('TXTR'), path.hash()}; + } + else if (hecl::IsPathYAML(path)) + { + FILE* fp = hecl::Fopen(path.getAbsolutePath().c_str(), _S("r")); + if (!fp) + return {}; + + athena::io::YAMLDocReader reader; + yaml_parser_set_input_file(reader.getParser(), fp); + bool res = reader.parse(); + fclose(fp); + if (!res) + return {}; + + SObjectTag resTag; + if (reader.ClassTypeOperation([&](const char* className) -> bool + { + if (!strcmp(className, "GPSM")) + { + resTag.type = SBIG('PART'); + return true; + } + else if (!strcmp(className, "FONT")) + { + resTag.type = SBIG('FONT'); + return true; + } + return false; + })) + { + resTag.id = path.hash(); + return resTag; + } + } + return {}; +} + +hecl::ProjectPath ProjectResourceFactoryMP1::GetCookedPath(const SObjectTag& tag, + const hecl::ProjectPath& working, + bool pcTarget) const +{ + if (!pcTarget) + return working.getCookedPath(*m_origSpec); + + switch (tag.type) + { + case SBIG('TXTR'): + case SBIG('CMDL'): + case SBIG('MREA'): + return working.getCookedPath(*m_pcSpec); + default: break; + } + + return working.getCookedPath(*m_origSpec); +} + +bool ProjectResourceFactoryMP1::DoCook(const SObjectTag& tag, + const hecl::ProjectPath& working, + const hecl::ProjectPath& cooked, + bool pcTarget) +{ + switch (tag.type) + { + case SBIG('TXTR'): + if (pcTarget) + return DataSpec::TXTR::CookPC(working, cooked); + else + return DataSpec::TXTR::Cook(working, cooked); + default: break; + } + return false; +} + +} diff --git a/Editor/ProjectResourceFactoryMP1.hpp b/Editor/ProjectResourceFactoryMP1.hpp new file mode 100644 index 000000000..a804f0cda --- /dev/null +++ b/Editor/ProjectResourceFactoryMP1.hpp @@ -0,0 +1,25 @@ +#ifndef URDE_PROJECT_RESOURCE_FACTORY_MP1_HPP +#define URDE_PROJECT_RESOURCE_FACTORY_MP1_HPP + +#include "ProjectResourceFactoryBase.hpp" + +namespace urde +{ + +class ProjectResourceFactoryMP1 : public ProjectResourceFactoryBase +{ +public: + ProjectResourceFactoryMP1(); + void IndexMP1Resources(const hecl::Database::Project& proj); + SObjectTag TagFromPath(const hecl::ProjectPath& path) const; + hecl::ProjectPath GetCookedPath(const SObjectTag& tag, + const hecl::ProjectPath& working, + bool pcTarget) const; + bool DoCook(const SObjectTag& tag, const hecl::ProjectPath& working, + const hecl::ProjectPath& cooked, + bool pcTarget); +}; + +} + +#endif // URDE_PROJECT_RESOURCE_FACTORY_MP1_HPP diff --git a/Runtime/CResFactory.hpp b/Runtime/CResFactory.hpp index 8611074a1..44fb032a8 100644 --- a/Runtime/CResFactory.hpp +++ b/Runtime/CResFactory.hpp @@ -17,11 +17,15 @@ public: struct SLoadingData { SObjectTag x0_tag; - IDvdRequest* x8_dvdReq; - IObj** xc_targetPtr; - void* x10_loadBuffer; - u32 x14_resSize; + IDvdRequest* x8_dvdReq = nullptr; + IObj** xc_targetPtr = nullptr; + void* x10_loadBuffer = nullptr; + u32 x14_resSize = 0; CVParamTransfer x18_cvXfer; + + SLoadingData() = default; + SLoadingData(const SObjectTag& tag, IObj** ptr, const CVParamTransfer& xfer) + : x0_tag(tag), xc_targetPtr(ptr), x18_cvXfer(xfer) {} }; private: std::unordered_map m_loadList; @@ -31,8 +35,16 @@ public: std::unique_ptr Build(const SObjectTag&, const CVParamTransfer&); void BuildAsync(const SObjectTag&, const CVParamTransfer&, IObj**); void CancelBuild(const SObjectTag&); - bool CanBuild(const SObjectTag& tag) {return x4_loader.ResourceExists(tag);} - const SObjectTag* GetResourceIdByName(const char* name) const {return x4_loader.GetResourceIdByName(name);} + + bool CanBuild(const SObjectTag& tag) + { + return x4_loader.ResourceExists(tag); + } + + const SObjectTag* GetResourceIdByName(const char* name) const + { + return x4_loader.GetResourceIdByName(name); + } std::vector> GetResourceIdToNameList() const { diff --git a/Runtime/RetroTypes.hpp b/Runtime/RetroTypes.hpp index e1a23cef7..e26418b46 100644 --- a/Runtime/RetroTypes.hpp +++ b/Runtime/RetroTypes.hpp @@ -18,6 +18,7 @@ struct SObjectTag { FourCC type; TResId id = -1; + operator bool() const {return id != -1;} bool operator!=(const SObjectTag& other) const {return id != other.id;} bool operator==(const SObjectTag& other) const {return id == other.id;} SObjectTag() = default; diff --git a/hecl b/hecl index beab85bd0..2fe1611e6 160000 --- a/hecl +++ b/hecl @@ -1 +1 @@ -Subproject commit beab85bd01599155d655872349d7a93b4e6b7373 +Subproject commit 2fe1611e63d2993a1892ce00c1a108226aaecb52