Refactor for original/pc dataspec handling

This commit is contained in:
Jack Andersen 2016-03-25 14:51:59 -10:00
parent a866939b09
commit fedc93912d
19 changed files with 859 additions and 234 deletions

View File

@ -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)

View File

@ -13,7 +13,7 @@ namespace DNAParticle
template <class IDType>
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;

View File

@ -2,13 +2,67 @@
#include <squish.h>
#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<mipHeight ; ++y)
{
unsigned mip_line_base = mipWidth * y;
unsigned in1_line_base = inWidth * (y*2);
unsigned in2_line_base = inWidth * (y*2+1);
for (x=0 ; x<mipWidth ; ++x)
{
uint8_t* out = &output[(mip_line_base+x)*chanCount];
for (c=0 ; c<chanCount ; ++c)
{
out[c] = 0;
out[c] += input[(in1_line_base+(x*2))*chanCount+c] / 4;
out[c] += input[(in1_line_base+(x*2+1))*chanCount+c] / 4;
out[c] += input[(in2_line_base+(x*2))*chanCount+c] / 4;
out[c] += input[(in2_line_base+(x*2+1))*chanCount+c] / 4;
}
}
}
}
static size_t ComputeMippedTexelCount(unsigned inWidth, unsigned inHeight)
{
size_t ret = 0;
while (inWidth > 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<numText ; ++i)
if (!strcmp(textStruct[i].key, "urde_nomip"))
mipmap = false;
/* Compute mipmap levels */
size_t numMips = 0;
if (mipmap && CountBits(width) == 1 && CountBits(height) == 1)
{
size_t index = std::min(width, height);
while (index >>= 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<uint8_t[]> rowBuf(new uint8_t[rowSize]);
/* Final mipmapped buf (RGBA components) */
std::unique_ptr<uint8_t[]> 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<height ; ++r)
{
png_read_row(pngRead, rowBuf.get(), nullptr);
switch (colorType)
{
case PNG_COLOR_TYPE_GRAY:
for (int i=0 ; i<width ; ++i)
{
size_t outbase = (r*width+i)*4;
bufOut[outbase] = rowBuf[i];
bufOut[outbase+1] = rowBuf[i];
bufOut[outbase+2] = rowBuf[i];
bufOut[outbase+3] = rowBuf[i];
}
break;
case PNG_COLOR_TYPE_GRAY_ALPHA:
for (int i=0 ; i<width ; ++i)
{
size_t inbase = i*2;
size_t outbase = (r*width+i)*4;
bufOut[outbase] = rowBuf[inbase];
bufOut[outbase+1] = rowBuf[inbase];
bufOut[outbase+2] = rowBuf[inbase];
bufOut[outbase+3] = rowBuf[inbase+1];
}
break;
case PNG_COLOR_TYPE_RGB:
for (int i=0 ; i<width ; ++i)
{
size_t inbase = i*3;
size_t outbase = (r*width+i)*4;
bufOut[outbase] = rowBuf[inbase];
bufOut[outbase+1] = rowBuf[inbase+1];
bufOut[outbase+2] = rowBuf[inbase+2];
bufOut[outbase+3] = 0xff;
}
break;
case PNG_COLOR_TYPE_RGB_ALPHA:
for (int i=0 ; i<width ; ++i)
{
size_t inbase = i*4;
size_t outbase = (r*width+i)*4;
bufOut[outbase] = rowBuf[inbase];
bufOut[outbase+1] = rowBuf[inbase+1];
bufOut[outbase+2] = rowBuf[inbase+2];
bufOut[outbase+3] = rowBuf[inbase+3];
}
break;
default: break;
}
}
png_destroy_read_struct(&pngRead, &info, nullptr);
fclose(inf);
/* Perform box-filter mipmap */
if (numMips > 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<numMips ; ++i)
{
BoxFilter(filterIn, 4, filterWidth, filterHeight, filterOut);
filterIn += filterWidth * filterHeight * 4;
filterWidth /= 2;
filterHeight /= 2;
filterOut += filterWidth * filterHeight * 4;
}
}
/* Do write out */
athena::io::FileWriter outf(outPath.getAbsolutePath(), true, false);
if (outf.hasError())
{
Log.report(logvisor::Error,
_S("Unable to open '%s' for writing"),
outPath.getAbsolutePath().c_str());
return false;
}
outf.writeInt32Big(16);
outf.writeInt16Big(width);
outf.writeInt16Big(height);
outf.writeInt32Big(numMips);
outf.writeUBytes(bufOut.get(), bufLen);
return true;
}
}

View File

