diff --git a/modules/assets/CMakeLists.txt b/modules/assets/CMakeLists.txt new file mode 100644 index 0000000..4659ea3 --- /dev/null +++ b/modules/assets/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library_module(assets + shader.cpp +) + +target_link_libraries( +assets +PUBLIC +logger +lt_debug +) + +add_test_module(assets + shader.test.cpp +) diff --git a/modules/assets/private/shader.cpp b/modules/assets/private/shader.cpp new file mode 100644 index 0000000..2be2e09 --- /dev/null +++ b/modules/assets/private/shader.cpp @@ -0,0 +1,73 @@ +#include + +namespace lt::assets { + +ShaderAsset::ShaderAsset(const std::filesystem::path &path) + : m_stream(path, std::ios::binary | std::ios::beg) +{ + constexpr auto total_metadata_size = // + sizeof(AssetMetadata) // + + sizeof(Metadata) // + + sizeof(BlobMetadata); + + ensure(m_stream.is_open(), "Failed to open shader asset at: {}", path.string()); + + m_stream.seekg(0, std::ifstream::end); + const auto file_size = static_cast(m_stream.tellg()); + + ensure( + file_size > total_metadata_size, + "Failed to open shader asset at: {}, file smaller than metadata: {} < {}", + path.string(), + total_metadata_size, + file_size + ); + + // NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast) + m_stream.seekg(0, std::ifstream::beg); + m_stream.read((char *)&m_asset_metadata, sizeof(m_asset_metadata)); + m_stream.read((char *)&m_metadata, sizeof(m_metadata)); + m_stream.read((char *)&m_code_blob_metadata, sizeof(m_code_blob_metadata)); + // NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast) + + ensure( + m_asset_metadata.type == asset_type_identifier, + "Failed to open shader asset at: {}, incorrect asset type: {} != {}", + path.string(), + m_asset_metadata.type, + asset_type_identifier + ); + + ensure( + m_asset_metadata.version == current_version, + "Failed to open shader asset at: {}, version mismatch: {} != {}", + path.string(), + m_asset_metadata.version, + current_version + ); + + ensure( + std::to_underlying(m_metadata.type) <= std::to_underlying(Type::compute), + "Failed to open shader asset at: {}, invalid shader type: {}", + path.string(), + std::to_underlying(m_metadata.type) + ); + + ensure( + m_code_blob_metadata.tag == std::to_underlying(BlobTag::code), + "Failed to open shader asset at: {}, invalid blob tag: {}", + path.string(), + m_code_blob_metadata.tag + ); + + ensure( + m_code_blob_metadata.offset + m_code_blob_metadata.compressed_size <= file_size, + "Failed to open shader asset at: {}, file smaller than blob: {} > {} + {}", + path.string(), + file_size, + m_code_blob_metadata.offset, + m_code_blob_metadata.compressed_size + ); +} + +} // namespace lt::assets diff --git a/modules/assets/private/shader.test.cpp b/modules/assets/private/shader.test.cpp new file mode 100644 index 0000000..9b59a88 --- /dev/null +++ b/modules/assets/private/shader.test.cpp @@ -0,0 +1,89 @@ +#include +#include +#include + +using ::lt::assets::AssetMetadata; +using ::lt::assets::BlobMetadata; +using ::lt::assets::ShaderAsset; +using ::lt::test::Case; +using ::lt::test::expect_eq; +using ::lt::test::expect_throw; +using ::lt::test::expect_true; +using ::lt::test::Suite; + +const auto test_data_path = std::filesystem::path { "./data/test_assets" }; +const auto tmp_path = std::filesystem::path { "/tmp/lt_assets_tests/" }; + +Suite raii = "shader_raii"_suite = [] { + std::filesystem::current_path(test_data_path); + std::filesystem::create_directories(tmp_path); + + Case { "happy path won't throw" } = [] { + + }; + + Case { "many won't freeze/throw" } = [] { + }; + + Case { "unhappy path throws" } = [] { + expect_throw([] { ShaderAsset { "random_path" }; }); + }; +}; + +// NOLINTNEXTLINE(cppcoreguidelines-interfaces-global-init) +Suite packing = "shader_pack"_suite = [] { + Case { "" } = [] { + const auto out_path = tmp_path / "shader_packing"; + auto dummy_blob = lt::assets::Blob {}; + for (auto idx : std::views::iota(0, 255)) + { + dummy_blob.emplace_back(static_cast(idx)); + } + + const auto expected_size = // + sizeof(AssetMetadata) // + + sizeof(ShaderAsset::Metadata) // + + sizeof(BlobMetadata) // + + dummy_blob.size(); + + ShaderAsset::pack( + out_path, + lt::assets::AssetMetadata { + .version = lt::assets::current_version, + .type = ShaderAsset::asset_type_identifier, + }, + ShaderAsset::Metadata { + .type = ShaderAsset::Type::vertex, + }, + std::move(dummy_blob) + ); + + auto stream = std::ifstream { + out_path, + std::ios::binary | std::ios::beg, + }; + expect_true(stream.is_open()); + + stream.seekg(0, std::ios::end); + const auto file_size = static_cast(stream.tellg()); + expect_eq(file_size, expected_size); + stream.close(); + + auto shader_asset = ShaderAsset { out_path }; + + const auto &asset_metadata = shader_asset.get_asset_metadata(); + expect_eq(asset_metadata.type, ShaderAsset::asset_type_identifier); + expect_eq(asset_metadata.version, lt::assets::current_version); + + const auto &metadata = shader_asset.get_metadata(); + expect_eq(metadata.type, ShaderAsset::Type::vertex); + + auto blob = shader_asset.unpack(ShaderAsset::BlobTag::code); + expect_eq(blob.size(), 255); + + for (auto idx : std::views::iota(0, 255)) + { + expect_eq(blob[idx], static_cast(idx)); + } + }; +}; diff --git a/modules/assets/public/compressors/lz4.hpp b/modules/assets/public/compressors/lz4.hpp new file mode 100644 index 0000000..40a1e26 --- /dev/null +++ b/modules/assets/public/compressors/lz4.hpp @@ -0,0 +1,3 @@ +#pragma once + +// TO BE DOOO diff --git a/modules/assets/public/metadata.hpp b/modules/assets/public/metadata.hpp new file mode 100644 index 0000000..4dd7e6a --- /dev/null +++ b/modules/assets/public/metadata.hpp @@ -0,0 +1,42 @@ +#pragma once + +namespace lt::assets { + +using Type_T = std::array; + +using Tag_T = uint8_t; + +using Version = uint8_t; + +using Blob = std::vector; + +constexpr auto current_version = Version { 1u }; + +enum class CompressionType : uint8_t +{ + none, + lz4, + lz4_hc, +}; + +struct AssetMetadata +{ + Version version; + + Type_T type; +}; + +struct BlobMetadata +{ + Tag_T tag; + + size_t offset; + + CompressionType compression_type; + + size_t compressed_size; + + size_t uncompressed_size; +}; + +} // namespace lt::assets diff --git a/modules/assets/public/shader.hpp b/modules/assets/public/shader.hpp new file mode 100644 index 0000000..ab68014 --- /dev/null +++ b/modules/assets/public/shader.hpp @@ -0,0 +1,132 @@ +#pragma once + +#include + +namespace lt::assets { + +class ShaderAsset +{ +public: + static constexpr auto asset_type_identifier = Type_T { "SHADER_________" }; + + enum class BlobTag : Tag_T + { + code, + }; + + enum class Type : uint8_t + { + vertex, + fragment, + geometry, + compute, + }; + + struct Metadata + { + Type type; + }; + + static void pack( + const std::filesystem::path &destination, + AssetMetadata asset_metadata, + Metadata metadata, + Blob code_blob + ) + { + auto stream = std::ofstream { + destination, + std::ios::binary | std::ios::trunc, + }; + ensure(stream.is_open(), "Failed to pack shader asset to {}", destination.string()); + + // NOLINTBEGIN(cppcoreguidelines-pro-type-cstyle-cast) + stream.write((char *)&asset_metadata, sizeof(asset_metadata)); + stream.write((char *)&metadata, sizeof(metadata)); + + auto code_blob_metadata = BlobMetadata { + .tag = std::to_underlying(BlobTag::code), + .offset = static_cast(stream.tellp()) + sizeof(BlobMetadata), + .compression_type = CompressionType::none, + .compressed_size = code_blob.size(), + .uncompressed_size = code_blob.size(), + }; + stream.write((char *)&code_blob_metadata, sizeof(BlobMetadata)); + + stream.write((char *)code_blob.data(), static_cast(code_blob.size())); + // NOLINTEND(cppcoreguidelines-pro-type-cstyle-cast) + } + + ShaderAsset(const std::filesystem::path &path); + + [[nodiscard]] auto get_asset_metadata() const -> const AssetMetadata & + { + return m_asset_metadata; + } + + [[nodiscard]] auto get_metadata() const -> const Metadata & + { + return m_metadata; + } + + [[nodiscard]] auto get_blob_metadata(BlobTag tag) const -> const BlobMetadata & + { + ensure( + tag == BlobTag::code, + "Invalid blob tag for shader asset: {}", + std::to_underlying(tag) + ); + + return m_code_blob_metadata; + } + + void unpack_to(BlobTag tag, std::span destination) const + { + ensure( + tag == BlobTag::code, + "Invalid blob tag for shader asset: {}", + std::to_underlying(tag) + ); + + ensure( + destination.size() >= m_code_blob_metadata.uncompressed_size, + "Failed to unpack shader blob {} to destination ({}) of size {} since it's smaller " + "than the blobl's uncompressed size: {}", + std::to_underlying(tag), + (size_t)(destination.data()), // NOLINT(cppcoreguidelines-pro-type-cstyle-cast) + destination.size(), + m_code_blob_metadata.uncompressed_size + ); + + m_stream.seekg(static_cast(m_code_blob_metadata.offset)); + m_stream.read( + (char *)destination.data(), // NOLINT(cppcoreguidelines-pro-type-cstyle-cast) + static_cast(m_code_blob_metadata.uncompressed_size) + ); + } + + [[nodiscard]] auto unpack(BlobTag tag) const -> Blob + { + ensure( + tag == BlobTag::code, + "Invalid blob tag for shader asset: {}", + std::to_underlying(tag) + ); + + auto blob = Blob(m_code_blob_metadata.uncompressed_size); + unpack_to(tag, blob); + + return blob; + } + +private: + AssetMetadata m_asset_metadata {}; + + Metadata m_metadata {}; + + BlobMetadata m_code_blob_metadata {}; + + mutable std::ifstream m_stream; +}; + +} // namespace lt::assets