From 563e00c1f9556c2b5f5c9cb956a9d5bb8c26fa5e Mon Sep 17 00:00:00 2001 From: Egor Orachyov Date: Sat, 11 May 2024 13:18:30 +0300 Subject: [PATCH] gh-73: add shader library bytecode file cache (save/load) --- engine/core/date_time.cpp | 34 ++++++-- engine/core/date_time.hpp | 5 +- engine/gfx/gfx_defs.hpp | 9 +++ engine/gfx/gfx_pipeline.hpp | 12 +++ engine/gfx/vulkan/vk_driver.cpp | 20 ++--- engine/gfx/vulkan/vk_shader.cpp | 2 +- engine/grc/shader_interface.hpp | 12 +-- engine/grc/shader_library.cpp | 135 +++++++++++++++++++++++++++++++- engine/grc/shader_library.hpp | 9 ++- engine/io/archive_file.cpp | 4 +- engine/io/yaml.cpp | 9 +++ engine/io/yaml.hpp | 3 + engine/rtti/traits.hpp | 1 + engine/system/engine.cpp | 7 ++ engine/system/engine.hpp | 2 + template/main.cpp | 15 ++++ 16 files changed, 244 insertions(+), 35 deletions(-) diff --git a/engine/core/date_time.cpp b/engine/core/date_time.cpp index 4feaed5ba..4622f44db 100644 --- a/engine/core/date_time.cpp +++ b/engine/core/date_time.cpp @@ -55,14 +55,7 @@ namespace wmoge { } DateTimeTm DateTime::to_tm() const { - std::tm s_tm; - std::time_t s_time_t = to_time_t(); - -#if defined(TARGET_WINDOWS) - localtime_s(&s_tm, &s_time_t); -#else - localtime_r(&s_time_t, &s_tm); -#endif + std::tm s_tm = to_tm_t(); DateTimeTm tm{}; tm.year = s_tm.tm_year; @@ -78,6 +71,19 @@ namespace wmoge { return Clock::to_time_t(m_value); } + std::tm DateTime::to_tm_t() const { + std::tm s_tm; + std::time_t s_time_t = to_time_t(); + +#if defined(TARGET_WINDOWS) + localtime_s(&s_tm, &s_time_t); +#else + localtime_r(&s_time_t, &s_tm); +#endif + + return s_tm; + } + std::string DateTime::to_string() const { std::stringstream str; DateTimeTm tm = to_tm(); @@ -92,6 +98,18 @@ namespace wmoge { return str.str(); } + std::string DateTime::to_formatted(const std::string& format) const { + char buffer[128]; + std::tm tm = to_tm_t(); + strftime(buffer, sizeof(buffer), format.c_str(), &tm); + return buffer; + } + + std::string DateTime::to_pretty_string() const { + static const std::string format = "%Y.%m.%d %H:%M:%S"; + return to_formatted(format); + } + DateTime DateTime::now() { DateTime t; t.m_value = Clock::now(); diff --git a/engine/core/date_time.hpp b/engine/core/date_time.hpp index 95bd172f4..38fd62965 100644 --- a/engine/core/date_time.hpp +++ b/engine/core/date_time.hpp @@ -68,7 +68,10 @@ namespace wmoge { [[nodiscard]] DateTimeTm to_tm() const; [[nodiscard]] std::time_t to_time_t() const; + [[nodiscard]] std::tm to_tm_t() const; [[nodiscard]] std::string to_string() const; + [[nodiscard]] std::string to_formatted(const std::string& format) const; + [[nodiscard]] std::string to_pretty_string() const; [[nodiscard]] TimePoint get_time_point() const { return m_value; } @@ -84,7 +87,7 @@ namespace wmoge { }; inline std::ostream& operator<<(std::ostream& stream, const DateTime& dt) { - stream << dt.to_string(); + stream << dt.to_pretty_string(); return stream; } diff --git a/engine/gfx/gfx_defs.hpp b/engine/gfx/gfx_defs.hpp index d94222221..f2a489a33 100644 --- a/engine/gfx/gfx_defs.hpp +++ b/engine/gfx/gfx_defs.hpp @@ -75,6 +75,15 @@ namespace wmoge { "PLATFORM_METAL_MACOS", "PLATFORM_MAX"}; + static constexpr const char* GfxShaderPlatformFileName[] = { + "none", + "vk_linux", + "vk_windows", + "vk_macos", + "dx12_windows", + "metal_macos", + "max"}; + /** @brief Gfx common device limits */ struct GfxLimits { /** Vertex shader max input elements */ diff --git a/engine/gfx/gfx_pipeline.hpp b/engine/gfx/gfx_pipeline.hpp index 51e9dce57..a5f631dd4 100644 --- a/engine/gfx/gfx_pipeline.hpp +++ b/engine/gfx/gfx_pipeline.hpp @@ -52,6 +52,8 @@ namespace wmoge { GfxDescSetLayouts m_layouts; }; + using GfxPsoLayoutRef = Ref; + /** * @class GfxPsoStateGraphics * @brief Gfx pipeline state description @@ -105,6 +107,8 @@ namespace wmoge { ~GfxPso() override = default; }; + using GfxPsoRef = Ref; + /** * @class GfxPsoGraphics * @brief Represents created and compiled graphics pipeline state object @@ -116,6 +120,8 @@ namespace wmoge { ~GfxPsoGraphics() override = default; }; + using GfxPsoGraphicsRef = Ref; + /** * @class GfxPsoCompute * @brief Represents created and compiled compute pipeline state object @@ -127,6 +133,8 @@ namespace wmoge { ~GfxPsoCompute() override = default; }; + using GfxPsoComputeRef = Ref; + /** * @class GfxAsyncPsoRequestGraphics * @brief Request for async pso compilation @@ -140,6 +148,8 @@ namespace wmoge { buffered_vector, 1> pso; }; + using GfxAsyncPsoRequestGraphicsRef = Ref; + /** * @class GfxAsyncPsoRequestCompute * @brief Request for async pso compilation @@ -153,6 +163,8 @@ namespace wmoge { buffered_vector, 1> pso; }; + using GfxAsyncPsoRequestComputeRef = Ref; + }// namespace wmoge namespace std { diff --git a/engine/gfx/vulkan/vk_driver.cpp b/engine/gfx/vulkan/vk_driver.cpp index 9bd31fb75..4050a2c38 100644 --- a/engine/gfx/vulkan/vk_driver.cpp +++ b/engine/gfx/vulkan/vk_driver.cpp @@ -69,6 +69,16 @@ namespace wmoge { m_engine_name = info.engine_name; m_required_extensions = info.required_ext; +#ifdef TARGET_WINDOWS + m_shader_patform = GfxShaderPlatform::VulkanWindows; +#endif +#ifdef TARGET_LINUX + m_shader_patform = GfxShaderPlatform::VulkanLinux; +#endif +#ifdef TARGET_MACOS + m_shader_patform = GfxShaderPlatform::VulkanMacOS; +#endif + #ifndef WMOGE_RELEASE m_use_validation = m_config->get_bool(SID("gfx.vulkan.validation_layer"), true); #endif @@ -144,16 +154,6 @@ namespace wmoge { // finally init wrapper for ctx immediate m_ctx_immediate_wrapper = std::make_unique(m_ctx_immediate.get()); -#ifdef TARGET_WINDOWS - m_shader_patform = GfxShaderPlatform::VulkanWindows; -#endif -#ifdef TARGET_LINUX - m_shader_patform = GfxShaderPlatform::VulkanLinux; -#endif -#ifdef TARGET_MACOS - m_shader_patform = GfxShaderPlatform::VulkanMacOS; -#endif - WG_LOG_INFO("init vulkan gfx driver"); } VKDriver::~VKDriver() { diff --git a/engine/gfx/vulkan/vk_shader.cpp b/engine/gfx/vulkan/vk_shader.cpp index afaaa526b..867b7e58f 100644 --- a/engine/gfx/vulkan/vk_shader.cpp +++ b/engine/gfx/vulkan/vk_shader.cpp @@ -47,7 +47,7 @@ namespace wmoge { void VKShader::create(GfxShaderDesc desc) { WG_AUTO_PROFILE_VULKAN("VKShader::create"); - m_desc = std::move(m_desc); + m_desc = std::move(desc); VkShaderModuleCreateInfo create_info{}; create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; diff --git a/engine/grc/shader_interface.hpp b/engine/grc/shader_interface.hpp index 8f114a712..4aa88b780 100644 --- a/engine/grc/shader_interface.hpp +++ b/engine/grc/shader_interface.hpp @@ -95,12 +95,12 @@ namespace wmoge { [[nodiscard]] const Ref& get_pso_layout() const { return m_pso_layout; } protected: - ShaderReflection m_reflection; - ShaderCache m_cache; - ShaderCompilerEnv m_env; - buffered_vector> m_layouts; - Ref m_pso_layout; - flat_map m_cached_declarations; + ShaderReflection m_reflection; + ShaderCache m_cache; + ShaderCompilerEnv m_env; + GfxDescSetLayouts m_layouts; + GfxPsoLayoutRef m_pso_layout; + flat_map m_cached_declarations; mutable RwMutexReadPrefer m_mutex; }; diff --git a/engine/grc/shader_library.cpp b/engine/grc/shader_library.cpp index af16df0ad..398d78ae0 100644 --- a/engine/grc/shader_library.cpp +++ b/engine/grc/shader_library.cpp @@ -1,3 +1,4 @@ +#include "shader_library.hpp" /**********************************************************************************/ /* Wmoge game engine */ /* Available at github https://github.com/EgorOrachyov/wmoge */ @@ -27,10 +28,13 @@ #include "shader_library.hpp" +#include "core/date_time.hpp" #include "core/log.hpp" #include "gfx/gfx_driver.hpp" +#include "io/archive_file.hpp" #include "platform/file_system.hpp" #include "profiler/profiler.hpp" +#include "rtti/traits.hpp" #include "system/ioc_container.hpp" #include @@ -38,9 +42,46 @@ #include #include #include +#include namespace wmoge { + struct FileShaderModule { + WG_RTTI_STRUCT(FileShaderModule); + + Ref bytecode; + GfxShaderModule module_type; + Sha256 source_hash; + Sha256 bytecode_hash; + Strid name; + }; + + WG_RTTI_STRUCT_BEGIN(FileShaderModule) { + WG_RTTI_FIELD(bytecode, {}); + WG_RTTI_FIELD(module_type, {}); + WG_RTTI_FIELD(source_hash, {}); + WG_RTTI_FIELD(bytecode_hash, {}); + WG_RTTI_FIELD(name, {}); + } + WG_RTTI_END; + + struct FileShaderLibrary { + WG_RTTI_STRUCT(FileShaderLibrary); + + GfxShaderPlatform platform; + DateTime timestamp; + std::size_t total_size; + std::vector modules; + }; + + WG_RTTI_STRUCT_BEGIN(FileShaderLibrary) { + WG_RTTI_FIELD(platform, {}); + WG_RTTI_FIELD(timestamp, {}); + WG_RTTI_FIELD(total_size, {}); + WG_RTTI_FIELD(modules, {}); + } + WG_RTTI_END; + ShaderModuleMap::ShaderModuleMap() { m_driver = IocContainer::iresolve_v(); } @@ -90,9 +131,9 @@ namespace wmoge { return query->second.shader; } - void ShaderModuleMap::fit_module(const ShaderModule& module) { + void ShaderModuleMap::fit_module(ShaderModule& module) { ShaderModule& new_entry = m_modules[module.bytecode_hash]; - new_entry = module; + new_entry = std::move(module); } void ShaderModuleMap::dump_modules(std::vector& out_modules) { @@ -101,6 +142,11 @@ namespace wmoge { } } + ShaderLibrary::ShaderLibrary() { + rtti_type(); + rtti_type(); + } + Ref ShaderLibrary::get_or_create_shader(GfxShaderPlatform platform, GfxShaderModule module_type, const Sha256& bytecode_hash) { WG_AUTO_PROFILE_GRC("ShaderLibrary::get_or_create_shader"); @@ -125,7 +171,7 @@ namespace wmoge { return m_libraries[int(platform)].find_shader(module_type, bytecode_hash); } - void ShaderLibrary::fit_module(GfxShaderPlatform platform, const ShaderModule& module) { + void ShaderLibrary::fit_module(GfxShaderPlatform platform, ShaderModule& module) { WG_AUTO_PROFILE_GRC("ShaderLibrary::fit_module"); std::unique_lock lock(m_mutex); @@ -143,4 +189,85 @@ namespace wmoge { m_libraries[int(platform)].dump_modules(out_modules); } -}// namespace wmoge \ No newline at end of file + std::string ShaderLibrary::get_cache_file_name(const std::string& folder, const GfxShaderPlatform platform) { + std::stringstream file_name; + file_name << folder; + file_name << "/shader_library." << GfxShaderPlatformFileName[int(platform)] << ".slf"; + return file_name.str(); + } + + Status ShaderLibrary::load_cache(const std::string& folder, const GfxShaderPlatform platform) { + WG_AUTO_PROFILE_GRC("ShaderLibrary::load_cache"); + + const std::string file_path = get_cache_file_name(folder, platform); + ArchiveReaderFile archive; + IoContext context; + + if (!archive.open(file_path)) { + WG_LOG_ERROR("failed to open shader library " << file_path << " for platform " << Enum::to_str(platform)); + return StatusCode::FailedOpenFile; + } + + FileShaderLibrary library; + WG_ARCHIVE_READ(context, archive, library); + + std::unique_lock lock(m_mutex); + + if (library.platform != platform) { + WG_LOG_ERROR("mismatched platfrom in file " << file_path); + return StatusCode::InvalidState; + } + + for (FileShaderModule& file_module : library.modules) { + ShaderModule module; + module.bytecode = std::move(file_module.bytecode); + module.bytecode_hash = std::move(file_module.bytecode_hash); + module.source_hash = std::move(file_module.source_hash); + module.module_type = std::move(file_module.module_type); + module.name = std::move(file_module.name); + m_libraries[int(platform)].fit_module(module); + } + + WG_LOG_INFO("load " << file_path << " created=" << library.timestamp << " size=" << StringUtils::from_mem_size(library.total_size)); + + return WG_OK; + } + + Status ShaderLibrary::save_cache(const std::string& folder, const GfxShaderPlatform platform) { + WG_AUTO_PROFILE_GRC("ShaderLibrary::save_cache"); + + std::vector modules; + dump_modules(platform, modules); + + FileShaderLibrary library; + library.platform = platform; + library.timestamp = DateTime::now(); + library.total_size = 0; + library.modules.reserve(modules.size()); + + for (ShaderModule& module : modules) { + FileShaderModule& file_module = library.modules.emplace_back(); + file_module.bytecode = std::move(module.bytecode); + file_module.bytecode_hash = std::move(module.bytecode_hash); + file_module.source_hash = std::move(module.source_hash); + file_module.module_type = std::move(module.module_type); + file_module.name = std::move(module.name); + library.total_size += file_module.bytecode->size(); + } + + const std::string file_path = get_cache_file_name(folder, platform); + ArchiveWriterFile archive; + IoContext context; + + if (!archive.open(file_path)) { + WG_LOG_ERROR("failed to open shader library " << file_path << " for platform " << Enum::to_str(platform)); + return StatusCode::FailedOpenFile; + } + + WG_ARCHIVE_WRITE(context, archive, library); + WG_LOG_INFO("save " << file_path << " at=" << library.timestamp << " size=" << StringUtils::from_mem_size(library.total_size)); + + return WG_OK; + } + +}// namespace wmoge diff --git a/engine/grc/shader_library.hpp b/engine/grc/shader_library.hpp index ebcfce295..ea27aeb99 100644 --- a/engine/grc/shader_library.hpp +++ b/engine/grc/shader_library.hpp @@ -68,7 +68,7 @@ namespace wmoge { Ref get_or_create_shader(GfxShaderModule module_type, const Sha256& bytecode_hash); std::optional> find_shader(GfxShaderModule module_type, const Sha256& bytecode_hash); - void fit_module(const ShaderModule& module); + void fit_module(ShaderModule& module); void dump_modules(std::vector& out_modules); private: @@ -91,12 +91,15 @@ namespace wmoge { */ class ShaderLibrary { public: - ShaderLibrary() = default; + ShaderLibrary(); Ref get_or_create_shader(GfxShaderPlatform platform, GfxShaderModule module_type, const Sha256& bytecode_hash); std::optional> find_shader(GfxShaderPlatform platform, GfxShaderModule module_type, const Sha256& bytecode_hash); - void fit_module(GfxShaderPlatform platform, const ShaderModule& module); + void fit_module(GfxShaderPlatform platform, ShaderModule& module); void dump_modules(GfxShaderPlatform platform, std::vector& out_modules); + std::string get_cache_file_name(const std::string& folder, const GfxShaderPlatform platform); + Status load_cache(const std::string& folder, const GfxShaderPlatform platform); + Status save_cache(const std::string& folder, const GfxShaderPlatform platform); private: std::array m_libraries; diff --git a/engine/io/archive_file.cpp b/engine/io/archive_file.cpp index 27a21db62..a06c78a1e 100644 --- a/engine/io/archive_file.cpp +++ b/engine/io/archive_file.cpp @@ -109,8 +109,8 @@ namespace wmoge { FileSystem* file_system = IocContainer::iresolve_v(); WG_CHECKED(file_system->open_file(file_path, m_file, {FileOpenMode::In, FileOpenMode::Binary})); - m_can_read = false; - m_can_write = true; + m_can_read = true; + m_can_write = false; return WG_OK; } diff --git a/engine/io/yaml.cpp b/engine/io/yaml.cpp index 3a076f69b..5e0a27456 100644 --- a/engine/io/yaml.cpp +++ b/engine/io/yaml.cpp @@ -119,6 +119,15 @@ namespace wmoge { return StatusCode::Ok; } + Status yaml_read(IoContext& context, YamlConstNodeRef node, std::size_t& value) { + node >> value; + return StatusCode::Ok; + } + Status yaml_write(IoContext& context, YamlNodeRef node, const std::size_t& value) { + node << value; + return StatusCode::Ok; + } + Status yaml_read(IoContext& context, YamlConstNodeRef node, Status& value) { return yaml_read(context, node, value.code()); } diff --git a/engine/io/yaml.hpp b/engine/io/yaml.hpp index 25074e415..1b660c73c 100644 --- a/engine/io/yaml.hpp +++ b/engine/io/yaml.hpp @@ -106,6 +106,9 @@ namespace wmoge { Status yaml_read(IoContext& context, YamlConstNodeRef node, std::int16_t& value); Status yaml_write(IoContext& context, YamlNodeRef node, const std::int16_t& value); + Status yaml_read(IoContext& context, YamlConstNodeRef node, std::size_t& value); + Status yaml_write(IoContext& context, YamlNodeRef node, const std::size_t& value); + Status yaml_read(IoContext& context, YamlConstNodeRef node, Status& value); Status yaml_write(IoContext& context, YamlNodeRef node, const Status& value); diff --git a/engine/rtti/traits.hpp b/engine/rtti/traits.hpp index 806b64e5b..593b61cae 100644 --- a/engine/rtti/traits.hpp +++ b/engine/rtti/traits.hpp @@ -402,6 +402,7 @@ namespace wmoge { WG_RTTI_FUNDAMENTAL_DECL(unsigned int, "uint"); WG_RTTI_FUNDAMENTAL_DECL(float, "float"); WG_RTTI_FUNDAMENTAL_DECL(bool, "bool"); + WG_RTTI_FUNDAMENTAL_DECL(std::size_t, "size_t"); WG_RTTI_FUNDAMENTAL_DECL(std::string, "string"); WG_RTTI_FUNDAMENTAL_DECL(Strid, "strid"); WG_RTTI_FUNDAMENTAL_DECL(UUID, "uuid"); diff --git a/engine/system/engine.cpp b/engine/system/engine.cpp index 6da338ae3..8e330eb19 100644 --- a/engine/system/engine.cpp +++ b/engine/system/engine.cpp @@ -43,6 +43,7 @@ #include "gameplay/game_token_manager.hpp" #include "gfx/vulkan/vk_driver.hpp" #include "grc/pso_cache.hpp" +#include "grc/shader_library.hpp" #include "grc/shader_manager.hpp" #include "grc/texture_manager.hpp" #include "hooks/hook_config.hpp" @@ -135,6 +136,9 @@ namespace wmoge { m_shader_manager = ioc->resolve_v(); m_shader_manager->load_compilers(); + m_shader_library = ioc->resolve_v(); + m_shader_library->load_cache("cache://", m_gfx_driver->get_shader_platform()); + m_pso_cache = ioc->resolve_v(); m_texture_manager = ioc->resolve_v(); m_render_engine = ioc->resolve_v(); @@ -212,6 +216,8 @@ namespace wmoge { Status Engine::shutdown() { WG_AUTO_PROFILE_SYSTEM("Engine::shutdown"); + m_shader_library->save_cache("cache://", m_gfx_driver->get_shader_platform()); + m_plugin_manager->shutdown(); m_layer_stack->clear(); m_task_manager->shutdown(); @@ -249,6 +255,7 @@ namespace wmoge { GfxDriver* Engine::gfx_driver() { return m_gfx_driver; } GfxCtx* Engine::gfx_ctx() { return m_gfx_ctx; } ShaderManager* Engine::shader_manager() { return m_shader_manager; } + ShaderLibrary* Engine::shader_library() { return m_shader_library; } PsoCache* Engine::pso_cache() { return m_pso_cache; } TextureManager* Engine::texture_manager() { return m_texture_manager; } AuxDrawManager* Engine::aux_draw_manager() { return m_aux_draw_manager; } diff --git a/engine/system/engine.hpp b/engine/system/engine.hpp index 2849f7df8..d0820857a 100644 --- a/engine/system/engine.hpp +++ b/engine/system/engine.hpp @@ -77,6 +77,7 @@ namespace wmoge { class GfxDriver* gfx_driver(); class GfxCtx* gfx_ctx(); class ShaderManager* shader_manager(); + class ShaderLibrary* shader_library(); class PsoCache* pso_cache(); class TextureManager* texture_manager(); class AuxDrawManager* aux_draw_manager(); @@ -118,6 +119,7 @@ namespace wmoge { class GfxDriver* m_gfx_driver = nullptr; class GfxCtx* m_gfx_ctx = nullptr; class ShaderManager* m_shader_manager = nullptr; + class ShaderLibrary* m_shader_library = nullptr; class PsoCache* m_pso_cache = nullptr; class TextureManager* m_texture_manager = nullptr; class AuxDrawManager* m_aux_draw_manager = nullptr; diff --git a/template/main.cpp b/template/main.cpp index d60b300a7..dd1e36188 100644 --- a/template/main.cpp +++ b/template/main.cpp @@ -164,6 +164,7 @@ class TemplateApplication : public GameApplication { ShaderPermutation permutation; permutation.technique_idx = 0; permutation.pass_idx = 0; + permutation.options.set(0); permutation.vert_attribs = {GfxVertAttrib::Pos3f, GfxVertAttrib::Uv02f, GfxVertAttrib::Col04f}; @@ -178,6 +179,20 @@ class TemplateApplication : public GameApplication { permutation.vert_attribs = {GfxVertAttrib::Pos3f, GfxVertAttrib::Uv02f, GfxVertAttrib::Col04f, GfxVertAttrib::Col14f}; shader->get_or_create_program(GfxShaderPlatform::VulkanWindows, permutation); + permutation.options.set(1); + + permutation.vert_attribs = {GfxVertAttrib::Pos3f, GfxVertAttrib::Uv02f, GfxVertAttrib::Col04f}; + shader->get_or_create_program(GfxShaderPlatform::VulkanWindows, permutation); + + permutation.vert_attribs = {GfxVertAttrib::Pos2f, GfxVertAttrib::Uv02f, GfxVertAttrib::Col04f}; + shader->get_or_create_program(GfxShaderPlatform::VulkanWindows, permutation); + + permutation.vert_attribs = {GfxVertAttrib::Pos3f, GfxVertAttrib::Uv02f, GfxVertAttrib::Uv12f, GfxVertAttrib::Col04f}; + shader->get_or_create_program(GfxShaderPlatform::VulkanWindows, permutation); + + permutation.vert_attribs = {GfxVertAttrib::Pos3f, GfxVertAttrib::Uv02f, GfxVertAttrib::Col04f, GfxVertAttrib::Col14f}; + shader->get_or_create_program(GfxShaderPlatform::VulkanWindows, permutation); + static float angle = 0.0f; static int frame_count = 0;