@ -11,6 +11,7 @@ struct TXTR
{
static bool Extract(PAKEntryReadStream& rs, const hecl::ProjectPath& outPath);
static bool Cook(const hecl::ProjectPath& inPath, const hecl::ProjectPath& outPath);
static bool CookPC(const hecl::ProjectPath& inPath, const hecl::ProjectPath& outPath);
};
}

View File

@ -82,11 +82,7 @@ struct SpecMP1 : 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;
@ -327,6 +323,14 @@ hecl::Database::DataSpecEntry SpecEntMP1 =
-> 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;}
};
}

View File

@ -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;}
};
}

View File

@ -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;}
};
}

View File

@ -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)

View File

@ -6,22 +6,9 @@ namespace urde
{
static logvisor::Module Log("URDE::ProjectManager");
void ProjectManager::IndexMP1Resources()
{
const std::vector<hecl::Database::Project::ProjectDataSpec>& 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);
{

View File

@ -3,7 +3,7 @@
#include <hecl/Database.hpp>
#include <athena/DNAYaml.hpp>
#include "ProjectResourceFactory.hpp"
#include "ProjectResourceFactoryMP1.hpp"
#include "Runtime/CSimplePool.hpp"
namespace urde
@ -18,11 +18,9 @@ class ProjectManager
ViewManager& m_vm;
std::unique_ptr<hecl::Database::Project> 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();}

View File

@ -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<std::string, std::unique_ptr<athena::io::YAMLNode>>& 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<urde::IObj> 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<urde::IObj> 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;
}
}

View File

@ -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<urde::SObjectTag, hecl::ProjectPath> m_tagToPath;
std::unordered_map<std::string, urde::SObjectTag> m_catalogNameToTag;
std::unordered_map<std::string, hecl::ProjectPath> 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<IDType>::NamedResource& res) -> bool
{ return res.name == nameView.str(); });
if (it == catalog.namedResources.end())
continue;
const typename DataSpec::NamedResourceCatalog<IDType>::NamedResource& nr = *it;
pshag::SObjectTag objTag = GetTag<IDType>(nr);
m_catalogNameToTag[nr.name.c_str()] = objTag;
m_tagToPath[objTag] = HECL::ProjectPath(path, ent.m_name);
#endif
}
}
}
#if 0
template <class IDType>
pshag::SObjectTag GetTag(const DataSpec::NamedResourceCatalog<DataSpec::UniqueID32>::NamedResource &nr,
typename std::enable_if<std::is_same<IDType, DataSpec::UniqueID32>::value>::type* = 0)
{ return { nr.type, nr.uid.toUint32() }; }
template <class IDType>
pshag::SObjectTag GetTag(const typename DataSpec::NamedResourceCatalog<IDType>::NamedResource& nr,
typename std::enable_if<std::is_same<IDType, DataSpec::UniqueID64>::value>::type* = 0)
{ return { nr.type, nr.uid.toUint64() }; }
#endif
public:
ProjectResourceFactory();
void BuildObjectMap(const hecl::Database::Project::ProjectDataSpec& spec);
std::unique_ptr<urde::IObj> 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

View File

@ -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<hecl::BlenderConnection>& 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<std::mutex> 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<std::mutex> 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<urde::IObj> ProjectResourceFactoryBase::Build(const urde::SObjectTag& tag,
const urde::CVParamTransfer& paramXfer)
{
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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::milliseconds>(
std::chrono::steady_clock::now() - start).count() > 8)
break;
/* Ensure requested resource is in the index */
std::unique_lock<std::mutex> 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);
}
}
}

View File

@ -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 <thread>
#include <mutex>
namespace urde
{
class ProjectResourceFactoryBase : public urde::IFactory
{
protected:
std::unordered_map<urde::SObjectTag, hecl::ProjectPath> m_tagToPath;
std::unordered_map<std::string, urde::SObjectTag> 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<hecl::BlenderConnection> m_backgroundBlender;
std::thread m_backgroundIndexTh;
std::mutex m_backgroundIndexMutex;
bool m_backgroundRunning = false;
std::unordered_map<SObjectTag, CResFactory::SLoadingData> 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<urde::IObj> 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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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<SObjectTag, SLoadingData> m_loadList;
@ -31,8 +35,16 @@ public:
std::unique_ptr<IObj> 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<std::pair<std::string, SObjectTag>> GetResourceIdToNameList() const
{

View File

@ -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;

2
hecl

@ -1 +1 @@
Subproject commit beab85bd01599155d655872349d7a93b4e6b7373
Subproject commit 2fe1611e63d2993a1892ce00c1a108226aaecb52