From 0bb17a33bd2d270cfc98e7ef21b2a570bc8abe48 Mon Sep 17 00:00:00 2001 From: Jan Date: Sat, 24 Feb 2024 20:33:26 +0100 Subject: [PATCH] feat: load materials from json --- src/Common/Game/T6/T6_Assets.h | 12 +- src/ObjCommon/Game/T6/Material/JsonMaterial.h | 14 +- .../AssetLoading/IAssetLoadingManager.h | 5 + .../T6/AssetLoaders/AssetLoaderMaterial.cpp | 59 +++ .../T6/AssetLoaders/AssetLoaderMaterial.h | 19 + .../Game/T6/Material/JsonMaterialLoader.cpp | 372 ++++++++++++++++++ .../Game/T6/Material/JsonMaterialLoader.h | 13 + src/ObjLoading/Game/T6/ObjLoaderT6.cpp | 4 +- 8 files changed, 481 insertions(+), 17 deletions(-) create mode 100644 src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderMaterial.cpp create mode 100644 src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderMaterial.h diff --git a/src/Common/Game/T6/T6_Assets.h b/src/Common/Game/T6/T6_Assets.h index 854e31fc..3cc64a64 100644 --- a/src/Common/Game/T6/T6_Assets.h +++ b/src/Common/Game/T6/T6_Assets.h @@ -2913,9 +2913,7 @@ namespace T6 GFXS_POLYGON_OFFSET_0 = 0, GFXS_POLYGON_OFFSET_1 = 1, GFXS_POLYGON_OFFSET_2 = 2, - GFXS_POLYGON_OFFSET_SHADOWMAP = 3, - - GFXS_POLYGON_OFFSET_COUNT + GFXS_POLYGON_OFFSET_SHADOWMAP = 3 }; enum GfxStencilOp @@ -2927,9 +2925,7 @@ namespace T6 GFXS_STENCILOP_DECRSAT = 0x4, GFXS_STENCILOP_INVERT = 0x5, GFXS_STENCILOP_INCR = 0x6, - GFXS_STENCILOP_DECR = 0x7, - - GFXS_STENCILOP_COUNT, + GFXS_STENCILOP_DECR = 0x7 }; enum GfxStencilFunc @@ -2941,9 +2937,7 @@ namespace T6 GFXS_STENCILFUNC_GREATER = 0x4, GFXS_STENCILFUNC_NOTEQUAL = 0x5, GFXS_STENCILFUNC_GREATEREQUAL = 0x6, - GFXS_STENCILFUNC_ALWAYS = 0x7, - - GFXS_STENCILFUNC_COUNT, + GFXS_STENCILFUNC_ALWAYS = 0x7 }; struct GfxStateBitsLoadBitsStructured diff --git a/src/ObjCommon/Game/T6/Material/JsonMaterial.h b/src/ObjCommon/Game/T6/Material/JsonMaterial.h index a1f30470..b5fd2ae9 100644 --- a/src/ObjCommon/Game/T6/Material/JsonMaterial.h +++ b/src/ObjCommon/Game/T6/Material/JsonMaterial.h @@ -199,9 +199,9 @@ namespace T6 inline void from_json(const nlohmann::json& in, JsonConstant& out) { - in.at("name").get_to(out.name); - in.at("nameFragment").get_to(out.nameFragment); - in.at("nameHash").get_to(out.nameHash); + in.value("name", nlohmann::json()).get_to(out.name); + in.value("nameFragment", nlohmann::json()).get_to(out.nameFragment); + in.value("nameHash", nlohmann::json()).get_to(out.nameHash); in.at("literal").get_to(out.literal); }; @@ -301,10 +301,10 @@ namespace T6 inline void from_json(const nlohmann::json& in, JsonTexture& out) { - in.at("name").get_to(out.name); - in.at("nameHash").get_to(out.nameHash); - in.at("nameStart").get_to(out.nameStart); - in.at("nameEnd").get_to(out.nameEnd); + in.value("name", nlohmann::json()).get_to(out.name); + in.value("nameHash", nlohmann::json()).get_to(out.nameHash); + in.value("nameStart", nlohmann::json()).get_to(out.nameStart); + in.value("nameEnd", nlohmann::json()).get_to(out.nameEnd); in.at("semantic").get_to(out.semantic); in.at("isMatureContent").get_to(out.isMatureContent); in.at("samplerState").get_to(out.samplerState); diff --git a/src/ObjLoading/AssetLoading/IAssetLoadingManager.h b/src/ObjLoading/AssetLoading/IAssetLoadingManager.h index aa6f1dcb..0d90a6f2 100644 --- a/src/ObjLoading/AssetLoading/IAssetLoadingManager.h +++ b/src/ObjLoading/AssetLoading/IAssetLoadingManager.h @@ -36,6 +36,11 @@ public: return AddAsset(assetType, assetName, asset, std::vector(), std::vector()); } + XAssetInfoGeneric* AddAsset(const asset_type_t assetType, const std::string& assetName, void* asset, std::vector dependencies) + { + return AddAsset(assetType, assetName, asset, std::move(dependencies), std::vector()); + } + virtual XAssetInfoGeneric* LoadDependency(asset_type_t assetType, const std::string& assetName) = 0; virtual IndirectAssetReference LoadIndirectAssetReference(asset_type_t assetType, const std::string& assetName) = 0; }; diff --git a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderMaterial.cpp b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderMaterial.cpp new file mode 100644 index 00000000..028120e2 --- /dev/null +++ b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderMaterial.cpp @@ -0,0 +1,59 @@ +#include "AssetLoaderMaterial.h" + +#include "Game/T6/Material/JsonMaterialLoader.h" +#include "Game/T6/T6.h" +#include "Pool/GlobalAssetPool.h" + +#include +#include + +using namespace T6; + +void* AssetLoaderMaterial::CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) +{ + auto* material = memory->Create(); + memset(material, 0, sizeof(Material)); + material->info.name = memory->Dup(assetName.c_str()); + + return material; +} + +bool AssetLoaderMaterial::CanLoadFromRaw() const +{ + return true; +} + +std::string AssetLoaderMaterial::GetFileNameForAsset(const std::string& assetName) +{ + std::string sanitizedFileName(assetName); + if (sanitizedFileName[0] == '*') + { + std::ranges::replace(sanitizedFileName, '*', '_'); + const auto parenthesisPos = sanitizedFileName.find('('); + if (parenthesisPos != std::string::npos) + sanitizedFileName.erase(parenthesisPos); + sanitizedFileName = "generated/" + sanitizedFileName; + } + + return std::format("materials/{}.json", sanitizedFileName); +} + +bool AssetLoaderMaterial::LoadFromRaw( + const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const +{ + const auto file = searchPath->Open(GetFileNameForAsset(assetName)); + if (!file.IsOpen()) + return false; + + auto* material = static_cast(memory->Alloc(sizeof(Material))); + memset(material, 0, sizeof(Material)); + material->info.name = memory->Dup(assetName.c_str()); + + std::vector dependencies; + if (LoadMaterialAsJson(*file.m_stream, *material, memory, manager, dependencies)) + manager->AddAsset(ASSET_TYPE_MATERIAL, assetName, material, std::move(dependencies)); + else + std::cerr << "Failed to load material \"" << assetName << "\"\n"; + + return true; +} diff --git a/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderMaterial.h b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderMaterial.h new file mode 100644 index 00000000..c47c026b --- /dev/null +++ b/src/ObjLoading/Game/T6/AssetLoaders/AssetLoaderMaterial.h @@ -0,0 +1,19 @@ +#pragma once +#include "AssetLoading/BasicAssetLoader.h" +#include "AssetLoading/IAssetLoadingManager.h" +#include "Game/T6/T6.h" +#include "SearchPath/ISearchPath.h" + +namespace T6 +{ + class AssetLoaderMaterial final : public BasicAssetLoader + { + static std::string GetFileNameForAsset(const std::string& assetName); + + public: + _NODISCARD void* CreateEmptyAsset(const std::string& assetName, MemoryManager* memory) override; + _NODISCARD bool CanLoadFromRaw() const override; + bool + LoadFromRaw(const std::string& assetName, ISearchPath* searchPath, MemoryManager* memory, IAssetLoadingManager* manager, Zone* zone) const override; + }; +} // namespace T6 diff --git a/src/ObjLoading/Game/T6/Material/JsonMaterialLoader.cpp b/src/ObjLoading/Game/T6/Material/JsonMaterialLoader.cpp index e69de29b..c0d8bb86 100644 --- a/src/ObjLoading/Game/T6/Material/JsonMaterialLoader.cpp +++ b/src/ObjLoading/Game/T6/Material/JsonMaterialLoader.cpp @@ -0,0 +1,372 @@ +#include "JsonMaterialLoader.h" + +#include "Game/T6/CommonT6.h" +#include "Game/T6/Material/JsonMaterial.h" + +#include +#include +#include + +using namespace nlohmann; + +namespace T6 +{ + class JsonLoader + { + public: + JsonLoader(std::istream& stream, MemoryManager& memory, IAssetLoadingManager& manager, std::vector& dependencies) + : m_stream(stream), + m_memory(memory), + m_manager(manager), + m_dependencies(dependencies) + + { + } + + bool Load(Material& material) const + { + const auto jRoot = json::parse(m_stream); + std::string type; + unsigned version; + + jRoot.at("_type").get_to(type); + jRoot.at("_version").get_to(version); + + if (type != "material" || version != 1u) + { + std::cerr << "Tried to load material \"" << material.info.name << "\" but did not find expected type material of version 1\n"; + return false; + } + + const auto jMaterial = jRoot.get(); + return CreateMaterialFromJson(jMaterial, material); + } + + private: + static void PrintError(const Material& material, const std::string& message) + { + std::cerr << "Cannot load material \"" << material.info.name << "\": " << message << "\n"; + } + + static bool CreateGameFlagsFromJson(const JsonMaterial& jMaterial, unsigned& gameFlags) + { + for (const auto gameFlag : jMaterial.gameFlags) + gameFlags |= gameFlag; + + return true; + } + + bool CreateTextureDefFromJson(const JsonTexture& jTexture, MaterialTextureDef& textureDef, const Material& material) const + { + if (jTexture.name) + { + if (jTexture.name->empty()) + { + PrintError(material, "textureDef name cannot be empty"); + return false; + } + + textureDef.nameStart = jTexture.name.value()[0]; + textureDef.nameEnd = jTexture.name.value()[jTexture.name->size() - 1]; + textureDef.nameHash = Common::R_HashString(jTexture.name.value().c_str(), 0); + } + else + { + if (!jTexture.nameStart || !jTexture.nameEnd || !jTexture.nameHash) + { + PrintError(material, "textureDefs without name must have nameStart, nameEnd and nameHash"); + return false; + } + + if (jTexture.nameStart->size() != 1 || jTexture.nameEnd->size() != 1) + { + PrintError(material, "nameStart and nameEnd must be a string of exactly one character"); + return false; + } + + textureDef.nameStart = jTexture.nameStart.value()[0]; + textureDef.nameEnd = jTexture.nameEnd.value()[0]; + textureDef.nameHash = jTexture.nameHash.value(); + } + + textureDef.samplerState.filter = jTexture.samplerState.filter; + textureDef.samplerState.mipMap = jTexture.samplerState.mipMap; + textureDef.samplerState.clampU = jTexture.samplerState.clampU; + textureDef.samplerState.clampV = jTexture.samplerState.clampV; + textureDef.samplerState.clampW = jTexture.samplerState.clampW; + + textureDef.semantic = jTexture.semantic; + textureDef.isMatureContent = jTexture.isMatureContent; + + auto* image = static_cast*>(m_manager.LoadDependency(ASSET_TYPE_IMAGE, jTexture.image)); + if (!image) + { + PrintError(material, std::format("Could not find textureDef image: {}", jTexture.image)); + return false; + } + m_dependencies.push_back(image); + textureDef.image = image->Asset(); + + return true; + } + + static bool CreateConstantDefFromJson(const JsonConstant& jConstant, MaterialConstantDef& constantDef, const Material& material) + { + if (jConstant.name) + { + const auto copyCount = std::min(jConstant.name->size() + 1, std::extent_v); + strncpy(constantDef.name, jConstant.name->c_str(), copyCount); + if (copyCount < std::extent_v) + memset(&constantDef.name[copyCount], 0, std::extent_v - copyCount); + constantDef.nameHash = Common::R_HashString(jConstant.name->c_str(), 0); + } + else + { + if (!jConstant.nameFragment || !jConstant.nameHash) + { + PrintError(material, "constantDefs without name must have nameFragment and nameHash"); + return false; + } + + const auto copyCount = std::min(jConstant.nameFragment->size() + 1, std::extent_v); + strncpy(constantDef.name, jConstant.nameFragment->c_str(), copyCount); + if (copyCount < std::extent_v) + memset(&constantDef.name[copyCount], 0, std::extent_v - copyCount); + constantDef.nameHash = jConstant.nameHash.value(); + } + + if (jConstant.literal.size() != 4) + { + PrintError(material, "constantDef literal must be array of size 4"); + return false; + } + + constantDef.literal.x = jConstant.literal[0]; + constantDef.literal.y = jConstant.literal[1]; + constantDef.literal.z = jConstant.literal[2]; + constantDef.literal.w = jConstant.literal[3]; + + return true; + } + + static bool + CreateStateBitsTableEntryFromJson(const JsonStateBitsTableEntry& jStateBitsTableEntry, GfxStateBits& stateBitsTableEntry, const Material& material) + { + auto& structured = stateBitsTableEntry.loadBits.structured; + + structured.srcBlendRgb = jStateBitsTableEntry.srcBlendRgb; + structured.dstBlendRgb = jStateBitsTableEntry.dstBlendRgb; + structured.blendOpRgb = jStateBitsTableEntry.blendOpRgb; + + if (jStateBitsTableEntry.alphaTest == JsonAlphaTest::DISABLED) + { + structured.alphaTestDisabled = 1; + structured.alphaTest = 0; + } + else if (jStateBitsTableEntry.alphaTest == JsonAlphaTest::GT0) + { + structured.alphaTestDisabled = 0; + structured.alphaTest = GFXS_ALPHA_TEST_GT_0; + } + else if (jStateBitsTableEntry.alphaTest == JsonAlphaTest::GE128) + { + structured.alphaTestDisabled = 0; + structured.alphaTest = GFXS_ALPHA_TEST_GE_128; + } + else + { + PrintError(material, "Invalid value for alphaTest"); + return false; + } + + if (jStateBitsTableEntry.cullFace == JsonCullFace::NONE) + structured.cullFace = GFXS0_CULL_NONE; + else if (jStateBitsTableEntry.cullFace == JsonCullFace::BACK) + structured.cullFace = GFXS0_CULL_BACK; + else if (jStateBitsTableEntry.cullFace == JsonCullFace::FRONT) + structured.cullFace = GFXS0_CULL_FRONT; + else + { + PrintError(material, "Invalid value for cull face"); + return false; + } + + structured.srcBlendAlpha = jStateBitsTableEntry.srcBlendAlpha; + structured.dstBlendAlpha = jStateBitsTableEntry.dstBlendAlpha; + structured.blendOpAlpha = jStateBitsTableEntry.blendOpAlpha; + structured.colorWriteRgb = jStateBitsTableEntry.colorWriteRgb; + structured.colorWriteAlpha = jStateBitsTableEntry.colorWriteAlpha; + structured.polymodeLine = jStateBitsTableEntry.polymodeLine; + structured.depthWrite = jStateBitsTableEntry.depthWrite; + + if (jStateBitsTableEntry.depthTest == JsonDepthTest::DISABLED) + structured.depthTestDisabled = 1; + else if (jStateBitsTableEntry.depthTest == JsonDepthTest::ALWAYS) + structured.depthTest = GFXS_DEPTHTEST_ALWAYS; + else if (jStateBitsTableEntry.depthTest == JsonDepthTest::LESS) + structured.depthTest = GFXS_DEPTHTEST_LESS; + else if (jStateBitsTableEntry.depthTest == JsonDepthTest::EQUAL) + structured.depthTest = GFXS_DEPTHTEST_EQUAL; + else if (jStateBitsTableEntry.depthTest == JsonDepthTest::LESS_EQUAL) + structured.depthTest = GFXS_DEPTHTEST_LESSEQUAL; + else + { + PrintError(material, "Invalid value for depth test"); + return false; + } + + structured.polygonOffset = jStateBitsTableEntry.polygonOffset; + + if (jStateBitsTableEntry.stencilFront) + { + structured.stencilFrontEnabled = 1; + structured.stencilFrontPass = jStateBitsTableEntry.stencilFront->pass; + structured.stencilFrontFail = jStateBitsTableEntry.stencilFront->fail; + structured.stencilFrontZFail = jStateBitsTableEntry.stencilFront->zfail; + structured.stencilFrontFunc = jStateBitsTableEntry.stencilFront->func; + } + + if (jStateBitsTableEntry.stencilBack) + { + structured.stencilBackEnabled = 1; + structured.stencilBackPass = jStateBitsTableEntry.stencilBack->pass; + structured.stencilBackFail = jStateBitsTableEntry.stencilBack->fail; + structured.stencilBackZFail = jStateBitsTableEntry.stencilBack->zfail; + structured.stencilBackFunc = jStateBitsTableEntry.stencilBack->func; + } + + return true; + } + + bool CreateMaterialFromJson(const JsonMaterial& jMaterial, Material& material) const + { + if (!CreateGameFlagsFromJson(jMaterial, material.info.gameFlags)) + return false; + + material.info.sortKey = static_cast(jMaterial.sortKey); + + if (jMaterial.textureAtlas) + { + material.info.textureAtlasRowCount = jMaterial.textureAtlas->rows; + material.info.textureAtlasColumnCount = jMaterial.textureAtlas->columns; + } + else + { + material.info.textureAtlasRowCount = 0; + material.info.textureAtlasColumnCount = 0; + } + + material.info.surfaceTypeBits = jMaterial.surfaceTypeBits; + material.info.layeredSurfaceTypes = jMaterial.layeredSurfaceTypes; + material.info.hashIndex = static_cast(jMaterial.hashIndex); + material.info.surfaceFlags = jMaterial.surfaceFlags; + material.info.contents = jMaterial.contents; + + if (jMaterial.stateBitsEntry.size() != std::extent_v) + { + PrintError(material, std::format("StateBitsEntry size is not {}", jMaterial.stateBitsEntry.size())); + return false; + } + for (auto i = 0u; i < std::extent_v; i++) + material.stateBitsEntry[i] = jMaterial.stateBitsEntry[i]; + + material.stateFlags = static_cast(jMaterial.stateFlags); + material.cameraRegion = jMaterial.cameraRegion; + material.probeMipBits = jMaterial.probeMipBits; + + auto* techniqueSet = static_cast*>(m_manager.LoadDependency(ASSET_TYPE_TECHNIQUE_SET, jMaterial.techniqueSet)); + if (!techniqueSet) + { + PrintError(material, "Could not find technique set"); + return false; + } + m_dependencies.push_back(techniqueSet); + material.techniqueSet = techniqueSet->Asset(); + + if (!jMaterial.textures.empty()) + { + material.textureCount = static_cast(jMaterial.textures.size()); + material.textureTable = static_cast(m_memory.Alloc(sizeof(MaterialTextureDef) * material.textureCount)); + memset(material.textureTable, 0, sizeof(MaterialTextureDef) * material.textureCount); + + for (auto i = 0u; i < material.textureCount; i++) + { + if (!CreateTextureDefFromJson(jMaterial.textures[i], material.textureTable[i], material)) + return false; + } + } + else + { + material.textureCount = 0; + material.textureTable = nullptr; + } + + if (!jMaterial.constants.empty()) + { + material.constantCount = static_cast(jMaterial.constants.size()); + material.constantTable = static_cast(m_memory.Alloc(sizeof(MaterialConstantDef) * material.constantCount)); + memset(material.constantTable, 0, sizeof(MaterialConstantDef) * material.constantCount); + + for (auto i = 0u; i < material.constantCount; i++) + { + if (!CreateConstantDefFromJson(jMaterial.constants[i], material.constantTable[i], material)) + return false; + } + } + else + { + material.constantCount = 0; + material.constantTable = nullptr; + } + + if (!jMaterial.stateBits.empty()) + { + material.stateBitsCount = static_cast(jMaterial.stateBits.size()); + material.stateBitsTable = static_cast(m_memory.Alloc(sizeof(GfxStateBitsTable) * material.stateBitsCount)); + memset(material.stateBitsTable, 0, sizeof(GfxStateBitsTable) * material.stateBitsCount); + + for (auto i = 0u; i < material.stateBitsCount; i++) + { + if (!CreateStateBitsTableEntryFromJson(jMaterial.stateBits[i], material.stateBitsTable[i], material)) + return false; + } + } + else + { + material.stateBitsCount = 0; + material.stateBitsTable = nullptr; + } + + if (jMaterial.thermalMaterial) + { + auto* thermalMaterial = static_cast*>(m_manager.LoadDependency(ASSET_TYPE_MATERIAL, jMaterial.thermalMaterial.value())); + if (!thermalMaterial) + { + PrintError(material, "Could not find thermal material"); + return false; + } + m_dependencies.push_back(thermalMaterial); + material.thermalMaterial = thermalMaterial->Asset(); + } + else + { + material.thermalMaterial = nullptr; + } + + return true; + } + + std::istream& m_stream; + MemoryManager& m_memory; + IAssetLoadingManager& m_manager; + std::vector& m_dependencies; + }; + + bool LoadMaterialAsJson( + std::istream& stream, Material& material, MemoryManager* memory, IAssetLoadingManager* manager, std::vector& dependencies) + { + const JsonLoader loader(stream, *memory, *manager, dependencies); + + return loader.Load(material); + } +} // namespace T6 diff --git a/src/ObjLoading/Game/T6/Material/JsonMaterialLoader.h b/src/ObjLoading/Game/T6/Material/JsonMaterialLoader.h index e69de29b..5e502e90 100644 --- a/src/ObjLoading/Game/T6/Material/JsonMaterialLoader.h +++ b/src/ObjLoading/Game/T6/Material/JsonMaterialLoader.h @@ -0,0 +1,13 @@ +#pragma once + +#include "AssetLoading/IAssetLoadingManager.h" +#include "Game/T6/T6.h" +#include "Utils/MemoryManager.h" + +#include + +namespace T6 +{ + bool LoadMaterialAsJson( + std::istream& stream, Material& material, MemoryManager* memory, IAssetLoadingManager* manager, std::vector& dependencies); +} // namespace T6 diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index dcff00e5..667e70ab 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -3,6 +3,7 @@ #include "AssetLoaders/AssetLoaderFontIcon.h" #include "AssetLoaders/AssetLoaderGfxImage.h" #include "AssetLoaders/AssetLoaderLocalizeEntry.h" +#include "AssetLoaders/AssetLoaderMaterial.h" #include "AssetLoaders/AssetLoaderPhysConstraints.h" #include "AssetLoaders/AssetLoaderPhysPreset.h" #include "AssetLoaders/AssetLoaderQdb.h" @@ -49,7 +50,8 @@ namespace T6 REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_DESTRUCTIBLEDEF, DestructibleDef)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_XANIMPARTS, XAnimParts)) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_XMODEL, XModel)) - REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_MATERIAL, Material)) + // REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_MATERIAL, Material)) + REGISTER_ASSET_LOADER(AssetLoaderMaterial) REGISTER_ASSET_LOADER(BASIC_LOADER(ASSET_TYPE_TECHNIQUE_SET, MaterialTechniqueSet)) REGISTER_ASSET_LOADER(AssetLoaderGfxImage) REGISTER_ASSET_LOADER(AssetLoaderSoundBank)