diff --git a/DataSpec/DNACommon/TXTR.cpp b/DataSpec/DNACommon/TXTR.cpp index dce834ebf..3fce7ce95 100644 --- a/DataSpec/DNACommon/TXTR.cpp +++ b/DataSpec/DNACommon/TXTR.cpp @@ -42,7 +42,7 @@ static inline uint8_t Lookup4BPP(const uint8_t* texels, int width, int x, int y) int ry = y % 8; int bidx = by * bwidth + bx; const uint8_t* btexels = &texels[32*bidx]; - return btexels[ry*4+rx/2] >> ((rx%2)?0:4) & 0xf; + return btexels[ry*4+rx/2] >> ((rx&1)?0:4) & 0xf; } static inline uint8_t Lookup8BPP(const uint8_t* texels, int width, int x, int y) diff --git a/Editor/CMakeLists.txt b/Editor/CMakeLists.txt index fd096d94c..c1fd8a10e 100644 --- a/Editor/CMakeLists.txt +++ b/Editor/CMakeLists.txt @@ -43,6 +43,7 @@ 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 ViewManager.hpp ViewManager.cpp Resource.hpp Resource.cpp Camera.hpp Camera.cpp) diff --git a/Editor/ProjectManager.cpp b/Editor/ProjectManager.cpp index bbf33342e..a3844ef8a 100644 --- a/Editor/ProjectManager.cpp +++ b/Editor/ProjectManager.cpp @@ -6,9 +6,22 @@ namespace URDE { static LogVisor::LogModule 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_vm(vm), m_objStore(m_factory) { if (!m_registeredSpecs) { @@ -96,6 +109,8 @@ bool ProjectManager::openProject(const HECL::SystemString& path) #endif + IndexMP1Resources(); + m_vm.BuildTestPART(m_objStore); m_vm.m_mainWindow->setTitle(m_proj->getProjectRootPath().getLastComponent()); m_vm.DismissSplash(); m_vm.FadeInEditors(); diff --git a/Editor/ProjectManager.hpp b/Editor/ProjectManager.hpp index b7ed49826..d9b2031af 100644 --- a/Editor/ProjectManager.hpp +++ b/Editor/ProjectManager.hpp @@ -3,6 +3,8 @@ #include #include +#include "ProjectResourceFactory.hpp" +#include "Runtime/CSimplePool.hpp" namespace URDE { @@ -21,6 +23,11 @@ class ProjectManager ViewManager& m_vm; std::unique_ptr m_proj; static bool m_registeredSpecs; + ProjectResourceFactory m_factory; + pshag::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 new file mode 100644 index 000000000..df6b5ddf1 --- /dev/null +++ b/Editor/ProjectResourceFactory.cpp @@ -0,0 +1,87 @@ +#include "ProjectResourceFactory.hpp" +#include "Runtime/IOStreams.hpp" + +#include "Runtime/Particle/CParticleDataFactory.hpp" +#include "Runtime/CTexture.hpp" + +namespace URDE +{ + +ProjectResourceFactory::ProjectResourceFactory() +{ + m_factoryMgr.AddFactory(HECL::FOURCC('TXTR'), pshag::FTextureFactory); + m_factoryMgr.AddFactory(HECL::FOURCC('PART'), pshag::FParticleFactory); +} + +void ProjectResourceFactory::RecursiveAddDirObjects(const HECL::ProjectPath& path) +{ + HECL::DirectoryEnumerator de = path.enumerateDir(); + for (const HECL::DirectoryEnumerator::Entry& ent : de) + { + if (ent.m_isDir) + RecursiveAddDirObjects(HECL::ProjectPath(path, ent.m_name)); + if (ent.m_name.size() == 13 && ent.m_name[4] == _S('_')) + { + HECL::SystemUTF8View entu8(ent.m_name); + u32 id = strtoul(entu8.c_str() + 5, nullptr, 16); + if (id) + { + pshag::SObjectTag objTag = {HECL::FourCC(entu8.c_str()), id}; + if (m_resPaths.find(objTag) == m_resPaths.end()) + m_resPaths[objTag] = HECL::ProjectPath(path, ent.m_name); + } + } + } +} + +void ProjectResourceFactory::BuildObjectMap(const HECL::Database::Project::ProjectDataSpec& spec) +{ + m_resPaths.clear(); + RecursiveAddDirObjects(spec.cookedPath); +} + +std::unique_ptr ProjectResourceFactory::Build(const pshag::SObjectTag& tag, + const pshag::CVParamTransfer& paramXfer) +{ + auto search = m_resPaths.find(tag); + if (search == m_resPaths.end()) + return {}; + + 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 pshag::SObjectTag& tag, + const pshag::CVParamTransfer& paramXfer, + pshag::IObj** objOut) +{ + std::unique_ptr obj = Build(tag, paramXfer); + *objOut = obj.release(); +} + +void ProjectResourceFactory::CancelBuild(const pshag::SObjectTag&) +{ +} + +bool ProjectResourceFactory::CanBuild(const pshag::SObjectTag& tag) +{ + auto search = m_resPaths.find(tag); + if (search == m_resPaths.end()) + return false; + + Athena::io::FileReader fr(search->second.getAbsolutePath(), 32 * 1024, false); + if (fr.hasError()) + return false; + + return true; +} + +const pshag::SObjectTag* ProjectResourceFactory::GetResourceIdByName(const char*) const +{ + return nullptr; +} + +} diff --git a/Editor/ProjectResourceFactory.hpp b/Editor/ProjectResourceFactory.hpp new file mode 100644 index 000000000..595815003 --- /dev/null +++ b/Editor/ProjectResourceFactory.hpp @@ -0,0 +1,28 @@ +#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 pshag::IFactory +{ + std::unordered_map m_resPaths; + pshag::CFactoryMgr m_factoryMgr; + void RecursiveAddDirObjects(const HECL::ProjectPath& path); +public: + ProjectResourceFactory(); + void BuildObjectMap(const HECL::Database::Project::ProjectDataSpec& spec); + + std::unique_ptr Build(const pshag::SObjectTag&, const pshag::CVParamTransfer&); + void BuildAsync(const pshag::SObjectTag&, const pshag::CVParamTransfer&, pshag::IObj**); + void CancelBuild(const pshag::SObjectTag&); + bool CanBuild(const pshag::SObjectTag&); + const pshag::SObjectTag* GetResourceIdByName(const char*) const; +}; + +} + +#endif // URDE_PROJECT_RESOURCE_FACTORY_HPP diff --git a/Editor/ViewManager.cpp b/Editor/ViewManager.cpp index b5cc274da..7814208c6 100644 --- a/Editor/ViewManager.cpp +++ b/Editor/ViewManager.cpp @@ -76,6 +76,16 @@ void ViewManager::DismissSplash() m_splash->close(); } +void ViewManager::BuildTestPART(pshag::IObjectStore& objStore) +{ + m_partGenDesc = objStore.GetObj({HECL::FOURCC('PART'), 0x0B4D0FBF}); + m_partGen.reset(new pshag::CElementGen(m_partGenDesc, + pshag::CElementGen::EModelOrientationType::Normal, + pshag::CElementGen::EOptionalSystemFlags::None)); + m_particleView.reset(new ParticleView(*this, m_viewResources, *m_rootView)); + m_rootView->accessContentViews().push_back(m_particleView.get()); +} + ViewManager::ViewManager(HECL::Runtime::FileStoreManager& fileMgr, HECL::CVarManager& cvarMgr) : m_fileStoreManager(fileMgr), m_cvarManager(cvarMgr), m_projManager(*this), m_fontCache(fileMgr), m_translator(URDE::SystemLocaleOrEnglish()), @@ -168,6 +178,9 @@ void ViewManager::init(boo::IApplication* app) root->updateSize(); m_mainWindow->setWaitCursor(false); + + pshag::CGraphics::InitializeBoo(gf, m_mainWindow->getCommandQueue()); + pshag::CElementGen::Initialize(); } bool ViewManager::proc() diff --git a/Editor/ViewManager.hpp b/Editor/ViewManager.hpp index bfc89ece2..28bdbcbf6 100644 --- a/Editor/ViewManager.hpp +++ b/Editor/ViewManager.hpp @@ -5,6 +5,8 @@ #include "ProjectManager.hpp" #include "Space.hpp" +#include "Runtime/Particle/CElementGen.hpp" + namespace URDE { class SplashScreen; @@ -31,6 +33,31 @@ class ViewManager : public Specter::IViewManager std::unique_ptr m_rootSpace; Specter::View* m_rootSpaceView = nullptr; + class ParticleView : public Specter::View + { + ViewManager& m_vm; + public: + ParticleView(ViewManager& vm, Specter::ViewResources& res, Specter::View& parent) + : View(res, parent), m_vm(vm) {} + void draw(boo::IGraphicsCommandQueue* gfxQ) + { + if (m_vm.m_partGen) + { + m_vm.m_partGen->Update(1.0 / 60.0); + pshag::CGraphics::SetModelMatrix(Zeus::CTransform::Identity()); + pshag::CGraphics::SetViewPointMatrix(Zeus::CTransform::Identity() + Zeus::CVector3f(0.f, -10.f, 0.f)); + boo::SWindowRect windowRect = m_vm.m_mainWindow->getWindowFrame(); + float aspect = windowRect.size[0] / float(windowRect.size[1]); + pshag::CGraphics::SetPerspective(55.0, aspect, 0.001f, 1000.f); + gfxQ->clearTarget(false, true); + m_vm.m_partGen->Render(); + } + } + }; + std::unique_ptr m_particleView; + pshag::TLockedToken m_partGenDesc; + std::unique_ptr m_partGen; + HECL::SystemString m_recentProjectsPath; std::vector m_recentProjects; HECL::SystemString m_recentFilesPath; @@ -53,6 +80,8 @@ class ViewManager : public Specter::IViewManager unsigned m_editorFrames = 120; void FadeInEditors() {m_editorFrames = 0;} + void BuildTestPART(pshag::IObjectStore& objStore); + Space* m_deferSplit = nullptr; Specter::SplitView::Axis m_deferSplitAxis; int m_deferSplitThisSlot; diff --git a/Editor/icons/icons.cpp b/Editor/icons/icons.cpp index d452cc26b..3f93a1dcb 100644 --- a/Editor/icons/icons.cpp +++ b/Editor/icons/icons.cpp @@ -29,7 +29,7 @@ boo::GraphicsDataToken InitializeIcons(Specter::ViewResources& viewRes) Log.report(LogVisor::FatalError, "unable to decompress icons"); g_IconAtlas.initializeAtlas(viewRes.m_factory->newStaticTexture(width, height, mips, boo::TextureFormat::RGBA8, - std::move(texels), destSz)); + texels.get(), destSz)); return viewRes.m_factory->commit(); } diff --git a/Editor/main.cpp b/Editor/main.cpp index e6753b8a1..779e28b23 100644 --- a/Editor/main.cpp +++ b/Editor/main.cpp @@ -24,7 +24,6 @@ struct Application : boo::IApplicationCallback int appMain(boo::IApplication* app) { - pshag::CElementGen::Initialize(); m_viewManager.init(app); while (m_running) { diff --git a/Runtime/CFactoryMgr.cpp b/Runtime/CFactoryMgr.cpp index e69de29bb..2e98adfdf 100644 --- a/Runtime/CFactoryMgr.cpp +++ b/Runtime/CFactoryMgr.cpp @@ -0,0 +1,36 @@ +#include "CFactoryMgr.hpp" +#include "IObj.hpp" + +namespace pshag +{ + +CFactoryFnReturn CFactoryMgr::MakeObject(const SObjectTag& tag, pshag::CInputStream& in, + const CVParamTransfer& paramXfer) +{ + auto search = m_factories.find(tag.type); + if (search == m_factories.end()) + return {}; + + return search->second(tag, in, paramXfer); +} + +CFactoryFnReturn CFactoryMgr::MakeObjectFromMemory(const SObjectTag& tag, void* buf, int size, + bool compressed, const CVParamTransfer& paramXfer) +{ + auto search = m_factories.find(tag.type); + if (search == m_factories.end()) + return {}; + + if (compressed) + { + CZipInputStream r(std::make_unique(buf, size)); + return search->second(tag, r, paramXfer); + } + else + { + Athena::io::MemoryReader r(buf, size); + return search->second(tag, r, paramXfer); + } +} + +} diff --git a/Runtime/CFactoryMgr.hpp b/Runtime/CFactoryMgr.hpp index 329ef6514..458d9692e 100644 --- a/Runtime/CFactoryMgr.hpp +++ b/Runtime/CFactoryMgr.hpp @@ -3,21 +3,27 @@ #include #include "RetroTypes.hpp" +#include "IOStreams.hpp" namespace pshag { class SObjectTag; class CVParamTransfer; -class CInputStream; - -typedef void(*CFactoryFnReturn)(const SObjectTag&, CInputStream&, const CVParamTransfer&); +class IObj; +using CFactoryFnReturn = std::unique_ptr; +using FFactoryFunc = std::function; class CFactoryMgr { - std::unordered_map m_factories; + std::unordered_map m_factories; public: - MakeObjectFromMemory(const SObjectTag&, void*, int, bool, const CVParamTransfer&); - void AddFactory(FourCC key, CFactoryFnReturn func) + CFactoryFnReturn MakeObject(const SObjectTag& tag, pshag::CInputStream& in, + const CVParamTransfer& paramXfer); + CFactoryFnReturn MakeObjectFromMemory(const SObjectTag& tag, void* buf, int size, bool compressed, + const CVParamTransfer& paramXfer); + void AddFactory(FourCC key, FFactoryFunc func) { m_factories[key] = func; } diff --git a/Runtime/CGraphics.hpp b/Runtime/CGraphics.hpp index 37bceb546..1ec2be61c 100644 --- a/Runtime/CGraphics.hpp +++ b/Runtime/CGraphics.hpp @@ -121,6 +121,17 @@ struct SClipScreenRect enum class ETexelFormat { + I4 = 0, + I8 = 1, + IA4 = 2, + IA8 = 3, + C4 = 4, + C8 = 5, + C14X2 = 6, + RGB565 = 7, + RGB5A3 = 8, + RGBA8 = 9, + CMPR = 10 }; class CGraphics @@ -163,7 +174,7 @@ public: static Zeus::CMatrix4f GetPerspectiveProjectionMatrix(); static const CProjectionState& GetProjectionState(); static void SetProjectionState(const CProjectionState&); - static void SetPerspective(float, float, float, float); + static void SetPerspective(float fovy, float aspect, float near, float far); static void FlushProjection(); static Zeus::CVector2i ProjectPoint(const Zeus::CVector3f& point); static SClipScreenRect ClipScreenRectFromMS(const Zeus::CVector3f& p1, const Zeus::CVector3f& p2); @@ -173,6 +184,12 @@ public: static boo::IGraphicsCommandQueue* g_BooMainCommandQueue; static boo::ITextureR* g_SpareTexture; + static void InitializeBoo(boo::IGraphicsDataFactory* factory, boo::IGraphicsCommandQueue* cc) + { + g_BooFactory = factory; + g_BooMainCommandQueue = cc; + } + static boo::IGraphicsBufferD* NewDynamicGPUBuffer(boo::BufferUse use, size_t stride, size_t count) { return g_BooFactory->newDynamicBuffer(use, stride, count); @@ -185,8 +202,9 @@ public: { g_BooMainCommandQueue->setShaderDataBinding(binding); } - static void DrawInstances(size_t start, size_t count, size_t instCount) + static void DrawInstances(boo::Primitive prim, size_t start, size_t count, size_t instCount) { + g_BooMainCommandQueue->setDrawPrimitive(prim); g_BooMainCommandQueue->drawInstances(start, count, instCount); } }; diff --git a/Runtime/CMakeLists.txt b/Runtime/CMakeLists.txt index 3b7089975..703e5f4f8 100644 --- a/Runtime/CMakeLists.txt +++ b/Runtime/CMakeLists.txt @@ -58,7 +58,7 @@ add_library(RuntimeCommon CPakFile.hpp CPakFile.cpp CStringExtras.hpp CCallStack.hpp - CTexture.hpp CTexture.cpp + CTexture.hpp CTextureBoo.cpp CLight.hpp CLight.cpp IOStreams.hpp IOStreams.cpp CMainFlowBase.hpp CMainFlowBase.cpp diff --git a/Runtime/CSimplePool.cpp b/Runtime/CSimplePool.cpp index 9409a9dba..5752adbe7 100644 --- a/Runtime/CSimplePool.cpp +++ b/Runtime/CSimplePool.cpp @@ -11,7 +11,9 @@ CSimplePool::CSimplePool(IFactory& factory) CToken CSimplePool::GetObj(const SObjectTag& tag, const CVParamTransfer& paramXfer) { - auto iter = std::find_if(x4_resources.begin(), x4_resources.end(), [&tag](std::pair pair)->bool{ + auto iter = std::find_if(x4_resources.begin(), x4_resources.end(), + [&tag](std::pair pair) -> bool + { return pair.first == tag; }); diff --git a/Runtime/CTexture.cpp b/Runtime/CTexture.cpp deleted file mode 100644 index fca85c639..000000000 --- a/Runtime/CTexture.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "CTexture.hpp" - -namespace pshag -{ - -void CTexture::Load(int slot, EClampMode clamp) -{ - -} - -} diff --git a/Runtime/CTexture.hpp b/Runtime/CTexture.hpp index 00310acac..d6ce03a9b 100644 --- a/Runtime/CTexture.hpp +++ b/Runtime/CTexture.hpp @@ -2,28 +2,54 @@ #define __PSHAG_CTEXTURE_HPP__ #include "GCNTypes.hpp" +#include "IObj.hpp" +#include "IOStreams.hpp" +#include "CGraphics.hpp" #include "boo/graphicsdev/IGraphicsDataFactory.hpp" namespace pshag { +class CVParamTransfer; class CTexture { + ETexelFormat x0_fmt; u16 x4_w; u16 x6_h; + u32 x8_mips; + boo::GraphicsDataToken m_booToken; boo::ITexture* m_booTex; + + size_t ComputeMippedTexelCount(); + size_t ComputeMippedBlockCountDXT1(); + void BuildI4FromGCN(CInputStream& in); + void BuildI8FromGCN(CInputStream& in); + void BuildIA4FromGCN(CInputStream& in); + void BuildIA8FromGCN(CInputStream& in); + void BuildC4FromGCN(CInputStream& in); + void BuildC8FromGCN(CInputStream& in); + void BuildC14X2FromGCN(CInputStream& in); + void BuildRGB565FromGCN(CInputStream& in); + void BuildRGB5A3FromGCN(CInputStream& in); + void BuildRGBA8FromGCN(CInputStream& in); + void BuildDXT1FromGCN(CInputStream& in); + public: + CTexture(CInputStream& in); enum class EClampMode { None, One }; + ETexelFormat GetTexelFormat() const {return x0_fmt;} u16 GetWidth() const {return x4_w;} u16 GetHeight() const {return x6_h;} void Load(int slot, EClampMode clamp); boo::ITexture* GetBooTexture() {return m_booTex;} }; +std::unique_ptr FTextureFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms); + } #endif // __PSHAG_CTEXTURE_HPP__ diff --git a/Runtime/CTextureBoo.cpp b/Runtime/CTextureBoo.cpp new file mode 100644 index 000000000..986b1bedb --- /dev/null +++ b/Runtime/CTextureBoo.cpp @@ -0,0 +1,679 @@ +#include "CTexture.hpp" +#include "CSimplePool.hpp" +#include "CToken.hpp" +#include "CGraphics.hpp" + +namespace pshag +{ + +/* GX uses this upsampling technique to prevent banding on downsampled texture formats */ +static inline uint8_t Convert3To8(uint8_t v) +{ + /* Swizzle bits: 00000123 -> 12312312 */ + return (v << 5) | (v << 2) | (v >> 1); +} + +static inline uint8_t Convert4To8(uint8_t v) +{ + /* Swizzle bits: 00001234 -> 12341234 */ + return (v << 4) | v; +} + +static inline uint8_t Convert5To8(uint8_t v) +{ + /* Swizzle bits: 00012345 -> 12345123 */ + return (v << 3) | (v >> 2); +} + +static inline uint8_t Convert6To8(uint8_t v) +{ + /* Swizzle bits: 00123456 -> 12345612 */ + return (v << 2) | (v >> 4); +} + +size_t CTexture::ComputeMippedTexelCount() +{ + size_t w = x4_w; + size_t h = x6_h; + size_t ret = w * h; + for (int i=x8_mips ; i>1 ; --i) + { + if (w > 1) + w /= 2; + if (h > 1) + h /= 2; + ret += w * h; + } + return ret; +} + +size_t CTexture::ComputeMippedBlockCountDXT1() +{ + size_t w = x4_w / 4; + size_t h = x6_h / 4; + size_t ret = w * h; + for (int i=x8_mips ; i>1 ; --i) + { + if (w > 1) + w /= 2; + if (h > 1) + h /= 2; + ret += w * h; + } + return ret; +} + +struct RGBA8 +{ + u8 r; + u8 g; + u8 b; + u8 a; +}; + +void CTexture::BuildI4FromGCN(CInputStream& in) +{ + size_t texelCount = ComputeMippedTexelCount(); + std::unique_ptr buf(new RGBA8[texelCount]); + + int w = x4_w; + int h = x6_h; + RGBA8* targetMip = buf.get(); + for (int mip=0 ; mip> ((x&1)?0:4) & 0xf); + target[x].g = target[x].r; + target[x].b = target[x].r; + target[x].a = target[x].r; + } + } + } + } + targetMip += w * h; + if (w > 1) + w /= 2; + if (h > 1) + h /= 2; + } + + m_booToken = CGraphics::g_BooFactory->newStaticTextureNoContext(x4_w, x6_h, x8_mips, + boo::TextureFormat::RGBA8, + buf.get(), texelCount * 4, + reinterpret_cast(&m_booTex)); +} + +void CTexture::BuildI8FromGCN(CInputStream& in) +{ + size_t texelCount = ComputeMippedTexelCount(); + std::unique_ptr buf(new RGBA8[texelCount]); + + int w = x4_w; + int h = x6_h; + RGBA8* targetMip = buf.get(); + for (int mip=0 ; mip 1) + w /= 2; + if (h > 1) + h /= 2; + } + + m_booToken = CGraphics::g_BooFactory->newStaticTextureNoContext(x4_w, x6_h, x8_mips, + boo::TextureFormat::RGBA8, + buf.get(), texelCount * 4, + reinterpret_cast(&m_booTex)); +} + +void CTexture::BuildIA4FromGCN(CInputStream& in) +{ + size_t texelCount = ComputeMippedTexelCount(); + std::unique_ptr buf(new RGBA8[texelCount]); + + int w = x4_w; + int h = x6_h; + RGBA8* targetMip = buf.get(); + for (int mip=0 ; mip> 4 & 0xf); + target[x].r = intensity; + target[x].g = intensity; + target[x].b = intensity; + target[x].a = Convert4To8(source[x] & 0xf); + } + } + } + } + targetMip += w * h; + if (w > 1) + w /= 2; + if (h > 1) + h /= 2; + } + + m_booToken = CGraphics::g_BooFactory->newStaticTextureNoContext(x4_w, x6_h, x8_mips, + boo::TextureFormat::RGBA8, + buf.get(), texelCount * 4, + reinterpret_cast(&m_booTex)); +} + +void CTexture::BuildIA8FromGCN(CInputStream& in) +{ + size_t texelCount = ComputeMippedTexelCount(); + std::unique_ptr buf(new RGBA8[texelCount]); + + int w = x4_w; + int h = x6_h; + RGBA8* targetMip = buf.get(); + for (int mip=0 ; mip> 8; + target[x].r = intensity; + target[x].g = intensity; + target[x].b = intensity; + target[x].a = source[x] & 0xff; + } + } + } + } + targetMip += w * h; + if (w > 1) + w /= 2; + if (h > 1) + h /= 2; + } + + m_booToken = CGraphics::g_BooFactory->newStaticTextureNoContext(x4_w, x6_h, x8_mips, + boo::TextureFormat::RGBA8, + buf.get(), texelCount * 4, + reinterpret_cast(&m_booTex)); +} + +static std::vector DecodePalette(int numEntries, CInputStream& in) +{ + std::vector ret; + ret.reserve(numEntries); + + enum class EPaletteType + { + IA8, + RGB565, + RGB5A3 + }; + + EPaletteType format = EPaletteType(in.readUint32Big()); + in.readUint32Big(); + switch (format) + { + case EPaletteType::IA8: + { + for (int e=0 ; e> 11 & 0x1f), + Convert6To8(texel >> 5 & 0x3f), + Convert5To8(texel & 0x1f), + 0xff}); + } + break; + } + case EPaletteType::RGB5A3: + { + for (int e=0 ; e> 10 & 0x1f), + Convert5To8(texel >> 5 & 0x1f), + Convert5To8(texel & 0x1f), + 0xff}); + } + else + { + ret.push_back({Convert4To8(texel >> 8 & 0xf), + Convert4To8(texel >> 4 & 0xf), + Convert4To8(texel & 0xf), + Convert3To8(texel >> 12 & 0x7)}); + } + } + break; + } + } + return ret; +} + +void CTexture::BuildC4FromGCN(CInputStream& in) +{ + size_t texelCount = ComputeMippedTexelCount(); + std::unique_ptr buf(new RGBA8[texelCount]); + std::vector palette = DecodePalette(16, in); + + int w = x4_w; + int h = x6_h; + RGBA8* targetMip = buf.get(); + for (int mip=0 ; mip> ((x&1)?0:4) & 0xf]; + } + } + } + targetMip += w * h; + if (w > 1) + w /= 2; + if (h > 1) + h /= 2; + } + + m_booToken = CGraphics::g_BooFactory->newStaticTextureNoContext(x4_w, x6_h, x8_mips, + boo::TextureFormat::RGBA8, + buf.get(), texelCount * 4, + reinterpret_cast(&m_booTex)); +} + +void CTexture::BuildC8FromGCN(CInputStream& in) +{ + size_t texelCount = ComputeMippedTexelCount(); + std::unique_ptr buf(new RGBA8[texelCount]); + std::vector palette = DecodePalette(256, in); + + int w = x4_w; + int h = x6_h; + RGBA8* targetMip = buf.get(); + for (int mip=0 ; mip 1) + w /= 2; + if (h > 1) + h /= 2; + } + + m_booToken = CGraphics::g_BooFactory->newStaticTextureNoContext(x4_w, x6_h, x8_mips, + boo::TextureFormat::RGBA8, + buf.get(), texelCount * 4, + reinterpret_cast(&m_booTex)); +} + +void CTexture::BuildC14X2FromGCN(CInputStream& in) +{ + +} + +void CTexture::BuildRGB565FromGCN(CInputStream& in) +{ + size_t texelCount = ComputeMippedTexelCount(); + std::unique_ptr buf(new RGBA8[texelCount]); + + int w = x4_w; + int h = x6_h; + RGBA8* targetMip = buf.get(); + for (int mip=0 ; mip> 11 & 0x1f); + target[x].g = Convert6To8(texel >> 5 & 0x3f); + target[x].b = Convert5To8(texel & 0x1f); + target[x].a = 0xff; + } + } + } + } + targetMip += w * h; + if (w > 1) + w /= 2; + if (h > 1) + h /= 2; + } + + m_booToken = CGraphics::g_BooFactory->newStaticTextureNoContext(x4_w, x6_h, x8_mips, + boo::TextureFormat::RGBA8, + buf.get(), texelCount * 4, + reinterpret_cast(&m_booTex)); +} + +void CTexture::BuildRGB5A3FromGCN(CInputStream& in) +{ + size_t texelCount = ComputeMippedTexelCount(); + std::unique_ptr buf(new RGBA8[texelCount]); + + int w = x4_w; + int h = x6_h; + RGBA8* targetMip = buf.get(); + for (int mip=0 ; mip> 10 & 0x1f); + target[x].g = Convert5To8(texel >> 5 & 0x1f); + target[x].b = Convert5To8(texel & 0x1f); + target[x].a = 0xff; + } + else + { + target[x].r = Convert4To8(texel >> 8 & 0xf); + target[x].g = Convert4To8(texel >> 4 & 0xf); + target[x].b = Convert4To8(texel & 0xf); + target[x].a = Convert3To8(texel >> 12 & 0x7); + } + } + } + } + } + targetMip += w * h; + if (w > 1) + w /= 2; + if (h > 1) + h /= 2; + } + + m_booToken = CGraphics::g_BooFactory->newStaticTextureNoContext(x4_w, x6_h, x8_mips, + boo::TextureFormat::RGBA8, + buf.get(), texelCount * 4, + reinterpret_cast(&m_booTex)); +} + +void CTexture::BuildRGBA8FromGCN(CInputStream& in) +{ + size_t texelCount = ComputeMippedTexelCount(); + std::unique_ptr buf(new RGBA8[texelCount]); + + int w = x4_w; + int h = x6_h; + RGBA8* targetMip = buf.get(); + for (int mip=0 ; mip 1) + w /= 2; + if (h > 1) + h /= 2; + } + + m_booToken = CGraphics::g_BooFactory->newStaticTextureNoContext(x4_w, x6_h, x8_mips, + boo::TextureFormat::RGBA8, + buf.get(), texelCount * 4, + reinterpret_cast(&m_booTex)); +} + +struct DXT1Block +{ + uint16_t color1; + uint16_t color2; + uint8_t lines[4]; +}; + +void CTexture::BuildDXT1FromGCN(CInputStream& in) +{ + size_t blockCount = ComputeMippedBlockCountDXT1(); + std::unique_ptr buf(new DXT1Block[blockCount]); + + int w = x4_w / 4; + int h = x6_h / 4; + DXT1Block* targetMip = buf.get(); + for (int mip=0 ; mip 1) + w /= 2; + if (h > 1) + h /= 2; + } + + m_booToken = CGraphics::g_BooFactory->newStaticTextureNoContext(x4_w, x6_h, x8_mips, + boo::TextureFormat::DXT1, + buf.get(), blockCount * 8, + reinterpret_cast(&m_booTex)); +} + +CTexture::CTexture(CInputStream& in) +{ + x0_fmt = ETexelFormat(in.readUint32Big()); + x4_w = in.readUint16Big(); + x6_h = in.readUint16Big(); + x8_mips = in.readUint32Big(); + + switch (x0_fmt) + { + case ETexelFormat::I4: + BuildI4FromGCN(in); + break; + case ETexelFormat::I8: + BuildI8FromGCN(in); + break; + case ETexelFormat::IA4: + BuildIA4FromGCN(in); + break; + case ETexelFormat::IA8: + BuildIA8FromGCN(in); + break; + case ETexelFormat::C4: + BuildC4FromGCN(in); + break; + case ETexelFormat::C8: + BuildC8FromGCN(in); + break; + case ETexelFormat::C14X2: + BuildC14X2FromGCN(in); + break; + case ETexelFormat::RGB565: + BuildRGB565FromGCN(in); + break; + case ETexelFormat::RGB5A3: + BuildRGB5A3FromGCN(in); + break; + case ETexelFormat::RGBA8: + BuildRGBA8FromGCN(in); + break; + case ETexelFormat::CMPR: + BuildDXT1FromGCN(in); + break; + } +} + +void CTexture::Load(int slot, EClampMode clamp) +{ + +} + +std::unique_ptr FTextureFactory(const SObjectTag& tag, CInputStream& in, const CVParamTransfer& vparms) +{ + return TToken::GetIObjObjectFor(std::make_unique(in)); +} + +} diff --git a/Runtime/IVParamObj.hpp b/Runtime/IVParamObj.hpp index 325dfe7e1..7f2fb0b44 100644 --- a/Runtime/IVParamObj.hpp +++ b/Runtime/IVParamObj.hpp @@ -17,7 +17,7 @@ class CVParamTransfer { std::shared_ptr m_ref; public: - CVParamTransfer(); + CVParamTransfer() = default; CVParamTransfer(IVParamObj* obj) : m_ref(obj) {} CVParamTransfer(const CVParamTransfer& other) : m_ref(other.m_ref) {} IVParamObj* GetObj() const {return m_ref.get();} diff --git a/Runtime/MP1/main.cpp b/Runtime/MP1/main.cpp index a018d3faf..37ecaf0a2 100644 --- a/Runtime/MP1/main.cpp +++ b/Runtime/MP1/main.cpp @@ -54,13 +54,13 @@ class CMain* g_main = nullptr; class CGameGlobalObjects { - CMemoryCardSys m_memoryCardSys; - CResFactory m_resFactory; - CSimplePool m_simplePool; - CCharacterFactoryBuilder m_charFactoryBuilder; - CAiFuncMap m_aiFuncMap; - CGameState m_gameState; - CInGameTweakManager m_tweakManager; + CMemoryCardSys x0_memoryCardSys; + CResFactory x20_resFactory; + CSimplePool x114_simplePool; + CCharacterFactoryBuilder x14c_charFactoryBuilder; + CAiFuncMap x188_aiFuncMap; + CGameState x1a8_gameState; + CInGameTweakManager x1c0_tweakManager; std::unique_ptr m_renderer; void AddPaksAndFactories() @@ -79,22 +79,22 @@ class CGameGlobalObjects public: CGameGlobalObjects() - : m_simplePool(m_resFactory) + : x114_simplePool(x20_resFactory) { - g_MemoryCardSys = &m_memoryCardSys; - g_ResFactory = &m_resFactory; - g_SimplePool = &m_simplePool; - g_CharFactoryBuilder = &m_charFactoryBuilder; - g_AiFuncMap = &m_aiFuncMap; - g_GameState = &m_gameState; - g_TweakManager = &m_tweakManager; + g_MemoryCardSys = &x0_memoryCardSys; + g_ResFactory = &x20_resFactory; + g_SimplePool = &x114_simplePool; + g_CharFactoryBuilder = &x14c_charFactoryBuilder; + g_AiFuncMap = &x188_aiFuncMap; + g_GameState = &x1a8_gameState; + g_TweakManager = &x1c0_tweakManager; } void PostInitialize(CMemorySys& memSys) { AddPaksAndFactories(); LoadStringTable(); - m_renderer.reset(AllocateRenderer(m_simplePool, memSys, m_resFactory)); + m_renderer.reset(AllocateRenderer(x114_simplePool, memSys, x20_resFactory)); } }; diff --git a/Runtime/Particle/CElementGen.cpp b/Runtime/Particle/CElementGen.cpp index d547bcd22..e84402736 100644 --- a/Runtime/Particle/CElementGen.cpp +++ b/Runtime/Particle/CElementGen.cpp @@ -183,7 +183,7 @@ void CElementGenShaders::Initialize() case boo::IGraphicsDataFactory::Platform::D3D12: m_bindFactory.reset(Initialize(*static_cast(CGraphics::g_BooFactory))); break; -#elif __APPLE__ +#elif BOO_HAS_METAL case boo::IGraphicsDataFactory::Platform::Metal: m_bindFactory.reset(Initialize(*static_cast(CGraphics::g_BooFactory))); break; @@ -1175,6 +1175,7 @@ void CElementGen::Render() void CElementGen::RenderModels() { + return; CGenDescription* desc = x1c_genDesc.GetObj(); if (x225_29_modelsUseLights) @@ -1365,6 +1366,7 @@ void CElementGen::RenderModels() void CElementGen::RenderLines() { + return; CGenDescription* desc = x1c_genDesc.GetObj(); CGlobalRandom gr(x230_randState); @@ -1498,7 +1500,7 @@ void CElementGen::RenderParticles() CUVElement* tind = desc->x58_TIND.get(); if (texr && tind) { - RenderParticlesIndirectTexture(); + //RenderParticlesIndirectTexture(); return; } @@ -1623,6 +1625,7 @@ void CElementGen::RenderParticles() } float size = 0.5f * particle.x2c_lineLengthOrSize; + fprintf(stderr, "%p (%f %f %f) %f\n", this, viewPoint.x, viewPoint.y, viewPoint.z, size); if (0.f == particle.x30_lineWidthOrRota) { switch (m_shaderClass) @@ -1698,11 +1701,11 @@ void CElementGen::RenderParticles() { case CElementGenShaders::EShaderClass::Tex: m_instBuf->load(g_instTexData.data(), g_instTexData.size() * sizeof(SParticleInstanceTex)); - CGraphics::DrawInstances(0, 4, g_instTexData.size()); + CGraphics::DrawInstances(boo::Primitive::TriStrips, 0, 4, g_instTexData.size()); break; case CElementGenShaders::EShaderClass::NoTex: m_instBuf->load(g_instNoTexData.data(), g_instNoTexData.size() * sizeof(SParticleInstanceNoTex)); - CGraphics::DrawInstances(0, 4, g_instNoTexData.size()); + CGraphics::DrawInstances(boo::Primitive::TriStrips, 0, 4, g_instNoTexData.size()); break; default: break; } @@ -1837,11 +1840,11 @@ void CElementGen::RenderParticles() { case CElementGenShaders::EShaderClass::Tex: m_instBuf->load(g_instTexData.data(), g_instTexData.size() * sizeof(SParticleInstanceTex)); - CGraphics::DrawInstances(0, 4, g_instTexData.size()); + CGraphics::DrawInstances(boo::Primitive::TriStrips, 0, 4, g_instTexData.size()); break; case CElementGenShaders::EShaderClass::NoTex: m_instBuf->load(g_instNoTexData.data(), g_instNoTexData.size() * sizeof(SParticleInstanceNoTex)); - CGraphics::DrawInstances(0, 4, g_instNoTexData.size()); + CGraphics::DrawInstances(boo::Primitive::TriStrips, 0, 4, g_instNoTexData.size()); break; default: break; } diff --git a/Runtime/Particle/CElementGen.hpp b/Runtime/Particle/CElementGen.hpp index ebb2ef554..f0e14353c 100644 --- a/Runtime/Particle/CElementGen.hpp +++ b/Runtime/Particle/CElementGen.hpp @@ -108,7 +108,7 @@ private: bool x224_25_LIT_; bool x224_26_AAPH; bool x224_27_ZBUF; - bool x224_28_zTest = true; + bool x224_28_zTest = false; bool x224_29_MBLR; bool x224_30_VMD1; bool x224_31_VMD2; diff --git a/Runtime/Particle/CElementGenShaders.hpp b/Runtime/Particle/CElementGenShaders.hpp index a80736ca3..9d8778db6 100644 --- a/Runtime/Particle/CElementGenShaders.hpp +++ b/Runtime/Particle/CElementGenShaders.hpp @@ -63,7 +63,7 @@ public: static IDataBindingFactory* Initialize(boo::GLDataFactory& factory); #if _WIN32 static IDataBindingFactory* Initialize(boo::ID3DDataFactory& factory); -#elif __APPLE__ +#elif BOO_HAS_METAL static IDataBindingFactory* Initialize(boo::MetalDataFactory& factory); #endif diff --git a/Runtime/Particle/CElementGenShadersGLSL.cpp b/Runtime/Particle/CElementGenShadersGLSL.cpp index d3f8aa0e6..5dcf290f2 100644 --- a/Runtime/Particle/CElementGenShadersGLSL.cpp +++ b/Runtime/Particle/CElementGenShadersGLSL.cpp @@ -7,7 +7,7 @@ namespace pshag static const char* VS_GLSL_TEX = "#version 330\n" -"layout(location=0) in vec3 posIn[4];\n" +"layout(location=0) in vec4 posIn[4];\n" "layout(location=4) in vec4 colorIn;\n" "layout(location=5) in vec2 uvsIn[4];\n" "\n" @@ -29,6 +29,7 @@ static const char* VS_GLSL_TEX = " vtf.color = colorIn * moduColor;\n" " vtf.uv = uvsIn[gl_VertexID];\n" " gl_Position = mvp * posIn[gl_VertexID];\n" +" gl_Position = vec4(posIn[gl_VertexID].x, posIn[gl_VertexID].z, 0.0, 1.0);\n" "}\n"; static const char* FS_GLSL_TEX = @@ -45,6 +46,7 @@ static const char* FS_GLSL_TEX = "void main()\n" "{\n" " colorOut = vtf.color * texture(texs[0], vtf.uv);\n" +" colorOut = vec4(1.0,1.0,1.0,1.0);\n" "}\n"; static const char* FS_GLSL_TEX_REDTOALPHA = @@ -62,11 +64,12 @@ static const char* FS_GLSL_TEX_REDTOALPHA = "{\n" " colorOut = vtf.color * texture(texs[0], vtf.uv);\n" " colorOut.a = colorOut.r;\n" +" colorOut = vec4(1.0,1.0,1.0,1.0);\n" "}\n"; static const char* VS_GLSL_INDTEX = "#version 330\n" -"layout(location=0) in vec3 posIn[4];\n" +"layout(location=0) in vec4 posIn[4];\n" "layout(location=4) in vec4 colorIn;\n" "layout(location=5) in vec4 uvsInTexrTind[4];\n" "layout(location=9) in vec2 uvsInScene[4];\n" @@ -110,11 +113,12 @@ static const char* FS_GLSL_INDTEX = "uniform sampler2D texs[3];\n" "void main()\n" "{\n" -" vec2 tindTexel = texture(texs[2], vtf.uvTind);\n" +" vec2 tindTexel = texture(texs[2], vtf.uvTind).xy;\n" " vec4 sceneTexel = texture(texs[1], vtf.uvScene + tindTexel);\n" " vec4 texrTexel = texture(texs[0], vtf.uvTexr);\n" " colorOut = vtf.color * sceneTexel + texrTexel;\n" " colorOut.a = vtf.color.a * texrTexel.a;" +" colorOut = vec4(1.0,1.0,1.0,1.0);\n" "}\n"; static const char* FS_GLSL_CINDTEX = @@ -132,14 +136,15 @@ static const char* FS_GLSL_CINDTEX = "uniform sampler2D texs[3];\n" "void main()\n" "{\n" -" vec2 tindTexel = texture(texs[2], vtf.uvTind);\n" +" vec2 tindTexel = texture(texs[2], vtf.uvTind).xy;\n" " vec4 sceneTexel = texture(texs[1], vtf.uvScene + tindTexel);\n" " colorOut = vtf.color * sceneTexel * texture(texs[0], vtf.uvTexr);\n" +" colorOut = vec4(1.0,1.0,1.0,1.0);\n" "}\n"; static const char* VS_GLSL_NOTEX = "#version 330\n" -"layout(location=0) in vec3 posIn[4];\n" +"layout(location=0) in vec4 posIn[4];\n" "layout(location=4) in vec4 colorIn;\n" "\n" "uniform ParticleUniform\n" @@ -172,6 +177,7 @@ static const char* FS_GLSL_NOTEX = "void main()\n" "{\n" " colorOut = vtf.color;\n" +" colorOut = vec4(1.0,1.0,1.0,1.0);\n" "}\n"; struct DataBindingFactory : CElementGenShaders::IDataBindingFactory diff --git a/Runtime/Particle/CEmitterElement.cpp b/Runtime/Particle/CEmitterElement.cpp index e69de29bb..74ed0cf0f 100644 --- a/Runtime/Particle/CEmitterElement.cpp +++ b/Runtime/Particle/CEmitterElement.cpp @@ -0,0 +1,18 @@ +#include "CEmitterElement.hpp" + +namespace pshag +{ + +bool CEESimpleEmitter::GetValue(int frame, Zeus::CVector3f& pPos, Zeus::CVector3f& pVel) const +{ +} + +bool CVESphere::GetValue(int frame, Zeus::CVector3f& pPos, Zeus::CVector3f& pVel) const +{ +} + +bool CVEAngleSphere::GetValue(int frame, Zeus::CVector3f& pPos, Zeus::CVector3f& pVel) const +{ +} + +} diff --git a/Runtime/Particle/CRealElement.cpp b/Runtime/Particle/CRealElement.cpp index 171a25772..bb3936798 100644 --- a/Runtime/Particle/CRealElement.cpp +++ b/Runtime/Particle/CRealElement.cpp @@ -105,6 +105,19 @@ bool CREClamp::GetValue(int frame,float& valOut) const return false; } +bool CREInitialRandom::GetValue(int frame, float& valOut) const +{ + if (frame == 0) + { + float a, b; + x4_min->GetValue(frame, a); + x8_max->GetValue(frame, b); + float rand = CRandom16::GetRandomNumber()->Float(); + valOut = b * rand + a * (1.0f - rand); + } + return false; +} + bool CRERandom::GetValue(int frame, float& valOut) const { float a, b; @@ -217,61 +230,61 @@ bool CRECompareEquals::GetValue(int frame, float& valOut) const bool CREParticleAccessParam1::GetValue(int frame, float& valOut) const { - valOut = CParticleGlobals::g_papValues[0]; + //valOut = CParticleGlobals::g_papValues[0]; return false; } bool CREParticleAccessParam2::GetValue(int frame, float& valOut) const { - valOut = CParticleGlobals::g_papValues[1]; + //valOut = CParticleGlobals::g_papValues[1]; return false; } bool CREParticleAccessParam3::GetValue(int frame, float& valOut) const { - valOut = CParticleGlobals::g_papValues[2]; + //valOut = CParticleGlobals::g_papValues[2]; return false; } bool CREParticleAccessParam4::GetValue(int frame, float& valOut) const { - valOut = CParticleGlobals::g_papValues[3]; + //valOut = CParticleGlobals::g_papValues[3]; return false; } bool CREParticleAccessParam5::GetValue(int frame, float& valOut) const { - valOut = CParticleGlobals::g_papValues[4]; + //valOut = CParticleGlobals::g_papValues[4]; return false; } bool CREParticleAccessParam6::GetValue(int frame, float& valOut) const { - valOut = CParticleGlobals::g_papValues[5]; + //valOut = CParticleGlobals::g_papValues[5]; return false; } bool CREParticleAccessParam7::GetValue(int frame, float& valOut) const { - valOut = CParticleGlobals::g_papValues[6]; + //valOut = CParticleGlobals::g_papValues[6]; return false; } bool CREParticleAccessParam8::GetValue(int frame, float& valOut) const { - valOut = CParticleGlobals::g_papValues[7]; + //valOut = CParticleGlobals::g_papValues[7]; return false; } bool CREPSLL::GetValue(int frame, float& valOut) const { - valOut = CParticleGlobals::g_particleMetrics->x2c_psll; + //valOut = CParticleGlobals::g_particleMetrics->x2c_psll; return false; } bool CREPRLW::GetValue(int frame, float& valOut) const { - valOut = CParticleGlobals::g_particleMetrics->x30_prlw; + //valOut = CParticleGlobals::g_particleMetrics->x30_prlw; return false; } diff --git a/Runtime/Particle/CVectorElement.cpp b/Runtime/Particle/CVectorElement.cpp index 2bba9d4ab..96a2dbee9 100644 --- a/Runtime/Particle/CVectorElement.cpp +++ b/Runtime/Particle/CVectorElement.cpp @@ -268,6 +268,12 @@ bool CVEParticleVelocity::GetValue(int /*frame*/, Zeus::CVector3f& valOut) const return false; } +bool CVESPOS::GetValue(int frame, Zeus::CVector3f& valOut) const +{ + /* TODO: Do */ + return false; +} + bool CVEPLCO::GetValue(int /*frame*/, Zeus::CVector3f& valOut) const { valOut = CParticleGlobals::g_particleMetrics->x10_plco; diff --git a/hecl b/hecl index 2c5df80a5..268a2071b 160000 --- a/hecl +++ b/hecl @@ -1 +1 @@ -Subproject commit 2c5df80a56a07f9147e82a5981dcd311304f3b1d +Subproject commit 268a2071b75323022ef4f4cf61f9f8441b6579d2 diff --git a/libSpecter b/libSpecter index ca6d7ff93..0d22ed8d6 160000 --- a/libSpecter +++ b/libSpecter @@ -1 +1 @@ -Subproject commit ca6d7ff93bb8cf951d47752c0892258330bf5caa +Subproject commit 0d22ed8d625a58d8eac7a63d09c80c490d581db2