diff --git a/Resources/Editor/Settings/EnvironmentMaps/bergen_4k.zenvmap b/Resources/Editor/Settings/EnvironmentMaps/bergen_4k.zenvmap new file mode 100644 index 00000000..5148003d Binary files /dev/null and b/Resources/Editor/Settings/EnvironmentMaps/bergen_4k.zenvmap differ diff --git a/Tetragrama/Components/DockspaceUIComponent.cpp b/Tetragrama/Components/DockspaceUIComponent.cpp index b908627e..4d4b707f 100644 --- a/Tetragrama/Components/DockspaceUIComponent.cpp +++ b/Tetragrama/Components/DockspaceUIComponent.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -14,15 +15,19 @@ using namespace ZEngine::Helpers; namespace Tetragrama::Components { - ImVec4 DockspaceUIComponent::s_asset_importer_report_msg_color = {1, 1, 1, 1}; - char DockspaceUIComponent::s_asset_importer_input_buffer[1024] = {0}; - char DockspaceUIComponent::s_save_as_input_buffer[1024] = {0}; - std::string DockspaceUIComponent::s_asset_importer_report_msg = ""; - float DockspaceUIComponent::s_editor_scene_serializer_progress = 0.0f; + ImVec4 DockspaceUIComponent::s_asset_importer_report_msg_color = {1, 1, 1, 1}; + char DockspaceUIComponent::s_asset_importer_input_buffer[1024] = {0}; + char DockspaceUIComponent::s_save_as_input_buffer[1024] = {0}; + std::string DockspaceUIComponent::s_asset_importer_report_msg = ""; + float DockspaceUIComponent::s_editor_scene_serializer_progress = 0.0f; - static bool s_is_scene_loading = false; - static char s_scene_serializer_log[DEFAULT_STR_BUFFER] = {0}; - static ImVec4 s_scene_serializer_log_color = {1, 1, 1, 1}; + ImVec4 DockspaceUIComponent::s_env_map_importer_report_msg_color = {1, 1, 1, 1}; + char DockspaceUIComponent::s_env_map_importer_input_buffer[1024] = {0}; + std::string DockspaceUIComponent::s_env_map_importer_report_msg = ""; + + static bool s_is_scene_loading = false; + static char s_scene_serializer_log[DEFAULT_STR_BUFFER] = {0}; + static ImVec4 s_scene_serializer_log_color = {1, 1, 1, 1}; DockspaceUIComponent::DockspaceUIComponent() {} @@ -35,10 +40,12 @@ namespace Tetragrama::Components parent->LocalArena.CreateSubArena(ZMega(1), &LocalArena); m_asset_importer = ZPushStructCtor(parent->Arena, ZEngine::Importers::AssimpImporter); + m_env_map_importer = ZPushStructCtor(parent->Arena, ZEngine::Importers::EnvironmentMapImporter); m_editor_serializer = ZPushStructCtor(parent->Arena, Serializers::EditorSceneSerializer); m_editor_serializer->Initialize(parent->Arena); m_asset_importer->Initialize(parent->Arena); + m_env_map_importer->Initialize(parent->Arena); m_dockspace_node_flag = ImGuiDockNodeFlags_NoWindowMenuButton | static_cast(ImGuiDockNodeFlags_PassthruCentralNode); m_window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; @@ -46,6 +53,7 @@ namespace Tetragrama::Components auto app = reinterpret_cast(ParentLayer->CurrentApp); m_editor_serializer->Context = app; m_asset_importer->Context = app; + m_env_map_importer->Context = app; auto editor_serializer_default_output = fmt::format("{0}{1}{2}", app->Configuration->WorkingSpacePath.c_str(), PLATFORM_OS_BACKSLASH, app->Configuration->ScenePath.c_str()); @@ -60,6 +68,11 @@ namespace Tetragrama::Components m_asset_importer->SetOnProgressCallback(OnAssetImporterProgress); m_asset_importer->SetOnLogCallback(OnAssetImporterLog); m_asset_importer->SetOnErrorCallback(OnAssetImporterError); + + m_env_map_importer->SetOnCompleteCallback(OnEnvMapImporterComplete); + m_env_map_importer->SetOnProgressCallback(OnEnvMapImporterProgress); + m_env_map_importer->SetOnLogCallback(OnEnvMapImporterLog); + m_env_map_importer->SetOnErrorCallback(OnEnvMapImporterError); } void DockspaceUIComponent::Update(ZEngine::Core::TimeStep dt) {} @@ -120,6 +133,7 @@ namespace Tetragrama::Components RenderSaveSceneAs(); RenderImporter(); + RenderEnvironmentMapImporter(); RenderExitPopup(); @@ -423,6 +437,158 @@ namespace Tetragrama::Components ZEngine::Helpers::secure_memset(s_asset_importer_input_buffer, 0, IM_ARRAYSIZE(s_asset_importer_input_buffer), IM_ARRAYSIZE(s_asset_importer_input_buffer)); } + void DockspaceUIComponent::RenderEnvironmentMapImporter() + { + if (!m_open_env_map_importer) + { + std::string_view buffer_view = s_env_map_importer_input_buffer; + if (!buffer_view.empty()) + { + ResetEnvironmentMapImporterBuffers(); + } + return; + } + + const char* str_id = "Environment Map Importer"; + ImGui::OpenPopup(str_id); + ImVec2 center = ImGui::GetMainViewport()->GetCenter(); + ImGui::SetNextWindowPos(center, ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::SetNextWindowSize(ImVec2(700, 100), ImGuiCond_Always); + + if (ImGui::BeginPopupModal(str_id, NULL, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::PushItemWidth(620); + ImGui::InputText("##EnvMapImporterUI", s_env_map_importer_input_buffer, IM_ARRAYSIZE(s_env_map_importer_input_buffer), ImGuiInputTextFlags_ReadOnly); + ImGui::PopItemWidth(); + + ImGui::SameLine(); + + if (ImGui::Button("...", ImVec2(50, 0))) + { + Helpers::UIDispatcher::RunAsync([this]() -> std::future { + if (ParentLayer && ParentLayer->CurrentApp) + { + auto window = ParentLayer->CurrentApp->CurrentWindow; + std::vector filters{".hdr", ".exr"}; + std::string filename = co_await window->OpenFileDialogAsync(filters); + + if (!filename.empty()) + { + ZEngine::Helpers::secure_memset(s_env_map_importer_input_buffer, 0, IM_ARRAYSIZE(s_env_map_importer_input_buffer), IM_ARRAYSIZE(s_env_map_importer_input_buffer)); + ZEngine::Helpers::secure_memcpy(s_env_map_importer_input_buffer, IM_ARRAYSIZE(s_env_map_importer_input_buffer), filename.c_str(), filename.size()); + } + } + }); + } + + ImGui::Separator(); + + ImGui::SetCursorPosX(ImGui::GetWindowSize().x - 180); + ImGui::SetCursorPosY(ImGui::GetWindowSize().y - ImGui::GetFrameHeightWithSpacing() - 5); + + bool is_import_button_enabled = !std::string_view(s_env_map_importer_input_buffer).empty() && !m_env_map_importer->IsImporting(); + + if (!is_import_button_enabled) + { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.5f, 0.5f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.5f, 0.5f, 0.5f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.5f, 0.5f, 0.5f, 1.0f)); + } + + if (ImGui::Button("Import", ImVec2(80, 0)) && is_import_button_enabled) + { + Helpers::UIDispatcher::RunAsync([this]() -> std::future { co_await OnImportEnvironmentMapAsync(s_env_map_importer_input_buffer); }); + } + + if (!is_import_button_enabled) + { + ImGui::PopStyleColor(3); + } + + if (m_env_map_importer->IsImporting()) + { + ImGui::SameLine(); + ImGui::TextDisabled("(importing...)"); + } + + ImGui::SameLine(); + if (ImGui::Button("Close", ImVec2(80, 0))) + { + m_open_env_map_importer = false; + ResetEnvironmentMapImporterBuffers(); + ImGui::CloseCurrentPopup(); + } + + ImGui::PushFont(ImGui::GetIO().Fonts->Fonts[0]); + ImGui::SetCursorPos(ImVec2(10, ImGui::GetWindowSize().y - 30)); + ImGui::TextColored(s_env_map_importer_report_msg_color, "%s", s_env_map_importer_report_msg.c_str()); + ImGui::PopFont(); + + ImGui::EndPopup(); + } + } + + void DockspaceUIComponent::ResetEnvironmentMapImporterBuffers() + { + s_env_map_importer_report_msg = ""; + s_env_map_importer_report_msg_color = {1.0f, 1.0f, 1.0f, 1.0f}; + ZEngine::Helpers::secure_memset(s_env_map_importer_input_buffer, 0, IM_ARRAYSIZE(s_env_map_importer_input_buffer), IM_ARRAYSIZE(s_env_map_importer_input_buffer)); + } + + std::future DockspaceUIComponent::OnImportEnvironmentMapAsync(const char* filename) + { + if (ZEngine::Helpers::secure_strlen(filename) == 0 || m_env_map_importer->IsImporting()) + { + co_return; + } + + LocalArena.Clear(); + + auto app = reinterpret_cast(ParentLayer->CurrentApp); + auto arena = &LocalArena; + auto asset_name = fs::path(filename).filename().replace_extension().string(); + auto output_file = fmt::format("{}.zenvmap", asset_name.c_str()); + + auto config = ZPushStruct(arena, ZEngine::Importers::ImportConfiguration); + config->OutputWorkingSpacePath.init(arena, app->Configuration->WorkingSpacePath.c_str()); + config->OutputAssetsPath.init(arena, "Settings/EnvironmentMaps"); + config->AssetName.init(arena, asset_name.c_str()); + config->OutputAssetFile.init(arena, output_file.c_str()); + + s_env_map_importer_report_msg_color = {1.0f, 1.0f, 1.0f, 1.0f}; + s_env_map_importer_report_msg = "Importing..."; + + m_env_map_importer->ImportAsync(filename, *config); + co_return; + } + + void DockspaceUIComponent::OnEnvMapImporterComplete(void* const, ZEngine::Core::Containers::ArrayView result) + { + if (result.size() > 0) + { + s_env_map_importer_report_msg_color = {0.0f, 1.0f, 0.0f, 1.0f}; + s_env_map_importer_report_msg = fmt::format("Saved"); + } + } + + void DockspaceUIComponent::OnEnvMapImporterProgress(void* const, float value) + { + s_env_map_importer_report_msg_color = {1.0f, 1.0f, 1.0f, 1.0f}; + s_env_map_importer_report_msg = fmt::format("Progress: {:.0f}%%", value * 100.f); + } + + void DockspaceUIComponent::OnEnvMapImporterError(void* const, std::string_view msg) + { + s_env_map_importer_report_msg_color = {1.0f, 0.0f, 0.0f, 1.0f}; + s_env_map_importer_report_msg = msg; + } + + void DockspaceUIComponent::OnEnvMapImporterLog(void* const, std::string_view msg) + { + s_env_map_importer_report_msg_color = {1.0f, 1.0f, 1.0f, 1.0f}; + s_env_map_importer_report_msg = msg; + } + void DockspaceUIComponent::ResetSaveAsBuffers() { ZEngine::Helpers::secure_memset(s_save_as_input_buffer, 0, IM_ARRAYSIZE(s_save_as_input_buffer), IM_ARRAYSIZE(s_save_as_input_buffer)); @@ -516,6 +682,8 @@ namespace Tetragrama::Components if (ImGui::MenuItem("Renderer")) { } + + ImGui::MenuItem("Import Environment Map", NULL, &m_open_env_map_importer); ImGui::EndMenu(); } diff --git a/Tetragrama/Components/DockspaceUIComponent.h b/Tetragrama/Components/DockspaceUIComponent.h index 40189a1c..44438b54 100644 --- a/Tetragrama/Components/DockspaceUIComponent.h +++ b/Tetragrama/Components/DockspaceUIComponent.h @@ -34,6 +34,17 @@ namespace Tetragrama::Components static void OnAssetImporterError(void* const, std::string_view); static void OnAssetImporterLog(void* const, std::string_view); + /* + * Environment Map Importer Funcs + */ + void RenderEnvironmentMapImporter(); + void ResetEnvironmentMapImporterBuffers(); + std::future OnImportEnvironmentMapAsync(const char* filename); + static void OnEnvMapImporterComplete(void* const context, ZEngine::Core::Containers::ArrayView result); + static void OnEnvMapImporterProgress(void* const, float value); + static void OnEnvMapImporterError(void* const, std::string_view); + static void OnEnvMapImporterLog(void* const, std::string_view); + /* * Editor Scene Funcs */ @@ -60,8 +71,13 @@ namespace Tetragrama::Components static char s_save_as_input_buffer[1024]; static float s_editor_scene_serializer_progress; + static ImVec4 s_env_map_importer_report_msg_color; + static std::string s_env_map_importer_report_msg; + static char s_env_map_importer_input_buffer[1024]; + private: bool m_open_asset_importer{false}; + bool m_open_env_map_importer{false}; bool m_open_exit{false}; bool m_pending_shutdown{false}; bool m_open_save_scene{false}; @@ -71,6 +87,7 @@ namespace Tetragrama::Components ImGuiWindowFlags m_window_flags; ZEngine::Importers::ImportConfiguration m_default_import_configuration; ZRawPtr(ZEngine::Importers::IAssetImporter) m_asset_importer; + ZRawPtr(ZEngine::Importers::IAssetImporter) m_env_map_importer; ZRawPtr(Serializers::EditorSceneSerializer) m_editor_serializer; }; } // namespace Tetragrama::Components diff --git a/ZEngine/ZEngine/Hardwares/AsyncResourceLoader.cpp b/ZEngine/ZEngine/Hardwares/AsyncResourceLoader.cpp index bdddef40..1bdf4427 100644 --- a/ZEngine/ZEngine/Hardwares/AsyncResourceLoader.cpp +++ b/ZEngine/ZEngine/Hardwares/AsyncResourceLoader.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -461,31 +462,52 @@ namespace ZEngine::Hardwares Textures::TextureHandle AsyncResourceLoader::Submit(uint8_t frame_index, uint8_t thread_index, const UploadRequest& request) { - std::unique_lock l(m_mutex); + std::unique_lock l(m_mutex); - auto abs_filename = std::filesystem::absolute(request.TextureUpload.Filename).string(); + auto abs_filename = std::filesystem::absolute(request.TextureUpload.Filename).string(); + auto file_ext = std::filesystem::path(abs_filename).extension().string(); - int w, h, ch; - if (!stbi_info(abs_filename.c_str(), &w, &h, &ch)) - { - return {}; - } - - const std::set known_cubmap_file_ext = {".hdr", ".exr"}; - auto file_ext = std::filesystem::path(request.TextureUpload.Filename).extension().string(); - - Specifications::TextureSpecification spec{.Width = (uint32_t) w, .Height = (uint32_t) h, .Format = Specifications::ImageFormat::R8G8B8A8_SRGB}; + Specifications::TextureSpecification spec{}; - if (known_cubmap_file_ext.contains(file_ext)) + if (file_ext == ".zenvmap") { - int face_size = w / 4; + Importers::EnvironmentMapFileHeader env_header{}; + if (!Importers::EnvironmentMapImporter::ReadEnvironmentMapFileHeader(abs_filename.c_str(), env_header)) + { + ZENGINE_CORE_ERROR("Failed to read .zenvmap header: {}", abs_filename) + return {}; + } spec.IsCubemap = true; - spec.LayerCount = 6; + spec.LayerCount = static_cast(env_header.LayerCount); spec.Format = Specifications::ImageFormat::R32G32B32A32_SFLOAT; + spec.Width = static_cast(env_header.FaceWidth); + spec.Height = static_cast(env_header.FaceHeight); + } + else + { + int w, h, ch; + if (!stbi_info(abs_filename.c_str(), &w, &h, &ch)) + { + return {}; + } + + const std::set known_cubmap_file_ext = {".hdr", ".exr"}; + spec.Width = static_cast(w); + spec.Height = static_cast(h); + spec.Format = Specifications::ImageFormat::R8G8B8A8_SRGB; + + if (known_cubmap_file_ext.contains(file_ext)) + { + int face_size = w / 4; + + spec.IsCubemap = true; + spec.LayerCount = 6; + spec.Format = Specifications::ImageFormat::R32G32B32A32_SFLOAT; - spec.Width = face_size; - spec.Height = face_size; + spec.Width = static_cast(face_size); + spec.Height = static_cast(face_size); + } } TextureFileRequest tex_file_req = {}; @@ -602,61 +624,77 @@ namespace ZEngine::Hardwares if (file_request.TextureSpec.IsCubemap) { - const float* image_data = stbi_loadf(file_request.Filename.data(), &width, &height, &channel, 4); - if (!image_data) - { - ZENGINE_CORE_ERROR("Failed to load texture file : {0}", file_request.Filename.data()) - continue; - } + auto cubemap_ext = std::filesystem::path(file_request.Filename.data()).extension().string(); - bool perform_convert_rgb_to_rgba = (channel == STBI_rgb); + if (cubemap_ext == ".zenvmap") + { + Buffers::Bitmap cubemap{}; + if (!Importers::EnvironmentMapImporter::DeserializeEnvironmentMapFile(file_request.Filename.data(), cubemap)) + { + ZENGINE_CORE_ERROR("Failed to deserialize .zenvmap: {}", file_request.Filename.data()) + continue; + } - std::vector output_buffer = {}; - if (perform_convert_rgb_to_rgba) + size_t buffer_byte = cubemap.Buffer.size() * sizeof(uint8_t); + upload_req.Buffer.resize(cubemap.Buffer.size()); + Helpers::secure_memmove(upload_req.Buffer.data(), buffer_byte, cubemap.Buffer.data(), buffer_byte); + } + else { - size_t total_pixel = width * height; - size_t buffer_size = total_pixel * 4; - output_buffer.resize(buffer_size); - stbir_resize_float(image_data, width, height, 0, output_buffer.data(), width, height, 0, 4); + const float* image_data = stbi_loadf(file_request.Filename.data(), &width, &height, &channel, 4); + if (!image_data) + { + ZENGINE_CORE_ERROR("Failed to load texture file : {0}", file_request.Filename.data()) + continue; + } - for (int i = 0; i < total_pixel; ++i) + bool perform_convert_rgb_to_rgba = (channel == STBI_rgb); + + std::vector output_buffer = {}; + if (perform_convert_rgb_to_rgba) { - int offset = i * 4; // RGBA format (4 channels) + size_t total_pixel = width * height; + size_t buffer_size = total_pixel * 4; + output_buffer.resize(buffer_size); + stbir_resize_float(image_data, width, height, 0, output_buffer.data(), width, height, 0, 4); - if (channel == 1) - { - output_buffer[offset + 3] = 255; - } - else if (channel == 2) + for (int i = 0; i < total_pixel; ++i) { - output_buffer[offset + 3] = image_data[i * 2 + 1]; - } - else if (channel == 3) - { - output_buffer[offset + 3] = 255; + int offset = i * 4; + + if (channel == 1) + { + output_buffer[offset + 3] = 255; + } + else if (channel == 2) + { + output_buffer[offset + 3] = image_data[i * 2 + 1]; + } + else if (channel == 3) + { + output_buffer[offset + 3] = 255; + } } } - } - else - { - size_t total_pixel = width * height; - size_t buffer_size = total_pixel * channel; - output_buffer.resize(buffer_size); - Helpers::secure_memset(output_buffer.data(), 0.f, buffer_size, buffer_size); - } + else + { + size_t total_pixel = width * height; + size_t buffer_size = total_pixel * channel; + output_buffer.resize(buffer_size); + Helpers::secure_memset(output_buffer.data(), 0.f, buffer_size, buffer_size); + } - stbi_image_free((void*) image_data); + stbi_image_free((void*) image_data); - Buffers::Bitmap in = {width, height, 4, Buffers::BitmapFormat::FLOAT, output_buffer.data()}; - Buffers::Bitmap vertical_cross = Buffers::Bitmap::EquirectangularMapToVerticalCross(in); - Buffers::Bitmap cubemap = Buffers::Bitmap::VerticalCrossToCubemap(vertical_cross); + Buffers::Bitmap in = {width, height, 4, Buffers::BitmapFormat::FLOAT, output_buffer.data()}; + Buffers::Bitmap vertical_cross = Buffers::Bitmap::EquirectangularMapToVerticalCross(in); + Buffers::Bitmap cubemap = Buffers::Bitmap::VerticalCrossToCubemap(vertical_cross); - // spec.Width = cubemap.Width; - // spec.Height = cubemap.Height; - size_t buffer_size = cubemap.Buffer.size(); - size_t buffer_byte = buffer_size * sizeof(uint8_t); - upload_req.Buffer.resize(buffer_size); - Helpers::secure_memmove(upload_req.Buffer.data(), buffer_byte, cubemap.Buffer.data(), buffer_byte); + size_t buffer_size = cubemap.Buffer.size(); + size_t buffer_byte = buffer_size * sizeof(uint8_t); + upload_req.Buffer.resize(buffer_size); + Helpers::secure_memmove(upload_req.Buffer.data(), buffer_byte, cubemap.Buffer.data(), buffer_byte); + } } else { diff --git a/ZEngine/ZEngine/Importers/AssetTypes.h b/ZEngine/ZEngine/Importers/AssetTypes.h index 64aa8828..b0fe2557 100644 --- a/ZEngine/ZEngine/Importers/AssetTypes.h +++ b/ZEngine/ZEngine/Importers/AssetTypes.h @@ -14,7 +14,8 @@ namespace ZEngine::Importers UNKNOWN = 0, MESH, MATERIAL, - TEXTURES + TEXTURES, + ENVIRONMENT_MAP }; struct AssetSubMesh diff --git a/ZEngine/ZEngine/Importers/EnvironmentMapImporter.cpp b/ZEngine/ZEngine/Importers/EnvironmentMapImporter.cpp new file mode 100644 index 00000000..e2dd5a51 --- /dev/null +++ b/ZEngine/ZEngine/Importers/EnvironmentMapImporter.cpp @@ -0,0 +1,102 @@ +#include +#include +#include +#include + +// stb_image implementation is defined once in AsyncResourceLoader.cpp. +#include + +using namespace ZEngine::Helpers; +using namespace ZEngine::Rendering::Buffers; +using namespace ZEngine::Core::Containers; + +namespace ZEngine::Importers +{ + std::future EnvironmentMapImporter::ImportAsync(const char* filename, const ImportConfiguration& cfg) + { + ThreadPoolHelper::Submit([this, path = std::string(filename), cfg] { + std::unique_lock l(m_mutex); + + Arena.Clear(); + auto thread_local_arena = &Arena; + ImportConfiguration config = {}; + + config.OutputWorkingSpacePath.init(thread_local_arena, cfg.OutputWorkingSpacePath.c_str()); + config.OutputAssetsPath.init(thread_local_arena, cfg.OutputAssetsPath.c_str()); + config.AssetName.init(thread_local_arena, cfg.AssetName.c_str()); + config.OutputAssetFile.init(thread_local_arena, cfg.OutputAssetFile.c_str()); + + std::string dir_path = fmt::format("{0}{1}{2}", config.OutputWorkingSpacePath.c_str(), PLATFORM_OS_BACKSLASH, config.OutputAssetsPath.c_str()); + std::string fullname_path = fmt::format("{0}{1}{2}", dir_path, PLATFORM_OS_BACKSLASH, config.OutputAssetFile.c_str()); + + if (std::filesystem::exists(fullname_path)) + { + REPORT_LOG(Context, fmt::format("Environment map already exists")) + return; + } + + m_is_importing.store(true, std::memory_order_release); + + int width = 0, height = 0, channel = 0; + stbi_set_flip_vertically_on_load(1); + const float* image_data = stbi_loadf(path.c_str(), &width, &height, &channel, 4); + + if (!image_data) + { + if (m_error_callback) + { + m_error_callback(Context, fmt::format("Failed to decode '{}': {}", path, stbi_failure_reason())); + } + m_is_importing.store(false, std::memory_order_release); + return; + } + + if (m_progress_callback) + { + m_progress_callback(Context, 0.33f); + } + + Bitmap equirect = {width, height, 4, BitmapFormat::FLOAT, image_data}; + stbi_image_free(const_cast(image_data)); + + Bitmap vertical_cross = Bitmap::EquirectangularMapToVerticalCross(equirect); + Bitmap cubemap = Bitmap::VerticalCrossToCubemap(vertical_cross); + + if (m_progress_callback) + { + m_progress_callback(Context, 0.75f); + } + + auto output = IAssetImporter::SerializeEnvironmentMapFile(cubemap, config); + + m_is_importing.store(false, std::memory_order_release); + + if (output.Type == AssetFileType::ENVIRONMENT_MAP) + { + if (m_progress_callback) + { + m_progress_callback(Context, 1.0f); + } + + Array outputs = {}; + outputs.init(thread_local_arena, 1); + outputs.push(output); + + if (m_complete_callback) + { + m_complete_callback(Context, ArrayView{outputs}); + } + } + else + { + if (m_error_callback) + { + m_error_callback(Context, fmt::format("Failed to write .zenvmap")); + } + } + }); + + co_return; + } + +} // namespace ZEngine::Importers diff --git a/ZEngine/ZEngine/Importers/EnvironmentMapImporter.h b/ZEngine/ZEngine/Importers/EnvironmentMapImporter.h new file mode 100644 index 00000000..dcc0e06c --- /dev/null +++ b/ZEngine/ZEngine/Importers/EnvironmentMapImporter.h @@ -0,0 +1,14 @@ +#pragma once +#include + +namespace ZEngine::Importers +{ + class EnvironmentMapImporter : public IAssetImporter + { + public: + EnvironmentMapImporter() = default; + virtual ~EnvironmentMapImporter() = default; + + virtual std::future ImportAsync(const char* filename, const ImportConfiguration& config) override; + }; +} // namespace ZEngine::Importers diff --git a/ZEngine/ZEngine/Importers/IAssetImporter.cpp b/ZEngine/ZEngine/Importers/IAssetImporter.cpp index 6f5785d9..8048253e 100644 --- a/ZEngine/ZEngine/Importers/IAssetImporter.cpp +++ b/ZEngine/ZEngine/Importers/IAssetImporter.cpp @@ -430,4 +430,79 @@ namespace ZEngine::Importers in.close(); return output; } + + AssetImporterOutput IAssetImporter::SerializeEnvironmentMapFile(const Rendering::Buffers::Bitmap& cubemap, const ImportConfiguration& config) + { + AssetImporterOutput output = {}; + + if (config.OutputAssetFile.empty()) + { + return output; + } + + std::string dir_path = fmt::format("{0}{1}{2}", config.OutputWorkingSpacePath.c_str(), PLATFORM_OS_BACKSLASH, config.OutputAssetsPath.c_str()); + std::string fullname_path = fmt::format("{0}{1}{2}", dir_path, PLATFORM_OS_BACKSLASH, config.OutputAssetFile.c_str()); + + std::error_code ec; + std::filesystem::create_directories(dir_path, ec); + std::ofstream out(fullname_path, std::ios::binary | std::ios::trunc); + + if (!out.is_open()) + { + return output; + } + + EnvironmentMapFileHeader header{ + .MagicNumber = ZENVMAP_MAGIC, + .Version = ASSET_FILE_VERSION, + .FaceWidth = cubemap.Width, + .FaceHeight = cubemap.Height, + .Channel = cubemap.Channel, + .LayerCount = cubemap.Depth, + .BufferByteSize = static_cast(cubemap.Buffer.size()), + }; + + out.write(reinterpret_cast(&header), sizeof(header)); + out.write(reinterpret_cast(cubemap.Buffer.data()), static_cast(cubemap.Buffer.size())); + out.close(); + + output = {.Type = AssetFileType::ENVIRONMENT_MAP, .Path = fullname_path, .RootPath = config.OutputWorkingSpacePath.c_str()}; + return output; + } + + bool IAssetImporter::DeserializeEnvironmentMapFile(const char* zenvmap_file, Rendering::Buffers::Bitmap& out_cubemap) + { + std::ifstream in(zenvmap_file, std::ios::binary); + if (!in.is_open()) + { + return false; + } + + EnvironmentMapFileHeader header{}; + in.read(reinterpret_cast(&header), sizeof(header)); + + if (!in.good() || header.MagicNumber != ZENVMAP_MAGIC) + { + return false; + } + + out_cubemap = Rendering::Buffers::Bitmap(header.FaceWidth, header.FaceHeight, header.LayerCount, header.Channel, Rendering::Buffers::BitmapFormat::FLOAT); + out_cubemap.Type = Rendering::Buffers::BitmapType::CUBE; + + in.read(reinterpret_cast(out_cubemap.Buffer.data()), static_cast(header.BufferByteSize)); + return in.good(); + } + + bool IAssetImporter::ReadEnvironmentMapFileHeader(const char* zenvmap_file, EnvironmentMapFileHeader& out_header) + { + std::ifstream in(zenvmap_file, std::ios::binary); + if (!in.is_open()) + { + return false; + } + + in.read(reinterpret_cast(&out_header), sizeof(EnvironmentMapFileHeader)); + return in.good() && (out_header.MagicNumber == ZENVMAP_MAGIC); + } + } // namespace ZEngine::Importers diff --git a/ZEngine/ZEngine/Importers/IAssetImporter.h b/ZEngine/ZEngine/Importers/IAssetImporter.h index 8f0ee079..80d9ec10 100644 --- a/ZEngine/ZEngine/Importers/IAssetImporter.h +++ b/ZEngine/ZEngine/Importers/IAssetImporter.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,17 @@ namespace ZEngine::Importers uuids::uuid Id = {}; }; + struct EnvironmentMapFileHeader + { + uint32_t MagicNumber = 0; + uint32_t Version = 0; + int32_t FaceWidth = 0; + int32_t FaceHeight = 0; + int32_t Channel = 0; + int32_t LayerCount = 0; + uint64_t BufferByteSize = 0; + }; + struct AssetImporterOutput { AssetFileType Type = AssetFileType::UNKNOWN; @@ -85,5 +97,9 @@ namespace ZEngine::Importers static void DeserializeTextureAssetFile(Core::Memory::ArenaAllocator* arena, const char* asset_file, Core::Containers::Array&); static bool ReadAssetMeshFileHeader(cstring asset_file, AssetMeshFileHeader&); + + static AssetImporterOutput SerializeEnvironmentMapFile(const Rendering::Buffers::Bitmap& cubemap, const ImportConfiguration& config); + static bool DeserializeEnvironmentMapFile(const char* zenvmap_file, Rendering::Buffers::Bitmap& out_cubemap); + static bool ReadEnvironmentMapFileHeader(const char* zenvmap_file, EnvironmentMapFileHeader& out_header); }; } // namespace ZEngine::Importers diff --git a/ZEngine/ZEngine/Rendering/Renderers/RendererPasses.cpp b/ZEngine/ZEngine/Rendering/Renderers/RendererPasses.cpp index df55817e..4086e5d3 100644 --- a/ZEngine/ZEngine/Rendering/Renderers/RendererPasses.cpp +++ b/ZEngine/ZEngine/Rendering/Renderers/RendererPasses.cpp @@ -237,7 +237,7 @@ namespace ZEngine::Rendering::Renderers void SkyboxPass::Setup(Hardwares::VulkanDevicePtr const device, cstring name, RenderGraphResourceBuilderPtr const res_builder, RenderGraphResourceInspectorPtr res_inspector) { - auto env_map_res = res_builder->CreateTexture("skybox_env_map", "Settings/EnvironmentMaps/bergen_4k.hdr"); + auto env_map_res = res_builder->CreateTexture("skybox_env_map", "Settings/EnvironmentMaps/bergen_4k.zenvmap"); m_env_map = env_map_res.ResourceInfo.TextureHandle; // m_vb_handle = device->CreateVertexBufferSet(); diff --git a/ZEngine/ZEngine/ZEngineDef.h b/ZEngine/ZEngine/ZEngineDef.h index 597e0203..b3616540 100644 --- a/ZEngine/ZEngine/ZEngineDef.h +++ b/ZEngine/ZEngine/ZEngineDef.h @@ -100,6 +100,7 @@ #define ZEMATERIAL_MAGIC MAKE_MAGIC('Z', 'M', 'A', 'T') #define ZETEXTURES_MAGIC MAKE_MAGIC('Z', 'T', 'E', 'X') #define ZESCENE_MAGIC MAKE_MAGIC('Z', 'S', 'C', 'N') +#define ZENVMAP_MAGIC MAKE_MAGIC('Z', 'E', 'N', 'V') #define ASSET_FILE_VERSION MAKE_VERSION(1, 0, 0) #define SCENE_FILE_VERSION MAKE_VERSION(1, 0, 0)