diff --git a/demo/demo/main.cpp b/demo/demo/main.cpp index 5c6729b726759538f1a236ede2f5d23e339af2f2..109fd0e87c85582d39eb4db05ee7d27449c3fb91 100644 --- a/demo/demo/main.cpp +++ b/demo/demo/main.cpp @@ -39,6 +39,7 @@ #include "fggl/data/assimp/module.hpp" #include "fggl/assets/module.hpp" +#include "fggl/assets/packed/module.hpp" #include "fggl/script/lua/module.hpp" #include "GameScene.h" @@ -59,7 +60,7 @@ static void setup_menu(fggl::App& app) { menu->add(labels.at(i), [&app, sceneName]() { auto* audio = app.service<fggl::audio::AudioService>(); - audio->play("click.ogg", false); + audio->play("ui/click.ogg", false); app.change_state(sceneName); }); } @@ -82,6 +83,7 @@ int main(int argc, const char* argv[]) { moduleManager.use<fggl::gfx::OpenGL4>(); moduleManager.use<fggl::display::GLFW>(); moduleManager.use<fggl::assets::AssetFolders>(); + moduleManager.use<fggl::assets::PackedAssets>(); moduleManager.use<fggl::entity::ECS>(); moduleManager.use<fggl::script::Lua>(); @@ -98,11 +100,15 @@ int main(int argc, const char* argv[]) { // create the application fggl::App app( &moduleManager, "fggl-demo" ); + auto* assetFinder = app.service<fggl::assets::CheckinAdapted>(); + assetFinder->discover("core"); + assetFinder->discover("viewer"); + // force asset loading { auto* assets = app.service<fggl::assets::AssetManager>(); auto* loader = app.service<fggl::assets::Loader>(); - loader->load("click.ogg", fggl::audio::OGG_VORBIS, assets); + loader->load("ui/click.ogg", fggl::audio::OGG_VORBIS, assets); } auto* windowing = app.service<fggl::display::WindowService>(); diff --git a/demo/demo/models/viewer.cpp b/demo/demo/models/viewer.cpp index bf7b3ebababdb36e212537ee2a47e5c0ce56e754..e073c29fce609c378f8b0ed8babb955767399ae6 100644 --- a/demo/demo/models/viewer.cpp +++ b/demo/demo/models/viewer.cpp @@ -113,7 +113,7 @@ namespace demo { auto *loader = owner().service<fggl::assets::Loader>(); auto *manager = owner().service<fggl::assets::AssetManager>(); - loader->load("backpack/backpack.obj", fggl::data::models::ASSIMP_MODEL, manager); + loader->load("backpack/backpack.obj", fggl::data::models::ASSIMP_MODEL, manager, "viewer"); // create camera setup_camera(world()); diff --git a/fggl/assets/CMakeLists.txt b/fggl/assets/CMakeLists.txt index d057f9945c730f3879fbdacfca19c020cf456921..d39a51326aa6d384a1155b3d3fb5e2ad4bec1a82 100644 --- a/fggl/assets/CMakeLists.txt +++ b/fggl/assets/CMakeLists.txt @@ -1,3 +1,4 @@ target_sources(fggl PRIVATE module.cpp - ) \ No newline at end of file + packed/module.cpp +) \ No newline at end of file diff --git a/fggl/assets/module.cpp b/fggl/assets/module.cpp index 5c25dacde21d820fb5bd9a2f9b0f6da1b4fdf1ae..e56972a325f9f392384cc883785fe9a68b36208d 100644 --- a/fggl/assets/module.cpp +++ b/fggl/assets/module.cpp @@ -23,7 +23,8 @@ namespace fggl::assets { bool AssetFolders::factory(modules::ModuleService service, modules::Services &services) { if (service == Loader::service) { auto storage = services.get<data::Storage>(); - services.create<Loader>(storage); + auto checkin = services.get<CheckinAdapted>(); + services.create<Loader>(storage, checkin); return true; } if (service == AssetManager::service) { diff --git a/fggl/assets/packed/module.cpp b/fggl/assets/packed/module.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3a5646923b4069f10d17ad2f62957651be89ccf0 --- /dev/null +++ b/fggl/assets/packed/module.cpp @@ -0,0 +1,37 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 20/08/22. +// + +#include "fggl/assets/packed/module.hpp" + +namespace fggl::assets { + + bool PackedAssets::factory(modules::ModuleService service, modules::Services &services) { + if (service == RawCheckin::service) { + services.create<RawCheckin>(); + return true; + } + if (service == CheckinAdapted::service) { + auto* storage = services.get<data::Storage>(); + auto* rawCheckin = services.get<RawCheckin>(); + services.create<CheckinAdapted>(storage, rawCheckin); + return true; + } + return false; + } + +} // namespace fggl::assets \ No newline at end of file diff --git a/fggl/assets/pipeline/CMakeLists.txt b/fggl/assets/pipeline/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/fggl/assets/pipeline/loader.cpp b/fggl/assets/pipeline/loader.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bd1c1d0ba7ca678defbe42e2a8143724820eb003 --- /dev/null +++ b/fggl/assets/pipeline/loader.cpp @@ -0,0 +1,215 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 19/11/22. +// + +#include <stack> +#include <vector> +#include <memory> +#include <set> +#include <map> +#include <concepts> + +#include "fggl/util/guid.hpp" + +namespace fggl::assets { + + using AssetName = util::OpaqueName<uint64_t, struct AssetRefStruct>; + + /** + * The base class representing an asset. + * + * This can be combined with the templated version. + */ + class Asset { + public: + Asset(AssetName name) : m_name(name) {} + virtual ~Asset() = default; + + inline void release() { + releaseImpl(); + } + + bool operator==(const Asset& asset) const { + return m_name == asset.m_name; + } + + bool operator!=(const Asset& asset) const { + return m_name != asset.m_name; + } + + private: + AssetName m_name; + virtual void releaseImpl() = 0; + }; + + /** + * Wrapper for types that cannot extend Asset themselves. + * + * @tparam T the asset to wrap + */ + template<typename T> + class AssetBox : public Asset { + public: + AssetBox(T* ptr) : m_ptr(ptr) {} + + ~AssetBox() override { + release(); + } + + T* ptr() { + return m_ptr; + } + + private: + T* m_ptr; + + void releaseImpl() override { + delete m_ptr; + m_ptr = nullptr; + } + + }; + + /** + * Asset Library. + * + * The currently usable set of assets loaded into the engine. + */ + class AssetLibrary { + public: + void load(AssetName name); + void unload(AssetName name); + + template<typename T> + requires std::derived_from<T, Asset> + void store(AssetName name, T* ptr) { + m_assets[name] = std::make_unique<T>(ptr); + } + + template<typename T> + requires std::derived_from<T, Asset> + void get(AssetName name) const { + Asset* asset = m_assets.at(name).get(); + return dynamic_cast<T>(asset); + } + + inline Asset* get(AssetName name) const { + return m_assets.at( name ).get(); + } + + private: + std::map<AssetName, std::unique_ptr<Asset>> m_assets; + + }; + + struct AssetRecord { + AssetName id; + std::vector<AssetName> dependencies; + + bool hasDepends(AssetName name) const; + void addDepend(AssetName name); + }; + + class AssetGraph { + public: + AssetGraph(AssetLibrary* loader); + + /** + * + * + * @param name + */ + void require(AssetName& name) { + if ( m_loaded.find(name) != m_loaded.end() ) { + return; + } + m_required.push(name); + } + + /** + * + * + * @return + */ + [[nodiscard]] + inline bool isLoadComplete() const { + return m_required.empty(); + } + + /** + * + * + * @param name + * @return + */ + [[nodiscard]] + inline bool isLoaded(AssetName name) const { + return m_loaded.find(name) != m_loaded.end(); + } + + /** + * + * @param name + * @param description + */ + void addDependency(AssetName& name, AssetName& description) { + auto& assets = m_assets.at(name); + assets.addDepend(description); + } + + /** + * + * @param name + * @param dependency + * @return + */ + [[nodiscard]] + bool hasDependency(AssetName& name , AssetName& dependency) const { + try { + auto &assets = m_assets.at(name); + return assets.hasDepends(dependency); + } catch ( std::out_of_range& ) { + return false; + } + } + + /** + * + */ + void process() { + auto asset = m_required.top(); + m_loader->load( asset ); + m_required.pop(); + } + + /** + * + */ + void finishLoading() { + while ( !m_loaded.empty() ) { + process(); + } + } + + private: + std::stack<AssetName> m_required; + std::set<AssetName> m_loaded; + std::map<AssetName, AssetRecord> m_assets; + AssetLibrary* m_loader; + }; + +} // namespace fggl::assets \ No newline at end of file diff --git a/fggl/audio/openal/audio.cpp b/fggl/audio/openal/audio.cpp index 1cfb4b7a123a0a6424a3e2907a2af381ca96a552..d763a6f5e37a889d573b54e9a63ebe215acc27cf 100644 --- a/fggl/audio/openal/audio.cpp +++ b/fggl/audio/openal/audio.cpp @@ -52,6 +52,35 @@ namespace fggl::audio::openal { return nullptr; } + bool load_vorbis_short(std::filesystem::path path, assets::MemoryBlock& block) { + // vorbis + auto* clip = new AudioClipShort(); + clip->sampleCount = stb_vorbis_decode_filename( path.c_str(), &clip->channels, &clip->sampleRate, &clip->data); + debug::info("clip loaded: channels={}, sampleRate={}, sampleCount={}", clip->channels, clip->sampleRate, clip->sampleCount); + + if ( clip->sampleCount == -1 ) { + return false; + } + + return false; + } + + assets::AssetTypeID check_vorbis(std::filesystem::path path) { + if ( path.extension() != ".ogg" ) { + return assets::INVALID_ASSET_TYPE; + } + + auto* clip = new AudioClipShort(); + clip->sampleCount = stb_vorbis_decode_filename( path.c_str(), &clip->channels, &clip->sampleRate, &clip->data); + + if ( clip->sampleCount == -1 ) { + return assets::INVALID_ASSET_TYPE; + } + + delete clip; + return ASSET_CLIP_SHORT; + } + void AudioSource::play(const AudioClipShort &clip, bool looping) { check_error("pre play"); diff --git a/fggl/audio/openal/module.cpp b/fggl/audio/openal/module.cpp index 056abd3a214a1a7a70e58a4dbf45ffb25a1e30cd..89aeb857d93558665556c24e8fbebd9ab1c8cd2f 100644 --- a/fggl/audio/openal/module.cpp +++ b/fggl/audio/openal/module.cpp @@ -29,6 +29,11 @@ namespace fggl::audio { assetLoader->setFactory(OGG_VORBIS, openal::load_vorbis, assets::LoadType::PATH); } + { + auto *checkin = services.get<assets::CheckinAdapted>(); + checkin->setLoader( RES_OGG_VORBIS, openal::load_vorbis_short, openal::check_vorbis ); + } + services.bind<audio::AudioService, openal::AudioServiceOAL>(assets); return true; } diff --git a/fggl/data/assimp/module.cpp b/fggl/data/assimp/module.cpp index 8ba764885f0c0cf1e698ebfe78565723a9d23153..4ae1bb6956dec034152891544789a635af8311ef 100644 --- a/fggl/data/assimp/module.cpp +++ b/fggl/data/assimp/module.cpp @@ -195,11 +195,63 @@ namespace fggl::data::models { return nullptr; } + static bool load_tex_stb(std::filesystem::path filePath, assets::MemoryBlock& block) { + stbi_set_flip_vertically_on_load(true); + + //load the texture data into memory + data::Texture2D* image = new data::Texture2D(); + image->data = stbi_load(filePath.c_str(), &image->size.x, &image->size.y, &image->channels, 3); + + if ( image->data == nullptr ) { + debug::warning("error reading texture: {}", stbi_failure_reason()); + delete image; + return false; + } else { + //manager->set(guid, image); + // TODO pass metadata to loader in a sensible way + block.size = image->channels * image->size.x * image->size.y; + block.data = (std::byte*)image->data; + delete image; + return true; + } + } + + static assets::AssetTypeID is_tex_stb(std::filesystem::path filePath) { + // detect jpgs + if ( filePath.extension() == ".jpg" || filePath.extension() == ".jpeg" ) { + return TEXTURE_RGBA; + } + + // detect png + if ( filePath.extension() == ".png" ) { + return TEXTURE_RGBA; + } + + return assets::INVALID_ASSET_TYPE; + } + + static bool load_model_assimp(std::filesystem::path filePath, assets::MemoryBlock& block) { + return false; //TODO + } + + static assets::AssetTypeID is_model_assimp(std::filesystem::path filePath) { + if ( filePath.extension() == ".obj" ){ + return MODEL_MULTI3D; + } + + return assets::INVALID_ASSET_TYPE; + } + bool AssimpModule::factory(modules::ModuleService service, modules::Services &serviceManager) { if ( service == MODEL_PROVIDER ) { - auto assetLoader = serviceManager.get<assets::Loader>(); + auto* assetLoader = serviceManager.get<assets::Loader>(); assetLoader->setFactory( ASSIMP_MODEL, load_assimp_model, assets::LoadType::PATH ); assetLoader->setFactory( DATA_TEXTURE2D, load_assimp_texture, assets::LoadType::PATH ); + + auto* checkin = serviceManager.get<assets::CheckinAdapted>(); + checkin->setLoader( MIME_JPG, load_tex_stb, is_tex_stb ); + checkin->setLoader( MIME_PNG, load_tex_stb, is_tex_stb ); + checkin->setLoader( MIME_OBJ, load_model_assimp, is_model_assimp ); } } diff --git a/include/fggl/assets/loader.hpp b/include/fggl/assets/loader.hpp index b58f506002576ea725d04913ea5a3ecf3f4f7267..0bc18d9e97ae12e87abf6490343a6c2c36a4b88d 100644 --- a/include/fggl/assets/loader.hpp +++ b/include/fggl/assets/loader.hpp @@ -27,6 +27,7 @@ #include <variant> #include "fggl/assets/types.hpp" +#include "fggl/assets/packed/adapter.hpp" #include "fggl/data/storage.hpp" namespace fggl::assets { @@ -46,7 +47,7 @@ namespace fggl::assets { public: constexpr const static modules::ModuleService service = modules::make_service("fggl::assets::Loader"); - explicit inline Loader(data::Storage *storage) : m_storage(storage) {} + explicit inline Loader(data::Storage *storage, CheckinAdapted *checkin) : m_storage(storage), m_checkin(checkin) {} explicit Loader(Loader *parent, data::Storage *storage) : m_parent(parent), m_storage(storage) {}; @@ -68,8 +69,17 @@ namespace fggl::assets { m_requests.push(ResourceRequest{guid, type}); } - void load(const AssetGUID &guid, const AssetType &type, void* userPtr = nullptr) { - auto path = m_storage->resolvePath(data::StorageType::Data, guid); + void load(const AssetGUID &guid, const AssetType &type, void* userPtr = nullptr, const std::string& pack = "core") { + + auto assetID = assets::make_asset_id(pack, guid); + std::filesystem::path path{}; + + // integrate the asset search routine, falling back to path scanning if needed + if ( m_checkin->exists(assetID) ) { + path = m_checkin->getPath(assetID); + } else { + path = m_storage->resolvePath(data::StorageType::Data, guid); + } auto factoryItr = m_factories.find(type); if ( factoryItr == m_factories.end() ) { @@ -120,6 +130,7 @@ namespace fggl::assets { Loader *m_parent = nullptr; using Config = std::pair<Checkin, LoadType>; data::Storage *m_storage; + CheckinAdapted *m_checkin; std::queue<ResourceRequest> m_requests; std::map<AssetType, Config> m_factories; }; diff --git a/include/fggl/assets/module.hpp b/include/fggl/assets/module.hpp index 1c61f2d7724dd0b7195078b2eb58f1e766951ed1..388268e7bb034b532f59502cc90fa4f9392e3cda 100644 --- a/include/fggl/assets/module.hpp +++ b/include/fggl/assets/module.hpp @@ -20,7 +20,10 @@ #define FGGL_ASSETS_MODULE_HPP #include "fggl/modules/module.hpp" + #include "fggl/data/module.hpp" + +#include "fggl/assets/packed/module.hpp" #include "fggl/assets/manager.hpp" #include "fggl/assets/loader.hpp" @@ -32,8 +35,9 @@ namespace fggl::assets { Loader::service, AssetManager::service }; - constexpr static const std::array<modules::ModuleService, 1> depends = { - data::Storage::service + constexpr static const std::array<modules::ModuleService, 2> depends = { + data::Storage::service, + CheckinAdapted::service }; static bool factory(modules::ModuleService name, modules::Services &serviceManager); }; diff --git a/include/fggl/assets/packed/adapter.hpp b/include/fggl/assets/packed/adapter.hpp new file mode 100644 index 0000000000000000000000000000000000000000..fd787ba7df0318dee6f4a957ebae1029dc9bc862 --- /dev/null +++ b/include/fggl/assets/packed/adapter.hpp @@ -0,0 +1,267 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 19/11/22. +// + +#ifndef FGGL_ASSETS_PACKED_ADAPTER_HPP +#define FGGL_ASSETS_PACKED_ADAPTER_HPP + +#include <stack> +#include <ranges> +#include <algorithm> + +#include "fggl/assets/packed/direct.hpp" +#include "fggl/data/storage.hpp" + +namespace fggl::assets { + + using ResourceType = util::OpaqueName<uint64_t, struct ResourceTypeStruct>; + constexpr ResourceType from_mime(const char* mime) { + return ResourceType::make( util::hash_fnv1a_64(mime) ); + }; + + struct ResourceRecord { + std::filesystem::path m_path; + AssetID assetID; + ResourceType m_fileType; + AssetTypeID m_assetType; + }; + + struct ManifestHeader { + uint64_t assetID; + uint64_t fileType; + uint64_t assetType; + uint64_t stringSize; + }; + + [[maybe_unused]] + inline bool NEEDS_CHECKIN(std::filesystem::path, MemoryBlock) { + debug::error("attempted to load asset which does not have a valid checkin yet"); + return false; + } + + /** + * Adapter for Raw Checkin. + * + * For debugging/development it's a pain to have to pack assets directly. Although its much slower, it can be useful + * to be able to load non-optimised formats at runtime. This adapter allows injecting these non-optimised formats + * into the production checkin system. + */ + class CheckinAdapted { + public: + constexpr const static modules::ModuleService service = modules::make_service("fggl::assets::checkin::debug"); + using FilePredicate = std::function<AssetTypeID(std::filesystem::path)>; + using FileLoader = std::function<bool(std::filesystem::path, MemoryBlock& block)>; + + CheckinAdapted(data::Storage* storage, RawCheckin* checkSvc) : m_storage(storage), m_checkin(checkSvc) {}; + + // asset loading + void load(AssetID asset) { + auto& assetRef = m_files.at(asset); + auto& loader = m_loaders.at(assetRef.m_fileType); + + MemoryBlock block; + auto result = loader(assetRef.m_path, block); + if ( !result ) { + return; + } + + m_checkin->check(assetRef.assetID, assetRef.m_assetType, block); + } + + inline bool exists(AssetID asset) const { + return m_files.find(asset) != m_files.end(); + } + + inline std::filesystem::path getPath(AssetID asset) const { + auto& file = m_files.at(asset); + return file.m_path; + } + + void discover( const char* packName, bool useCache = false, bool updateCache = true) { + std::string packRoot = "packs/"; + auto packDir = m_storage->resolvePath( data::StorageType::Data, packRoot + packName ); + discover(packDir, useCache, updateCache); + } + + // asset discovery + void discover( std::filesystem::path& packDir, bool useCache=true, bool updateCache=false ) { + if ( useCache && has_manifest(packDir)) { + // check if we've cached the search + load_manifest(packDir.filename(), packDir); + return; + } + + std::vector<AssetID> packFiles; + std::stack< std::filesystem::path > paths; + paths.push(packDir); + + while ( !paths.empty() ) { + auto path = paths.top(); + paths.pop(); + + if ( std::filesystem::is_directory(path) ) { + std::ranges::for_each( std::filesystem::directory_iterator{path}, [&paths](auto& path) { + paths.push(path); + }); + } else if ( std::filesystem::is_regular_file(path) ) { + process_file( path, packDir, packFiles ); + } + } + + // update the cache and remember what pack maps to what asset + if ( updateCache ) { + save_manifest(packDir.filename(), packDir, packFiles); + } + m_packs[ packDir.filename() ] = packFiles; + } + + inline void setLoader(ResourceType type, FileLoader loader, FilePredicate predicate = nullptr) { + assert( loader != nullptr ); + m_loaders[type] = loader; + + if ( predicate != nullptr ) { + m_predicates[type] = predicate; + } + } + + private: + data::Storage* m_storage; + RawCheckin* m_checkin; + std::map<AssetID, ResourceRecord> m_files; + + std::map<ResourceType, FilePredicate> m_predicates; + std::map<ResourceType, FileLoader> m_loaders; + std::map<std::string, std::vector<AssetID>> m_packs; + + void process_file(std::filesystem::path path, std::filesystem::path packDir, std::vector<AssetID> packFiles) { + for( auto& [rType, pred] : m_predicates ) { + // check the predicate, is this valid? + auto aType = pred(path); + if ( aType != INVALID_ASSET_TYPE ) { + // it was, so we can finish processing + auto packName = packDir.filename(); + auto relPath = std::filesystem::relative(path, packDir); + ResourceRecord rr{ + .m_path = path, + .assetID = make_asset_id(packName, relPath.generic_string()), + .m_fileType = rType, + .m_assetType = aType, + }; + m_files[rr.assetID] = rr; + packFiles.push_back( rr.assetID ); + debug::trace("discovered {} ({}) from pack '{}'", rr.assetID.get(), relPath.c_str(), packName.c_str() ); + break; + } + } + } + + inline bool has_manifest(const std::string& packName) { + auto packManifest = m_storage->resolvePath( data::StorageType::Cache, packName + "_manifest.bin" ); + return std::filesystem::exists(packManifest); + } + + void load_manifest_entry(FILE* file, const std::filesystem::path& packRoot) { + // read our entry ( id, ftype, atype, pathLen ) + ManifestHeader header{}; + std::fread(&header, sizeof(ManifestHeader), 1, file); + + // read the relative asset path + char* relPath = new char[header.stringSize + 1]; + std::fread( relPath, sizeof(char), header.stringSize, file ); + relPath[ header.stringSize + 1 ] = '\0'; + + // calculate and verify path + std::filesystem::path fullPath = packRoot / relPath; + delete[] relPath; + + if ( !std::filesystem::exists(fullPath) ) { + debug::warning("pack manifest for {} contained invalid path {}", packRoot.filename().c_str(), fullPath.c_str()); + return; + } + + // entry seems valid, load it + ResourceRecord rr { + .m_path = fullPath, + .assetID = AssetID::make(header.assetID), + .m_fileType = ResourceType::make(header.fileType), + .m_assetType = AssetTypeID::make(header.assetType) + }; + m_files[ rr.assetID ] = rr; + m_packs[ packRoot.filename() ].push_back( rr.assetID ); + debug::trace("discovered {} ({}) from pack {}", rr.assetID.get(), fullPath.c_str(), packRoot.filename().c_str() ); + } + + void load_manifest(const std::string& packName, const std::filesystem::path& packRoot) { + auto packManifest = m_storage->resolvePath( data::StorageType::Cache, packName + "_manifest.bin" ); + if ( !std::filesystem::exists(packManifest) ) { + return; + } + + // open the manifest file and start extracting entries + FILE* file = std::fopen(packManifest.c_str(), "r"); + if ( file == nullptr ) { + debug::warning("error opening manifest: {}", packManifest.c_str()); + return; + } + + // read the number of entries + uint64_t entries{0}; + std::fread(&entries, sizeof(uint64_t), 1, file); + for ( uint64_t i = 0; i < entries; ++i) { + load_manifest_entry(file, packRoot); + } + + std::fclose( file ); + } + + void save_manifest(const std::string& packName, const std::filesystem::path& packRoot, const std::vector<AssetID>& assets) { + auto packManifest = m_storage->resolvePath( data::StorageType::Cache, packName + "_manifest.bin", true); + + FILE* file = std::fopen(packManifest.c_str(), "w"); + if ( file == nullptr) { + debug::warning("error saving manifest {}, missing dir?", packManifest.c_str()); + return; + } + + const uint64_t entries{ assets.size() }; + std::fwrite( &entries, sizeof(uint64_t), 1, file); + + // write the entries + for ( uint64_t i = 0; i < entries; ++i ) { + auto& assetID = assets[i]; + + auto& assetRecord = m_files.at(assetID); + auto relPath = std::filesystem::relative(assetRecord.m_path, packRoot); + auto relPathStr = relPath.generic_string(); + + ManifestHeader mh { + .assetID = assetRecord.assetID.get(), + .fileType = assetRecord.m_fileType.get(), + .assetType = assetRecord.m_assetType.get(), + .stringSize = relPathStr.size() + }; + std::fwrite( &mh, sizeof(ManifestHeader), 1, file ); + std::fwrite( relPathStr.c_str(), sizeof(char), relPathStr.size(), file ); + } + + std::fclose(file); + } + }; + +} + +#endif //FGGL_ASSETS_PACKED_ADAPTER_HPP diff --git a/include/fggl/assets/packed/direct.hpp b/include/fggl/assets/packed/direct.hpp new file mode 100644 index 0000000000000000000000000000000000000000..faa01aedcd1ca28bc5c9abfec8ea7be3e69c8e37 --- /dev/null +++ b/include/fggl/assets/packed/direct.hpp @@ -0,0 +1,76 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 19/11/22. +// + +#ifndef FGGL_ASSETS_PACKED_DIRECT_HPP +#define FGGL_ASSETS_PACKED_DIRECT_HPP + +#include <functional> +#include <map> + +#include "fggl/assets/types.hpp" +#include "fggl/util/safety.hpp" +#include "fggl/util/guid.hpp" +#include "fggl/modules/module.hpp" + +/** + * Raw Checkin. + * + * This is a version of the checkin system where the check-in functions are shown a raw block of memory and its their + * job to parse and load something meaningful from that. + */ +namespace fggl::assets { + + using AssetID = util::OpaqueName<uint64_t, struct AssetIDTag>; + + constexpr AssetID make_asset_id(const std::string& pack, const std::string& path, const std::string& view = "") { + auto ref = pack + ":" + path; + if ( !view.empty() ) { + ref += "[" + view + "]"; + } + return AssetID::make( util::hash_fnv1a_64(ref.c_str()) ); + } + + using AssetTypeID = util::OpaqueName<uint64_t, struct AssetTypeTag>; + constexpr auto INVALID_ASSET_TYPE = AssetTypeID::make(0); + + constexpr AssetTypeID make_asset_type(const char* type) { + return AssetTypeID::make( util::hash_fnv1a_64(type) ); + } + + class RawCheckin { + public: + constexpr const static modules::ModuleService service = modules::make_service("fggl::assets::checkin"); + using DecodeAndCheckFunc = std::function<void(AssetGUID, MemoryBlock& block)>; + + void check(AssetID, AssetTypeID, MemoryBlock& block) const; + + inline void check(int64_t id, uint64_t type, MemoryBlock& block ) const { + check( AssetID::make(id), AssetTypeID::make(type), block ); + } + + inline void setCheckin(AssetTypeID type, DecodeAndCheckFunc func) { + m_check[type] = func; + } + + private: + std::map<AssetTypeID, DecodeAndCheckFunc> m_check; + }; + +} + +#endif //FGGL_ASSETS_PACKED_DIRECT_HPP diff --git a/include/fggl/assets/packed/module.hpp b/include/fggl/assets/packed/module.hpp new file mode 100644 index 0000000000000000000000000000000000000000..8ecc4b2583d2a5f20c2af511b4804b07073026af --- /dev/null +++ b/include/fggl/assets/packed/module.hpp @@ -0,0 +1,45 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 27/06/22. +// + +#ifndef FGGL_ASSETS_PACKED_MODULE_HPP +#define FGGL_ASSETS_PACKED_MODULE_HPP + +#include "fggl/modules/module.hpp" +#include "fggl/data/module.hpp" +#include "fggl/assets/loader.hpp" + +#include "fggl/assets/packed/adapter.hpp" +#include "fggl/assets/packed/direct.hpp" + +namespace fggl::assets { + + struct PackedAssets { + constexpr static const char *name = "fggl::assets::packed"; + constexpr static const std::array<modules::ModuleService, 2> provides = { + RawCheckin::service, + CheckinAdapted::service + }; + constexpr static const std::array<modules::ModuleService, 1> depends = { + data::Storage::service + }; + static bool factory(modules::ModuleService name, modules::Services &serviceManager); + }; + +} // namespace fggl::assets + +#endif //FGGL_ASSETS_PACKED_MODULE_HPP diff --git a/include/fggl/assets/packed/packed.hpp b/include/fggl/assets/packed/packed.hpp new file mode 100644 index 0000000000000000000000000000000000000000..4a6c0912dd907bfb56a7e7eace8ae873ef4dc8dd --- /dev/null +++ b/include/fggl/assets/packed/packed.hpp @@ -0,0 +1,80 @@ +/* + * This file is part of FGGL. + * + * FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with FGGL. + * If not, see <https://www.gnu.org/licenses/>. + */ + +// +// Created by webpigeon on 19/11/22. +// + +#ifndef FGGL_ASSETS_PACKED_PACKED_HPP +#define FGGL_ASSETS_PACKED_PACKED_HPP + +#include <cstdint> +#include <cstdio> +#include <memory> + +#include "fggl/assets/types.hpp" +#include "fggl/assets/packed/direct.hpp" + +/** + * Packed file reader. + * + * Read assets stored as sequential [header,data] blocks. This reader does not care about dependencies, it assumes this + * was handled before storage (ie, asset dependencies are assumed to be stored before the asset that relies on them). + * The caller is also responsible for ensuring that assets in other archives are already loaded by the time the system + * assembles the composite assets into something usable. + * + * If either of these constraints are violated, the results are undefined. + */ +namespace fggl::assets { + + struct Header { + uint64_t name; + uint64_t type; + std::size_t size; + }; + + bool read_header(std::FILE* stream, Header* header) { + constexpr auto headerSize = sizeof(Header); + auto readBytes = std::fread(header, headerSize, 1, stream); + return readBytes == headerSize; + } + + bool read_data(std::FILE* stream, void* block, std::size_t size) { + auto readBytes = std::fread(block, size, 1, stream); + return readBytes == size; + } + + void read_archive(RawCheckin* checkin, std::FILE* stream) { + while ( !std::feof(stream) ) { + Header header; + bool headRead = read_header(stream, &header); + if ( headRead && header.size != 0 ) { + // header has data + void* memBlock = std::malloc( header.size ); + bool valid = read_data( stream, memBlock, header.size ); + + // read the data, check it in + if (valid) { + MemoryBlock block{ + .data = (std::byte*)memBlock, + .size = header.size + }; + checkin->check(header.name, header.type, block); + } + } + } + } +} + +#endif //FGGL_ASSETS_PACKED_PACKED_HPP diff --git a/include/fggl/assets/types.hpp b/include/fggl/assets/types.hpp index 4b93825aee0042a3cbf81d599c93f2f2cef0ac0b..4abcdc702c521985a3f39361ac252e5833bf6f47 100644 --- a/include/fggl/assets/types.hpp +++ b/include/fggl/assets/types.hpp @@ -30,22 +30,19 @@ namespace fggl::assets { using AssetGUID = std::string; using AssetPath = std::filesystem::path; - struct Asset { - AssetType m_type; - virtual void release() = 0; - virtual bool active() = 0; - }; - struct MemoryBlock { - void *data; + std::byte *data; std::size_t size; + + template<typename T> + T* viewAs(std::size_t offset = 0) { + static_assert( std::is_standard_layout<T>::value ); + return (T*)( data[offset] ); + } }; using AssetRefRaw = std::shared_ptr<void>; - template<typename T> - using AssetRef = std::shared_ptr<T>; - class Loader; using AssetData = std::variant<MemoryBlock, AssetPath *, FILE *>; using Checkin = std::function<AssetRefRaw(Loader* loader, const AssetGUID &, const AssetData &, void* userPtr)>; diff --git a/include/fggl/audio/audio.hpp b/include/fggl/audio/audio.hpp index 1d9c91234b26f031b859247bcad7259cb873e2a5..67c8889b8fd80d889500e1b351da07d46f595d21 100644 --- a/include/fggl/audio/audio.hpp +++ b/include/fggl/audio/audio.hpp @@ -20,6 +20,7 @@ #include "fggl/data/storage.hpp" #include "fggl/modules/module.hpp" #include "fggl/assets/module.hpp" +#include "fggl/assets/packed/module.hpp" //! backend independent audio interface namespace fggl::audio { @@ -45,6 +46,9 @@ namespace fggl::audio { using AudioClipShort = AudioClip<short>; using AudioClipByte = AudioClip<char>; + constexpr auto ASSET_CLIP_SHORT = assets::make_asset_type("Audio:Clip:Short"); + constexpr auto ASSET_CLIP_BYTE = assets::make_asset_type("Audio:Clip:Byte"); + constexpr modules::ModuleService SERVICE_AUDIO_PLAYBACK = modules::make_service("fggl::audio::AudioService"); /** diff --git a/include/fggl/audio/openal/audio.hpp b/include/fggl/audio/openal/audio.hpp index b1d4bcfa759ba985701d2e01454a90b066ba8862..cf59cab2ce31253226ccd93edf45f8038856e43b 100644 --- a/include/fggl/audio/openal/audio.hpp +++ b/include/fggl/audio/openal/audio.hpp @@ -38,6 +38,9 @@ namespace fggl::audio::openal { assets::AssetRefRaw load_vorbis(assets::Loader* loader, const assets::AssetGUID &, const assets::AssetData &, void* userPtr); + bool load_vorbis_short(std::filesystem::path path, assets::MemoryBlock& block); + assets::AssetTypeID check_vorbis(std::filesystem::path path); + enum class AudioType { MONO_8 = AL_FORMAT_MONO8, MONO_16 = AL_FORMAT_MONO16, diff --git a/include/fggl/audio/openal/module.hpp b/include/fggl/audio/openal/module.hpp index 260ef675c7d244a8a659754ba63ea376fdc092c9..4ca559d7a17da573aeea85e388ed008aa26f812f 100644 --- a/include/fggl/audio/openal/module.hpp +++ b/include/fggl/audio/openal/module.hpp @@ -23,6 +23,7 @@ #include <string> #include "fggl/assets/module.hpp" +#include "fggl/assets/packed/module.hpp" #include "fggl/audio/audio.hpp" #include "fggl/audio/openal/audio.hpp" @@ -30,14 +31,16 @@ namespace fggl::audio { constexpr auto OGG_VORBIS = assets::AssetType::make("audio/vorbis"); + constexpr auto RES_OGG_VORBIS = assets::from_mime("audio/vorbis"); struct OpenAL { constexpr static const char *name = "fggl::audio::OpenAL"; constexpr static const std::array<modules::ModuleService, 1> provides = { SERVICE_AUDIO_PLAYBACK }; - constexpr static const std::array<modules::ModuleService, 1> depends = { - assets::AssetManager::service + constexpr static const std::array<modules::ModuleService, 2> depends = { + assets::AssetManager::service, + assets::CheckinAdapted::service }; static bool factory(modules::ModuleService name, modules::Services &serviceManager); }; diff --git a/include/fggl/data/assimp/module.hpp b/include/fggl/data/assimp/module.hpp index 5743dc6052c66f5c6dc87954c2587d639a3ee7fb..d5c40dbf56fdc0c77cee9bf7842ddc8766f3c9c7 100644 --- a/include/fggl/data/assimp/module.hpp +++ b/include/fggl/data/assimp/module.hpp @@ -21,6 +21,7 @@ #include "fggl/modules/module.hpp" #include "fggl/assets/loader.hpp" +#include "fggl/assets/packed/module.hpp" #include "fggl/data/texture.hpp" namespace fggl::data::models { @@ -28,13 +29,21 @@ namespace fggl::data::models { constexpr auto MODEL_PROVIDER = modules::make_service("fggl::data::Model"); constexpr auto ASSIMP_MODEL = assets::AssetType::make("model::assimp"); + constexpr auto MIME_JPG = assets::from_mime("image/jpeg"); + constexpr auto MIME_PNG = assets::from_mime("image/png"); + constexpr auto MIME_OBJ = assets::from_mime("model/obj"); + + constexpr auto MODEL_MULTI3D = assets::make_asset_type("model/multi3D"); + constexpr auto TEXTURE_RGBA = assets::make_asset_type("texture/rgba"); + struct AssimpModule { constexpr static const char *name = "fggl::data::Assimp"; constexpr static const std::array<modules::ModuleService, 1> provides = { MODEL_PROVIDER }; - constexpr static const std::array<modules::ModuleService, 1> depends = { - assets::Loader::service + constexpr static const std::array<modules::ModuleService, 2> depends = { + assets::Loader::service, + assets::CheckinAdapted::service }; static bool factory(modules::ModuleService service, modules::Services &serviceManager); }; diff --git a/include/fggl/gfx/ogl4/module.hpp b/include/fggl/gfx/ogl4/module.hpp index 9de8e7903a67d16c18e6ae05fbc051717c5140c0..abde2d309df64630823c95ec1fe306c7fa299771 100644 --- a/include/fggl/gfx/ogl4/module.hpp +++ b/include/fggl/gfx/ogl4/module.hpp @@ -24,6 +24,8 @@ #include "fggl/modules/module.hpp" #include "fggl/assets/manager.hpp" +#include "fggl/assets/packed/module.hpp" + #include "fggl/entity/loader/loader.hpp" #include "fggl/gfx/interfaces.hpp" @@ -39,9 +41,10 @@ namespace fggl::gfx { constexpr static const std::array<modules::ModuleService, 1> provides = { WindowGraphics::service }; - constexpr static const std::array<modules::ModuleService, 4> depends = { + constexpr static const std::array<modules::ModuleService, 5> depends = { data::Storage::service, assets::AssetManager::service, + assets::CheckinAdapted::service, gui::FontLibrary::service, entity::EntityFactory::service }; diff --git a/include/fggl/gui/module.hpp b/include/fggl/gui/module.hpp index e2abbd15f81dde1c8fd52118c02ef5ac4b8a8b7c..3e54d06f6210f001cb8b361257904f93f1f9d6c6 100644 --- a/include/fggl/gui/module.hpp +++ b/include/fggl/gui/module.hpp @@ -21,16 +21,28 @@ #include "fggl/gui/fonts.hpp" #include "fggl/data/module.hpp" +#include "fggl/assets/packed/module.hpp" namespace fggl::gui { + constexpr auto MIME_TTF = assets::from_mime("font/ttf"); + constexpr auto ASSET_FONT_TTF = assets::make_asset_type("font/ttf"); + + static assets::AssetTypeID is_font(std::filesystem::path path) { + if ( path.extension() == ".ttf" ){ + return ASSET_FONT_TTF; + } + return assets::INVALID_ASSET_TYPE; + } + struct FreeType { constexpr static const char *name = "fggl::gui::FreeType"; constexpr static const std::array<modules::ModuleService, 1> provides = { FontLibrary::service }; - constexpr static const std::array<modules::ModuleService, 1> depends = { - data::Storage::service + constexpr static const std::array<modules::ModuleService, 2> depends = { + data::Storage::service, + assets::CheckinAdapted::service }; static bool factory(modules::ModuleService name, modules::Services &serviceManager); }; @@ -39,6 +51,10 @@ namespace fggl::gui { if (service == FontLibrary::service) { auto storage = services.get<data::Storage>(); services.create<FontLibrary>(storage); + + auto* checkin = services.get<assets::CheckinAdapted>(); + checkin->setLoader( MIME_TTF, assets::NEEDS_CHECKIN, is_font ); + return true; } return false; diff --git a/tools/pack/src/binary.hpp b/tools/pack/src/binary.hpp index 8c52fdcf8ad0ec80b33c123bc784526b19b8e2f5..04f56a36450f6207bad3c123fc28a3bb92d77d06 100644 --- a/tools/pack/src/binary.hpp +++ b/tools/pack/src/binary.hpp @@ -30,8 +30,8 @@ // Created by webpigeon on 11/09/22. // -#ifndef FGGL_TOOLS_PACK_INCLUDES_BINARY_HPP -#define FGGL_TOOLS_PACK_INCLUDES_BINARY_HPP +#ifndef FGGL_TOOLS_PACK_SRC_BINARY_HPP +#define FGGL_TOOLS_PACK_SRC_BINARY_HPP #include <cstdint> #include <iostream> @@ -132,4 +132,4 @@ namespace fggl::data { } -#endif //FGGL_TOOLS_PACK_INCLUDES_BINARY_HPP +#endif //FGGL_TOOLS_PACK_SRC_BINARY_HPP